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 <yusuf@delphilabs.io>
This commit is contained in:
parent
bb6a9b99e3
commit
184a27e987
BIN
public/images/bridges/tfm.png
Normal file
BIN
public/images/bridges/tfm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -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<BNCoin[]> {
|
||||
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<BNCoin[]> {
|
||||
).flat()
|
||||
const poolPrices = await requestPoolPrices(assetsWithPoolIds, pythAndOraclePrices)
|
||||
|
||||
return [...pythAndOraclePrices, ...poolPrices]
|
||||
return [...pythAndOraclePrices, ...poolPrices, usdPrice]
|
||||
} catch (ex) {
|
||||
console.error(ex)
|
||||
throw ex
|
||||
|
@ -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 (
|
||||
<FormattedNumber
|
||||
className='text-right text-xs'
|
||||
amount={demagnify(
|
||||
row.original.amount,
|
||||
ASSETS.find((asset) => asset.denom === row.original.denom) ?? ASSETS[0],
|
||||
)}
|
||||
options={{ maxDecimals: 4 }}
|
||||
amount={Number(BN(amount).toPrecision(2))}
|
||||
options={{ maxDecimals: 2, abbreviated: true }}
|
||||
animate
|
||||
/>
|
||||
)
|
||||
|
@ -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) {
|
||||
/>
|
||||
) : (
|
||||
<DisplayCurrency
|
||||
coin={new BNCoin({ amount: props.current.toString(), denom: baseCurrency.denom })}
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, props.current)}
|
||||
className='text-sm'
|
||||
/>
|
||||
)}
|
||||
@ -117,7 +116,7 @@ function Item(props: ItemProps) {
|
||||
/>
|
||||
) : (
|
||||
<DisplayCurrency
|
||||
coin={new BNCoin({ amount: props.change.toString(), denom: baseCurrency.denom })}
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, props.change)}
|
||||
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
|
||||
/>
|
||||
)}
|
||||
|
@ -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: <WalletSelect /> })
|
||||
}, [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: <AccountFundFirst /> })
|
||||
}
|
||||
}, [createAccount, navigate, pathname, address, setIsCreating])
|
||||
|
||||
return (
|
||||
<FullOverlayContent
|
||||
|
@ -1,10 +1,17 @@
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { Heart } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useHealthComputer from 'hooks/useHealthComputer'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { calculateAccountBalanceValue } from 'utils/accounts'
|
||||
import { formatHealth } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
account: Account
|
||||
@ -13,27 +20,35 @@ interface Props {
|
||||
export default function AccountDetailsController() {
|
||||
const account = useCurrentAccount()
|
||||
const address = useStore((s) => s.address)
|
||||
const focusComponent = useStore((s) => s.focusComponent)
|
||||
|
||||
if (!account || !address) return null
|
||||
if (!account || !address || focusComponent) return null
|
||||
|
||||
return <AccountDetails account={account} />
|
||||
}
|
||||
|
||||
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 (
|
||||
<div
|
||||
data-testid='account-details'
|
||||
className='w-16 rounded-base border border-white/20 bg-white/5 backdrop-blur-sticky'
|
||||
>
|
||||
<div className='flex w-full flex-wrap justify-center py-4'>
|
||||
<Gauge tooltip='Health Factor' percentage={20} icon={<Heart />} />
|
||||
<Gauge tooltip='Health Factor' percentage={healthFactor} icon={<Heart />} />
|
||||
<Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'>
|
||||
Health
|
||||
</Text>
|
||||
<Text size='xs' className='w-full text-center'>
|
||||
{formatHealth(health)}
|
||||
</Text>
|
||||
<FormattedNumber
|
||||
className={'w-full text-center text-xs'}
|
||||
amount={healthFactor}
|
||||
options={{ maxDecimals: 0, minDecimals: 0 }}
|
||||
animate
|
||||
/>
|
||||
</div>
|
||||
<div className='w-full border border-x-0 border-white/20 py-4'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
@ -43,13 +58,11 @@ function AccountDetails(props: Props) {
|
||||
4.5x
|
||||
</Text>
|
||||
</div>
|
||||
<div className='w-full py-4'>
|
||||
<div className='w-full px-1 py-4'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
Balance
|
||||
</Text>
|
||||
<Text size='xs' className='w-full text-center'>
|
||||
$300M
|
||||
</Text>
|
||||
<DisplayCurrency coin={coin} className='w-full truncate text-center text-2xs ' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
170
src/components/Account/AccountFund.tsx
Normal file
170
src/components/Account/AccountFund.tsx
Normal file
@ -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 | string>(null)
|
||||
const [fundingAssets, setFundingAssets] = useState<Coin[]>([])
|
||||
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: <WalletBridges /> })
|
||||
}
|
||||
}, [baseBalance])
|
||||
|
||||
useEffect(() => {
|
||||
if (accounts && !selectedAccountId && accountId)
|
||||
setSelectedAccountId(currentAccount?.id ?? accountId)
|
||||
}, [accounts, selectedAccountId, accountId, currentAccount])
|
||||
|
||||
if (!selectedAccountId) return null
|
||||
|
||||
return (
|
||||
<FullOverlayContent
|
||||
title={`Fund Credit Account #${selectedAccountId}`}
|
||||
copy='In order to start trading with this account, you need to deposit funds.'
|
||||
docs='fund'
|
||||
>
|
||||
<Card className='w-full bg-white/5 p-6'>
|
||||
{!hasAssetSelected && <Text>Please select an asset.</Text>}
|
||||
{selectedDenoms.map((denom) => {
|
||||
const asset = getAssetByDenom(denom) as Asset
|
||||
|
||||
const balance = walletBalances.find(byDenom(asset.denom))?.amount ?? '0'
|
||||
return (
|
||||
<div
|
||||
key={asset.symbol}
|
||||
className='w-full rounded-base border border-white/20 bg-white/5 p-4'
|
||||
>
|
||||
<TokenInputWithSlider
|
||||
asset={asset}
|
||||
onChange={(amount) => updateFundingAssets(amount, asset.denom)}
|
||||
amount={BN_ZERO}
|
||||
max={BN(balance)}
|
||||
balances={walletBalances}
|
||||
maxText='Max'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
<Button
|
||||
className='mt-4 w-full'
|
||||
text='Select assets'
|
||||
color='tertiary'
|
||||
rightIcon={<Plus />}
|
||||
iconClassName='w-3'
|
||||
onClick={handleSelectAssetsClick}
|
||||
/>
|
||||
<div className='mt-4 border border-transparent border-t-white/10 pt-4'>
|
||||
<SwitchWithLabel
|
||||
name='isLending'
|
||||
label='Lend assets to earn yield'
|
||||
value={isAutoLendEnabled}
|
||||
onChange={() => toggleAutoLend(selectedAccountId)}
|
||||
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!`}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className='mt-4 w-full'
|
||||
text='Fund account'
|
||||
color='tertiary'
|
||||
disabled={!hasFundingAssets}
|
||||
showProgressIndicator={isFunding}
|
||||
onClick={handleClick}
|
||||
size='lg'
|
||||
/>
|
||||
</Card>
|
||||
</FullOverlayContent>
|
||||
)
|
||||
}
|
@ -2,6 +2,7 @@ import classNames from 'classnames'
|
||||
import { useEffect } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import AccountFundFirst from 'components/Account/AccountFund'
|
||||
import AccountStats from 'components/Account/AccountStats'
|
||||
import Button from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
@ -9,16 +10,18 @@ import { ArrowCircledTopRight, ArrowDownLine, ArrowUpLine, TrashBin } from 'comp
|
||||
import Radio from 'components/Radio'
|
||||
import SwitchWithLabel from 'components/SwitchWithLabel'
|
||||
import Text from 'components/Text'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { calculateAccountDeposits } from 'utils/accounts'
|
||||
import { calculateAccountBalanceValue, calculateAccountDepositsValue } from 'utils/accounts'
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
|
||||
interface Props {
|
||||
setShowFundAccount: (showFundAccount: boolean) => void
|
||||
accounts: Account[]
|
||||
}
|
||||
|
||||
@ -34,18 +37,17 @@ export default function AccountList(props: Props) {
|
||||
const { data: prices } = usePrices()
|
||||
const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLendEnabledAccountIds()
|
||||
const deleteAccount = useStore((s) => s.deleteAccount)
|
||||
|
||||
const accountSelected = !!accountId && !isNaN(Number(accountId))
|
||||
const selectedAccountDetails = props.accounts.find((account) => account.id === accountId)
|
||||
const selectedAccountBalance = selectedAccountDetails
|
||||
? calculateAccountDeposits(selectedAccountDetails, prices)
|
||||
? calculateAccountBalanceValue(selectedAccountDetails, prices)
|
||||
: BN_ZERO
|
||||
|
||||
async function deleteAccountHandler() {
|
||||
if (!accountSelected) return
|
||||
const isSuccess = await deleteAccount({ fee: hardcodedFee, accountId: accountId })
|
||||
if (isSuccess) {
|
||||
navigate(`/wallets/${address}/accounts`)
|
||||
navigate(getRoute(getPage(pathname), address))
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +63,7 @@ export default function AccountList(props: Props) {
|
||||
return (
|
||||
<div className='flex w-full flex-wrap p-4'>
|
||||
{props.accounts.map((account) => {
|
||||
const positionBalance = calculateAccountDeposits(account, prices)
|
||||
const positionBalance = calculateAccountDepositsValue(account, prices)
|
||||
const isActive = accountId === account.id
|
||||
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id)
|
||||
|
||||
@ -103,6 +105,10 @@ export default function AccountList(props: Props) {
|
||||
color='tertiary'
|
||||
leftIcon={<ArrowUpLine />}
|
||||
onClick={() => {
|
||||
if (positionBalance.isLessThanOrEqualTo(0)) {
|
||||
useStore.setState({ focusComponent: <AccountFundFirst /> })
|
||||
return
|
||||
}
|
||||
useStore.setState({ fundAndWithdrawModal: 'fund' })
|
||||
}}
|
||||
/>
|
||||
|
@ -1,11 +1,10 @@
|
||||
import classNames from 'classnames'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||
import AccountFundFirst from 'components/Account/AccountFund'
|
||||
import AccountList from 'components/Account/AccountList'
|
||||
import CreateAccount from 'components/Account/CreateAccount'
|
||||
import FundAccount from 'components/Account/FundAccount'
|
||||
import Button from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Account, Plus, PlusCircled } from 'components/Icons'
|
||||
@ -18,7 +17,7 @@ import useStore from 'store'
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { isNumber } from 'utils/parsers'
|
||||
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide'
|
||||
const ACCOUNT_MENU_BUTTON_ID = 'account-menu-button'
|
||||
|
||||
@ -28,6 +27,7 @@ interface Props {
|
||||
|
||||
export default function AccountMenuContent(props: Props) {
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation()
|
||||
const { accountId, address } = useParams()
|
||||
const createAccount = useStore((s) => s.createAccount)
|
||||
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||
@ -39,12 +39,8 @@ export default function AccountMenuContent(props: Props) {
|
||||
const isAccountSelected = isNumber(accountId)
|
||||
|
||||
const selectedAccountDetails = props.accounts.find((account) => account.id === accountId)
|
||||
const [showFundAccount, setShowFundAccount] = useState<boolean>(
|
||||
isAccountSelected && !selectedAccountDetails?.deposits?.length,
|
||||
)
|
||||
|
||||
const isLoadingAccount = isAccountSelected && selectedAccountDetails?.id !== accountId
|
||||
const showCreateAccount = !hasCreditAccounts || isCreating
|
||||
|
||||
const checkHasFunds = useCallback(() => {
|
||||
return (
|
||||
@ -59,11 +55,15 @@ export default function AccountMenuContent(props: Props) {
|
||||
const accountId = await createAccount({ fee: hardcodedFee })
|
||||
setIsCreating(false)
|
||||
|
||||
accountId && navigate(`/wallets/${address}/accounts/${accountId}`)
|
||||
}, [address, createAccount, navigate, setIsCreating, setShowMenu])
|
||||
if (accountId) {
|
||||
navigate(getRoute(getPage(pathname), address, accountId))
|
||||
useStore.setState({ focusComponent: <AccountFundFirst /> })
|
||||
}
|
||||
}, [createAccount, navigate, pathname, address, setShowMenu, setIsCreating])
|
||||
|
||||
const handleCreateAccountClick = useCallback(() => {
|
||||
if (!checkHasFunds()) {
|
||||
setShowMenu(!showMenu)
|
||||
if (!checkHasFunds() && !hasCreditAccounts) {
|
||||
useStore.setState({ focusComponent: <WalletBridges /> })
|
||||
return
|
||||
}
|
||||
@ -71,8 +71,6 @@ export default function AccountMenuContent(props: Props) {
|
||||
useStore.setState({ focusComponent: <AccountCreateFirst /> })
|
||||
return
|
||||
}
|
||||
|
||||
setShowMenu(!showMenu)
|
||||
}, [checkHasFunds, hasCreditAccounts, setShowMenu, showMenu])
|
||||
|
||||
useEffect(() => {
|
||||
@ -98,65 +96,43 @@ export default function AccountMenuContent(props: Props) {
|
||||
: 'Create Account'}
|
||||
</Button>
|
||||
<Overlay
|
||||
className={classNames(
|
||||
'max-w-screen right-0 mt-2 flex h-[530px] w-[336px]',
|
||||
!showFundAccount && 'overflow-hidden',
|
||||
)}
|
||||
className='max-w-screen right-0 mt-2 flex h-[530px] w-[336px] overflow-hidden'
|
||||
show={showMenu}
|
||||
setShow={setShowMenu}
|
||||
>
|
||||
{!showFundAccount && !showCreateAccount ? (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex h-[54px] w-full items-center justify-between bg-white/5 px-4 py-3',
|
||||
'border border-transparent border-b-white/10',
|
||||
)}
|
||||
>
|
||||
<Text size='lg' className='font-bold'>
|
||||
Accounts
|
||||
</Text>
|
||||
<Button
|
||||
color='secondary'
|
||||
className='w-[108px]'
|
||||
rightIcon={<Plus />}
|
||||
iconClassName='h-2.5 w-2.5'
|
||||
text='Create'
|
||||
onClick={performCreateAccount}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex h-[54px] w-full items-center justify-between bg-white/5 px-4 py-3',
|
||||
'border border-transparent border-b-white/10',
|
||||
)}
|
||||
>
|
||||
<Text size='lg' className='font-bold'>
|
||||
Accounts
|
||||
</Text>
|
||||
<Button
|
||||
color='secondary'
|
||||
className='w-[108px]'
|
||||
rightIcon={<Plus />}
|
||||
iconClassName='h-2.5 w-2.5'
|
||||
text='Create'
|
||||
showProgressIndicator={isCreating}
|
||||
onClick={performCreateAccount}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
menuClasses,
|
||||
'overflow-y-scroll scroll-smooth',
|
||||
'top-[54px] h-[calc(100%-54px)] items-start',
|
||||
)}
|
||||
>
|
||||
{isAccountSelected && isLoadingAccount && (
|
||||
<div className='flex h-full w-full items-center justify-center p-4'>
|
||||
<CircularProgress size={40} />
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
menuClasses,
|
||||
!showFundAccount && 'overflow-y-scroll scroll-smooth',
|
||||
'top-[54px] h-[calc(100%-54px)] items-start',
|
||||
)}
|
||||
>
|
||||
{isAccountSelected && isLoadingAccount && (
|
||||
<div className='flex h-full w-full items-center justify-center p-4'>
|
||||
<CircularProgress size={40} />
|
||||
</div>
|
||||
)}
|
||||
{hasCreditAccounts && !showFundAccount && !isLoadingAccount && (
|
||||
<AccountList accounts={props.accounts} setShowFundAccount={setShowFundAccount} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={classNames(
|
||||
menuClasses,
|
||||
!showFundAccount && 'overflow-y-scroll scroll-smooth',
|
||||
'inset-0 h-full items-end bg-account',
|
||||
)}
|
||||
>
|
||||
{showCreateAccount ? (
|
||||
<CreateAccount createAccount={performCreateAccount} isCreating={isCreating} />
|
||||
) : showFundAccount ? (
|
||||
<FundAccount setShowFundAccount={setShowFundAccount} setShowMenu={setShowMenu} />
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{hasCreditAccounts && !isLoadingAccount && <AccountList accounts={props.accounts} />}
|
||||
</div>
|
||||
</Overlay>
|
||||
</div>
|
||||
)
|
||||
|
@ -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 (
|
||||
<div className='w-full flex-wrap'>
|
||||
<DisplayCurrency
|
||||
coin={new BNCoin({ amount: props.balance.toString(), denom: baseCurrency.denom })}
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, props.balance)}
|
||||
className='w-full text-xl'
|
||||
/>
|
||||
<div className='mt-1 flex w-full items-center'>
|
||||
|
@ -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) {
|
||||
<Card className='mb-4 h-min min-w-fit bg-white/10' contentClassName='flex'>
|
||||
<Item>
|
||||
<DisplayCurrency
|
||||
coin={new BNCoin({ amount: accountBalance.toString(), denom: baseCurrency.denom })}
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalance)}
|
||||
className='text-sm'
|
||||
/>
|
||||
</Item>
|
||||
|
@ -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 (
|
||||
<div className='relative z-10 w-full p-4'>
|
||||
<Text size='lg' className='mb-2 font-bold'>
|
||||
Create a Credit Account
|
||||
</Text>
|
||||
<Text className='mb-4 text-white/70'>
|
||||
Please approve the transaction in your wallet in order to create your first Credit Account.
|
||||
</Text>
|
||||
<Button
|
||||
className='w-full'
|
||||
showProgressIndicator={props.isCreating}
|
||||
text='Create Account'
|
||||
rightIcon={<ArrowRight />}
|
||||
onClick={props.createAccount}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -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<Asset>(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 (
|
||||
<>
|
||||
<div className='absolute right-4 top-4'>
|
||||
<Button
|
||||
onClick={() => props.setShowFundAccount(false)}
|
||||
leftIcon={<Cross />}
|
||||
className='h-8 w-8'
|
||||
iconClassName='h-2 w-2'
|
||||
color='tertiary'
|
||||
size='xs'
|
||||
/>
|
||||
</div>
|
||||
<div className='relative z-10 w-full p-4'>
|
||||
<Text size='lg' className='mb-2 font-bold'>
|
||||
{`Fund Account ${accountId}`}
|
||||
</Text>
|
||||
<Text className='mb-4 text-white/70'>
|
||||
Deposit assets from your Osmosis address to your Mars credit account. Bridge assets if
|
||||
your Osmosis address has no assets.
|
||||
</Text>
|
||||
<TokenInputWithSlider
|
||||
asset={asset}
|
||||
onChange={onChangeAmount}
|
||||
onChangeAsset={onChangeAsset}
|
||||
amount={amount}
|
||||
max={max}
|
||||
className='mb-4'
|
||||
disabled={isFunding}
|
||||
hasSelect
|
||||
balances={balances}
|
||||
/>
|
||||
<div className='mb-4 w-full border-b border-white/10' />
|
||||
<SwitchWithLabel
|
||||
name='isLending'
|
||||
label='Lend assets to earn yield'
|
||||
value={autoLendEnabledAccountIds.includes(accountId)}
|
||||
onChange={() => 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)}
|
||||
/>
|
||||
<Button
|
||||
className='w-full'
|
||||
showProgressIndicator={isFunding}
|
||||
disabled={amount.isEqualTo(0)}
|
||||
text='Fund Account'
|
||||
rightIcon={<ArrowRight />}
|
||||
onClick={onDeposit}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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 (
|
||||
<TitleAndSubCell
|
||||
title={
|
||||
<FormattedNumber
|
||||
amount={props.amount.toNumber()}
|
||||
options={{ decimals: props.asset.decimals, abbreviated: true }}
|
||||
animate
|
||||
/>
|
||||
}
|
||||
sub={
|
||||
<DisplayCurrency
|
||||
coin={new BNCoin({ amount: props.amount.toString(), denom: props.asset.denom })}
|
||||
/>
|
||||
}
|
||||
className='justify-end'
|
||||
/>
|
||||
<div className='flex flex-col gap-[0.5] text-xs'>
|
||||
<FormattedNumber
|
||||
amount={props.amount.toNumber()}
|
||||
options={{ decimals: props.asset.decimals, abbreviated: true }}
|
||||
animate
|
||||
/>
|
||||
<DisplayCurrency
|
||||
className='justify-end text-xs text-white/50'
|
||||
coin={BNCoin.fromDenomAndBigNumber(props.asset.denom, props.amount)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -7,6 +7,14 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function AssetImage(props: Props) {
|
||||
if (!props.asset.logo)
|
||||
return (
|
||||
<div
|
||||
className={props.className}
|
||||
style={{ width: `${props.size}px`, height: `${props.size}px` }}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Image
|
||||
src={props.asset.logo}
|
||||
|
@ -29,18 +29,24 @@ export default function DisplayCurrency(props: Props) {
|
||||
[displayCurrency, displayCurrencies],
|
||||
)
|
||||
|
||||
const isUSD = displayCurrencyAsset.id === 'USD'
|
||||
const prefix = isUSD
|
||||
? `${props.isApproximation ? '~ ' : ''}$`
|
||||
: `${props.isApproximation ? '~ ' : ''}`
|
||||
const suffix = isUSD
|
||||
? ''
|
||||
: ` ${displayCurrencyAsset.symbol ? ` ${displayCurrencyAsset.symbol}` : ''}`
|
||||
|
||||
return (
|
||||
<FormattedNumber
|
||||
className={props.className}
|
||||
amount={convertToDisplayAmount(props.coin, displayCurrency, prices).toNumber()}
|
||||
options={{
|
||||
minDecimals: 0,
|
||||
minDecimals: isUSD ? 2 : 0,
|
||||
maxDecimals: 2,
|
||||
abbreviated: true,
|
||||
prefix: `${props.isApproximation ? '~ ' : ''}${
|
||||
displayCurrencyAsset.prefix ? displayCurrencyAsset.prefix : ''
|
||||
}`,
|
||||
suffix: displayCurrencyAsset.symbol ? ` ${displayCurrencyAsset.symbol}` : '',
|
||||
prefix,
|
||||
suffix,
|
||||
}}
|
||||
animate
|
||||
/>
|
||||
|
@ -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',
|
||||
|
@ -38,15 +38,15 @@ export const FormattedNumber = React.memo(
|
||||
reduceMotion
|
||||
)
|
||||
return (
|
||||
<span className={classNames('number', props.className)}>
|
||||
<p className={classNames('number', props.className)}>
|
||||
{formatValue(props.amount.toString(), props.options)}
|
||||
</span>
|
||||
</p>
|
||||
)
|
||||
|
||||
return (
|
||||
<animated.span className={classNames('number', props.className)}>
|
||||
<animated.p className={classNames('number', props.className)}>
|
||||
{springAmount.number.to((num) => formatValue(num, props.options))}
|
||||
</animated.span>
|
||||
</animated.p>
|
||||
)
|
||||
},
|
||||
(prevProps, nextProps) => prevProps.amount === nextProps.amount,
|
||||
|
@ -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 (
|
||||
<div className='min-h-[600px] w-100'>
|
||||
<div className={classNames('min-h-[600px] w-100', props.className)}>
|
||||
<Text size='4xl' className='w-full pb-2 text-center'>
|
||||
{props.title}
|
||||
</Text>
|
||||
|
@ -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}
|
||||
</div>
|
||||
)}
|
||||
<FormattedNumber
|
||||
className={classNames(labelClassName, 'text-2xs')}
|
||||
amount={Math.round(percentage)}
|
||||
options={{ maxDecimals: 0, minDecimals: 0 }}
|
||||
animate
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
|
@ -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
|
||||
</Text>
|
||||
</div>
|
||||
<AddVaultAssetTable
|
||||
<AssetSelectTable
|
||||
assets={poolAssets}
|
||||
onChangeSelected={onChangePoolDenoms}
|
||||
selectedDenoms={selectedPoolDenoms}
|
||||
@ -101,7 +101,7 @@ export default function AddVaultAssetsModalContent(props: Props) {
|
||||
these assets below.
|
||||
</Text>
|
||||
</div>
|
||||
<AddVaultAssetTable
|
||||
<AssetSelectTable
|
||||
assets={stableAssets}
|
||||
onChangeSelected={onChangeOtherDenoms}
|
||||
selectedDenoms={selectedOtherDenoms}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import Modal from 'components/Modal'
|
||||
import AddVaultAssetsModalContent from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent'
|
||||
@ -41,6 +42,9 @@ export default function AddVaultBorrowAssetsModal() {
|
||||
) : (
|
||||
<CircularProgress />
|
||||
)}
|
||||
<div className='flex w-full p-4'>
|
||||
<Button className='w-full' onClick={onClose} color='tertiary' text='Select Assets' />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -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<ColumnDef<BorrowAsset>[]>(
|
||||
() => [
|
||||
{
|
||||
header: 'Asset',
|
||||
accessorKey: 'symbol',
|
||||
id: 'symbol',
|
||||
cell: ({ row }) => {
|
||||
const asset = getAssetByDenom(row.original.denom)
|
||||
if (!asset) return null
|
||||
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<Checkbox checked={row.getIsSelected()} onChange={row.getToggleSelectedHandler()} />
|
||||
<AssetImage asset={asset} size={24} className='ml-4' />
|
||||
<div className='ml-2 text-left'>
|
||||
<Text size='sm' className='mb-0.5 text-white'>
|
||||
{asset.symbol}
|
||||
</Text>
|
||||
<Text size='xs'>{asset.name}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'borrowRate',
|
||||
accessorKey: 'borrowRate',
|
||||
header: 'Borrow Rate',
|
||||
cell: ({ row }) => (
|
||||
<>
|
||||
<Text size='sm' className='mb-0.5 text-white'>
|
||||
{formatPercent(row.original.borrowRate ?? 0)}
|
||||
</Text>
|
||||
<Text size='xs'>APY</Text>
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
|
||||
return columns
|
||||
}
|
@ -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<SortingState>([{ id: 'symbol', desc: false }])
|
||||
const [selected, setSelected] = useState<RowSelectionState>(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,
|
75
src/components/Modals/AssetsSelect/useAssetTableColumns.tsx
Normal file
75
src/components/Modals/AssetsSelect/useAssetTableColumns.tsx
Normal file
@ -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<ColumnDef<AssetTableRow>[]>(
|
||||
() => [
|
||||
{
|
||||
header: 'Asset',
|
||||
accessorKey: 'symbol',
|
||||
id: 'symbol',
|
||||
cell: ({ row }) => {
|
||||
const asset = getAssetByDenom(row.original.asset.denom) as Asset
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<Checkbox checked={row.getIsSelected()} onChange={row.getToggleSelectedHandler()} />
|
||||
<AssetImage asset={asset} size={24} className='ml-4' />
|
||||
<div className='ml-2 text-left'>
|
||||
<Text size='sm' className='mb-0.5 text-white'>
|
||||
{asset.symbol}
|
||||
</Text>
|
||||
<Text size='xs'>{asset.name}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<div className='flex items-center'>
|
||||
<Text size='sm' className='mb-0.5 text-white'>
|
||||
{formatPercent(asset.borrowRate ?? 0)}
|
||||
</Text>
|
||||
<Text size='xs'>APY</Text>
|
||||
</div>
|
||||
)
|
||||
if (!balance) return null
|
||||
const coin = new BNCoin({ denom: row.original.asset.denom, amount: balance })
|
||||
return (
|
||||
<div className='flex flex-wrap items-center'>
|
||||
<DisplayCurrency coin={coin} className='mb-0.5 w-full text-white' />
|
||||
<FormattedNumber
|
||||
className='w-full text-xs'
|
||||
options={{ minDecimals: 2, maxDecimals: asset.decimals }}
|
||||
amount={demagnify(balance, asset)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
|
||||
return columns
|
||||
}
|
@ -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(),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
SettingsModal,
|
||||
UnlockModal,
|
||||
VaultModal,
|
||||
WalletAssets,
|
||||
WithdrawFromVaultsModal,
|
||||
} from 'components/Modals'
|
||||
|
||||
@ -21,6 +22,7 @@ export default function ModalsContainer() {
|
||||
<UnlockModal />
|
||||
<VaultModal />
|
||||
<WithdrawFromVaultsModal />
|
||||
<WalletAssets />
|
||||
<AlertDialogController />
|
||||
</>
|
||||
)
|
||||
|
@ -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<number>(0)
|
||||
const [inputRef, setInputRef] = useState<React.RefObject<HTMLInputElement>>()
|
||||
const [isCustom, setIsCustom] = useState(false)
|
||||
@ -58,9 +58,15 @@ export default function SettingsModal() {
|
||||
displayCurrencies.map((asset, index) => ({
|
||||
label: (
|
||||
<div className='flex w-full gap-2' key={index}>
|
||||
<AssetImage asset={asset} size={16} />
|
||||
{asset.denom === 'usd' ? (
|
||||
<Text size='sm' className='h-4 w-4 text-center leading-4'>
|
||||
{asset.symbol}
|
||||
</Text>
|
||||
) : (
|
||||
<AssetImage asset={asset} size={16} />
|
||||
)}
|
||||
<Text size='sm' className='leading-4'>
|
||||
{asset.symbol}
|
||||
{asset.name}
|
||||
</Text>
|
||||
</div>
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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<string>('')
|
||||
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<string[]>(
|
||||
currentSelectedDenom?.filter((denom) => filteredAssets.findIndex(byDenom(denom))) || [],
|
||||
)
|
||||
|
||||
const onChangeDenoms = useCallback(
|
||||
(denoms: string[]) => {
|
||||
setSelectedDenoms(denoms)
|
||||
props.onChangeDenoms(denoms)
|
||||
},
|
||||
[props.onChangeDenoms],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='border-b border-white/5 bg-white/10 px-4 py-3'>
|
||||
<SearchBar
|
||||
value={searchString}
|
||||
placeholder={`Search for e.g. "ETH" or "Ethereum"`}
|
||||
onChange={setSearchString}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-h-[446px] overflow-y-scroll scrollbar-hide'>
|
||||
<AssetSelectTable
|
||||
assets={filteredAssets}
|
||||
onChangeSelected={onChangeDenoms}
|
||||
selectedDenoms={selectedDenoms}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
37
src/components/Modals/WalletAssets/index.tsx
Normal file
37
src/components/Modals/WalletAssets/index.tsx
Normal file
@ -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<string[]>([])
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
useStore.setState({
|
||||
walletAssetsModal: { isOpen: false, selectedDenoms },
|
||||
})
|
||||
}, [selectedDenoms])
|
||||
|
||||
if (!modal?.isOpen) return null
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header={<Text>Your wallet</Text>}
|
||||
onClose={onClose}
|
||||
modalClassName='max-w-modal-xs'
|
||||
headerClassName='bg-white/10 border-b-white/5 border-b items-center p-4'
|
||||
>
|
||||
<WalletAssetsModalContent
|
||||
defaultSelectedDenoms={modal.selectedDenoms}
|
||||
onChangeDenoms={setSelectedDenoms}
|
||||
/>
|
||||
<div className='flex w-full p-4'>
|
||||
<Button className='w-full' onClick={onClose} color='tertiary' text='Select Assets' />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -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'
|
||||
|
@ -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',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-0 opacity-0 transition-opacity gradient-primary-to-secondary',
|
||||
'absolute inset-0 bg-martian-red opacity-0 transition-opacity',
|
||||
props.checked && 'opacity-100',
|
||||
)}
|
||||
/>
|
||||
|
@ -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: <WalletSelect /> })
|
||||
}, [currentWallet, disconnectWallet])
|
||||
|
||||
useEffect(() => {
|
||||
if (hasFunds) {
|
||||
useStore.setState({ focusComponent: <WalletFetchBalancesAndAccounts /> })
|
||||
return
|
||||
}
|
||||
|
||||
if (BN(baseBalance).isGreaterThanOrEqualTo(hardcodedFee.amount[0].amount) && !isLoading)
|
||||
setHasFunds(true)
|
||||
}, [baseBalance, isLoading, hasFunds, setHasFunds])
|
||||
|
||||
return (
|
||||
<FullOverlayContent
|
||||
title='No supported assets'
|
||||
@ -61,6 +87,19 @@ export default function WalletBridges() {
|
||||
<Bridge key={bridge.name} {...bridge} />
|
||||
))}
|
||||
</div>
|
||||
{IS_TESTNET && (
|
||||
<div className='flex w-full flex-wrap gap-3'>
|
||||
<Text size='lg' className='mt-4 text-white'>
|
||||
Need Testnet Funds?
|
||||
</Text>
|
||||
<Bridge
|
||||
key='osmosis-faucet'
|
||||
name='Osmosis Testnet Faucet'
|
||||
url='https://faucet.osmotest5.osmosis.zone/'
|
||||
image='/images/tokens/osmo.svg'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</FullOverlayContent>
|
||||
)
|
||||
}
|
||||
|
@ -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 ? (
|
||||
<CircularProgress size={12} />
|
||||
) : (
|
||||
`${formatValue(walletAmount.toString(), { suffix: ` ${baseAsset.symbol}` })}`
|
||||
<FormattedNumber
|
||||
amount={walletAmount.toNumber()}
|
||||
options={{ suffix: ` ${baseAsset.symbol}` }}
|
||||
animate
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
|
@ -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 <FetchLoading />
|
||||
if (BN(baseBalance).isLessThan(hardcodedFee.amount[0].amount)) return <WalletBridges />
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
}
|
||||
|
1
src/constants/oracle.ts
Normal file
1
src/constants/oracle.ts
Normal file
@ -0,0 +1 @@
|
||||
export const ORACLE_DENOM = 'usd'
|
@ -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
|
||||
},
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { GetState, SetState } from 'zustand'
|
||||
|
||||
import { ASSETS } from 'constants/assets'
|
||||
|
||||
export default function createCommonSlice(set: SetState<CommonSlice>, get: GetState<CommonSlice>) {
|
||||
return {
|
||||
accounts: null,
|
||||
|
@ -14,6 +14,7 @@ export default function createModalSlice(set: SetState<ModalSlice>, get: GetStat
|
||||
settingsModal: false,
|
||||
unlockModal: null,
|
||||
vaultModal: null,
|
||||
walletAssetsModal: null,
|
||||
withdrawFromVaultsModal: null,
|
||||
}
|
||||
}
|
||||
|
6
src/types/interfaces/asset.d.ts
vendored
6
src/types/interfaces/asset.d.ts
vendored
@ -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
|
||||
|
4
src/types/interfaces/components/Modals/AssetSelect.d.ts
vendored
Normal file
4
src/types/interfaces/components/Modals/AssetSelect.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
interface AssetTableRow {
|
||||
balance?: string
|
||||
asset: BorrowAsset | Asset
|
||||
}
|
1
src/types/interfaces/components/docLink.d.ts
vendored
Normal file
1
src/types/interfaces/components/docLink.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
type DocLinkType = 'wallet' | 'account' | 'terms' | 'fund'
|
4
src/types/interfaces/store/broadcast.d.ts
vendored
4
src/types/interfaces/store/broadcast.d.ts
vendored
@ -20,7 +20,7 @@ interface BroadcastSlice {
|
||||
}) => Promise<boolean>
|
||||
createAccount: (options: { fee: StdFee }) => Promise<string | null>
|
||||
deleteAccount: (options: { fee: StdFee; accountId: string }) => Promise<boolean>
|
||||
deposit: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise<boolean>
|
||||
deposit: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise<boolean>
|
||||
unlock: (options: {
|
||||
fee: StdFee
|
||||
accountId: string
|
||||
@ -37,7 +37,7 @@ interface BroadcastSlice {
|
||||
accountId: string
|
||||
actions: Action[]
|
||||
}) => Promise<boolean>
|
||||
withdraw: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise<boolean>
|
||||
withdraw: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise<boolean>
|
||||
lend: (options: {
|
||||
fee: StdFee
|
||||
accountId: string
|
||||
|
9
src/types/interfaces/store/modals.d.ts
vendored
9
src/types/interfaces/store/modals.d.ts
vendored
@ -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[]
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user