diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40314753..6cd3ac37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,12 @@ jobs: npm ci npm run build npm test + - name: "Build React UI" + working-directory: src/ui + run: | + npm ci + npm test -- --watchAll=false + npm run build - name: "Build post-deployment package" working-directory: postdeploy run: | diff --git a/src/ui/src/App.js b/src/ui/src/App.js index 788f70f5..dc2cf63f 100644 --- a/src/ui/src/App.js +++ b/src/ui/src/App.js @@ -42,7 +42,6 @@ const App = () => { useEffect(() => { const doMarketAction = () => { - console.log('doMarketAction'); dispatch(marketAction(comptroller, protocolAddresses, server)); }; doMarketAction(); diff --git a/src/ui/src/App.test.js b/src/ui/src/App.test.js index f0b6a29f..76247839 100644 --- a/src/ui/src/App.test.js +++ b/src/ui/src/App.test.js @@ -1,8 +1,35 @@ import { render, screen } from '@testing-library/react'; -import App from './App'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import Switch from './components/Switch'; +import { isRecoveryMode } from './components/Constants'; -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); +const originalEnvironment = process.env.REACT_APP_ENV; +const theme = createTheme(); + +const renderSwitch = () => render( + + + +); + +afterEach(() => { + process.env.REACT_APP_ENV = originalEnvironment; +}); + +test('disables collateral controls in Guard recovery mode', () => { + process.env.REACT_APP_ENV = 'mainnet'; + + renderSwitch(); + + expect(isRecoveryMode()).toBe(true); + expect(screen.getByRole('checkbox')).toBeDisabled(); +}); + +test('enables collateral controls outside Guard recovery mode', () => { + process.env.REACT_APP_ENV = 'tezosx-previewnet'; + + renderSwitch(); + + expect(isRecoveryMode()).toBe(false); + expect(screen.getByRole('checkbox')).toBeEnabled(); }); diff --git a/src/ui/src/components/AllMarketModal/index.js b/src/ui/src/components/AllMarketModal/index.js index b79876bb..96788e2a 100644 --- a/src/ui/src/components/AllMarketModal/index.js +++ b/src/ui/src/components/AllMarketModal/index.js @@ -23,7 +23,9 @@ const AllMarketModal = (props) => { const classes = useStyles(); const dispatch = useDispatch(); - const { open, close, tokenDetails, tab} = props; + const { + open, close, tokenDetails, tab + } = props; const { account } = useSelector((state) => state.addWallet); const { protocolAddresses, comptroller } = useSelector((state) => state.nodes); @@ -52,7 +54,7 @@ const AllMarketModal = (props) => { const [pendingLimit, setPendingLimit] = useState(''); const [pendingLimitUsed, setPendingLimitUsed] = useState(''); - const buttonOne = useSupplyErrorText(tokenValue, useMaxAmount); + const buttonOne = useSupplyErrorText(tokenValue, useMaxAmount, tokenDetails); const buttonTwo = useBorrowErrorText(tokenValue, borrowLimit, tokenDetails); const handleOpenInitialize = () => setInitializeModal(true); @@ -62,6 +64,9 @@ const AllMarketModal = (props) => { const handleCloseError = () => setErrorModal(false); const supplyToken = async () => { + if (!tokenDetails.isListed || tokenDetails.mintPaused) { + return; + } // eslint-disable-next-line no-shadow const { opGroup, error } = await supplyTokenAction(tokenDetails, amount, close, setTokenText, handleOpenInitialize, protocolAddresses, publicKeyHash); setOpGroup(opGroup); @@ -69,6 +74,9 @@ const AllMarketModal = (props) => { }; const borrowToken = async () => { + if (!tokenDetails.isListed || tokenDetails.borrowPaused) { + return; + } // eslint-disable-next-line no-shadow const { opGroup, error } = await borrowTokenAction(tokenDetails, amount, close, setTokenText, handleOpenInitialize, protocolAddresses, publicKeyHash); setOpGroup(opGroup); @@ -171,7 +179,7 @@ const AllMarketModal = (props) => { APYTextTwo="Borrow APY" CurrentStateText= "Currently Supplying" CurrentStateTextTwo= "Currently Borrowing" - tab={tab ? tab : "one"} + tab={tab || 'one'} open={open} close={close} tokenDetails={tokenDetails} diff --git a/src/ui/src/components/BorrowModal/index.js b/src/ui/src/components/BorrowModal/index.js index 842cdf95..1eb364ac 100644 --- a/src/ui/src/components/BorrowModal/index.js +++ b/src/ui/src/components/BorrowModal/index.js @@ -22,7 +22,9 @@ import { useStyles } from './style'; const BorrowModal = (props) => { const classes = useStyles(); const dispatch = useDispatch(); - const { open, close, tokenDetails, tab } = props; + const { + open, close, tokenDetails, tab + } = props; const { account } = useSelector((state) => state.addWallet); const { protocolAddresses, comptroller } = useSelector((state) => state.nodes); @@ -61,6 +63,9 @@ const BorrowModal = (props) => { const handleCloseError = () => setErrorModal(false); const borrowToken = async () => { + if (!tokenDetails.isListed || tokenDetails.borrowPaused) { + return; + } // eslint-disable-next-line no-shadow const { opGroup, error } = await borrowTokenAction( tokenDetails, @@ -69,7 +74,7 @@ const BorrowModal = (props) => { setTokenText, handleOpenInitialize, protocolAddresses, - publicKeyHash, + publicKeyHash ); setOpGroup(opGroup); setEvaluationError(error); @@ -84,7 +89,7 @@ const BorrowModal = (props) => { setTokenText, handleOpenInitialize, protocolAddresses, - publicKeyHash, + publicKeyHash ); setOpGroup(opGroup); setEvaluationError(error); @@ -174,7 +179,7 @@ const BorrowModal = (props) => { const pendingBorrowLimit = totalCollateral - pendingBorrowing; setPendingLimit(pendingBorrowLimit); setPendingLimitUsed( - new BigNumber(pendingBorrowing).dividedBy(new BigNumber(totalCollateral)).multipliedBy(100), + new BigNumber(pendingBorrowing).dividedBy(new BigNumber(totalCollateral)).multipliedBy(100) ); } diff --git a/src/ui/src/components/Constants/index.js b/src/ui/src/components/Constants/index.js index 01d6421c..860fca86 100644 --- a/src/ui/src/components/Constants/index.js +++ b/src/ui/src/components/Constants/index.js @@ -39,6 +39,8 @@ export const getAddresses = () => { } }; +export const isRecoveryMode = () => Boolean(getAddresses().comptrollerDataSource); + const addresses = getAddresses(); export const tokens = [ diff --git a/src/ui/src/components/Dashboard/BorrowMarketTokenTable.tsx b/src/ui/src/components/Dashboard/BorrowMarketTokenTable.tsx index 1d1be894..4188ca52 100644 --- a/src/ui/src/components/Dashboard/BorrowMarketTokenTable.tsx +++ b/src/ui/src/components/Dashboard/BorrowMarketTokenTable.tsx @@ -16,18 +16,21 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import { Button, Typography } from '@mui/material'; -import { decimalify, getExplorerLink, nFormatter, roundValue } from '../../util'; +import { + decimalify, getExplorerLink, nFormatter, roundValue +} from '../../util'; import AllMarketModal from '../AllMarketModal'; import TableSkeleton from '../Skeleton'; import { useStyles } from './style'; -import { getAddresses } from '../Constants'; +import { getAddresses, isRecoveryMode } from '../Constants'; const BorrowMarketTokenTable = (props) => { const classes = useStyles(); const { tableData } = props; const { address } = useSelector((state: any) => state.addWallet.account); + const recoveryMode = isRecoveryMode(); const [tokenDetails, setTokenDetails] = useState(); const [openMktModal, setMktModal] = useState(false); @@ -47,6 +50,15 @@ const BorrowMarketTokenTable = (props) => { setMktModal(true); }; + const formatBorrowRate = (borrowRate) => { + if (borrowRate <= 0) { + return '0'; + } + return new BigNumber(borrowRate).gt(new BigNumber(10000000000000000)) + ? roundValue(decimalify(borrowRate, 18)) + : '<0.01'; + }; + return ( {tokenDetails && ( @@ -87,21 +99,23 @@ const BorrowMarketTokenTable = (props) => { - + {recoveryMode ? ( + Unavailable + ) : <> {data.marketSize > 0 ? decimalify( - data.available.toString(), - decimals[data.title], - decimals[data.title], - ) < 0.01 + data.available.toString(), + decimals[data.title], + decimals[data.title] + ) < 0.01 ? '>0.00' : nFormatter( - decimalify( - data.available.toString(), - decimals[data.title], - decimals[data.title], - ), - ) + decimalify( + data.available.toString(), + decimals[data.title], + decimals[data.title] + ) + ) : '0'}{' '} {data.title} {' '} @@ -110,26 +124,21 @@ const BorrowMarketTokenTable = (props) => { $ {data.marketSize > 0 ? nFormatter( - decimalify( - ( - (data.marketSize - data.totalBorrowed) * - data.usdPrice - ).toString(), - decimals[data.title], - decimals[data.title], - ), - ) + decimalify( + ( + (data.marketSize - data.totalBorrowed) + * data.usdPrice + ).toString(), + decimals[data.title], + decimals[data.title] + ) + ) : '0.00'} - + } - {data.borrowRate > 0 - ? // checks if rate is lower than 0.1% (all rates lower than 0.01% is shown as <0.01%) - new BigNumber(data.borrowRate).gt(new BigNumber(10000000000000000)) - ? roundValue(decimalify(data.borrowRate, 18)) - : '<0.01' - : '0'} + {formatBorrowRate(data.borrowRate)} % @@ -139,6 +148,12 @@ const BorrowMarketTokenTable = (props) => { size="medium" className={classes.borrowButton} onClick={() => handleClickMktModal(data)} + disabled={!data.isListed || data.borrowPaused} + title={ + !data.isListed || data.borrowPaused + ? 'Borrowing is temporarily disabled for this market.' + : undefined + } sx={{ textTransform: 'capitalize' }} > Borrow @@ -148,11 +163,11 @@ const BorrowMarketTokenTable = (props) => { diff --git a/src/ui/src/components/Dashboard/BorrowedTokenTable.tsx b/src/ui/src/components/Dashboard/BorrowedTokenTable.tsx index 7bd0c76a..169c9025 100644 --- a/src/ui/src/components/Dashboard/BorrowedTokenTable.tsx +++ b/src/ui/src/components/Dashboard/BorrowedTokenTable.tsx @@ -17,27 +17,27 @@ import TableRow from '@mui/material/TableRow'; import { Button, tooltipClasses, Typography } from '@mui/material'; // eslint-disable-next-line object-curly-newline -import { decimalify, formatTokenData, nFormatter, roundValue, truncateNum } from '../../util'; +import { decimalify, formatTokenData, nFormatter, roundValue } from '../../util'; import TableSkeleton from '../Skeleton'; import BorrowModal from '../BorrowModal'; import { useStyles } from './style'; import LightTooltip from '../Tooltip/LightTooltip'; +import { isRecoveryMode } from '../Constants'; const BorrowedTokenTable = (props) => { const classes = useStyles(); const { tableData } = props; + const recoveryMode = isRecoveryMode(); const { address } = useSelector((state: any) => state.addWallet.account); const { allMarkets } = useSelector((state: any) => state.market); - const { totalCollateral } = useSelector((state: any) => state.supplyComposition.supplyComposition); const [tokenDetails, setTokenDetails] = useState(); const [openMktModal, setMktModal] = useState(false); const [loading, setLoading] = useState(true); const [openRepayTab, setRepayTab] = useState(false); - const closeModal = () => { setMktModal(false); setRepayTab(false); @@ -56,6 +56,15 @@ const BorrowedTokenTable = (props) => { const borrowedData = formatTokenData(tableData); + const formatRate = (rate) => { + if (rate <= 0) { + return '0'; + } + return new BigNumber(rate).gt(new BigNumber(10000000000000000)) + ? roundValue(decimalify(rate, 18)) + : '<0.01'; + }; + useEffect(() => { allMarkets.map((x) => { if ((address && x.walletBalance) || (!address && x.marketSize)) { @@ -93,8 +102,8 @@ const BorrowedTokenTable = (props) => { )} )} - {borrowedData && - borrowedData.map((data) => ( + {borrowedData + && borrowedData.map((data) => ( handleClickMktModal(data)}>
@@ -110,12 +119,7 @@ const BorrowedTokenTable = (props) => { - {data.rate > 0 - ? // checks if rate is lower than 0.1% (all rates lower than 0.01% is shown as <0.01%) - new BigNumber(data.rate).gt(new BigNumber(10000000000000000)) - ? roundValue(decimalify(data.rate, 18)) - : '<0.01' - : '0'} + {formatRate(data.rate)} % @@ -135,48 +139,50 @@ const BorrowedTokenTable = (props) => { ).replace(/\.?0+$/, '')} ${data.title}`} - $ - {data.balanceUnderlying > 0 - ? nFormatter( - decimalify( - (data.outstandingLoan * data.usdPrice).toString(), - decimals[data.title], - decimals[data.title] + {recoveryMode + ? 'Unavailable' + : <>{'$'}{data.balanceUnderlying > 0 + ? nFormatter( + decimalify( + (data.outstandingLoan * data.usdPrice).toString(), + decimals[data.title], + decimals[data.title] + ) ) - ) - : '0.00'} + : '0.00'}} } placement="top" > - {data.outstandingLoan > 0 && - decimalify( - data.outstandingLoan.toString(), - decimals[data.title], - decimals[data.title], - ) < 0.01 + {( + data.outstandingLoan > 0 + && decimalify( + data.outstandingLoan.toString(), + decimals[data.title], + decimals[data.title] + ) < 0.01 + ) ? '>0.00' : nFormatter( - decimalify( - data.outstandingLoan.toString(), - decimals[data.title], - decimals[data.title], - ), - )}{' '} + decimalify( + data.outstandingLoan.toString(), + decimals[data.title], + decimals[data.title] + ) + )}{' '} {data.title}
- $ - {nFormatter( + {recoveryMode ? 'Unavailable' : <>{'$'}{nFormatter( decimalify( (data.outstandingLoan * data.usdPrice).toString(), decimals[data.title], - decimals[data.title], - ), - )} + decimals[data.title] + ) + )}} diff --git a/src/ui/src/components/Dashboard/SuppliedTokenTable.tsx b/src/ui/src/components/Dashboard/SuppliedTokenTable.tsx index c5e719c5..90c37207 100644 --- a/src/ui/src/components/Dashboard/SuppliedTokenTable.tsx +++ b/src/ui/src/components/Dashboard/SuppliedTokenTable.tsx @@ -29,10 +29,12 @@ import { decimalify, formatTokenData, nFormatter, roundValue, truncateNum } from import { useStyles } from './style'; import LightTooltip from '../Tooltip/LightTooltip'; +import { isRecoveryMode } from '../Constants'; const SuppliedTokenTable = (props) => { const classes = useStyles(); const { tableData } = props; + const recoveryMode = isRecoveryMode(); const { address } = useSelector((state: any) => state.addWallet.account); const { allMarkets } = useSelector((state: any) => state.market); @@ -71,6 +73,15 @@ const SuppliedTokenTable = (props) => { setWithdrawTab(true); }; + const formatRate = (rate) => { + if (rate <= 0) { + return '0'; + } + return new BigNumber(rate).gt(new BigNumber(10000000000000000)) + ? roundValue(decimalify(rate, 18)) + : '<0.01'; + }; + const suppliedData = formatTokenData(tableData); useEffect(() => { @@ -120,8 +131,8 @@ const SuppliedTokenTable = (props) => { )} )} - {suppliedData && - suppliedData.map((data) => ( + {suppliedData + && suppliedData.map((data) => ( handleClickMktModal(data, event)}>
@@ -137,12 +148,7 @@ const SuppliedTokenTable = (props) => { - {data.rate > 0 - ? // checks if rate is lower than 0.1% (all rates lower than 0.01% is shown as <0.01%) - new BigNumber(data.rate).gt(new BigNumber(10000000000000000)) - ? roundValue(decimalify(data.rate, 18)) - : '<0.01' - : '0'} + {formatRate(data.rate)} % @@ -163,50 +169,54 @@ const SuppliedTokenTable = (props) => { ).replace(/\.?0+$/, '')} ${data.title}`} - $ - {data.balanceUnderlying > 0 - ? nFormatter( - decimalify( - (data.balanceUnderlying * data.usdPrice).toString(), - decimals[data.title], - decimals[data.title] + {recoveryMode + ? 'Unavailable' + : <>{'$'}{data.balanceUnderlying > 0 + ? nFormatter( + decimalify( + (data.balanceUnderlying * data.usdPrice).toString(), + decimals[data.title], + decimals[data.title] + ) ) - ) - : '0.00'} + : '0.00'}} } placement="top" > - {data.balanceUnderlying >= 1 && - decimalify( - data.balanceUnderlying, - decimals[data.title], - decimals[data.title], - ) < 0.01 + {( + data.balanceUnderlying >= 1 + && decimalify( + data.balanceUnderlying, + decimals[data.title], + decimals[data.title] + ) < 0.01 + ) ? '>0.00' : truncateNum( - decimalify( - data.balanceUnderlying, - decimals[data.title], - decimals[data.title], - ), - )}{' '} + decimalify( + data.balanceUnderlying, + decimals[data.title], + decimals[data.title] + ) + )}{' '} {data.title}
- $ - {data.balanceUnderlying > 0 - ? nFormatter( - decimalify( - (data.balanceUnderlying * data.usdPrice).toString(), - decimals[data.title], - decimals[data.title], - ), - ) - : '0.00'} + {recoveryMode + ? 'Unavailable' + : <>{'$'}{data.balanceUnderlying > 0 + ? nFormatter( + decimalify( + (data.balanceUnderlying * data.usdPrice).toString(), + decimals[data.title], + decimals[data.title] + ) + ) + : '0.00'}} @@ -217,8 +227,12 @@ const SuppliedTokenTable = (props) => { variant='contained' className={classes.detailsButton} onClick={() => handleClickWithdraw(data)} - disabled={data.redeemPaused} - title={data.redeemPaused ? 'Withdrawals are temporarily paused for this market.' : undefined} + disabled={!data.isListed || data.redeemPaused} + title={ + !data.isListed || data.redeemPaused + ? 'Withdrawals are temporarily disabled for this market.' + : undefined + } sx={{ textTransform: 'capitalize' }} > Withdraw diff --git a/src/ui/src/components/Dashboard/SupplyMarketTokenTable.tsx b/src/ui/src/components/Dashboard/SupplyMarketTokenTable.tsx index 7b04665e..bfbdfdaa 100644 --- a/src/ui/src/components/Dashboard/SupplyMarketTokenTable.tsx +++ b/src/ui/src/components/Dashboard/SupplyMarketTokenTable.tsx @@ -22,11 +22,13 @@ import AllMarketModal from '../AllMarketModal'; import TableSkeleton from '../Skeleton'; import { useStyles } from './style'; +import { isRecoveryMode } from '../Constants'; const SupplyMarketTokenTable = (props) => { const classes = useStyles(); const { tableData } = props; const { address } = useSelector((state: any) => state.addWallet.account); + const recoveryMode = isRecoveryMode(); const [tokenDetails, setTokenDetails] = useState(); const [openMktModal, setMktModal] = useState(false); @@ -40,6 +42,15 @@ const SupplyMarketTokenTable = (props) => { setMktModal(true); }; + const formatSupplyRate = (supplyRate) => { + if (supplyRate <= 0) { + return '0'; + } + return new BigNumber(supplyRate).gt(new BigNumber(10000000000000000)) + ? roundValue(decimalify(supplyRate, 18)) + : '<0.01'; + }; + return ( {tokenDetails && } @@ -58,7 +69,7 @@ const SupplyMarketTokenTable = (props) => { {tableData?.map((data) => ( {(address && data.walletBalance) || (!address && data.marketSize) ? ( - handleClickMktModal(data)}> + !data.mintPaused && handleClickMktModal(data)}>
{ {data.walletBalance > 0 ? decimalify( - data.walletBalance.toString(), - decimals[data.title], - decimals[data.title], - ) < 0.01 + data.walletBalance.toString(), + decimals[data.title], + decimals[data.title] + ) < 0.01 ? '>0.00' : nFormatter( - decimalify( - data.walletBalance.toString(), - decimals[data.title], - decimals[data.title], - ), - ) + decimalify( + data.walletBalance.toString(), + decimals[data.title], + decimals[data.title] + ) + ) : '0'}{' '} {data.title} {' '}
- $ - {data.walletBalance > 0 - ? nFormatter( - decimalify( - (data.walletBalance * data.usdPrice).toString(), - decimals[data.title], - decimals[data.title], - ), - ) - : '0.00'} + {recoveryMode + ? 'Unavailable' + : <>{'$'}{data.walletBalance > 0 + ? nFormatter( + decimalify( + (data.walletBalance * data.usdPrice).toString(), + decimals[data.title], + decimals[data.title] + ) + ) + : '0.00'}} - {data.supplyRate > 0 - ? // checks if rate is lower than 0.1% (all rates lower than 0.01% is shown as <0.01%) - new BigNumber(data.supplyRate).gt(new BigNumber(10000000000000000)) - ? roundValue(decimalify(data.supplyRate, 18)) - : '<0.01' - : '0'} + {formatSupplyRate(data.supplyRate)} % @@ -127,6 +134,12 @@ const SupplyMarketTokenTable = (props) => { size="medium" className={classes.supplyButton} onClick={() => handleClickMktModal(data)} + disabled={!data.isListed || data.mintPaused} + title={ + (!data.isListed || data.mintPaused) + ? 'Supplying is temporarily disabled for this market.' + : undefined + } sx={{ textTransform: 'capitalize' }} > Supply diff --git a/src/ui/src/components/Dashboard/index.tsx b/src/ui/src/components/Dashboard/index.tsx index ecb79ae9..ed73bbfc 100644 --- a/src/ui/src/components/Dashboard/index.tsx +++ b/src/ui/src/components/Dashboard/index.tsx @@ -5,7 +5,7 @@ import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import Grid from '@mui/material/Grid'; -import { Box, Typography } from '@mui/material'; +import { Typography } from '@mui/material'; import BorrowedTokenTable from './BorrowedTokenTable'; import SuppliedTokenTable from './SuppliedTokenTable'; @@ -45,11 +45,11 @@ const Dashboard = () => { - Assets to Supply + Assets to Supply - Assets to Borrow + Assets to Borrow diff --git a/src/ui/src/components/Dashboard/style.ts b/src/ui/src/components/Dashboard/style.ts index 6c88a94d..fa97a1d2 100644 --- a/src/ui/src/components/Dashboard/style.ts +++ b/src/ui/src/components/Dashboard/style.ts @@ -5,11 +5,11 @@ export const useStyles = makeStyles({ root: { '& .MuiTypography-root': { '@media(max-width: 501px)': { - fontSize: '0.75rem', + fontSize: '0.75rem' }, '@media(max-width: 320px)': { - fontSize: '0.625rem', - }, + fontSize: '0.625rem' + } }, '& .MuiTable-root': { flexDirection: 'column', @@ -18,8 +18,8 @@ export const useStyles = makeStyles({ justifyContent: 'center', '& .MuiTableRow-root': { paddingRight: '0rem', - marginRight: '0rem', - }, + marginRight: '0rem' + } }, '& .MuiTableCell-root': { color: '#000', @@ -27,61 +27,61 @@ export const useStyles = makeStyles({ padding: '.75rem', whiteSpace: 'nowrap', '@media(min-width: 1200px)': { - paddingLeft: '1.5rem', + paddingLeft: '1.5rem' }, '@media(max-width: 501px)': { - padding: '.5rem', - }, + padding: '.5rem' + } }, '& .MuiTableHead-root ': { '& .MuiTableRow-root': { - height: '3.5rem', + height: '3.5rem' }, '& .MuiTableCell-root': { color: '#000', opacity: '1', fontSize: '.875rem', '@media(min-width: 768px)': { - minWidth: '5.5625rem', + minWidth: '5.5625rem' }, '@media(max-width: 1200px)': { - fontSize: '0.75rem', + fontSize: '0.75rem' }, '@media(max-width: 501px)': { - fontSize: '0.625rem', + fontSize: '0.625rem' }, '@media(max-width: 320px)': { - fontSize: '0.75rem', - }, - }, + fontSize: '0.75rem' + } + } }, '& .MuiTableBody-root ': { '& .MuiTableRow-root:last-of-type': { '& .MuiTableCell-root': { padding: '-10px', marginRight: '10px', - borderBottom: '0', - }, + borderBottom: '0' + } }, '& .MuiTableRow-root': { height: '4.5rem', '&:hover': { - background: '#DDF5FC66', - }, + background: '#DDF5FC66' + } }, '& .MuiTableCell-root': { fontWeight: '400', '@media(min-width: 1200px)': { - fontSize: '1rem', + fontSize: '1rem' }, '@media(max-width: 768px)': { - fontSize: '0.875rem', + fontSize: '0.875rem' }, '@media(max-width: 501px)': { - fontSize: '0.75rem', - }, - }, - }, + fontSize: '0.75rem' + } + } + } }, tableCon: { padding: '0px', @@ -89,18 +89,18 @@ export const useStyles = makeStyles({ borderBottom: '0', borderRadius: '1rem', '&::-webkit-scrollbar': { - display: 'none', + display: 'none' }, - boxShadow: '2px 4px 23px 5px rgba(163,163,163,0.096)', + boxShadow: '2px 4px 23px 5px rgba(163,163,163,0.096)' }, questionCircleIcon: { width: '1rem', height: '1rem', - marginBottom: '-3px', + marginBottom: '-3px' }, img: { - //width: "7%", - //height: "7%", + // width: "7%", + // height: "7%", // marginRight: "20%", width: '2.5rem', height: '2.5rem', @@ -108,18 +108,18 @@ export const useStyles = makeStyles({ '@media (min-width: 769px) and (max-width: 1024px)': { width: '2.5rem', height: '2.5rem', - marginRight: '12px', + marginRight: '12px' }, '@media(max-width: 768px)': { width: '2.5rem', height: '2.5rem', - marginRight: '12px', + marginRight: '12px' }, '@media(max-width: 501px)': { width: '2.5rem', height: '2.5rem', - marginRight: '12px', - }, + marginRight: '12px' + } }, tokenSymbol: { display: 'inline', @@ -130,11 +130,11 @@ export const useStyles = makeStyles({ letterSpacing: '0.005em', lineHeight: '1.875rem', '@media(max-width: 1200px)': { - fontSize: '1rem', + fontSize: '1rem' }, '@media(max-width: 768px)': { - fontSize: '1rem', - }, + fontSize: '1rem' + } }, token: { display: 'flex', @@ -142,18 +142,18 @@ export const useStyles = makeStyles({ alignItems: 'center', alignContent: 'center', alignText: 'center', - padding: '0px', + padding: '0px' }, tokenTitle: { display: 'flex', - flexDirection: 'column', + flexDirection: 'column' }, borrowButton: { marginRight: '0%', borderRadius: '8px', '@media(max-width: 768px)': {}, '@media(max-width: 501px)': {}, - width: '100%', + width: '100%' }, detailsButton: { color: '#2C2C2C', @@ -162,9 +162,9 @@ export const useStyles = makeStyles({ backgroundColor: '#F2F3F7', '&:hover': { backgroundColor: 'lightgrey', - boxShadow: 'none', + boxShadow: 'none' }, - width: '100%', + width: '100%' }, tokenName: { display: 'inline', @@ -175,22 +175,22 @@ export const useStyles = makeStyles({ letterSpacing: '0.005em', lineHeight: '1.875rem', '@media(max-width: 1200px)': { - fontSize: '1rem', + fontSize: '1rem' }, '@media(max-width: 768px)': { - fontSize: '1rem', - }, + fontSize: '1rem' + } }, clearFont: { color: '#000', opacity: '0.87', - fontWeight: '400', + fontWeight: '400' }, faintFont: { color: '#000', opacity: '0.6', fontWeight: '400', - fontSize: '0.875rem', + fontSize: '0.875rem' }, compositionOne: {}, dashboard: { @@ -199,58 +199,58 @@ export const useStyles = makeStyles({ paddingBottom: '1.5rem', '@media(max-width: 1024px)': { paddingLeft: '4rem', - paddingRight: '4rem', + paddingRight: '4rem' }, '@media(max-width: 900px)': { paddingLeft: '2rem', - paddingRight: '2rem', + paddingRight: '2rem' }, '@media(max-width: 768px)': { paddingLeft: '4rem', - paddingRight: '4rem', + paddingRight: '4rem' }, '@media(max-width: 501px)': { paddingLeft: '1rem', - paddingRight: '1rem', - }, + paddingRight: '1rem' + } }, borrowTablePadding: { '@media(min-width: 600px)': { - paddingRight: '1rem', + paddingRight: '1rem' }, '@media(min-width: 900px)': { - paddingRight: '2rem', + paddingRight: '2rem' }, '@media(min-width: 1024px)': { - paddingLeft: '0.9375rem', + paddingLeft: '0.9375rem' }, '@media(min-width: 1200px)': { - paddingLeft: '0.875rem', - }, + paddingLeft: '0.875rem' + } }, supplyTablePadding: { '@media(min-width: 600px)': { - paddingRight: '1rem', + paddingRight: '1rem' }, '@media(min-width: 900px)': { - paddingRight: '2rem', + paddingRight: '2rem' }, '@media(min-width: 1024px)': { - paddingRight: '2rem', + paddingRight: '2rem' }, '@media(min-width: 1200px)': { - paddingRight: '3rem', - }, + paddingRight: '3rem' + } }, firstCell: { - width: '10%', + width: '10%' }, secondCell: {}, thirdCell: {}, fourthCell: { width: '30%', '@media(min-width: 1200px)': { - paddingRight: '1.5rem !important', + paddingRight: '1.5rem !important' } }, fifthCell: { @@ -260,7 +260,7 @@ export const useStyles = makeStyles({ backgroundColor: 'white' }, '@media(min-width: 1200px)': { - paddingRight: '1.5rem !important', + paddingRight: '1.5rem !important' }, paddingLeft: '0 !important', '@media(min-width: 423px) and (max-width: 499px)': { @@ -270,7 +270,7 @@ export const useStyles = makeStyles({ opacity: '1 !important', backgroundColor: 'white', boxShadow: 'inset 0 -1px 0 #E0E0E0', - borderBottom: '0 !important', + borderBottom: '0 !important' }, '@media(min-width: 626px) and (max-width: 720px)': { position: 'sticky', @@ -279,7 +279,7 @@ export const useStyles = makeStyles({ opacity: '1 !important', backgroundColor: 'white', boxShadow: 'inset 0 -1px 0 #E0E0E0', - borderBottom: '0 !important', + borderBottom: '0 !important' }, '@media(min-width: 1423px) and (max-width: 1615px)': { position: 'sticky', @@ -288,28 +288,28 @@ export const useStyles = makeStyles({ opacity: '1 !important', backgroundColor: 'white', boxShadow: 'inset 0 -1px 0 #E0E0E0', - borderBottom: '0 !important', + borderBottom: '0 !important' } }, borrowCell: { '@media(min-width: 1200px)': { - paddingRight: '1.5rem !important', + paddingRight: '1.5rem !important' }, '@media(min-width: 423px) and (max-width: 499px)': { - right: '21% !important', + right: '21% !important' }, '@media(min-width: 626px) and (max-width: 720px)': { - right: '19% !important', + right: '19% !important' }, - '@media(min-width: 1423px) and (max-width: 1615px)': { - right: '17% !important', + '@media(min-width: 1423px) and (max-width: 1615px)': { + right: '17% !important' } }, supplyButton: { borderRadius: '8px', marginRight: '0%', marginLeft: '0%', - width: '100%', + width: '100%' }, tableTitle2: { color: '#000', @@ -318,7 +318,7 @@ export const useStyles = makeStyles({ display: 'inline-block', fontSize: '1.25rem', fontWeight: '500', - lineHeight: '30px', + lineHeight: '30px' }, tableTitle: { color: '#000', @@ -326,7 +326,7 @@ export const useStyles = makeStyles({ padding: ' 1.5rem 0 .5rem', fontSize: '1.25rem', fontWeight: '500', - lineHeight: '30px', + lineHeight: '30px' }, emptyStateText: { color: '#000', @@ -335,8 +335,8 @@ export const useStyles = makeStyles({ lineHeight: '30px', letterSpacing: '0.005em', '@media(min-width: 768px)': { - padding: '21px 35px 21px 30px !important', - }, + padding: '21px 35px 21px 30px !important' + } }, collateralPadding: {}, switchPadding: {}, @@ -347,7 +347,7 @@ export const useStyles = makeStyles({ opacity: '1 !important', backgroundColor: 'white', borderBottom: '0 !important', - boxShadow: 'inset 0 -1px 0 #E0E0E0', + boxShadow: 'inset 0 -1px 0 #E0E0E0' }, stickyCellRight: { position: 'sticky', @@ -356,11 +356,11 @@ export const useStyles = makeStyles({ opacity: '1 !important', backgroundColor: 'white', boxShadow: 'inset 0 -1px 0 #E0E0E0', - borderBottom: '0 !important', + borderBottom: '0 !important' }, stickyCellHover: { '.MuiTableRow-root:hover &': { - backgroundColor: '#F5FCFE', + backgroundColor: '#F5FCFE' } }, tooltipPrimaryText: { @@ -377,24 +377,24 @@ export const useStyles = makeStyles({ }, withdrawCell: { '@media(min-width: 1200px)': { - paddingRight: '1.5rem !important', + paddingRight: '1.5rem !important' }, - width: '17%', + width: '17%' }, repayCell: { '@media(min-width: 1200px)': { - paddingRight: '1.5rem !important', - }, + paddingRight: '1.5rem !important' + } }, borrowCellHeader: { '@media(min-width: 423px) and (max-width: 499px)': { - right: '21% !important', + right: '21% !important' }, '@media(min-width: 626px) and (max-width: 720px)': { - right: '19% !important', + right: '19% !important' }, - '@media(min-width: 1423px) and (max-width: 1615px)': { - right: '17% !important', + '@media(min-width: 1423px) and (max-width: 1615px)': { + right: '17% !important' } } }); diff --git a/src/ui/src/components/DashboardModal/index.js b/src/ui/src/components/DashboardModal/index.js index c1f8f2d5..a7a3f60e 100644 --- a/src/ui/src/components/DashboardModal/index.js +++ b/src/ui/src/components/DashboardModal/index.js @@ -25,6 +25,7 @@ import CustomizedProgressBars from '../ProgressBar'; import { useStyles } from './style'; import LightTooltip from '../Tooltip/LightTooltip'; +import { isRecoveryMode } from '../Constants'; const sanitizeInput = (input) => { let val = input.replace(/[^0-9.]/g, ''); @@ -73,7 +74,7 @@ const DashboardModal = (props) => { errorText, pendingLimit, pendingLimitUsed, - tab, + tab } = props; const [tabValue, setTabValue] = useState(tab); @@ -84,6 +85,7 @@ const DashboardModal = (props) => { const { address, underlyingBalances } = useSelector((state) => state.addWallet.account); const { totalCollateral } = useSelector((state) => state.supplyComposition.supplyComposition); const { borrowing, borrowLimit } = useSelector((state) => state.borrowComposition.borrowComposition); + const recoveryMode = isRecoveryMode(); const tezBalance = decimalify(underlyingBalances?.XTZ, decimals.XTZ); const isDisabled = !(tokenValue > 0 && address) || disabled; @@ -130,10 +132,10 @@ const DashboardModal = (props) => { title={ tokenDetails.walletBalance ? `${decimalify( - tokenDetails.walletBalance, - decimals[tokenDetails.title], - decimals[tokenDetails.title], - )} ${tokenDetails.banner}` + tokenDetails.walletBalance, + decimals[tokenDetails.title], + decimals[tokenDetails.title] + )} ${tokenDetails.banner}` : '' } placement="bottom" @@ -141,12 +143,12 @@ const DashboardModal = (props) => { {tokenDetails.walletBalance ? nFormatter( - decimalify( - tokenDetails.walletBalance, - decimals[tokenDetails.title], - decimals[tokenDetails.title], - ), - ) + decimalify( + tokenDetails.walletBalance, + decimals[tokenDetails.title], + decimals[tokenDetails.title] + ) + ) : '0'}{' '} {tokenDetails.banner} @@ -179,7 +181,7 @@ const DashboardModal = (props) => { inputProps={{ className: classes.inputText, pattern: '[0-9]+', - inputMode: 'decimal', + inputMode: 'decimal' }} className={classes.textField} /> @@ -241,41 +243,41 @@ const DashboardModal = (props) => { tabValue === 'one' ? tokenDetails.supply.balanceUnderlying > 0 ? `${decimalify( - tokenDetails.supply.balanceUnderlying, - decimals[tokenDetails.title], - decimals[tokenDetails.title], - )} ${tokenDetails.banner}` + tokenDetails.supply.balanceUnderlying, + decimals[tokenDetails.title], + decimals[tokenDetails.title] + )} ${tokenDetails.banner}` : '' : tokenDetails.borrow.balanceUnderlying > 0 - ? `${decimalify( - tokenDetails.borrow.balanceUnderlying, - decimals[tokenDetails.title], - decimals[tokenDetails.title], - )} ${tokenDetails.banner}` - : '' + ? `${decimalify( + tokenDetails.borrow.balanceUnderlying, + decimals[tokenDetails.title], + decimals[tokenDetails.title] + )} ${tokenDetails.banner}` + : '' }`} placement="bottom" > - {tabValue === 'one' && - (tokenDetails.supply.balanceUnderlying > 0 + {tabValue === 'one' + && (tokenDetails.supply.balanceUnderlying > 0 ? truncateNum( - decimalify( - tokenDetails.supply.balanceUnderlying, - decimals[tokenDetails.title], - decimals[tokenDetails.title], - ), - ) + decimalify( + tokenDetails.supply.balanceUnderlying, + decimals[tokenDetails.title], + decimals[tokenDetails.title] + ) + ) : '0')} - {tabValue === 'two' && - (tokenDetails.borrow.balanceUnderlying > 0 + {tabValue === 'two' + && (tokenDetails.borrow.balanceUnderlying > 0 ? truncateNum( - decimalify( - tokenDetails.borrow.balanceUnderlying, - decimals[tokenDetails.title], - decimals[tokenDetails.title], - ), - ) + decimalify( + tokenDetails.borrow.balanceUnderlying, + decimals[tokenDetails.title], + decimals[tokenDetails.title] + ) + ) : '0')}{' '} {tokenDetails.title} @@ -290,7 +292,7 @@ const DashboardModal = (props) => { title={`${decimalify( tokenDetails.balanceUnderlying, decimals[tokenDetails.title], - decimals[tokenDetails.title], + decimals[tokenDetails.title] )} ${tokenDetails.banner}`} placement="bottom" > @@ -299,8 +301,8 @@ const DashboardModal = (props) => { decimalify( tokenDetails.balanceUnderlying, decimals[tokenDetails.title], - decimals[tokenDetails.title], - ), + decimals[tokenDetails.title] + ) )}{' '} {tokenDetails.title} @@ -333,20 +335,20 @@ const DashboardModal = (props) => { sm={3} className={`${classes.modalText} ${classes.modalTextRight} ${classes.imgTitle}`} > - {tabValue === 'one' && - (tokenDetails.supplyRate > 0 + {tabValue === 'one' + && (tokenDetails.supplyRate > 0 ? roundValue(decimalify(tokenDetails.supplyRate, 18)) : '0')} - {tabValue === 'two' && - (tokenDetails.borrowRate > 0 || tokenValue !== '' + {tabValue === 'two' + && (tokenDetails.borrowRate > 0 || tokenValue !== '' ? roundValue( - decimalify( - tokenDetails.borrowRateFn( - undecimalify(tokenValue, decimals[tokenDetails.title]), - ), - 18, - ), - ) + decimalify( + tokenDetails.borrowRateFn( + undecimalify(tokenValue, decimals[tokenDetails.title]) + ), + 18 + ) + ) : '0')} {'%'} @@ -383,14 +385,13 @@ const DashboardModal = (props) => { visibility ? '' : classes.visibility }`} > - $ - {pendingLimit + {recoveryMode ? 'Unavailable' : <>{'$'}{pendingLimit ? pendingLimit > 0 ? nFormatter(pendingLimit, 2) : '0.00' : limit > 0 - ? nFormatter(limit, 2) - : '0.00'} + ? nFormatter(limit, 2) + : '0.00'}} @@ -413,29 +414,30 @@ const DashboardModal = (props) => { visibility ? '' : classes.visibility }`} > - {address && pendingLimitUsed + {recoveryMode ? 'Unavailable' : <>{address && pendingLimitUsed ? pendingLimitUsed > 0 ? pendingLimitUsed > 100 ? 100 : roundValue(pendingLimitUsed) : '0' : limitUsed > 0 - ? limitUsed > 100 - ? 100 - : roundValue(limitUsed) - : '0'} - % + ? limitUsed > 100 + ? 100 + : roundValue(limitUsed) + : '0'}%} - - + + {recoveryMode + ? Unavailable + : } diff --git a/src/ui/src/components/DashboardModal/style.js b/src/ui/src/components/DashboardModal/style.js index 8e25a019..952be282 100644 --- a/src/ui/src/components/DashboardModal/style.js +++ b/src/ui/src/components/DashboardModal/style.js @@ -1,282 +1,282 @@ /* eslint-disable import/prefer-default-export */ -import { makeStyles } from "@mui/styles"; +import { makeStyles } from '@mui/styles'; export const useStyles = makeStyles({ - root: { - "& .MuiDialog-paper": { - maxWidth: "472px", - textAlign: "center", - borderRadius: "0", - color: "#000", - height: "auto", - maxHeight: "calc(100% - 1rem)", - overflow: "auto", - "@media(min-width: 900px)": { - maxHeight: "calc(100% - 2.5rem)", - }, - "@media(max-width: 501px)": { - minWidth: "90%", - }, - }, - "& .MuiDialogTitle-root": { - padding: "1.625rem 2.5rem 0", - "@media(max-width: 501px)": { - paddingTop: "1.4rem", - }, - }, - "& .MuiDialogActions-root": { - padding: "2.75rem 2.875rem", - "@media(max-width: 768px)": { - padding: "1.875rem 2.875rem", - }, - "@media(max-width: 501px)": { - padding: "1.875rem 2rem 1.4rem", - }, - "@media(max-width: 320px)": { - paddingLeft: "1rem", - paddingRight: "1rem", - }, - }, - "& .MuiOutlinedInput-notchedOutline": { - border: "0", - }, - "& .MuiButton-text": { - textTransform: "none", - }, - "& .Mui-disabled": { - opacity: "0.5", - color: "#fff", - }, - }, - img: { - width: "1.5rem", - height: "1.5rem", - margin: "0 .5rem -.3rem 0", - "@media(max-width: 501px)": { - width: "1rem", - height: "1rem", - margin: "0 4px -3px 0", - }, - }, - imgTitle: { - display: "inline-block", - }, - fTokenImgCon: { - marginTop: "1rem", - overflow: "visible !important", - }, - fTokenImg: { - width: "5rem", - height: "5rem", - "@media(max-width: 501px)": { - width: "3rem", - height: "3rem", - }, - }, - headerText: { - fontWeight: "300", - fontSize: "1rem", - lineHeight: "1.875rem", - letterSpacing: "0.05em", - textAlign: "justify", - color: "#000", - opacity: "0.6", - "@media(min-width: 1024px)": { - margin: "1.25rem 3.125rem", - padding: "0", - }, - "@media(max-width: 500px)": { - fontSize: ".875rem", - }, - }, - contentBoxOne: { - padding: "1.5625rem 2.5rem !important", - borderTop: "2px solid #E0E0E0", - borderBottom: "2px solid #E0E0E0", - textAlign: "left", - "@media(max-width: 1200px)": { - padding: "1rem 2.5rem !important", - }, - "@media(max-width: 768px)": { - padding: "1.25rem 2.5rem !important", - }, - "@media(max-width: 501px)": { - padding: "1.25rem 2rem !important", - borderTop: "1px solid #E0E0E0", - borderBottom: "1px solid #E0E0E0", - }, - }, - contentBoxTwo: { - padding: "0 2.5rem .5rem", - "@media(max-width: 501px)": { - padding: "0 2rem ", - }, - }, - limit: { - paddingTop: "1.875rem !important", - paddingBottom: "1.25rem !important", - }, - limitUsed: { - paddingBottom: "1.25rem !important", - }, - progressBarCon: { - paddingBottom: "0 !important", - minHeight: "8px", - }, - btnMain: { - borderRadius: ".5rem", - width: "380px", - height: "48px", - fontSize: "1.25rem", - fontWeight: "500", - lineHeight: "1.875rem", - textTransform: "unset", - "@media(max-width: 501px)": { - width: "100%", - }, - }, - modalTextRight: { - whiteSpace: "nowrap", - textAlign: "end", - }, - close: { - position: "absolute", - top: "1rem", - right: "0.8125rem", - width: "1.5rem", - height: "1.5rem", - "&:hover": { - background: "transparent", - }, - }, - closeBtn: { - width: "1.5rem", - height: "1.5rem", - }, - faintFont: { - color: "#000", - opacity: "0.6", - }, - modalText: { - color: "#000", - opacity: "0.87", - fontSize: "1.25rem", - fontWeight: "300", - lineHeight: "30px", - "@media(max-width: 501px)": { - fontSize: "0.875rem", - }, - "@media(max-width: 320px)": { - fontSize: "0.75rem", - }, - }, - visibility: { - visibility: "hidden", - }, - formFieldCon: { - padding: "51px 0px 47px !important", - "@media(max-width: 768px)": { - padding: "35px 0px !important", - }, - "@media(max-width: 501px)": { - padding: "0 0px 1.25rem !important", - }, - }, - form: { - display: "flex", - alignItems: "center", - justifyContent: "space-evenly", - paddingRight: "51px", - paddingLeft: "127px", - "@media(max-width: 501px)": { - padding: "1rem", - justifyContent: "start", - }, - }, - textField: { - height: "3.5rem", - width: "186px", + root: { + '& .MuiDialog-paper': { + maxWidth: '472px', + textAlign: 'center', + borderRadius: '0', + color: '#000', + height: 'auto', + maxHeight: 'calc(100% - 1rem)', + overflow: 'auto', + '@media(min-width: 900px)': { + maxHeight: 'calc(100% - 2.5rem)' + }, + '@media(max-width: 501px)': { + minWidth: '90%' + } + }, + '& .MuiDialogTitle-root': { + padding: '1.625rem 2.5rem 0', + '@media(max-width: 501px)': { + paddingTop: '1.4rem' + } + }, + '& .MuiDialogActions-root': { + padding: '2.75rem 2.875rem', + '@media(max-width: 768px)': { + padding: '1.875rem 2.875rem' + }, + '@media(max-width: 501px)': { + padding: '1.875rem 2rem 1.4rem' + }, + '@media(max-width: 320px)': { + paddingLeft: '1rem', + paddingRight: '1rem' + } + }, + '& .MuiOutlinedInput-notchedOutline': { + border: '0' + }, + '& .MuiButton-text': { + textTransform: 'none' + }, + '& .Mui-disabled': { + opacity: '0.5', + color: '#fff' + } + }, + img: { + width: '1.5rem', + height: '1.5rem', + margin: '0 .5rem -.3rem 0', + '@media(max-width: 501px)': { + width: '1rem', + height: '1rem', + margin: '0 4px -3px 0' + } + }, + imgTitle: { + display: 'inline-block' + }, + fTokenImgCon: { + marginTop: '1rem', + overflow: 'visible !important' + }, + fTokenImg: { + width: '5rem', + height: '5rem', + '@media(max-width: 501px)': { + width: '3rem', + height: '3rem' + } + }, + headerText: { + fontWeight: '300', + fontSize: '1rem', + lineHeight: '1.875rem', + letterSpacing: '0.05em', + textAlign: 'justify', + color: '#000', + opacity: '0.6', + '@media(min-width: 1024px)': { + margin: '1.25rem 3.125rem', + padding: '0' + }, + '@media(max-width: 500px)': { + fontSize: '.875rem' + } + }, + contentBoxOne: { + padding: '1.5625rem 2.5rem !important', + borderTop: '2px solid #E0E0E0', + borderBottom: '2px solid #E0E0E0', + textAlign: 'left', + '@media(max-width: 1200px)': { + padding: '1rem 2.5rem !important' + }, + '@media(max-width: 768px)': { + padding: '1.25rem 2.5rem !important' + }, + '@media(max-width: 501px)': { + padding: '1.25rem 2rem !important', + borderTop: '1px solid #E0E0E0', + borderBottom: '1px solid #E0E0E0' + } + }, + contentBoxTwo: { + padding: '0 2.5rem .5rem', + '@media(max-width: 501px)': { + padding: '0 2rem ' + } + }, + limit: { + paddingTop: '1.875rem !important', + paddingBottom: '1.25rem !important' + }, + limitUsed: { + paddingBottom: '1.25rem !important' + }, + progressBarCon: { + paddingBottom: '0 !important', + minHeight: '8px' + }, + btnMain: { + borderRadius: '.5rem', + width: '380px', + height: '48px', + fontSize: '1.25rem', + fontWeight: '500', + lineHeight: '1.875rem', + textTransform: 'unset', + '@media(max-width: 501px)': { + width: '100%' + } + }, + modalTextRight: { + whiteSpace: 'nowrap', + textAlign: 'end' + }, + close: { + position: 'absolute', + top: '1rem', + right: '0.8125rem', + width: '1.5rem', + height: '1.5rem', + '&:hover': { + background: 'transparent' + } + }, + closeBtn: { + width: '1.5rem', + height: '1.5rem' + }, + faintFont: { + color: '#000', + opacity: '0.6' + }, + modalText: { + color: '#000', + opacity: '0.87', + fontSize: '1.25rem', + fontWeight: '300', + lineHeight: '30px', + '@media(max-width: 501px)': { + fontSize: '0.875rem' + }, + '@media(max-width: 320px)': { + fontSize: '0.75rem' + } + }, + visibility: { + visibility: 'hidden' + }, + formFieldCon: { + padding: '51px 0px 47px !important', + '@media(max-width: 768px)': { + padding: '35px 0px !important' + }, + '@media(max-width: 501px)': { + padding: '0 0px 1.25rem !important' + } + }, + form: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-evenly', + paddingRight: '51px', + paddingLeft: '127px', + '@media(max-width: 501px)': { + padding: '1rem', + justifyContent: 'start' + } + }, + textField: { + height: '3.5rem', + width: '186px', - "@media(max-width: 501px)": { - marginLeft: "16%", - }, - "@media(max-width: 320px)": { - width: "145px", - }, - }, - inputText: { - color: "#000", - opacity: "0.87", - fontSize: "40px", - fontWeight: 300, - textAlign: "center", - lineHeight: "60px", - letterSpacing: "0.01em", - height: "3.5rem", - padding: "0", - boxSizing: "border-box", - "&::placeholder": { - color: "#000", - opacity: "0,6", - }, - "@media(max-width: 501px)": { - fontSize: "2rem", + '@media(max-width: 501px)': { + marginLeft: '16%' + }, + '@media(max-width: 320px)': { + width: '145px' + } + }, + inputText: { + color: '#000', + opacity: '0.87', + fontSize: '40px', + fontWeight: 300, + textAlign: 'center', + lineHeight: '60px', + letterSpacing: '0.01em', + height: '3.5rem', + padding: '0', + boxSizing: 'border-box', + '&::placeholder': { + color: '#000', + opacity: '0,6' + }, + '@media(max-width: 501px)': { + fontSize: '2rem' + } }, - }, - tabBox: { - "@media(max-width: 501px)": { - marginRight: "-20px", - marginLeft: "-20px", - width: "calc(100% + 40px)", - }, - }, - inputBtn: { - fontSize: "1rem", - fontWeight: "300", - lineHeight: "1.875rem", - letterSpacing: "0.005em", - textAlign: "left", - color: "#4F4F4F", - "&:hover": { - color: "#3391F6", - background: "transparent", - }, - "&:disabled": { - color: "#4F4F4F", - }, - "@media(max-width: 501px)": { - fontSize: "0.78rem", - paddingLeft: "5px", - marginRight: "0px", - }, - "@media(max-width: 320px)": { - fontSize: "0.7rem", - }, - }, - warningText: { - marginTop: "0.5625rem", - color: "#000", - opacity: "0.87", - textAlign: "justify", - fontWeight: "300", - fontSize: "1rem", - lineHeight: "1.5rem", - letterSpacing: "0.005em", - "@media(max-width: 501px)": { - fontSize: "0.875rem", - }, - "@media(max-width: 320px)": { - fontSize: "0.75rem", - }, - }, - margin0: { - margin: "0 !important", - }, - borderBottom0: { - borderBottom: "0", - }, - borderTop0: { - borderTop: "0", - }, + tabBox: { + '@media(max-width: 501px)': { + marginRight: '-20px', + marginLeft: '-20px', + width: 'calc(100% + 40px)' + } + }, + inputBtn: { + fontSize: '1rem', + fontWeight: '300', + lineHeight: '1.875rem', + letterSpacing: '0.005em', + textAlign: 'left', + color: '#4F4F4F', + '&:hover': { + color: '#3391F6', + background: 'transparent' + }, + '&:disabled': { + color: '#4F4F4F' + }, + '@media(max-width: 501px)': { + fontSize: '0.78rem', + paddingLeft: '5px', + marginRight: '0px' + }, + '@media(max-width: 320px)': { + fontSize: '0.7rem' + } + }, + warningText: { + marginTop: '0.5625rem', + color: '#000', + opacity: '0.87', + textAlign: 'justify', + fontWeight: '300', + fontSize: '1rem', + lineHeight: '1.5rem', + letterSpacing: '0.005em', + '@media(max-width: 501px)': { + fontSize: '0.875rem' + }, + '@media(max-width: 320px)': { + fontSize: '0.75rem' + } + }, + margin0: { + margin: '0 !important' + }, + borderBottom0: { + borderBottom: '0' + }, + borderTop0: { + borderTop: '0' + } }); diff --git a/src/ui/src/components/Footer/style.js b/src/ui/src/components/Footer/style.js index 6c3153e4..75a406ca 100644 --- a/src/ui/src/components/Footer/style.js +++ b/src/ui/src/components/Footer/style.js @@ -5,18 +5,18 @@ export const useStyles = makeStyles({ root: { '& .MuiTypography-root': { '@media(min-width: 968px)': { - fontSize: '0.875rem', + fontSize: '0.875rem' }, '@media(max-width: 768px)': { - fontSize: '0.75rem', - }, + fontSize: '0.75rem' + } }, '& .MuiIconButton-root': { padding: '0', '&:hover': { - background: 'transparent', - }, - }, + background: 'transparent' + } + } }, footerCon: { height: '3.1875rem', @@ -30,8 +30,8 @@ export const useStyles = makeStyles({ borderTop: '1px solid rgba(0, 0, 0, 0.45)', '@media(max-width: 501px)': { paddingLeft: '0.5rem', - paddingRight: '0.5rem', - }, + paddingRight: '0.5rem' + } }, betaLink: { whiteSpace: 'nowrap', @@ -45,14 +45,14 @@ export const useStyles = makeStyles({ marginRight: '0.3rem', display: 'block', '@media(min-width: 968px)': { - fontSize: '0.875rem', + fontSize: '0.875rem' }, '@media(max-width: 768px)': { - fontSize: '0.75rem', + fontSize: '0.75rem' }, '@media(max-width: 501px)': { - marginLeft: '1rem', - }, + marginLeft: '1rem' + } }, grid: { display: 'flex', @@ -62,8 +62,8 @@ export const useStyles = makeStyles({ justifyContent: 'space-between', textAlign: 'start', paddingLeft: '0px', - marginLeft: '0px', - }, + marginLeft: '0px' + } }, footerLink: { color: '#000', @@ -75,17 +75,17 @@ export const useStyles = makeStyles({ textDecoration: 'none', marginLeft: '1.0625rem', '@media(min-width: 968px)': { - fontSize: '0.875rem', + fontSize: '0.875rem' }, '@media(max-width: 768px)': { - fontSize: '0.75rem', + fontSize: '0.75rem' }, '@media(max-width: 501px)': { - marginLeft: '0.3rem', - }, + marginLeft: '0.3rem' + } }, activeLink: { - borderBottom: '1px solid #000', + borderBottom: '1px solid #000' }, icon: { width: '1.25rem', @@ -93,12 +93,12 @@ export const useStyles = makeStyles({ marginLeft: '1.6875rem', '@media(max-width: 501px)': { marginLeft: '0.3rem', - marginRight: '0.3rem', - }, + marginRight: '0.3rem' + } }, copyrightIcon: { width: '1.3125rem', height: '1.3125rem', - marginTop: '-4px', - }, + marginTop: '-4px' + } }); diff --git a/src/ui/src/components/Header/composition.js b/src/ui/src/components/Header/composition.js index ee562a02..71881acf 100644 --- a/src/ui/src/components/Header/composition.js +++ b/src/ui/src/components/Header/composition.js @@ -11,6 +11,7 @@ import StackedBars from '../StackedBars/index.tsx'; import { nFormatter } from '../../util'; import { useStyles } from './style'; +import { isRecoveryMode } from '../Constants'; const Composition = (props) => { const classes = useStyles(); @@ -19,15 +20,20 @@ const Composition = (props) => { } = props; const { totalCollateral } = useSelector((state) => state.supplyComposition.supplyComposition); + const recoveryMode = isRecoveryMode(); + let compositionContent = Unavailable; + if (!recoveryMode && supplyBar) { + compositionContent = ; + } + if (!recoveryMode && !supplyBar) { + compositionContent = ; + } return ( {title} - {supplyBar - ? - : - } + {compositionContent} @@ -38,12 +44,12 @@ const Composition = (props) => { {dataTitle} - ${( + {recoveryMode ? 'Unavailable' : <>{'$'}{( ((data.supplying > 0) && nFormatter(data.supplying)) || ((data.borrowing > 0) && nFormatter(data.borrowing)) ) || '0.00' - } + }} @@ -54,13 +60,13 @@ const Composition = (props) => { {dataLimitTitle} - ${( + {recoveryMode ? 'Unavailable' : <>{'$'}{( ((data.collateralized > 0) && nFormatter(data.collateralized)) // displaying borrow limit as the total collateral value without deducting value borrowed || ((totalCollateral > 0) && nFormatter(totalCollateral)) ) || '0.00' - } + }} diff --git a/src/ui/src/components/Header/index.js b/src/ui/src/components/Header/index.js index d6542242..408feeae 100644 --- a/src/ui/src/components/Header/index.js +++ b/src/ui/src/components/Header/index.js @@ -1,22 +1,20 @@ // eslint-disable-next-line no-use-before-define -import React, { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; +import React, { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; -import Grid from "@mui/material/Grid"; -import { Typography } from "@mui/material"; +import Grid from '@mui/material/Grid'; +import { Typography } from '@mui/material'; -import Composition from "./composition"; +import Composition from './composition'; -import { HeaderCon, classes1, useStyles } from "./style"; -import supplyingIcon from "../../assets/supplyingIcon.svg"; -import collateralizedIcon from "../../assets/collateralizedIcon.svg"; -import borrowingIcon from "../../assets/borrowing.svg"; -import borrowLimitIcon from "../../assets/borrowLimitIcon.svg"; -import questionCircle from "../../assets/questionCircle.svg"; +import { HeaderCon, classes1, useStyles } from './style'; +import supplyingIcon from '../../assets/supplyingIcon.svg'; +import collateralizedIcon from '../../assets/collateralizedIcon.svg'; +import borrowingIcon from '../../assets/borrowing.svg'; +import borrowLimitIcon from '../../assets/borrowLimitIcon.svg'; -import { supplyCompositionAction } from "../../reduxContent/supplyComposition/actions"; -import { borrowCompositionAction } from "../../reduxContent/borrowComposition/actions"; -import { LightTooltip } from "../StackedBars/style.tsx"; +import { supplyCompositionAction } from '../../reduxContent/supplyComposition/actions'; +import { borrowCompositionAction } from '../../reduxContent/borrowComposition/actions'; // eslint-disable-next-line import/no-dynamic-require const config = require(`../../library/${process.env.REACT_APP_ENV || 'mainnet'}-network-config.json`); @@ -40,8 +38,8 @@ const Header = () => { return ( - {network !== "mainnet" && - `Note: Tezfin is currently operating on the Tezos test network ${networkType}.`} + {network !== 'mainnet' + && `Note: Tezfin is currently operating on the Tezos test network ${networkType}.`} { const classes = useStyles(); const dispatch = useDispatch(); @@ -76,7 +75,7 @@ const Nav = () => { onClose={() => setPopover(null)} anchorOrigin={{ vertical: 'bottom', - horizontal: 'left', + horizontal: 'left' }} className={classes.root} > diff --git a/src/ui/src/components/Header/style.js b/src/ui/src/components/Header/style.js index d61656e9..3c0ffe42 100644 --- a/src/ui/src/components/Header/style.js +++ b/src/ui/src/components/Header/style.js @@ -106,11 +106,11 @@ export const useStyles = makeStyles({ paddingLeft: '0rem' }, '@media(min-width: 1024px)': { - paddingLeft: '0.9375rem', + paddingLeft: '0.9375rem' }, '@media(min-width: 1200px)': { - paddingLeft: '0.875rem', - }, + paddingLeft: '0.875rem' + } }, compositionTitle: { color: '#000', @@ -222,26 +222,26 @@ export const useStyles = makeStyles({ progressBarOne: { paddingTop: '7px', '@media(min-width: 600px)': { - paddingRight: '1rem', + paddingRight: '1rem' }, '@media(min-width: 900px)': { - paddingRight: '2rem', + paddingRight: '2rem' }, '@media(min-width: 1024px)': { - paddingRight: '2rem', + paddingRight: '2rem' }, '@media(min-width: 1200px)': { - paddingRight: '3rem', - }, + paddingRight: '3rem' + } }, progressBarTwo: { paddingTop: '7px', '@media(min-width: 600px)': { - paddingRight: '1rem', + paddingRight: '1rem' }, '@media(min-width: 900px)': { - paddingRight: '2rem', - }, + paddingRight: '2rem' + } }, addWalletCon: { paddingTop: '2.1875rem', diff --git a/src/ui/src/components/ProgressBar/index.js b/src/ui/src/components/ProgressBar/index.js index a0e584dc..e71de09c 100644 --- a/src/ui/src/components/ProgressBar/index.js +++ b/src/ui/src/components/ProgressBar/index.js @@ -3,10 +3,8 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress'; import { Typography, tooltipClasses } from '@mui/material'; -import LightTooltip from '../Tooltip/LightTooltip'; - +import LightTooltip from '../Tooltip/LightTooltip.tsx'; import { progressBarStyles, tooltipStyles } from './style'; import { truncateNum } from '../../util'; diff --git a/src/ui/src/components/StackedBars/style.tsx b/src/ui/src/components/StackedBars/style.tsx index 9dae36da..c8b87d2f 100644 --- a/src/ui/src/components/StackedBars/style.tsx +++ b/src/ui/src/components/StackedBars/style.tsx @@ -1,7 +1,6 @@ import { makeStyles } from '@mui/styles'; -import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; -import { styled } from '@mui/material/styles'; +// eslint-disable-next-line import/prefer-default-export export const useStyles = makeStyles({ progressBar: { display: 'flex', diff --git a/src/ui/src/components/StatusModal/SuccessModal.js b/src/ui/src/components/StatusModal/SuccessModal.js index c4fcedf6..0f063496 100644 --- a/src/ui/src/components/StatusModal/SuccessModal.js +++ b/src/ui/src/components/StatusModal/SuccessModal.js @@ -12,7 +12,7 @@ import useStyles from './style'; const SuccessModal = (props) => { const classes = useStyles(); const { - open, close, token, tokenText, amount + open, close, token, tokenText } = props; return ( diff --git a/src/ui/src/components/SupplyModal/index.js b/src/ui/src/components/SupplyModal/index.js index cf0f1ddc..898f4bd8 100644 --- a/src/ui/src/components/SupplyModal/index.js +++ b/src/ui/src/components/SupplyModal/index.js @@ -46,7 +46,7 @@ const SupplyModal = (props) => { const [tokenValue, setTokenValue] = useState(''); const [currentTab, setCurrentTab] = useState(''); - const buttonOne = useSupplyErrorText(tokenValue, useMaxAmount); + const buttonOne = useSupplyErrorText(tokenValue, useMaxAmount, tokenDetails); const buttonTwo = useWithdrawErrorText(tokenValue, useMaxAmount, tokenDetails); const handleOpenInitialize = () => setInitializeModal(true); @@ -56,16 +56,20 @@ const SupplyModal = (props) => { const handleCloseError = () => setErrorModal(false); const supplyToken = async () => { - // eslint-disable-next-line no-shadow - const { opGroup, error } = await supplyTokenAction(tokenDetails, amount, close, setTokenText, handleOpenInitialize, protocolAddresses, publicKeyHash); - setOpGroup(opGroup); - setEvaluationError(error); + if (!tokenDetails.isListed || tokenDetails.mintPaused) { + return; + } + const { opGroup: nextOpGroup, error: nextError } = await supplyTokenAction(tokenDetails, amount, close, setTokenText, handleOpenInitialize, protocolAddresses, publicKeyHash); + setOpGroup(nextOpGroup); + setEvaluationError(nextError); }; const withdrawToken = async () => { + if (!tokenDetails.isListed || tokenDetails.redeemPaused) { + return; + } if (amount === undecimalify(useMaxAmount, decimals[tokenDetails.title])) { - // eslint-disable-next-line no-shadow - const { opGroup, error } = await withdrawTokenAction( + const { opGroup: nextOpGroup, error: nextError } = await withdrawTokenAction( tokenDetails, tokenDetails.balance.toString(), false, @@ -73,12 +77,12 @@ const SupplyModal = (props) => { setTokenText, handleOpenInitialize, protocolAddresses, - publicKeyHash, + publicKeyHash ); - setOpGroup(opGroup); - setEvaluationError(error); + setOpGroup(nextOpGroup); + setEvaluationError(nextError); } else { - const { opGroup, error } = await withdrawTokenAction( + const { opGroup: nextOpGroup, error: nextError } = await withdrawTokenAction( tokenDetails, amount, true, @@ -86,10 +90,10 @@ const SupplyModal = (props) => { setTokenText, handleOpenInitialize, protocolAddresses, - publicKeyHash, + publicKeyHash ); - setOpGroup(opGroup); - setEvaluationError(error); + setOpGroup(nextOpGroup); + setEvaluationError(nextError); } }; diff --git a/src/ui/src/components/Switch/index.js b/src/ui/src/components/Switch/index.js index 6bf96c1b..3a7c07ee 100644 --- a/src/ui/src/components/Switch/index.js +++ b/src/ui/src/components/Switch/index.js @@ -3,16 +3,17 @@ import React, { forwardRef } from 'react'; import { styled } from '@mui/system'; import { useSwitch } from '@mui/core/SwitchUnstyled'; import MuiSwitch from '@mui/material/Switch'; +import { isRecoveryMode } from '../Constants'; const CustomSwitch = styled( forwardRef((props, ref) => ( - )), + )) )(({ theme }) => ({ width: 42, height: 26, @@ -31,21 +32,20 @@ const CustomSwitch = styled( transform: 'translateX(18px) scale(1)', color: '#EADDFF', '& + .MuiSwitch-track': { - border: '0px solid red', backgroundColor: '#9F329F', opacity: 1, - border: 0, + border: 0 }, '&.Mui-disabled': { border: '6px solid #fff', - opacity: 0.5, - }, + opacity: 0.5 + } }, '&.Mui-focusVisible .MuiSwitch-thumb': {}, - '&.Mui-disabled + .MuiSwitch-thumb': {}, + '&.Mui-disabled + .MuiSwitch-thumb': {} }, '& .MuiSwitch-thumb': { - boxSizing: 'border-box', + boxSizing: 'border-box' }, '& .MuiSwitch-track': { border: '2px solid #79747E', @@ -53,9 +53,9 @@ const CustomSwitch = styled( backgroundColor: '#E6E0E9', opacity: 1, transition: theme.transitions.create(['background-color'], { - duration: 500, - }), - }, + duration: 500 + }) + } })); const BasicSwitch = forwardRef((props, ref) => { @@ -63,7 +63,7 @@ const BasicSwitch = forwardRef((props, ref) => { return ( { // Switch component export default function Switch(props) { const { data } = props; + const recoveryMode = isRecoveryMode(); return ( -
- +
event.stopPropagation() : undefined} + > +
); } diff --git a/src/ui/src/components/Tooltip/LightTooltip.tsx b/src/ui/src/components/Tooltip/LightTooltip.tsx index 83d77f05..491038eb 100644 --- a/src/ui/src/components/Tooltip/LightTooltip.tsx +++ b/src/ui/src/components/Tooltip/LightTooltip.tsx @@ -15,13 +15,13 @@ const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( boxShadow: `0px 4px 4px -1px rgba(12, 12, 13, 0.05), 0px 4px 4px -1px rgba(12, 12, 13, 0.1)`, border: '3px solid #2157E4', - borderRadius: 8, + borderRadius: 8 }, [`& .${tooltipClasses.arrow}`]: { color: 'rgba(255, 255, 255, 1)', filter: 'drop-shadow(0px 2px 4px 0px rgba(0, 0, 0, 0.5))', '&:before': { - border: '3px solid #2157E4', + border: '3px solid #2157E4' } } })); diff --git a/src/ui/src/reduxContent/addWallet/reducers.js b/src/ui/src/reduxContent/addWallet/reducers.js index a2e662ad..89c7f4b2 100644 --- a/src/ui/src/reduxContent/addWallet/reducers.js +++ b/src/ui/src/reduxContent/addWallet/reducers.js @@ -5,16 +5,17 @@ const initState = { account: {} }; -const addWalletReducer = (state = initState, action) => { +const addWalletReducer = (state, action) => { + const currentState = state || initState; switch (action.type) { case GET_ACCOUNT: return { - ...state, + ...currentState, isFetching: false, account: action.payload }; default: - return state; + return currentState; } }; diff --git a/src/ui/src/reduxContent/borrowComposition/reducers.js b/src/ui/src/reduxContent/borrowComposition/reducers.js index 1eab8996..0a8d0e28 100644 --- a/src/ui/src/reduxContent/borrowComposition/reducers.js +++ b/src/ui/src/reduxContent/borrowComposition/reducers.js @@ -5,16 +5,17 @@ const initState = { borrowComposition: {} }; -const borrowCompositionReducer = (state = initState, action) => { +const borrowCompositionReducer = (state, action) => { + const currentState = state || initState; switch (action.type) { case GET_BORROW_COMPOSITION_DATA: return { - ...state, + ...currentState, isFetching: false, borrowComposition: action.payload }; default: - return state; + return currentState; } }; diff --git a/src/ui/src/reduxContent/market/actions.js b/src/ui/src/reduxContent/market/actions.js index 75e3f7ad..bd3b99c4 100644 --- a/src/ui/src/reduxContent/market/actions.js +++ b/src/ui/src/reduxContent/market/actions.js @@ -1,6 +1,8 @@ import { BigNumber } from 'bignumber.js'; import { TezosLendingPlatform } from 'tezoslendingplatformjs'; -import { GET_ALL_MARKET_DATA, GET_BORROWED_MARKET_DATA, GET_MARKET_DATA, GET_SUPPLIED_MARKET_DATA } from './types'; +import { + GET_ALL_MARKET_DATA, GET_BORROWED_MARKET_DATA, GET_MARKET_DATA, GET_SUPPLIED_MARKET_DATA +} from './types'; import { formatTokenData } from '../../util'; import { tokens } from '../../components/Constants'; @@ -31,31 +33,40 @@ export const allMarketAction = (account, markets) => (dispatch) => { const suppliedMarket = TezosLendingPlatform.getSuppliedMarkets(account, markets); const borrowedMarket = TezosLendingPlatform.getBorrowedMarkets(account, markets); - marketTokens.map((token) => { - if (Object.keys(markets).length > 0 && markets.hasOwnProperty(token.assetType)) { - token.supply = { ...suppliedMarket[token.assetType] }; - token.borrow = { ...borrowedMarket[token.assetType] }; - token.usdPrice = new BigNumber(markets[token.assetType].currentPrice.toString()) - .div(new BigNumber(10).pow(new BigNumber(6))) - .toFixed(4); - token.marketSize = markets[token.assetType].supply.totalAmount.toString(); - token.totalBorrowed = markets[token.assetType].borrow.totalAmount.toString(); - token.available = markets[token.assetType].available; - token.supplyRate = markets[token.assetType].supply.rate.toString(); - token.borrowRate = markets[token.assetType].borrow.rate.toString(); - token.borrowRateFn = markets[token.assetType].borrow.rateFn; - token.walletBalance = ''; - token.collateralFactor = new BigNumber(markets[token.assetType].collateralFactor.toString()) - .div(new BigNumber(10).pow(new BigNumber(18))) - .toFixed(); - token.redeemPaused = markets[token.assetType].redeemPaused; - if (Object.keys(walletBalance).length > 0 && walletBalance.hasOwnProperty(token.assetType)) { - token.walletBalance = walletBalance[token.assetType].toString(); - } + const formattedTokens = marketTokens.map((token) => { + if (Object.keys(markets).length > 0 && Object.prototype.hasOwnProperty.call(markets, token.assetType)) { + const market = markets[token.assetType]; + const tokenWalletBalance = Object.keys(walletBalance).length > 0 + && Object.prototype.hasOwnProperty.call(walletBalance, token.assetType) + ? walletBalance[token.assetType].toString() + : ''; + + return { + ...token, + supply: { ...suppliedMarket[token.assetType] }, + borrow: { ...borrowedMarket[token.assetType] }, + usdPrice: new BigNumber(market.currentPrice.toString()) + .div(new BigNumber(10).pow(new BigNumber(6))) + .toFixed(4), + marketSize: market.supply.totalAmount.toString(), + totalBorrowed: market.borrow.totalAmount.toString(), + available: market.available, + supplyRate: market.supply.rate.toString(), + borrowRate: market.borrow.rate.toString(), + borrowRateFn: market.borrow.rateFn, + walletBalance: tokenWalletBalance, + collateralFactor: new BigNumber(market.collateralFactor.toString()) + .div(new BigNumber(10).pow(new BigNumber(18))) + .toFixed(), + isListed: market.isListed, + mintPaused: market.mintPaused, + borrowPaused: market.borrowPaused, + redeemPaused: market.redeemPaused + }; } - return marketTokens; + return { ...token }; }); - dispatch({ type: GET_ALL_MARKET_DATA, payload: marketTokens }); + dispatch({ type: GET_ALL_MARKET_DATA, payload: formattedTokens }); }; /** @@ -74,11 +85,14 @@ export const suppliedMarketAction = (markets) => (dispatch) => { logo, fLogo, usdPrice, - address, + address, walletBalance, collateralFactor, + isListed, + mintPaused, + borrowPaused, redeemPaused, - supply, + supply }) => ({ assetType, banner, @@ -87,17 +101,20 @@ export const suppliedMarketAction = (markets) => (dispatch) => { logo, fLogo, usdPrice, - address, + address, walletBalance, collateralFactor, + isListed, + mintPaused, + borrowPaused, redeemPaused, - ...supply, - }), + ...supply + }) ); dispatch({ type: GET_SUPPLIED_MARKET_DATA, - payload: formatTokenData(suppliedTokens), + payload: formatTokenData(suppliedTokens) }); }; @@ -110,22 +127,41 @@ export const suppliedMarketAction = (markets) => (dispatch) => { export const borrowedMarketAction = (markets) => (dispatch) => { // eslint-disable-next-line object-curly-newline const borrowedTokens = markets.map( - ({ assetType, banner, title, name, logo, address, usdPrice, walletBalance, collateralFactor, borrow }) => ({ + ({ assetType, banner, title, name, logo, + address, usdPrice, - address, walletBalance, collateralFactor, - ...borrow, - }), + isListed, + mintPaused, + borrowPaused, + redeemPaused, + borrow + }) => ({ + assetType, + banner, + title, + name, + logo, + usdPrice, + address, + walletBalance, + collateralFactor, + isListed, + mintPaused, + borrowPaused, + redeemPaused, + ...borrow + }) ); dispatch({ type: GET_BORROWED_MARKET_DATA, - payload: formatTokenData(borrowedTokens), + payload: formatTokenData(borrowedTokens) }); }; diff --git a/src/ui/src/reduxContent/market/reducers.js b/src/ui/src/reduxContent/market/reducers.js index d11d8427..ee9b7f8a 100644 --- a/src/ui/src/reduxContent/market/reducers.js +++ b/src/ui/src/reduxContent/market/reducers.js @@ -13,34 +13,35 @@ const initState = { borrowedMarkets: [] }; -const marketReducer = (state = initState, action) => { +const marketReducer = (state, action) => { + const currentState = state || initState; switch (action.type) { case GET_MARKET_DATA: return { - ...state, + ...currentState, isFetching: false, markets: action.payload }; case GET_ALL_MARKET_DATA: return { - ...state, + ...currentState, isFetching: false, allMarkets: action.payload }; case GET_SUPPLIED_MARKET_DATA: return { - ...state, + ...currentState, isFetching: false, suppliedMarkets: action.payload }; case GET_BORROWED_MARKET_DATA: return { - ...state, + ...currentState, isFetching: false, borrowedMarkets: action.payload }; default: - return state; + return currentState; } }; diff --git a/src/ui/src/reduxContent/nodes/actions.js b/src/ui/src/reduxContent/nodes/actions.js index f5475678..4a51fac3 100644 --- a/src/ui/src/reduxContent/nodes/actions.js +++ b/src/ui/src/reduxContent/nodes/actions.js @@ -1,11 +1,12 @@ import { - Comptroller -} from "tezoslendingplatformjs"; + Comptroller +} from 'tezoslendingplatformjs'; import { GET_COMPTROLLER, GET_PROTOCOL_ADDRESSES, GET_TEZOS_NODE } from './types'; import { getAddresses } from '../../components/Constants'; +// eslint-disable-next-line import/no-dynamic-require const config = require(`../../library/${ - process.env.REACT_APP_ENV || "mainnet" + process.env.REACT_APP_ENV || 'mainnet' }-network-config.json`); const addresses = getAddresses(); diff --git a/src/ui/src/reduxContent/nodes/reducers.js b/src/ui/src/reduxContent/nodes/reducers.js index cf52a415..bb5d47dd 100644 --- a/src/ui/src/reduxContent/nodes/reducers.js +++ b/src/ui/src/reduxContent/nodes/reducers.js @@ -6,25 +6,26 @@ const initState = { comptroller: undefined }; -const nodesReducer = (state = initState, action) => { +const nodesReducer = (state, action) => { + const currentState = state || initState; switch (action.type) { case GET_TEZOS_NODE: return { - ...state, + ...currentState, tezosNode: action.payload }; case GET_PROTOCOL_ADDRESSES: return { - ...state, + ...currentState, protocolAddresses: action.payload }; case GET_COMPTROLLER: return { - ...state, + ...currentState, comptroller: action.payload }; default: - return state; + return currentState; } }; diff --git a/src/ui/src/reduxContent/supplyComposition/reducers.js b/src/ui/src/reduxContent/supplyComposition/reducers.js index fe2f4005..d734d111 100644 --- a/src/ui/src/reduxContent/supplyComposition/reducers.js +++ b/src/ui/src/reduxContent/supplyComposition/reducers.js @@ -5,16 +5,17 @@ const initState = { supplyComposition: {} }; -const supplyCompositionReducer = (state = initState, action) => { +const supplyCompositionReducer = (state, action) => { + const currentState = state || initState; switch (action.type) { case GET_SUPPLY_COMPOSITION_DATA: return { - ...state, + ...currentState, isFetching: false, supplyComposition: action.payload }; default: - return state; + return currentState; } }; diff --git a/src/ui/src/setupTests.js b/src/ui/src/setupTests.js index 8f2609b7..91b9a666 100644 --- a/src/ui/src/setupTests.js +++ b/src/ui/src/setupTests.js @@ -3,3 +3,7 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; +import { TextDecoder, TextEncoder } from 'util'; + +global.TextDecoder = TextDecoder; +global.TextEncoder = TextEncoder; diff --git a/src/ui/src/theme.js b/src/ui/src/theme.js index 5fe8178d..6fd7726d 100644 --- a/src/ui/src/theme.js +++ b/src/ui/src/theme.js @@ -17,31 +17,31 @@ const theme = createTheme(adaptV4Theme({ MuiCssBaseline: { styleOverrides: { html: { - fontFamily: 'Inter, sans-serif', + fontFamily: 'Inter, sans-serif' }, body: { - fontFamily: 'Inter, sans-serif', + fontFamily: 'Inter, sans-serif' } } }, MuiButton: { styleOverrides: { root: { - fontFamily: 'Inter, sans-serif', + fontFamily: 'Inter, sans-serif' } } }, MuiTableCell: { styleOverrides: { root: { - fontFamily: 'Inter, sans-serif', + fontFamily: 'Inter, sans-serif' } } }, MuiTypography: { styleOverrides: { root: { - fontFamily: 'Inter, sans-serif', + fontFamily: 'Inter, sans-serif' } } } diff --git a/src/ui/src/util/index.js b/src/ui/src/util/index.js index 9bacac4f..a8933b18 100644 --- a/src/ui/src/util/index.js +++ b/src/ui/src/util/index.js @@ -1,6 +1,5 @@ import { TezosToolkit, OpKind } from '@taquito/taquito'; import { BeaconWallet } from '@taquito/beacon-wallet'; -import { NetworkType } from '@taquito/beacon-wallet/types'; import { BigNumber } from 'bignumber.js'; import bigInt from 'big-integer'; @@ -12,7 +11,7 @@ const Tezos = new TezosToolkit(config.infra.tezosNode); // For mainnet/shadownet omit rpcUrl: some mobile wallets (e.g. Kukai iOS) treat a custom // rpcUrl as a different network and reject the permission request. const beaconNetwork = config.infra.network === 'tezosx-previewnet' - ? { type: NetworkType.CUSTOM, rpcUrl: config.infra.tezosNode, name: 'TezosX Previewnet' } + ? { type: 'custom', rpcUrl: config.infra.tezosNode, name: 'TezosX Previewnet' } : { type: config.infra.network }; const wallet = new BeaconWallet({ name: config.dappName, network: beaconNetwork }); Tezos.setWalletProvider(wallet); @@ -69,12 +68,12 @@ export const evaluateTransaction = async (operations) => { // On previewnet estimation is unreliable — let the wallet estimate if (config.infra.network === 'tezosx-previewnet') { const batch = Tezos.wallet.batch( - operations.map(op => ({ ...op, kind: OpKind.TRANSACTION })) + operations.map((op) => ({ ...op, kind: OpKind.TRANSACTION })) ); return { opGroup: batch }; } // estimate each operation to get gas/storage/fee limits - const paramsWithKind = operations.map(op => ({ ...op, kind: OpKind.TRANSACTION })); + const paramsWithKind = operations.map((op) => ({ ...op, kind: OpKind.TRANSACTION })); const estimates = await Tezos.estimate.batch(paramsWithKind); // apply estimates to operations const estimatedOps = operations.map((op, i) => ({ @@ -82,12 +81,11 @@ export const evaluateTransaction = async (operations) => { kind: OpKind.TRANSACTION, gasLimit: estimates[i].gasLimit, storageLimit: estimates[i].storageLimit, - fee: estimates[i].suggestedFeeMutez, + fee: estimates[i].suggestedFeeMutez })); const batch = Tezos.wallet.batch(estimatedOps); return { opGroup: batch }; } catch (error) { - console.log('evaluateTX', error); return { error }; } }; @@ -104,7 +102,6 @@ export const confirmTransaction = async (batch) => { const op = await batch.send(); return { response: op }; } catch (error) { - console.log(error); return { error }; } }; @@ -121,7 +118,6 @@ export const verifyTransaction = async (op) => { const confirm = await op.confirmation(1); return { confirm }; } catch (error) { - console.log(error); return { error }; } }; @@ -190,10 +186,7 @@ export const roundValue = (num, decimals = 2) => BigNumber(num).toFixed(decimals // eslint-disable-next-line default-param-last export const nFormatter = (num, formatDecimals = 4) => { const suffix = [ - { value: 1, symbol: '' }, - { value: 1e3, symbol: 'k' }, - { value: 1e6, symbol: 'M' }, - { value: 1e9, symbol: 'B' }, + { value: 1, symbol: '' }, { value: 1e3, symbol: 'k' }, { value: 1e6, symbol: 'M' }, { value: 1e9, symbol: 'B' } ]; let i; // eslint-disable-next-line no-plusplus diff --git a/src/ui/src/util/maxAction.js b/src/ui/src/util/maxAction.js index 45f7b8af..b6f6dd93 100644 --- a/src/ui/src/util/maxAction.js +++ b/src/ui/src/util/maxAction.js @@ -17,20 +17,20 @@ export const supplyingMaxAction = (tabValue, tokenDetails, setMaxAmount) => { if (tokenDetails.title.toLowerCase() === 'xtz'.toLowerCase()) { decimalify(tokenDetails.walletBalance, decimals[tokenDetails.title]) > 0.1 ? setMaxAmount( - decimalify( - tokenDetails.walletBalance.toString(), - decimals[tokenDetails.title], - decimals[tokenDetails.title], - ) - 0.1, - ) + decimalify( + tokenDetails.walletBalance.toString(), + decimals[tokenDetails.title], + decimals[tokenDetails.title] + ) - 0.1 + ) : setMaxAmount(0); } else { setMaxAmount( decimalify( tokenDetails.walletBalance.toString(), decimals[tokenDetails.title], - decimals[tokenDetails.title], - ), + decimals[tokenDetails.title] + ) ); } } @@ -39,8 +39,8 @@ export const supplyingMaxAction = (tabValue, tokenDetails, setMaxAmount) => { decimalify( tokenDetails.balanceUnderlying.toString(), decimals[tokenDetails.title], - decimals[tokenDetails.title], - ), + decimals[tokenDetails.title] + ) ); } }; @@ -59,7 +59,7 @@ export const borrowingMaxAction = (tabValue, tokenDetails, borrowLimit, setMaxAm new BigNumber(borrowLimit) .dividedBy(new BigNumber(tokenDetails.usdPrice)) .dividedBy(new BigNumber(2)) - .toFixed(decimals[tokenDetails.title]), + .toFixed(decimals[tokenDetails.title]) ); limit >= 0 ? setMaxAmount(limit) : setMaxAmount(0); } @@ -69,8 +69,8 @@ export const borrowingMaxAction = (tabValue, tokenDetails, borrowLimit, setMaxAm decimalify( tokenDetails.outstandingLoan.toString(), decimals[tokenDetails.title], - decimals[tokenDetails.title], - ), + decimals[tokenDetails.title] + ) ); } }; @@ -88,20 +88,20 @@ export const marketsMaxAction = (tabValue, tokenDetails, borrowLimit, setMaxAmou if (tokenDetails.title.toLowerCase() === 'xtz'.toLowerCase()) { decimalify(tokenDetails.walletBalance, decimals[tokenDetails.title]) > 0.1 ? setMaxAmount( - decimalify( - tokenDetails.walletBalance.toString(), - decimals[tokenDetails.title], - decimals[tokenDetails.title], - ) - 0.1, - ) + decimalify( + tokenDetails.walletBalance.toString(), + decimals[tokenDetails.title], + decimals[tokenDetails.title] + ) - 0.1 + ) : setMaxAmount(0); } else { setMaxAmount( decimalify( tokenDetails.walletBalance.toString(), decimals[tokenDetails.title], - decimals[tokenDetails.title], - ), + decimals[tokenDetails.title] + ) ); } } @@ -110,7 +110,7 @@ export const marketsMaxAction = (tabValue, tokenDetails, borrowLimit, setMaxAmou new BigNumber(borrowLimit) .dividedBy(new BigNumber(tokenDetails.usdPrice)) .dividedBy(new BigNumber(2)) - .toFixed(decimals[tokenDetails.title]), + .toFixed(decimals[tokenDetails.title]) ); limit >= 0 ? setMaxAmount(limit) : setMaxAmount(0); } diff --git a/src/ui/src/util/modalHooks.js b/src/ui/src/util/modalHooks.js index a024f845..4eb74e27 100644 --- a/src/ui/src/util/modalHooks.js +++ b/src/ui/src/util/modalHooks.js @@ -13,22 +13,29 @@ import { decimalify } from './index'; * @param tokenValue amount to be supplied. * @param limit Max amount a user is able to supply in a transaction. */ -export const useSupplyErrorText = (tokenValue, limit) => { +export const useSupplyErrorText = (tokenValue, limit, tokenDetails) => { const [text, setText] = useState('Supply'); const [errorText, setErrorText] = useState(''); const [disabled, setDisabled] = useState(false); useEffect(() => { - if (new BigNumber(tokenValue).gt(new BigNumber(limit))) { + if (!tokenDetails.isListed || tokenDetails.mintPaused) { + setErrorText('Supplying is temporarily disabled for this market.'); + setDisabled(true); + } else if (new BigNumber(tokenValue).gt(new BigNumber(limit))) { setText('Insufficient Funds'); setErrorText(''); setDisabled(true); + } else { + setText('Supply'); + setErrorText(''); + setDisabled(false); } return () => { setText('Supply'); setDisabled(false); }; - }, [tokenValue, limit]); + }, [tokenValue, limit, tokenDetails.isListed, tokenDetails.mintPaused]); return { text, errorText, disabled }; }; @@ -63,7 +70,11 @@ export const useBorrowErrorText = (tokenValue, borrowLimit, tokenDetails) => { const availableBorrowAmount = new BigNumber(marketSize).minus(new BigNumber(totalBorrowed)).toNumber(); useEffect(() => { - if ((Number(tokenValue) > 0) && (Number(tokenValue) > availableBorrowAmount)) { + if (!tokenDetails.isListed || tokenDetails.borrowPaused) { + setText('Borrow'); + setErrorText('Borrowing is temporarily disabled for this market.'); + setDisabled(true); + } else if ((Number(tokenValue) > 0) && (Number(tokenValue) > availableBorrowAmount)) { setErrorText('You cannot borrow more than the amount available on the market.'); setDisabled(true); } else if (new BigNumber(tokenValue).gt(new BigNumber(limit))) { @@ -80,7 +91,11 @@ export const useBorrowErrorText = (tokenValue, borrowLimit, tokenDetails) => { setErrorText(''); setDisabled(false); }; - }, [tokenValue, limit]); + }, [tokenValue, + limit, + availableBorrowAmount, + tokenDetails.isListed, + tokenDetails.borrowPaused]); return { text, errorText, disabled }; }; @@ -119,9 +134,9 @@ export const useWithdrawErrorText = (tokenValue, limit, tokenDetails) => { ).toNumber(); useEffect(() => { - if (tokenDetails.redeemPaused) { + if (!tokenDetails.isListed || tokenDetails.redeemPaused) { setDisabled(true); - setErrorText('Withdrawals are temporarily paused for this market.'); + setErrorText('Withdrawals are temporarily disabled for this market.'); } else if (new BigNumber(tokenValue).gt(new BigNumber(limit))) { setDisabled(true); setErrorText('You cannot withdraw an amount greater than the amount you supply.'); @@ -136,7 +151,16 @@ export const useWithdrawErrorText = (tokenValue, limit, tokenDetails) => { setErrorText(''); setDisabled(false); }; - }, [tokenValue, limit, tokenDetails.redeemPaused]); + }, [ + tokenValue, + limit, + borrowing, + pendingSupplyingUsdLimit, + pendingCollateralizedUsdLimit, + tokenDetails.collateral, + tokenDetails.isListed, + tokenDetails.redeemPaused + ]); return { text, errorText, disabled }; }; diff --git a/src/util/src/Comptroller.ts b/src/util/src/Comptroller.ts index 92a6ec3d..aff758aa 100644 --- a/src/util/src/Comptroller.ts +++ b/src/util/src/Comptroller.ts @@ -30,23 +30,28 @@ export namespace Comptroller { markets: MarketMap; } - export async function GetStorage(address: string, protocolAddresses: ProtocolAddresses, server: string): Promise { - const contract = await getContract(server, address); - const storage: any = await contract.storage(); - + export async function GetMarkets( + storage: any, + dataStorage: any, + protocolAddresses: ProtocolAddresses, + ): Promise { + const recoveryMode = Boolean(protocolAddresses.comptrollerDataSource); const markets: MarketMap = {}; await Promise.all(Object.values(protocolAddresses.fTokens).map(async (fTokenAddr) => { try { - const marketEntry = await storage.markets.get(fTokenAddr); - if (marketEntry) { + const marketEntry = await dataStorage.markets.get(fTokenAddr); + const guardMarketEntry = recoveryMode + ? await storage.markets.get(fTokenAddr) + : marketEntry; + if (marketEntry && guardMarketEntry) { const asset = protocolAddresses.fTokensReverse[fTokenAddr]; markets[asset] = { assetType: marketEntry.name as AssetType, - borrowPaused: marketEntry.borrowPaused, + borrowPaused: recoveryMode || marketEntry.borrowPaused, collateralFactor: Number(marketEntry.collateralFactor.toString()), - isListed: marketEntry.isListed, - mintPaused: marketEntry.mintPaused, - redeemPaused: marketEntry.redeemPaused, + isListed: guardMarketEntry.isListed, + mintPaused: recoveryMode || marketEntry.mintPaused, + redeemPaused: guardMarketEntry.redeemPaused ?? marketEntry.redeemPaused ?? false, price: bigInt(marketEntry.price.toString()), updateLevel: Number(marketEntry.updateLevel.toString()), }; @@ -55,14 +60,28 @@ export namespace Comptroller { log.error(`Failed to get Comptroller.Markets for ${fTokenAddr}: ${e}`); } })); + return markets; + } + + export async function GetStorage(address: string, protocolAddresses: ProtocolAddresses, server: string): Promise { + const contract = await getContract(server, address); + const storage: any = await contract.storage(); + const dataContract = protocolAddresses.comptrollerDataSource + ? await getContract(server, protocolAddresses.comptrollerDataSource) + : contract; + const dataStorage: any = protocolAddresses.comptrollerDataSource + ? await dataContract.storage() + : storage; + + const markets = await GetMarkets(storage, dataStorage, protocolAddresses); return { administrator: storage.administrator, - closeFactorMantissa: bigInt(storage.closeFactorMantissa.toString()), - liquidationIncentiveMantissa: bigInt(storage.liquidationIncentiveMantissa.toString()), - oracleAddress: storage.oracleAddress, + closeFactorMantissa: bigInt(dataStorage.closeFactorMantissa.toString()), + liquidationIncentiveMantissa: bigInt(dataStorage.liquidationIncentiveMantissa.toString()), + oracleAddress: dataStorage.oracleAddress, pendingAdministrator: storage.pendingAdministrator || undefined, - transferPaused: storage.transferPaused, + transferPaused: dataStorage.transferPaused, markets, }; } @@ -72,7 +91,10 @@ export namespace Comptroller { */ export async function GetCollaterals(address: string, _comptroller: Storage, protocolAddresses: ProtocolAddresses, server: string): Promise { try { - const contract = await getContract(server, protocolAddresses.comptroller); + const contract = await getContract( + server, + protocolAddresses.comptrollerDataSource || protocolAddresses.comptroller, + ); const storage: any = await contract.storage(); const collateralsSet = await storage.collaterals.get(address); if (!collateralsSet) return []; @@ -107,7 +129,7 @@ export namespace Comptroller { ): TransferParams[] { const address = targetAddress || pkh; return [{ - to: protocolAddresses.comptroller, + to: protocolAddresses.comptrollerDataSource || protocolAddresses.comptroller, amount: 0, mutez: true, parameter: { entrypoint: 'updateAccountLiquidityWithView', value: { string: address } }, diff --git a/src/util/src/TezosLendingPlatform.ts b/src/util/src/TezosLendingPlatform.ts index fa047f18..48085a8f 100644 --- a/src/util/src/TezosLendingPlatform.ts +++ b/src/util/src/TezosLendingPlatform.ts @@ -72,6 +72,9 @@ export namespace TezosLendingPlatform { reserves: fToken.totalReserves, reserveFactor: fToken.reserveFactorMantissa.toJSNumber(), collateralFactor: comptroller.markets[underlying.assetType].collateralFactor, + isListed: comptroller.markets[underlying.assetType].isListed, + mintPaused: comptroller.markets[underlying.assetType].mintPaused, + borrowPaused: comptroller.markets[underlying.assetType].borrowPaused, redeemPaused: comptroller.markets[underlying.assetType].redeemPaused, exchangeRate: FToken.getExchangeRate(fToken), storage: fToken, @@ -105,7 +108,7 @@ export namespace TezosLendingPlatform { ); const rateModel = await InterestRateModel.GetStorage( server, - protocolAddresses.interestRateModel[asset], + fTokenStorage.interestRateModel, ); const oraclePrice = await PriceFeed.GetPrice( protocolAddresses.fTokensReverse[fTokenAddress], @@ -488,8 +491,15 @@ export namespace TezosLendingPlatform { pkh: string, ): TransferParams[] { let ops: TransferParams[] = []; - // Data relevance (updateAccountLiquidityWithView) - ops = ops.concat(Comptroller.DataRelevanceOpGroup([], protocolAddresses, pkh)); + if (protocolAddresses.comptrollerDataSource) { + ops = ops.concat(FToken.AccrueInterestOpGroup( + [redeem.underlying], + protocolAddresses, + pkh, + )); + } else { + ops = ops.concat(Comptroller.DataRelevanceOpGroup([], protocolAddresses, pkh)); + } // Redeem ops.push(FToken.RedeemOperation(redeem, protocolAddresses.fTokens[redeem.underlying], pkh)); return ops; @@ -517,7 +527,9 @@ export namespace TezosLendingPlatform { let ops: TransferParams[] = []; // Accrue interest ops = ops.concat(FToken.AccrueInterestOpGroup( - Object.keys(protocolAddresses.fTokens) as AssetType[], + protocolAddresses.comptrollerDataSource + ? [repayBorrow.underlying] + : Object.keys(protocolAddresses.fTokens) as AssetType[], protocolAddresses, pkh, )); diff --git a/src/util/src/const.ts b/src/util/src/const.ts index 723f1b97..4232996b 100644 --- a/src/util/src/const.ts +++ b/src/util/src/const.ts @@ -138,9 +138,10 @@ export const mainnetAddresses: ProtocolAddresses = { balancesMapId: 729395, } }, - comptroller: "KT1P6Lryn3ikbyf5jywWBBRP5fkztE5ZafGe", + comptroller: "KT1TqVTu1tj5srznevEZtN7141oje5Fdf7gh", + comptrollerDataSource: "KT1P6Lryn3ikbyf5jywWBBRP5fkztE5ZafGe", interestRateModel: { - XTZ: "KT1EoQZAnsKNSkCKSRQtPBspC5ZBZJ9YoeME", + XTZ: "KT1NnUwvf3TqF8PaiGNJ6mFvVxb2of3XQyT9", USDT: "KT1PMLT8RaBHsKyhxGb9thRSW91Rg5G9vKQF", USD: "KT1PMLT8RaBHsKyhxGb9thRSW91Rg5G9vKQF", TZBTC: "KT1LkLYxgXqFSWTWWc2Q8HmxnrRTkv4qsXkE", diff --git a/src/util/src/types.ts b/src/util/src/types.ts index 092ea990..6fc20b33 100644 --- a/src/util/src/types.ts +++ b/src/util/src/types.ts @@ -102,6 +102,7 @@ export interface ProtocolAddresses { fTokensReverse: { [address: string]: AssetType }; underlying: { [assetType: string]: UnderlyingAsset }; comptroller: string; + comptrollerDataSource?: string; interestRateModel: { [underlying: string]: string }; governance: string; oracle: string; @@ -182,6 +183,9 @@ export interface Market { currentPrice: bigInt.BigInteger; reserveFactor: number; collateralFactor: number; + isListed: boolean; + mintPaused: boolean; + borrowPaused: boolean; redeemPaused: boolean; exchangeRate: BigNumber; storage: FToken.Storage; diff --git a/src/util/test/RecoveryMod.ts b/src/util/test/RecoveryMod.ts new file mode 100644 index 00000000..584c8595 --- /dev/null +++ b/src/util/test/RecoveryMod.ts @@ -0,0 +1,127 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { Comptroller } from '../src/Comptroller'; +import { AssetType, TokenStandard } from '../src/enum'; +import { TezosLendingPlatform } from '../src/TezosLendingPlatform'; +import { Network, ProtocolAddresses } from '../src/types'; + +const fXTZ = 'KT1-fXTZ'; +const fUSDT = 'KT1-fUSDT'; + +function protocolAddresses(recoveryMode: boolean): ProtocolAddresses { + return { + fTokens: { + [AssetType.XTZ]: fXTZ, + [AssetType.USDT]: fUSDT, + }, + fTokensReverse: { + [fXTZ]: AssetType.XTZ, + [fUSDT]: AssetType.USDT, + }, + underlying: { + [AssetType.XTZ]: { + assetType: AssetType.XTZ, + tokenStandard: TokenStandard.XTZ, + decimals: 6, + }, + }, + comptroller: 'KT1-guard', + comptrollerDataSource: recoveryMode ? 'KT1-legacy' : undefined, + interestRateModel: {}, + governance: 'KT1-governance', + oracle: 'KT1-oracle', + network: Network.Mainnet, + }; +} + +function entrypoints(operations: any[]): string[] { + return operations.map((operation) => operation.parameter.entrypoint); +} + +describe('recovery operation groups', () => { + it('accrues only the selected market before redeeming', () => { + const operations = TezosLendingPlatform.RedeemOpGroup( + { underlying: AssetType.XTZ, amount: 10, amountInUnderlying: false }, + AssetType.XTZ, + protocolAddresses(true), + 'tz1-user', + ); + + expect(entrypoints(operations)).to.deep.equal(['accrueInterest', 'redeem']); + expect(operations.map((operation) => operation.to)).to.deep.equal([fXTZ, fXTZ]); + }); + + it('keeps the legacy liquidity update before redeeming', () => { + const operations = TezosLendingPlatform.RedeemOpGroup( + { underlying: AssetType.XTZ, amount: 10, amountInUnderlying: false }, + AssetType.XTZ, + protocolAddresses(false), + 'tz1-user', + ); + + expect(entrypoints(operations)).to.deep.equal(['updateAccountLiquidityWithView', 'redeem']); + expect(operations[0].to).to.equal('KT1-guard'); + }); + + it('accrues only the market being repaid in recovery mode', () => { + const operations = TezosLendingPlatform.RepayBorrowOpGroup( + { underlying: AssetType.XTZ, amount: 10 }, + protocolAddresses(true), + 'tz1-user', + ); + + expect(entrypoints(operations)).to.deep.equal(['accrueInterest', 'repayBorrow']); + expect(operations.map((operation) => operation.to)).to.deep.equal([fXTZ, fXTZ]); + }); + + it('keeps all-market accrual for legacy repayments', () => { + const operations = TezosLendingPlatform.RepayBorrowOpGroup( + { underlying: AssetType.XTZ, amount: 10 }, + protocolAddresses(false), + 'tz1-user', + ); + + expect(entrypoints(operations)).to.deep.equal(['accrueInterest', 'accrueInterest', 'repayBorrow']); + expect(operations.map((operation) => operation.to)).to.deep.equal([fXTZ, fUSDT, fXTZ]); + }); +}); + +describe('Guard and legacy market storage', () => { + const legacyMarket = { + name: AssetType.XTZ, + borrowPaused: false, + collateralFactor: { toString: () => '500000000000000000' }, + isListed: true, + mintPaused: false, + price: { toString: () => '1000000000000000000' }, + updateLevel: { toString: () => '1' }, + }; + const guardMarket = { + isListed: true, + redeemPaused: false, + }; + + it('disables mint and borrow when Guard storage has no pause fields', async () => { + const guardStorage = { markets: { get: async () => guardMarket } }; + const legacyStorage = { markets: { get: async () => legacyMarket } }; + const addresses = protocolAddresses(true); + addresses.fTokens = { [AssetType.XTZ]: fXTZ }; + + const markets = await Comptroller.GetMarkets(guardStorage, legacyStorage, addresses); + + expect(markets[AssetType.XTZ].mintPaused).to.equal(true); + expect(markets[AssetType.XTZ].borrowPaused).to.equal(true); + expect(markets[AssetType.XTZ].redeemPaused).to.equal(false); + }); + + it('preserves legacy pause values outside recovery mode', async () => { + const storage = { markets: { get: async () => legacyMarket } }; + const addresses = protocolAddresses(false); + addresses.fTokens = { [AssetType.XTZ]: fXTZ }; + + const markets = await Comptroller.GetMarkets(storage, storage, addresses); + + expect(markets[AssetType.XTZ].mintPaused).to.equal(false); + expect(markets[AssetType.XTZ].borrowPaused).to.equal(false); + }); +}); \ No newline at end of file