From 184a27e98764802bc30e793804ede9ed422de253 Mon Sep 17 00:00:00 2001 From: Linkie Link Date: Thu, 27 Jul 2023 11:26:32 +0200 Subject: [PATCH] MP-2891 and MP-2801 (#321) * MP-2801: fund new credit account * tidy: cleanup * tidy: refactor * feat: replaced all possible BN(0) & BN(1) occurrences with constants * fix: adjusted according to feedback * fix: adjustments according to feedback * fix: PR comment updates * fix: reduced complexity * feat: animated the wallet balance for the demo * fix: enhanced wallet connection to select first account * fix: adjusted the calculations and added USD as displayCurrency * fix: adjusted according to feedback * feat: added TFM bridge * fix: changed forceFetchPrice --------- Co-authored-by: Yusuf Seyrek --- public/images/bridges/tfm.png | Bin 0 -> 1755 bytes src/api/prices/getPrices.ts | 9 +- .../Account/AccountBalancesTable.tsx | 22 +-- src/components/Account/AccountComposition.tsx | 19 +- src/components/Account/AccountCreateFirst.tsx | 21 ++- src/components/Account/AccountDetails.tsx | 31 +++- src/components/Account/AccountFund.tsx | 170 ++++++++++++++++++ src/components/Account/AccountList.tsx | 24 ++- src/components/Account/AccountMenuContent.tsx | 114 +++++------- src/components/Account/AccountStats.tsx | 6 +- src/components/Account/AccountSummary.tsx | 11 +- src/components/Account/CreateAccount.tsx | 28 --- src/components/Account/FundAccount.tsx | 115 ------------ src/components/AmountAndValue.tsx | 29 ++- src/components/AssetImage.tsx | 8 + src/components/DisplayCurrency.tsx | 16 +- src/components/DocsLink.tsx | 20 ++- src/components/FormattedNumber.tsx | 8 +- src/components/FullOverlayContent.tsx | 7 +- src/components/Gauge.tsx | 7 - .../AddVaultBorrowAssetsModalContent.tsx | 6 +- ...ddVaultBorrowAssetsModal.tsx => index.tsx} | 4 + .../useAddVaultAssetTableColumns.tsx | 54 ------ .../AssetSelectTable.tsx} | 26 ++- .../AssetsSelect/useAssetTableColumns.tsx | 75 ++++++++ .../FundAndWithdrawModalContent.tsx | 20 ++- src/components/Modals/ModalsContainer.tsx | 2 + src/components/Modals/Settings/index.tsx | 16 +- .../Modals/Vault/VaultBorrowings.tsx | 4 +- .../WalletAssets/WalletAssetsModalContent.tsx | 68 +++++++ src/components/Modals/WalletAssets/index.tsx | 37 ++++ src/components/Modals/index.tsx | 3 +- src/components/Switch.tsx | 4 +- src/components/Wallet/WalletBridges.tsx | 43 ++++- .../Wallet/WalletConnectedButton.tsx | 10 +- .../Wallet/WalletFetchBalancesAndAccounts.tsx | 7 +- src/constants/assets.ts | 19 +- src/constants/bridges.ts | 5 + src/constants/defaultSettings.ts | 3 +- src/constants/oracle.ts | 1 + src/store/slices/broadcast.ts | 34 ++-- src/store/slices/common.ts | 2 + src/store/slices/modal.ts | 1 + src/types/interfaces/asset.d.ts | 6 +- .../components/Modals/AssetSelect.d.ts | 4 + src/types/interfaces/components/docLink.d.ts | 1 + src/types/interfaces/store/broadcast.d.ts | 4 +- src/types/interfaces/store/modals.d.ts | 9 +- src/utils/accounts.ts | 24 ++- src/utils/formatters.ts | 14 +- 50 files changed, 727 insertions(+), 444 deletions(-) create mode 100644 public/images/bridges/tfm.png create mode 100644 src/components/Account/AccountFund.tsx delete mode 100644 src/components/Account/CreateAccount.tsx delete mode 100644 src/components/Account/FundAccount.tsx rename src/components/Modals/AddVaultAssets/{AddVaultBorrowAssetsModal.tsx => index.tsx} (88%) delete mode 100644 src/components/Modals/AddVaultAssets/useAddVaultAssetTableColumns.tsx rename src/components/Modals/{AddVaultAssets/AddVaultAssetTable.tsx => AssetsSelect/AssetSelectTable.tsx} (84%) create mode 100644 src/components/Modals/AssetsSelect/useAssetTableColumns.tsx create mode 100644 src/components/Modals/WalletAssets/WalletAssetsModalContent.tsx create mode 100644 src/components/Modals/WalletAssets/index.tsx create mode 100644 src/constants/oracle.ts create mode 100644 src/types/interfaces/components/Modals/AssetSelect.d.ts create mode 100644 src/types/interfaces/components/docLink.d.ts diff --git a/public/images/bridges/tfm.png b/public/images/bridges/tfm.png new file mode 100644 index 0000000000000000000000000000000000000000..d5b955601b1b6160ecc49c933cadd893d85c034a GIT binary patch literal 1755 zcmaJ?c~BEq98S?vL;;6_4r*|XfKi(}9EpT9Bp?Vuh*(lP95Go)gk)o~kOV~$Ml1!j zf`Sr;QrbdAVGs-|sDKElSfJ%Tt1_0$Du{}R#%>U>f0XXb?tAYy-}n8_H=FG5x82Ni zjVTI+GV}K0@Q~HY@R}GS-{Z+>Gi0%WJ^1i0C>&M^6d;Nvg7$)dw^R@T@<4$|v;QvW zib5^HN&@*XpX*B(LQ=fIfWfP!a)gaSxiZyqfiMz;fxTdaM8?28tE|ER5)lIvy5h zhoJaee}D}sK!AoP;e(21ui10vm=E{`vM>n$rJ*SLLgIcBnqAANT)gia}Ne_ zs1SwIc^uC5yP;YP9UgMD!hty;LWuM(e51jYoX%E&0vJ*RLQvFvA^pV=3@OEs93WHiP5_oG z5K3eQ%jQ`MmrM7SDPe(32zqlE7(@gwk%;IdHqnXZ?o1<7ot;P|4-XoVex>nPipARA zkb9ms=IBRZ7U|daRa*CE=~;U$Tba|stO2dIug`9(8M@M|y|LB&;CS^z*X-IMm0pi^ zH_0hzQ8$&^m#ok^@jzZee$e#prPRKwD-&_;Bj}$;{rNi;pgzj(c+? zFC?alaJ+9#j#gHi8XuI2ZtM*`pUj9%(8jw;(@%EYVh(A*zr(Q|{a*z!TjlbpL2M3O zksRFTxx3+t5%0Yt7~I}fHsuz4RX-yduC6{L(X}>j+nVJf9;ZAjWw33XT+cbUd)XH@ z`dz9cT4%@9;Q2tz#cvvK1-uguB6d?K&r73DVm9^FuaUVnFF zI@hS(I)6zS6oCFynpNrtKi#1(fR3eZzcDOJco(?o-Y2X7*t5SjMdsP z1X&jOC9SR)uHTlyw#jJNS@ZpG6*lpd{&nCQ^t-}@pWKZej)4A{nyA2Ps_kXRDyQCU zD~D%pe-_bWd1qNSamDBl&qt%tGp5#0b}PDY4%GzHT)R~s74ZkMmOsKX2Qsd!#*Eqn z8gw7x%T6%P4_XbT({FkSeacYdYWwreQDao>7!y{VF5Z<@rXvyVGOI^?=IG=H@eTn6{MDeXx>*)sukAMCw zE?l$OgEx_yb~K%aJ%lzMEVw|a6V+4ngLg*mx4j;7JZ)N#o`l68DZ3VQ@mgpqtln(uL?sY XlXJ&I2JAjH{BFEG{5Zw#VF~{LiuKAX literal 0 HcmV?d00001 diff --git a/src/api/prices/getPrices.ts b/src/api/prices/getPrices.ts index ab91e172..d7519fea 100644 --- a/src/api/prices/getPrices.ts +++ b/src/api/prices/getPrices.ts @@ -1,11 +1,12 @@ -import { getAssetsMustHavePriceInfo } from 'utils/assets' -import { partition } from 'utils/array' +import getOraclePrices from 'api/prices/getOraclePrices' import getPoolPrice from 'api/prices/getPoolPrice' import fetchPythPrices from 'api/prices/getPythPrices' import { BNCoin } from 'types/classes/BNCoin' -import getOraclePrices from 'api/prices/getOraclePrices' +import { partition } from 'utils/array' +import { getAssetsMustHavePriceInfo } from 'utils/assets' export default async function getPrices(): Promise { + const usdPrice = new BNCoin({ denom: 'usd', amount: '1' }) try { const assetsToFetchPrices = getAssetsMustHavePriceInfo() const [assetsWithPythPriceFeedId, assetsWithOraclePrices, assetsWithPoolIds] = @@ -19,7 +20,7 @@ export default async function getPrices(): Promise { ).flat() const poolPrices = await requestPoolPrices(assetsWithPoolIds, pythAndOraclePrices) - return [...pythAndOraclePrices, ...poolPrices] + return [...pythAndOraclePrices, ...poolPrices, usdPrice] } catch (ex) { console.error(ex) throw ex diff --git a/src/components/Account/AccountBalancesTable.tsx b/src/components/Account/AccountBalancesTable.tsx index 4ff9931c..5be4f619 100644 --- a/src/components/Account/AccountBalancesTable.tsx +++ b/src/components/Account/AccountBalancesTable.tsx @@ -14,13 +14,14 @@ import { FormattedNumber } from 'components/FormattedNumber' import { SortAsc, SortDesc, SortNone } from 'components/Icons' import Text from 'components/Text' import { ASSETS } from 'constants/assets' -import usePrices from 'hooks/usePrices' -import useStore from 'store' -import { BNCoin } from 'types/classes/BNCoin' -import { convertToDisplayAmount, demagnify } from 'utils/formatters' -import { DISPLAY_CURRENCY_KEY } from 'constants/localStore' import { DEFAULT_SETTINGS } from 'constants/defaultSettings' +import { DISPLAY_CURRENCY_KEY } from 'constants/localStore' import useLocalStorage from 'hooks/useLocalStorage' +import usePrices from 'hooks/usePrices' +import { BNCoin } from 'types/classes/BNCoin' +import { getAssetByDenom } from 'utils/assets' +import { convertToDisplayAmount, demagnify } from 'utils/formatters' +import { BN } from 'utils/helpers' interface Props { data: Account @@ -106,14 +107,15 @@ export const AccountBalancesTable = (props: Props) => { accessorKey: 'size', header: 'Size', cell: ({ row }) => { + const amount = demagnify( + row.original.amount, + getAssetByDenom(row.original.denom) ?? ASSETS[0], + ) return ( asset.denom === row.original.denom) ?? ASSETS[0], - )} - options={{ maxDecimals: 4 }} + amount={Number(BN(amount).toPrecision(2))} + options={{ maxDecimals: 2, abbreviated: true }} animate /> ) diff --git a/src/components/Account/AccountComposition.tsx b/src/components/Account/AccountComposition.tsx index 4cce8fd3..e6ba557d 100644 --- a/src/components/Account/AccountComposition.tsx +++ b/src/components/Account/AccountComposition.tsx @@ -6,14 +6,14 @@ import { FormattedNumber } from 'components/FormattedNumber' import { ArrowRight } from 'components/Icons' import Text from 'components/Text' import { BN_ZERO } from 'constants/math' +import { ORACLE_DENOM } from 'constants/oracle' import usePrices from 'hooks/usePrices' -import useStore from 'store' import { BNCoin } from 'types/classes/BNCoin' import { calculateAccountApr, calculateAccountBorrowRate, - calculateAccountDebt, - calculateAccountDeposits, + calculateAccountDebtValue, + calculateAccountDepositsValue, calculateAccountPnL, } from 'utils/accounts' @@ -33,10 +33,10 @@ interface ItemProps { export default function AccountComposition(props: Props) { const { data: prices } = usePrices() - const balance = calculateAccountDeposits(props.account, prices) - const balanceChange = props.change ? calculateAccountDeposits(props.change, prices) : BN_ZERO - const debtBalance = calculateAccountDebt(props.account, prices) - const debtBalanceChange = props.change ? calculateAccountDebt(props.change, prices) : BN_ZERO + const balance = calculateAccountDepositsValue(props.account, prices) + const balanceChange = props.change ? calculateAccountDepositsValue(props.change, prices) : BN_ZERO + const debtBalance = calculateAccountDebtValue(props.account, prices) + const debtBalanceChange = props.change ? calculateAccountDebtValue(props.change, prices) : BN_ZERO const pnL = calculateAccountPnL(props.account, prices) const pnLChange = props.change ? calculateAccountPnL(props.change, prices) : BN_ZERO const apr = calculateAccountApr(props.account, prices) @@ -78,7 +78,6 @@ export default function AccountComposition(props: Props) { } function Item(props: ItemProps) { - const baseCurrency = useStore((s) => s.baseCurrency) const increase = props.isBadIncrease ? props.current.isGreaterThan(props.change) : props.current.isLessThan(props.change) @@ -99,7 +98,7 @@ function Item(props: ItemProps) { /> ) : ( )} @@ -117,7 +116,7 @@ function Item(props: ItemProps) { /> ) : ( )} diff --git a/src/components/Account/AccountCreateFirst.tsx b/src/components/Account/AccountCreateFirst.tsx index d80e0dcb..3343f2fc 100644 --- a/src/components/Account/AccountCreateFirst.tsx +++ b/src/components/Account/AccountCreateFirst.tsx @@ -1,25 +1,34 @@ -import { useCallback } from 'react' -import { useNavigate } from 'react-router-dom' +import { useCallback, useEffect } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' +import AccountFundFirst from 'components/Account/AccountFund' import FullOverlayContent from 'components/FullOverlayContent' +import WalletSelect from 'components/Wallet/WalletSelect' import useToggle from 'hooks/useToggle' import useStore from 'store' import { hardcodedFee } from 'utils/constants' +import { getPage, getRoute } from 'utils/route' export default function AccountCreateFirst() { const navigate = useNavigate() + const { pathname } = useLocation() const address = useStore((s) => s.address) const createAccount = useStore((s) => s.createAccount) const [isCreating, setIsCreating] = useToggle(false) + useEffect(() => { + if (!address) useStore.setState({ focusComponent: }) + }, [address]) + const handleClick = useCallback(async () => { setIsCreating(true) const accountId = await createAccount({ fee: hardcodedFee }) setIsCreating(false) - // TODO: set focusComponent to fund account - useStore.setState({ focusComponent: null }) - accountId && navigate(`/wallets/${address}/accounts/${accountId}`) - }, [address, createAccount, navigate, setIsCreating]) + if (accountId) { + navigate(getRoute(getPage(pathname), address, accountId)) + useStore.setState({ focusComponent: }) + } + }, [createAccount, navigate, pathname, address, setIsCreating]) return ( s.address) + const focusComponent = useStore((s) => s.focusComponent) - if (!account || !address) return null + if (!account || !address || focusComponent) return null return } function AccountDetails(props: Props) { const { health } = useHealthComputer(props.account) + const { data: prices } = usePrices() + const accountBalanceValue = calculateAccountBalanceValue(props.account, prices) + const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalanceValue) + const healthFactor = BN(100).minus(formatHealth(health)).toNumber() return (
- } /> + } /> Health - - {formatHealth(health)} - +
@@ -43,13 +58,11 @@ function AccountDetails(props: Props) { 4.5x
-
+
Balance - - $300M - +
) diff --git a/src/components/Account/AccountFund.tsx b/src/components/Account/AccountFund.tsx new file mode 100644 index 00000000..e4077e54 --- /dev/null +++ b/src/components/Account/AccountFund.tsx @@ -0,0 +1,170 @@ +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useParams } from 'react-router-dom' + +import Button from 'components/Button' +import Card from 'components/Card' +import FullOverlayContent from 'components/FullOverlayContent' +import { Plus } from 'components/Icons' +import SwitchWithLabel from 'components/SwitchWithLabel' +import Text from 'components/Text' +import TokenInputWithSlider from 'components/TokenInputWithSlider' +import WalletBridges from 'components/Wallet/WalletBridges' +import { BN_ZERO } from 'constants/math' +import useAccounts from 'hooks/useAccounts' +import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds' +import useCurrentAccount from 'hooks/useCurrentAccount' +import useToggle from 'hooks/useToggle' +import useWalletBalances from 'hooks/useWalletBalances' +import useStore from 'store' +import { byDenom } from 'utils/array' +import { getAssetByDenom, getBaseAsset } from 'utils/assets' +import { hardcodedFee } from 'utils/constants' +import { BN } from 'utils/helpers' + +export default function AccountFund() { + const address = useStore((s) => s.address) + const deposit = useStore((s) => s.deposit) + const walletAssetModal = useStore((s) => s.walletAssetsModal) + const { accountId } = useParams() + const { data: accounts } = useAccounts(address) + const currentAccount = useCurrentAccount() + const [isFunding, setIsFunding] = useToggle(false) + const [selectedAccountId, setSelectedAccountId] = useState(null) + const [fundingAssets, setFundingAssets] = useState([]) + const { data: walletBalances } = useWalletBalances(address) + const baseAsset = getBaseAsset() + const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLendEnabledAccountIds() + const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId ?? '0') + const hasAssetSelected = fundingAssets.length > 0 + const hasFundingAssets = fundingAssets.length > 0 && fundingAssets.every((a) => a.amount !== '0') + + const baseBalance = useMemo( + () => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0', + [walletBalances, baseAsset], + ) + + const selectedDenoms = useMemo(() => { + return walletAssetModal?.selectedDenoms ?? [] + }, [walletAssetModal?.selectedDenoms]) + + const handleClick = useCallback(async () => { + setIsFunding(true) + if (!accountId) return + const result = await deposit({ + fee: hardcodedFee, + accountId, + coins: fundingAssets, + }) + setIsFunding(false) + if (result) useStore.setState({ focusComponent: null, walletAssetsModal: null }) + }, [fundingAssets, accountId, setIsFunding, deposit]) + + const handleSelectAssetsClick = useCallback(() => { + useStore.setState({ + walletAssetsModal: { + isOpen: true, + selectedDenoms, + }, + }) + }, [selectedDenoms]) + + useEffect(() => { + const currentSelectedDenom = fundingAssets.map((asset) => asset.denom) + + if ( + selectedDenoms.every((denom) => currentSelectedDenom.includes(denom)) && + selectedDenoms.length === currentSelectedDenom.length + ) + return + + const newFundingAssets = selectedDenoms.map((denom) => ({ + denom, + amount: fundingAssets.find((asset) => asset.denom === denom)?.amount ?? '0', + })) + + setFundingAssets(newFundingAssets) + }, [selectedDenoms, fundingAssets]) + + const updateFundingAssets = useCallback( + (amount: BigNumber, denom: string) => { + const assetToUpdate = fundingAssets.find((asset) => asset.denom === denom) + if (assetToUpdate) { + assetToUpdate.amount = amount.toString() + setFundingAssets([...fundingAssets.filter((a) => a.denom !== denom), assetToUpdate]) + } + }, + [fundingAssets], + ) + + useEffect(() => { + if (BN(baseBalance).isLessThan(hardcodedFee.amount[0].amount)) { + useStore.setState({ focusComponent: }) + } + }, [baseBalance]) + + useEffect(() => { + if (accounts && !selectedAccountId && accountId) + setSelectedAccountId(currentAccount?.id ?? accountId) + }, [accounts, selectedAccountId, accountId, currentAccount]) + + if (!selectedAccountId) return null + + return ( + + + {!hasAssetSelected && Please select an asset.} + {selectedDenoms.map((denom) => { + const asset = getAssetByDenom(denom) as Asset + + const balance = walletBalances.find(byDenom(asset.denom))?.amount ?? '0' + return ( +
+ updateFundingAssets(amount, asset.denom)} + amount={BN_ZERO} + max={BN(balance)} + balances={walletBalances} + maxText='Max' + /> +
+ ) + })} + + - {!showFundAccount && !showCreateAccount ? ( - <> -
- - Accounts - -
+
+ {isAccountSelected && isLoadingAccount && ( +
+
-
- {isAccountSelected && isLoadingAccount && ( -
- -
- )} - {hasCreditAccounts && !showFundAccount && !isLoadingAccount && ( - - )} -
- - ) : ( -
- {showCreateAccount ? ( - - ) : showFundAccount ? ( - - ) : null} -
- )} + )} + {hasCreditAccounts && !isLoadingAccount && } +
) diff --git a/src/components/Account/AccountStats.tsx b/src/components/Account/AccountStats.tsx index d5d3031c..56155511 100644 --- a/src/components/Account/AccountStats.tsx +++ b/src/components/Account/AccountStats.tsx @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js' import AccountHealth from 'components/Account/AccountHealth' import DisplayCurrency from 'components/DisplayCurrency' -import useStore from 'store' +import { ORACLE_DENOM } from 'constants/oracle' import { BNCoin } from 'types/classes/BNCoin' interface Props { @@ -12,12 +12,10 @@ interface Props { } export default function AccountStats(props: Props) { - const baseCurrency = useStore((s) => s.baseCurrency) - return (
diff --git a/src/components/Account/AccountSummary.tsx b/src/components/Account/AccountSummary.tsx index eac57a4b..e54d5348 100644 --- a/src/components/Account/AccountSummary.tsx +++ b/src/components/Account/AccountSummary.tsx @@ -7,11 +7,11 @@ import DisplayCurrency from 'components/DisplayCurrency' import { ArrowChartLineUp } from 'components/Icons' import Text from 'components/Text' import { BN_ZERO } from 'constants/math' +import { ORACLE_DENOM } from 'constants/oracle' import useIsOpenArray from 'hooks/useIsOpenArray' import usePrices from 'hooks/usePrices' -import useStore from 'store' import { BNCoin } from 'types/classes/BNCoin' -import { calculateAccountDeposits } from 'utils/accounts' +import { calculateAccountDepositsValue } from 'utils/accounts' interface Props { account?: Account @@ -21,8 +21,9 @@ interface Props { export default function AccountSummary(props: Props) { const [isOpen, toggleOpen] = useIsOpenArray(2, true) const { data: prices } = usePrices() - const baseCurrency = useStore((s) => s.baseCurrency) - const accountBalance = props.account ? calculateAccountDeposits(props.account, prices) : BN_ZERO + const accountBalance = props.account + ? calculateAccountDepositsValue(props.account, prices) + : BN_ZERO if (!props.account) return null return ( @@ -30,7 +31,7 @@ export default function AccountSummary(props: Props) { diff --git a/src/components/Account/CreateAccount.tsx b/src/components/Account/CreateAccount.tsx deleted file mode 100644 index 409146f0..00000000 --- a/src/components/Account/CreateAccount.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import Button from 'components/Button' -import { ArrowRight } from 'components/Icons' -import Text from 'components/Text' - -interface Props { - isCreating: boolean - createAccount: () => void -} - -export default function CreateAccount(props: Props) { - return ( -
- - Create a Credit Account - - - Please approve the transaction in your wallet in order to create your first Credit Account. - -
- ) -} diff --git a/src/components/Account/FundAccount.tsx b/src/components/Account/FundAccount.tsx deleted file mode 100644 index 8500f3a2..00000000 --- a/src/components/Account/FundAccount.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import BigNumber from 'bignumber.js' -import { useCallback, useState } from 'react' -import { useParams } from 'react-router-dom' - -import Button from 'components/Button' -import { ArrowRight, Cross } from 'components/Icons' -import SwitchWithLabel from 'components/SwitchWithLabel' -import Text from 'components/Text' -import TokenInputWithSlider from 'components/TokenInputWithSlider' -import { ASSETS } from 'constants/assets' -import useToggle from 'hooks/useToggle' -import useStore from 'store' -import { getAmount } from 'utils/accounts' -import { hardcodedFee } from 'utils/constants' -import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds' -import { BN_ZERO } from 'constants/math' - -interface Props { - setShowFundAccount: (show: boolean) => void - setShowMenu: (show: boolean) => void -} - -export default function FundAccount(props: Props) { - const { accountId } = useParams() - const deposit = useStore((s) => s.deposit) - const balances = useStore((s) => s.balances) - - const [amount, setAmount] = useState(BN_ZERO) - const [asset, setAsset] = useState(ASSETS[0]) - const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLendEnabledAccountIds() - const [isFunding, setIsFunding] = useToggle() - - const max = getAmount(asset.denom, balances ?? []) - - const onChangeAmount = useCallback((amount: BigNumber) => { - setAmount(amount) - }, []) - - const onChangeAsset = useCallback((asset: Asset) => { - setAsset(asset) - }, []) - - async function onDeposit() { - if (!accountId) return - setIsFunding(true) - const result = await deposit({ - fee: hardcodedFee, - accountId, - coin: { - denom: asset.denom, - amount: amount.toString(), - }, - }) - setIsFunding(false) - if (result) { - props.setShowMenu(false) - props.setShowFundAccount(false) - } - } - - if (!accountId) return null - - return ( - <> -
-
-
- - {`Fund Account ${accountId}`} - - - Deposit assets from your Osmosis address to your Mars credit account. Bridge assets if - your Osmosis address has no assets. - - -
- toggleAutoLend(accountId)} - className='mb-4' - tooltip="Fund your account and lend assets effortlessly! By lending, you'll earn attractive interest (APY) without impacting your LTV. It's a win-win situation - don't miss out on this easy opportunity to grow your holdings!" - disabled={isFunding || amount.isEqualTo(0)} - /> -
- - ) -} diff --git a/src/components/AmountAndValue.tsx b/src/components/AmountAndValue.tsx index f2189018..eae90b8f 100644 --- a/src/components/AmountAndValue.tsx +++ b/src/components/AmountAndValue.tsx @@ -1,6 +1,5 @@ -import { FormattedNumber } from 'components/FormattedNumber' -import TitleAndSubCell from 'components/TitleAndSubCell' import DisplayCurrency from 'components/DisplayCurrency' +import { FormattedNumber } from 'components/FormattedNumber' import { BNCoin } from 'types/classes/BNCoin' interface Props { @@ -10,20 +9,16 @@ interface Props { export default function AmountAndValue(props: Props) { return ( - - } - sub={ - - } - className='justify-end' - /> +
+ + +
) } diff --git a/src/components/AssetImage.tsx b/src/components/AssetImage.tsx index 840c6fc5..47e88658 100644 --- a/src/components/AssetImage.tsx +++ b/src/components/AssetImage.tsx @@ -7,6 +7,14 @@ interface Props { } export default function AssetImage(props: Props) { + if (!props.asset.logo) + return ( +
+ ) + return ( diff --git a/src/components/DocsLink.tsx b/src/components/DocsLink.tsx index 0c7347db..417be4d5 100644 --- a/src/components/DocsLink.tsx +++ b/src/components/DocsLink.tsx @@ -2,22 +2,28 @@ import { ExternalLink } from 'components/Icons' import Text from 'components/Text' interface Props { - type: 'wallet' | 'account' | 'terms' + type: DocLinkType } function getData(type: string) { - if (type === 'wallet') - return [ - 'New with wallets?', - 'Learn more', - 'https://docs.marsprotocol.io/docs/learn/workstation/basics/basics-intro', - ] if (type === 'account') return [ 'Why mint your account?', 'Learn more', 'https://docs.marsprotocol.io/docs/learn/workstation/rover/rover-intro', ] + if (type === 'fund') + return [ + 'Why fund your account?', + 'Learn more', + 'https://docs.marsprotocol.io/docs/learn/workstation/rover/managing-credit-accounts', + ] + if (type === 'wallet') + return [ + 'New with wallets?', + 'Learn more', + 'https://docs.marsprotocol.io/docs/learn/workstation/basics/basics-intro', + ] return [ 'By continuing you accept our', 'terms of service', diff --git a/src/components/FormattedNumber.tsx b/src/components/FormattedNumber.tsx index 29e45174..bee2b963 100644 --- a/src/components/FormattedNumber.tsx +++ b/src/components/FormattedNumber.tsx @@ -38,15 +38,15 @@ export const FormattedNumber = React.memo( reduceMotion ) return ( - +

{formatValue(props.amount.toString(), props.options)} - +

) return ( - + {springAmount.number.to((num) => formatValue(num, props.options))} - + ) }, (prevProps, nextProps) => prevProps.amount === nextProps.amount, diff --git a/src/components/FullOverlayContent.tsx b/src/components/FullOverlayContent.tsx index 4eb7337a..750bd6cd 100644 --- a/src/components/FullOverlayContent.tsx +++ b/src/components/FullOverlayContent.tsx @@ -1,3 +1,5 @@ +import classNames from 'classnames' + import Button from 'components/Button' import DocsLink from 'components/DocsLink' import Text from 'components/Text' @@ -5,14 +7,15 @@ import Text from 'components/Text' interface Props { title: string copy: string + className?: string children?: React.ReactNode button?: ButtonProps - docs?: 'wallet' | 'account' | 'terms' + docs?: DocLinkType } export default function FullOverlayContent(props: Props) { return ( -
+
{props.title} diff --git a/src/components/Gauge.tsx b/src/components/Gauge.tsx index 4f9fb46c..0e948498 100644 --- a/src/components/Gauge.tsx +++ b/src/components/Gauge.tsx @@ -1,7 +1,6 @@ import classNames from 'classnames' import { ReactElement, ReactNode } from 'react' -import { FormattedNumber } from 'components/FormattedNumber' import { Tooltip } from 'components/Tooltip' import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { REDUCE_MOTION_KEY } from 'constants/localStore' @@ -87,12 +86,6 @@ export const Gauge = ({ {icon}
)} -
) diff --git a/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent.tsx b/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent.tsx index 9d89fe13..1365e644 100644 --- a/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent.tsx +++ b/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent.tsx @@ -1,6 +1,6 @@ import { useCallback, useMemo, useState } from 'react' -import AddVaultAssetTable from 'components/Modals/AddVaultAssets/AddVaultAssetTable' +import AssetSelectTable from 'components/Modals/AssetsSelect/AssetSelectTable' import SearchBar from 'components/SearchBar' import Text from 'components/Text' import useMarketBorrowings from 'hooks/useMarketBorrowings' @@ -89,7 +89,7 @@ export default function AddVaultAssetsModalContent(props: Props) { Leverage will be set at 50% for both assets by default
-
- )} +
+
) } diff --git a/src/components/Modals/AddVaultAssets/useAddVaultAssetTableColumns.tsx b/src/components/Modals/AddVaultAssets/useAddVaultAssetTableColumns.tsx deleted file mode 100644 index 8967cb72..00000000 --- a/src/components/Modals/AddVaultAssets/useAddVaultAssetTableColumns.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { ColumnDef } from '@tanstack/react-table' -import React from 'react' -import Image from 'next/image' - -import Checkbox from 'components/Checkbox' -import Text from 'components/Text' -import { formatPercent } from 'utils/formatters' -import { getAssetByDenom } from 'utils/assets' -import AssetImage from 'components/AssetImage' - -export default function useAddVaultAssetTableColumns() { - const columns = React.useMemo[]>( - () => [ - { - header: 'Asset', - accessorKey: 'symbol', - id: 'symbol', - cell: ({ row }) => { - const asset = getAssetByDenom(row.original.denom) - if (!asset) return null - - return ( -
- - -
- - {asset.symbol} - - {asset.name} -
-
- ) - }, - }, - { - id: 'borrowRate', - accessorKey: 'borrowRate', - header: 'Borrow Rate', - cell: ({ row }) => ( - <> - - {formatPercent(row.original.borrowRate ?? 0)} - - APY - - ), - }, - ], - [], - ) - - return columns -} diff --git a/src/components/Modals/AddVaultAssets/AddVaultAssetTable.tsx b/src/components/Modals/AssetsSelect/AssetSelectTable.tsx similarity index 84% rename from src/components/Modals/AddVaultAssets/AddVaultAssetTable.tsx rename to src/components/Modals/AssetsSelect/AssetSelectTable.tsx index c9e1317c..3a76bffa 100644 --- a/src/components/Modals/AddVaultAssets/AddVaultAssetTable.tsx +++ b/src/components/Modals/AssetsSelect/AssetSelectTable.tsx @@ -10,31 +10,43 @@ import classNames from 'classnames' import { useEffect, useMemo, useState } from 'react' import { SortAsc, SortDesc, SortNone } from 'components/Icons' -import useAddVaultAssetTableColumns from 'components/Modals/AddVaultAssets/useAddVaultAssetTableColumns' import Text from 'components/Text' +import useAssetTableColumns from 'components/Modals/AssetsSelect/useAssetTableColumns' +import useStore from 'store' +import { byDenom } from 'utils/array' interface Props { - assets: BorrowAsset[] + assets: Asset[] | BorrowAsset[] selectedDenoms: string[] onChangeSelected: (denoms: string[]) => void } -export default function AddVaultAssetTable(props: Props) { +export default function AssetSelectTable(props: Props) { const defaultSelected = useMemo(() => { - return props.assets.reduce((acc, asset, index) => { + const assets = props.assets as BorrowAsset[] + return assets.reduce((acc, asset, index) => { if (props.selectedDenoms?.includes(asset.denom)) { acc[index] = true } return acc }, {} as { [key: number]: boolean }) }, [props.selectedDenoms, props.assets]) - const [sorting, setSorting] = useState([{ id: 'symbol', desc: false }]) const [selected, setSelected] = useState(defaultSelected) - const columns = useAddVaultAssetTableColumns() + const balances = useStore((s) => s.balances) + const columns = useAssetTableColumns() + const tableData: AssetTableRow[] = useMemo(() => { + return props.assets.map((asset) => { + const balancesForAsset = balances.find(byDenom(asset.denom)) + return { + asset, + balance: balancesForAsset?.amount ?? '0', + } + }) + }, [balances, props.assets]) const table = useReactTable({ - data: props.assets, + data: tableData, columns, state: { sorting, diff --git a/src/components/Modals/AssetsSelect/useAssetTableColumns.tsx b/src/components/Modals/AssetsSelect/useAssetTableColumns.tsx new file mode 100644 index 00000000..ec61a7dc --- /dev/null +++ b/src/components/Modals/AssetsSelect/useAssetTableColumns.tsx @@ -0,0 +1,75 @@ +import { ColumnDef } from '@tanstack/react-table' +import React from 'react' + +import AssetImage from 'components/AssetImage' +import Checkbox from 'components/Checkbox' +import DisplayCurrency from 'components/DisplayCurrency' +import { FormattedNumber } from 'components/FormattedNumber' +import Text from 'components/Text' +import { BNCoin } from 'types/classes/BNCoin' +import { getAssetByDenom } from 'utils/assets' +import { demagnify, formatPercent } from 'utils/formatters' + +export default function useAssetTableColumns() { + const columns = React.useMemo[]>( + () => [ + { + header: 'Asset', + accessorKey: 'symbol', + id: 'symbol', + cell: ({ row }) => { + const asset = getAssetByDenom(row.original.asset.denom) as Asset + return ( +
+ + +
+ + {asset.symbol} + + {asset.name} +
+
+ ) + }, + }, + { + id: 'details', + header: (data) => { + const tableData = data.table.options.data as AssetTableRow[] + const assetData = tableData.length && (tableData[0].asset as BorrowAsset) + if (assetData && assetData.borrowRate) return 'Borrow Rate' + return 'Balance' + }, + cell: ({ row }) => { + const asset = row.original.asset as BorrowAsset + const balance = row.original.balance + if (asset.borrowRate) + return ( +
+ + {formatPercent(asset.borrowRate ?? 0)} + + APY +
+ ) + if (!balance) return null + const coin = new BNCoin({ denom: row.original.asset.denom, amount: balance }) + return ( +
+ + +
+ ) + }, + }, + ], + [], + ) + + return columns +} diff --git a/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx b/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx index 7155e319..e7dde561 100644 --- a/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx +++ b/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx @@ -61,19 +61,23 @@ export default function FundWithdrawModalContent(props: Props) { result = await deposit({ fee: hardcodedFee, accountId: props.account.id, - coin: { - denom: currentAsset.denom, - amount: amount.toString(), - }, + coins: [ + { + denom: currentAsset.denom, + amount: amount.toString(), + }, + ], }) } else { result = await withdraw({ fee: hardcodedFee, accountId: props.account.id, - coin: { - denom: currentAsset.denom, - amount: amount.toString(), - }, + coins: [ + { + denom: currentAsset.denom, + amount: amount.toString(), + }, + ], }) } diff --git a/src/components/Modals/ModalsContainer.tsx b/src/components/Modals/ModalsContainer.tsx index b532d969..820cd524 100644 --- a/src/components/Modals/ModalsContainer.tsx +++ b/src/components/Modals/ModalsContainer.tsx @@ -7,6 +7,7 @@ import { SettingsModal, UnlockModal, VaultModal, + WalletAssets, WithdrawFromVaultsModal, } from 'components/Modals' @@ -21,6 +22,7 @@ export default function ModalsContainer() { + ) diff --git a/src/components/Modals/Settings/index.tsx b/src/components/Modals/Settings/index.tsx index a44f7bfa..a0934621 100644 --- a/src/components/Modals/Settings/index.tsx +++ b/src/components/Modals/Settings/index.tsx @@ -18,12 +18,12 @@ import { REDUCE_MOTION_KEY, SLIPPAGE_KEY, } from 'constants/localStore' +import { BN_ZERO } from 'constants/math' import useAlertDialog from 'hooks/useAlertDialog' import useLocalStorage from 'hooks/useLocalStorage' import useStore from 'store' -import { getAllAssets, getDisplayCurrencies } from 'utils/assets' +import { getDisplayCurrencies, getEnabledMarketAssets } from 'utils/assets' import { BN } from 'utils/helpers' -import { BN_ZERO } from 'constants/math' const slippages = [0.02, 0.03] @@ -31,7 +31,7 @@ export default function SettingsModal() { const modal = useStore((s) => s.settingsModal) const { open: showResetDialog } = useAlertDialog() const displayCurrencies = getDisplayCurrencies() - const assets = getAllAssets() + const assets = getEnabledMarketAssets() const [customSlippage, setCustomSlippage] = useState(0) const [inputRef, setInputRef] = useState>() const [isCustom, setIsCustom] = useState(false) @@ -58,9 +58,15 @@ export default function SettingsModal() { displayCurrencies.map((asset, index) => ({ label: (
- + {asset.denom === 'usd' ? ( + + {asset.symbol} + + ) : ( + + )} - {asset.symbol} + {asset.name}
), diff --git a/src/components/Modals/Vault/VaultBorrowings.tsx b/src/components/Modals/Vault/VaultBorrowings.tsx index 93cab2a1..656b7438 100644 --- a/src/components/Modals/Vault/VaultBorrowings.tsx +++ b/src/components/Modals/Vault/VaultBorrowings.tsx @@ -8,14 +8,14 @@ import { ArrowRight, ExclamationMarkCircled } from 'components/Icons' import Slider from 'components/Slider' import Text from 'components/Text' import TokenInput from 'components/TokenInput' +import { BN_ZERO } from 'constants/math' import useMarketAssets from 'hooks/useMarketAssets' import usePrices from 'hooks/usePrices' import useStore from 'store' import { BNCoin } from 'types/classes/BNCoin' +import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { findCoinByDenom, getAssetByDenom } from 'utils/assets' import { formatPercent } from 'utils/formatters' -import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types' -import { BN_ZERO } from 'constants/math' export interface VaultBorrowingsProps { updatedAccount: Account diff --git a/src/components/Modals/WalletAssets/WalletAssetsModalContent.tsx b/src/components/Modals/WalletAssets/WalletAssetsModalContent.tsx new file mode 100644 index 00000000..4b10e255 --- /dev/null +++ b/src/components/Modals/WalletAssets/WalletAssetsModalContent.tsx @@ -0,0 +1,68 @@ +import { useCallback, useMemo, useState } from 'react' + +import AssetSelectTable from 'components/Modals/AssetsSelect/AssetSelectTable' +import SearchBar from 'components/SearchBar' +import useStore from 'store' +import { byDenom } from 'utils/array' +import { getAssetByDenom } from 'utils/assets' + +interface Props { + defaultSelectedDenoms: string[] + onChangeDenoms: (denoms: string[]) => void +} + +export default function WalletAssetsModalContent(props: Props) { + const [searchString, setSearchString] = useState('') + const balances = useStore((s) => s.balances) + + const assets = useMemo(() => { + const assetsInWallet: Asset[] = [] + balances.forEach((balance) => { + const asset = getAssetByDenom(balance.denom) + if (asset && asset.isMarket) assetsInWallet.push(asset) + }) + + return assetsInWallet + }, [balances]) + + const filteredAssets: Asset[] = useMemo(() => { + return assets.filter( + (asset) => + asset.name.toLowerCase().includes(searchString.toLowerCase()) || + asset.denom.toLowerCase().includes(searchString.toLowerCase()) || + asset.symbol.toLowerCase().includes(searchString.toLowerCase()), + ) + }, [assets, searchString]) + + const currentSelectedDenom = useStore((s) => s.walletAssetsModal?.selectedDenoms) + const [selectedDenoms, setSelectedDenoms] = useState( + currentSelectedDenom?.filter((denom) => filteredAssets.findIndex(byDenom(denom))) || [], + ) + + const onChangeDenoms = useCallback( + (denoms: string[]) => { + setSelectedDenoms(denoms) + props.onChangeDenoms(denoms) + }, + [props.onChangeDenoms], + ) + + return ( + <> +
+ +
+
+ +
+ + ) +} diff --git a/src/components/Modals/WalletAssets/index.tsx b/src/components/Modals/WalletAssets/index.tsx new file mode 100644 index 00000000..4c707163 --- /dev/null +++ b/src/components/Modals/WalletAssets/index.tsx @@ -0,0 +1,37 @@ +import { useCallback, useState } from 'react' + +import Button from 'components/Button' +import Modal from 'components/Modal' +import WalletAssetsModalContent from 'components/Modals/WalletAssets/WalletAssetsModalContent' +import Text from 'components/Text' +import useStore from 'store' + +export default function WalletAssetsModal() { + const modal = useStore((s) => s.walletAssetsModal) + const [selectedDenoms, setSelectedDenoms] = useState([]) + + const onClose = useCallback(() => { + useStore.setState({ + walletAssetsModal: { isOpen: false, selectedDenoms }, + }) + }, [selectedDenoms]) + + if (!modal?.isOpen) return null + + return ( + Your wallet} + onClose={onClose} + modalClassName='max-w-modal-xs' + headerClassName='bg-white/10 border-b-white/5 border-b items-center p-4' + > + +
+
+
+ ) +} diff --git a/src/components/Modals/index.tsx b/src/components/Modals/index.tsx index b7f12396..413946d8 100644 --- a/src/components/Modals/index.tsx +++ b/src/components/Modals/index.tsx @@ -1,4 +1,4 @@ -export { default as AddVaultBorrowAssetsModal } from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal' +export { default as AddVaultBorrowAssetsModal } from 'components/Modals/AddVaultAssets' export { default as AlertDialogController } from 'components/Modals/AlertDialog' export { default as BorrowModal } from 'components/Modals/BorrowModal' export { default as FundAndWithdrawModal } from 'components/Modals/FundWithdraw' @@ -6,4 +6,5 @@ export { default as LendAndReclaimModalController } from 'components/Modals/Lend export { default as SettingsModal } from 'components/Modals/Settings' export { default as UnlockModal } from 'components/Modals/Unlock' export { default as VaultModal } from 'components/Modals/Vault' +export { default as WalletAssets } from 'components/Modals/WalletAssets' export { default as WithdrawFromVaultsModal } from 'components/Modals/WithdrawFromVaultsModal' diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx index f4025504..6c63d47e 100644 --- a/src/components/Switch.tsx +++ b/src/components/Switch.tsx @@ -31,13 +31,13 @@ export default function Switch(props: Props) { 'isolate flex cursor-pointer items-center justify-between overflow-hidden', 'relative h-5 w-10 rounded-full bg-white/20 shadow-sm', 'before:content-[" "] before:absolute before:left-[1px] before:top-[1px]', - 'before:z-1 before:h-4.5 before:w-4.5 before:rounded-full before:bg-white before:transition-transform', + 'before:z-1 before:m-0.5 before:h-3.5 before:w-3.5 before:rounded-full before:bg-white before:transition-transform', 'peer-checked:active group peer-checked:before:translate-x-5', )} > diff --git a/src/components/Wallet/WalletBridges.tsx b/src/components/Wallet/WalletBridges.tsx index 3ad8d4a8..36b4ab5e 100644 --- a/src/components/Wallet/WalletBridges.tsx +++ b/src/components/Wallet/WalletBridges.tsx @@ -1,17 +1,24 @@ import { useShuttle } from '@delphi-labs/shuttle-react' import Image from 'next/image' -import { useCallback } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import Button from 'components/Button' import FullOverlayContent from 'components/FullOverlayContent' import { ChevronRight } from 'components/Icons' import Text from 'components/Text' +import WalletFetchBalancesAndAccounts from 'components/Wallet/WalletFetchBalancesAndAccounts' import WalletSelect from 'components/Wallet/WalletSelect' import { BRIDGES } from 'constants/bridges' import { CHAINS } from 'constants/chains' -import { ENV } from 'constants/env' +import { ENV, IS_TESTNET } from 'constants/env' import useCurrentWallet from 'hooks/useCurrentWallet' +import useToggle from 'hooks/useToggle' +import useWalletBalances from 'hooks/useWalletBalances' import useStore from 'store' +import { byDenom } from 'utils/array' +import { getBaseAsset } from 'utils/assets' +import { hardcodedFee } from 'utils/constants' +import { BN } from 'utils/helpers' const currentChainId = ENV.CHAIN_ID const currentChain = CHAINS[currentChainId] @@ -33,8 +40,17 @@ function Bridge({ name, url, image }: Bridge) { } export default function WalletBridges() { + const address = useStore((s) => s.address) const currentWallet = useCurrentWallet() const { disconnectWallet } = useShuttle() + const { data: walletBalances, isLoading } = useWalletBalances(address) + const baseAsset = getBaseAsset() + const [hasFunds, setHasFunds] = useToggle(false) + + const baseBalance = useMemo( + () => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0', + [walletBalances, baseAsset], + ) const handleClick = useCallback(() => { if (!currentWallet) return @@ -42,6 +58,16 @@ export default function WalletBridges() { useStore.setState({ focusComponent: }) }, [currentWallet, disconnectWallet]) + useEffect(() => { + if (hasFunds) { + useStore.setState({ focusComponent: }) + return + } + + if (BN(baseBalance).isGreaterThanOrEqualTo(hardcodedFee.amount[0].amount) && !isLoading) + setHasFunds(true) + }, [baseBalance, isLoading, hasFunds, setHasFunds]) + return ( ))}
+ {IS_TESTNET && ( +
+ + Need Testnet Funds? + + +
+ )} ) } diff --git a/src/components/Wallet/WalletConnectedButton.tsx b/src/components/Wallet/WalletConnectedButton.tsx index b10ef153..ebffd0a5 100644 --- a/src/components/Wallet/WalletConnectedButton.tsx +++ b/src/components/Wallet/WalletConnectedButton.tsx @@ -12,14 +12,14 @@ import Overlay from 'components/Overlay' import Text from 'components/Text' import { CHAINS } from 'constants/chains' import { IS_TESTNET } from 'constants/env' +import { BN_ZERO } from 'constants/math' import useCurrentWallet from 'hooks/useCurrentWallet' import useToggle from 'hooks/useToggle' import useWalletBalances from 'hooks/useWalletBalances' import useStore from 'store' import { ChainInfoID } from 'types/enums/wallet' import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets' -import { formatValue, truncate } from 'utils/formatters' -import { BN_ZERO } from 'constants/math' +import { truncate } from 'utils/formatters' export default function WalletConnectedButton() { // --------------- @@ -103,7 +103,11 @@ export default function WalletConnectedButton() { {isLoading ? ( ) : ( - `${formatValue(walletAmount.toString(), { suffix: ` ${baseAsset.symbol}` })}` + )}
diff --git a/src/components/Wallet/WalletFetchBalancesAndAccounts.tsx b/src/components/Wallet/WalletFetchBalancesAndAccounts.tsx index 26e7096b..51dfbeb3 100644 --- a/src/components/Wallet/WalletFetchBalancesAndAccounts.tsx +++ b/src/components/Wallet/WalletFetchBalancesAndAccounts.tsx @@ -1,4 +1,5 @@ import { Suspense, useEffect, useMemo } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' import AccountCreateFirst from 'components/Account/AccountCreateFirst' import { CircularProgress } from 'components/CircularProgress' @@ -11,6 +12,7 @@ import { byDenom } from 'utils/array' import { getBaseAsset } from 'utils/assets' import { hardcodedFee } from 'utils/constants' import { BN } from 'utils/helpers' +import { getPage, getRoute } from 'utils/route' function FetchLoading() { return ( @@ -25,6 +27,8 @@ function FetchLoading() { function Content() { const address = useStore((s) => s.address) + const navigate = useNavigate() + const { pathname } = useLocation() const { data: accounts } = useAccounts(address) const { data: walletBalances, isLoading } = useWalletBalances(address) const baseAsset = getBaseAsset() @@ -39,9 +43,10 @@ function Content() { accounts.length !== 0 && BN(baseBalance).isGreaterThanOrEqualTo(hardcodedFee.amount[0].amount) ) { + navigate(getRoute(getPage(pathname), address, accounts[0].id)) useStore.setState({ accounts: accounts, balances: walletBalances, focusComponent: null }) } - }, [accounts, walletBalances, baseBalance]) + }, [accounts, baseBalance, navigate, pathname, address, walletBalances]) if (isLoading) return if (BN(baseBalance).isLessThan(hardcodedFee.amount[0].amount)) return diff --git a/src/constants/assets.ts b/src/constants/assets.ts index 13240190..13cb49ac 100644 --- a/src/constants/assets.ts +++ b/src/constants/assets.ts @@ -133,9 +133,9 @@ export const ASSETS: Asset[] = [ pythPriceFeedId: 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a', }, { - symbol: 'gamm/pool/6', + symbol: 'OSMO-USDC.n', name: 'OSMO-USDC.n Pool Token', - id: 'gamm/pool/6', + id: 'OSMO-USDC.n', denom: 'gamm/pool/6', color: '', logo: '', @@ -147,4 +147,19 @@ export const ASSETS: Asset[] = [ isStable: false, forceFetchPrice: true, }, + { + symbol: '$', + name: 'US Dollar', + id: 'USD', + denom: 'usd', + color: '', + logo: '', + decimals: 2, + hasOraclePrice: false, + isEnabled: false, + isMarket: false, + isDisplayCurrency: true, + isStable: false, + forceFetchPrice: false, + }, ] diff --git a/src/constants/bridges.ts b/src/constants/bridges.ts index f0fb1466..bcca3982 100644 --- a/src/constants/bridges.ts +++ b/src/constants/bridges.ts @@ -1,4 +1,9 @@ export const BRIDGES: Bridge[] = [ + { + name: 'TFM Bridge', + url: 'https://tfm.com/bridge?chainTo=osmosis-1', + image: '/images/bridges/tfm.png', + }, { name: 'Gravity bridge', url: 'https://bridge.blockscape.network', diff --git a/src/constants/defaultSettings.ts b/src/constants/defaultSettings.ts index 1c6addcf..ca62b5ac 100644 --- a/src/constants/defaultSettings.ts +++ b/src/constants/defaultSettings.ts @@ -1,9 +1,10 @@ import { ASSETS } from 'constants/assets' +import { ORACLE_DENOM } from 'constants/oracle' export const DEFAULT_SETTINGS: Settings = { reduceMotion: false, lendAssets: false, preferredAsset: ASSETS[0].denom, - displayCurrency: ASSETS[0].denom, + displayCurrency: ORACLE_DENOM, slippage: 0.02, } diff --git a/src/constants/oracle.ts b/src/constants/oracle.ts new file mode 100644 index 00000000..fa37dcff --- /dev/null +++ b/src/constants/oracle.ts @@ -0,0 +1 @@ +export const ORACLE_DENOM = 'usd' diff --git a/src/store/slices/broadcast.ts b/src/store/slices/broadcast.ts index d5e81b82..f7672086 100644 --- a/src/store/slices/broadcast.ts +++ b/src/store/slices/broadcast.ts @@ -1,6 +1,6 @@ +import { MsgExecuteContract } from '@delphi-labs/shuttle-react' import { isMobile } from 'react-device-detect' import { GetState, SetState } from 'zustand' -import { MsgExecuteContract } from '@delphi-labs/shuttle-react' import { ENV } from 'constants/env' import { Store } from 'store' @@ -108,24 +108,20 @@ export default function createBroadcastSlice( return !!response.result }, - deposit: async (options: { fee: StdFee; accountId: string; coin: Coin }) => { + deposit: async (options: { fee: StdFee; accountId: string; coins: Coin[] }) => { const msg: CreditManagerExecuteMsg = { update_credit_account: { account_id: options.accountId, - actions: [ - { - deposit: options.coin, - }, - ], + actions: options.coins.map((coin) => ({ + deposit: coin, + })), }, } - const response = await get().executeMsg({ msg, fee: options.fee, funds: [options.coin] }) + const response = await get().executeMsg({ msg, fee: options.fee, funds: options.coins }) - handleResponseMessages( - response, - `Deposited ${formatAmountWithSymbol(options.coin)} to Account ${options.accountId}`, - ) + const depositString = options.coins.map((coin) => formatAmountWithSymbol(coin)).join('and ') + handleResponseMessages(response, `Deposited ${depositString} to Account ${options.accountId}`) return !!response.result }, unlock: async (options: { @@ -200,23 +196,21 @@ export default function createBroadcastSlice( handleResponseMessages(response, `Deposited into vault`) return !!response.result }, - withdraw: async (options: { fee: StdFee; accountId: string; coin: Coin }) => { + withdraw: async (options: { fee: StdFee; accountId: string; coins: Coin[] }) => { const msg: CreditManagerExecuteMsg = { update_credit_account: { account_id: options.accountId, - actions: [ - { - withdraw: options.coin, - }, - ], + actions: options.coins.map((coin) => ({ + withdraw: coin, + })), }, } const response = await get().executeMsg({ msg, fee: options.fee }) - + const withdrawString = options.coins.map((coin) => formatAmountWithSymbol(coin)).join('and ') handleResponseMessages( response, - `Withdrew ${formatAmountWithSymbol(options.coin)} from Account ${options.accountId}`, + `Withdrew ${withdrawString} from Account ${options.accountId}`, ) return !!response.result }, diff --git a/src/store/slices/common.ts b/src/store/slices/common.ts index deebd552..f361e148 100644 --- a/src/store/slices/common.ts +++ b/src/store/slices/common.ts @@ -1,5 +1,7 @@ import { GetState, SetState } from 'zustand' +import { ASSETS } from 'constants/assets' + export default function createCommonSlice(set: SetState, get: GetState) { return { accounts: null, diff --git a/src/store/slices/modal.ts b/src/store/slices/modal.ts index 3deb5ffa..288edf97 100644 --- a/src/store/slices/modal.ts +++ b/src/store/slices/modal.ts @@ -14,6 +14,7 @@ export default function createModalSlice(set: SetState, get: GetStat settingsModal: false, unlockModal: null, vaultModal: null, + walletAssetsModal: null, withdrawFromVaultsModal: null, } } diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts index aa2c6505..573e9141 100644 --- a/src/types/interfaces/asset.d.ts +++ b/src/types/interfaces/asset.d.ts @@ -11,7 +11,8 @@ interface Asset { | 'USDC.n' | 'WBTC.axl' | 'WETH.axl' - | 'gamm/pool/6' + | 'OSMO-USDC.n' + | '$' id: | 'OSMO' | 'ATOM' @@ -21,7 +22,8 @@ interface Asset { | 'axlWBTC' | 'axlWETH' | 'nUSDC' - | 'gamm/pool/6' + | 'OSMO-USDC.n' + | 'USD' prefix?: string contract_addr?: string logo: string diff --git a/src/types/interfaces/components/Modals/AssetSelect.d.ts b/src/types/interfaces/components/Modals/AssetSelect.d.ts new file mode 100644 index 00000000..3770c1db --- /dev/null +++ b/src/types/interfaces/components/Modals/AssetSelect.d.ts @@ -0,0 +1,4 @@ +interface AssetTableRow { + balance?: string + asset: BorrowAsset | Asset +} diff --git a/src/types/interfaces/components/docLink.d.ts b/src/types/interfaces/components/docLink.d.ts new file mode 100644 index 00000000..c1541f7a --- /dev/null +++ b/src/types/interfaces/components/docLink.d.ts @@ -0,0 +1 @@ +type DocLinkType = 'wallet' | 'account' | 'terms' | 'fund' diff --git a/src/types/interfaces/store/broadcast.d.ts b/src/types/interfaces/store/broadcast.d.ts index 6d25db72..bf43aed3 100644 --- a/src/types/interfaces/store/broadcast.d.ts +++ b/src/types/interfaces/store/broadcast.d.ts @@ -20,7 +20,7 @@ interface BroadcastSlice { }) => Promise createAccount: (options: { fee: StdFee }) => Promise deleteAccount: (options: { fee: StdFee; accountId: string }) => Promise - deposit: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise + deposit: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise unlock: (options: { fee: StdFee accountId: string @@ -37,7 +37,7 @@ interface BroadcastSlice { accountId: string actions: Action[] }) => Promise - withdraw: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise + withdraw: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise lend: (options: { fee: StdFee accountId: string diff --git a/src/types/interfaces/store/modals.d.ts b/src/types/interfaces/store/modals.d.ts index f0e6aaab..776dac40 100644 --- a/src/types/interfaces/store/modals.d.ts +++ b/src/types/interfaces/store/modals.d.ts @@ -8,9 +8,10 @@ interface ModalSlice { fundAndWithdrawModal: 'fund' | 'withdraw' | null lendAndReclaimModal: LendAndReclaimModalConfig | null settingsModal: boolean - vaultModal: VaultModal | null - withdrawFromVaultsModal: DepositedVault[] | null unlockModal: UnlockModal | null + vaultModal: VaultModal | null + walletAssetsModal: WalletAssetModal | null + withdrawFromVaultsModal: DepositedVault[] | null } interface AlertDialogButton { @@ -52,3 +53,7 @@ interface AddVaultBorrowingsModal { interface UnlockModal { vault: DepositedVault } +interface WalletAssetModal { + isOpen?: boolean + selectedDenoms: string[] +} diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index d0651ba2..354b5786 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -1,43 +1,49 @@ import BigNumber from 'bignumber.js' +import { BN_ZERO } from 'constants/math' +import { BNCoin } from 'types/classes/BNCoin' import { Positions, VaultPosition, } from 'types/generated/mars-credit-manager/MarsCreditManager.types' +import { getAssetByDenom } from 'utils/assets' import { BN } from 'utils/helpers' -import { BNCoin } from 'types/classes/BNCoin' -import { BN_ZERO } from 'constants/math' -export const calculateAccountBalance = ( +export const calculateAccountBalanceValue = ( account: Account | AccountChange, prices: BNCoin[], ): BigNumber => { - const totalDepositValue = calculateAccountDeposits(account, prices) - const totalDebtValue = calculateAccountDebt(account, prices) + const totalDepositValue = calculateAccountDepositsValue(account, prices) + const totalDebtValue = calculateAccountDebtValue(account, prices) return totalDepositValue.minus(totalDebtValue) } -export const calculateAccountDeposits = ( +export const calculateAccountDepositsValue = ( account: Account | AccountChange, prices: BNCoin[], ): BigNumber => { if (!account.deposits) return BN_ZERO return account.deposits.reduce((acc, deposit) => { + const asset = getAssetByDenom(deposit.denom) + if (!asset) return acc const price = prices.find((price) => price.denom === deposit.denom)?.amount ?? 0 - const depositValue = BN(deposit.amount).multipliedBy(price) + const amount = BN(deposit.amount).shiftedBy(-asset.decimals) + const depositValue = amount.multipliedBy(price) return acc.plus(depositValue) }, BN_ZERO) } -export const calculateAccountDebt = ( +export const calculateAccountDebtValue = ( account: Account | AccountChange, prices: BNCoin[], ): BigNumber => { if (!account.debts) return BN_ZERO return account.debts.reduce((acc, debt) => { + const asset = getAssetByDenom(debt.denom) + if (!asset) return acc const price = prices.find((price) => price.denom === debt.denom)?.amount ?? 0 - const debtAmount = BN(debt.amount) + const debtAmount = BN(debt.amount).shiftedBy(-asset.decimals) const debtValue = debtAmount.multipliedBy(price) return acc.plus(debtValue) }, BN_ZERO) diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts index 026cf50e..76cbbfff 100644 --- a/src/utils/formatters.ts +++ b/src/utils/formatters.ts @@ -2,8 +2,9 @@ import BigNumber from 'bignumber.js' import moment from 'moment' import { BN_ZERO } from 'constants/math' +import { ORACLE_DENOM } from 'constants/oracle' import { BNCoin } from 'types/classes/BNCoin' -import { getEnabledMarketAssets } from 'utils/assets' +import { getAllAssets, getEnabledMarketAssets } from 'utils/assets' import { BN } from 'utils/helpers' export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string { @@ -177,15 +178,14 @@ export function demagnify(amount: number | string | BigNumber, asset: Asset | Ps export function convertToDisplayAmount(coin: BNCoin, displayCurrency: string, prices: BNCoin[]) { const price = prices.find((price) => price.denom === coin.denom) - const asset = getEnabledMarketAssets().find((asset) => asset.denom === coin.denom) + const asset = getAllAssets().find((asset) => asset.denom === coin.denom) const displayPrice = prices.find((price) => price.denom === displayCurrency) - if (!price || !asset || !displayPrice) return BN_ZERO + if (!price || !displayPrice || !asset) return BN_ZERO - return coin.amount - .shiftedBy(-1 * asset.decimals) - .multipliedBy(price.amount) - .dividedBy(displayPrice.amount) + const decimals = asset.denom === ORACLE_DENOM ? 0 : asset.decimals * -1 + + return coin.amount.shiftedBy(decimals).multipliedBy(price.amount).dividedBy(displayPrice.amount) } export function convertLiquidityRateToAPR(rate: number) {