Mp depostied vaults table (#271)

* added correct resolving of account positions

* solve rendering bug for lp amount

* bugfix: add slippage to minlpamount

* fix DisplayCurrency to accept only BNCoin

* bugfix: remove prices from store

* add basic depostied vaults table

* Farm: Added deposited table

* finish deposited table, remove featured vaults:
This commit is contained in:
Bob van der Helm 2023-06-29 12:55:47 +02:00 committed by GitHub
parent 9007e31707
commit 697e83b7cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 309 additions and 233 deletions

View File

@ -6,6 +6,7 @@ import useStore from 'store'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings' import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings'
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults' import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
import { BNCoin } from 'types/classes/BNCoin'
jest.mock('hooks/usePrices', () => jest.mock('hooks/usePrices', () =>
jest.fn(() => ({ jest.fn(() => ({
@ -38,8 +39,8 @@ const mockedVault: Vault = {
}, },
cap: { cap: {
denom: 'test', denom: 'test',
max: 10, max: BN(10),
used: 2, used: BN(2),
}, },
} }
describe('<VaultBorrowings />', () => { describe('<VaultBorrowings />', () => {
@ -80,7 +81,7 @@ describe('<VaultBorrowings />', () => {
it('should render DisplayCurrency correctly', () => { it('should render DisplayCurrency correctly', () => {
expect(mockedDisplayCurrency).toHaveBeenCalledTimes(1) expect(mockedDisplayCurrency).toHaveBeenCalledTimes(1)
expect(mockedDisplayCurrency).toHaveBeenCalledWith( expect(mockedDisplayCurrency).toHaveBeenCalledWith(
{ coin: { denom: 'uosmo', amount: '0' } }, { coin: new BNCoin({ denom: 'uosmo', amount: '0' }) },
expect.anything(), expect.anything(),
) )
}) })

View File

@ -1,6 +1,7 @@
import { getCreditManagerQueryClient } from 'api/cosmwasm-client' import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { resolvePositionResponse } from 'utils/resolvers'
export default async function getAccount(accountId: string): Promise<Account> { export default async function getAccount(accountId: string): Promise<Account> {
const creditManagerQueryClient = await getCreditManagerQueryClient() const creditManagerQueryClient = await getCreditManagerQueryClient()
@ -8,17 +9,7 @@ export default async function getAccount(accountId: string): Promise<Account> {
const accountPosition: Positions = await creditManagerQueryClient.positions({ accountId }) const accountPosition: Positions = await creditManagerQueryClient.positions({ accountId })
if (accountPosition) { if (accountPosition) {
const debts = accountPosition.debts.map((debt) => new BNCoin(debt)) return resolvePositionResponse(accountPosition)
const lends = accountPosition.lends.map((lend) => new BNCoin(lend))
const deposits = accountPosition.deposits.map((deposit) => new BNCoin(deposit))
return {
id: accountPosition.account_id,
debts,
deposits,
lends,
vaults: accountPosition.vaults,
}
} }
return new Promise((_, reject) => reject('No account found')) return new Promise((_, reject) => reject('No account found'))

View File

@ -2,8 +2,12 @@ import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env' import { ENV } from 'constants/env'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function getVaultConfigs(coins: Coin[], lpDenom: string): Promise<BigNumber> { export default async function getVaultConfigs(
if (!ENV.ADDRESS_CREDIT_MANAGER) return BN(0) coins: Coin[],
lpDenom: string,
slippage: number,
): Promise<BigNumber> {
if (!ENV.ADDRESS_CREDIT_MANAGER) return BN(Infinity)
const creditManagerQueryClient = await getCreditManagerQueryClient() const creditManagerQueryClient = await getCreditManagerQueryClient()
try { try {
@ -13,6 +17,8 @@ export default async function getVaultConfigs(coins: Coin[], lpDenom: string): P
lpTokenOut: lpDenom, lpTokenOut: lpDenom,
}), }),
) )
.times(1 - slippage)
.integerValue()
} catch (ex) { } catch (ex) {
throw ex throw ex
} }

View File

@ -4,6 +4,7 @@ import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
import { getVaultUtilizations } from 'api/vaults/getVaultUtilizations' import { getVaultUtilizations } from 'api/vaults/getVaultUtilizations'
import { ENV } from 'constants/env' import { ENV } from 'constants/env'
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults' import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import { BN } from 'utils/helpers'
export default async function getVaults(): Promise<Vault[]> { export default async function getVaults(): Promise<Vault[]> {
const vaultConfigs = await getVaultConfigs([]) const vaultConfigs = await getVaultConfigs([])
@ -24,12 +25,12 @@ export default async function getVaults(): Promise<Vault[]> {
const vault: Vault = { const vault: Vault = {
...vaultMetaData, ...vaultMetaData,
cap: { cap: {
max: Number(vaultConfig.deposit_cap.amount), max: BN(vaultConfig.deposit_cap.amount),
denom: vaultConfig.deposit_cap.denom, denom: vaultConfig.deposit_cap.denom,
used: Number( used: BN(
vaultUtilizations.find( vaultUtilizations.find(
(vaultUtilization) => vaultUtilization.vault.address === vaultConfig.addr, (vaultUtilization) => vaultUtilization.vault.address === vaultConfig.addr,
) || 0, )?.utilization.amount || 0,
), ),
}, },
apy: apr ? convertAprToApy(apr.apr, 365) : null, apy: apr ? convertAprToApy(apr.apr, 365) : null,

View File

@ -17,6 +17,8 @@ import { ASSETS } from 'constants/assets'
import useStore from 'store' import useStore from 'store'
import { convertToDisplayAmount, demagnify } from 'utils/formatters' import { convertToDisplayAmount, demagnify } from 'utils/formatters'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { BNCoin } from 'types/classes/BNCoin'
import usePrices from 'hooks/usePrices'
interface Props { interface Props {
data: Account data: Account
@ -24,7 +26,8 @@ interface Props {
export const AccountBalancesTable = (props: Props) => { export const AccountBalancesTable = (props: Props) => {
const displayCurrency = useStore((s) => s.displayCurrency) const displayCurrency = useStore((s) => s.displayCurrency)
const prices = useStore((s) => s.prices) const { data: prices } = usePrices()
const [sorting, setSorting] = React.useState<SortingState>([]) const [sorting, setSorting] = React.useState<SortingState>([])
const balanceData = React.useMemo<AccountBalanceRow[]>(() => { const balanceData = React.useMemo<AccountBalanceRow[]>(() => {
@ -41,7 +44,7 @@ export const AccountBalancesTable = (props: Props) => {
amount: deposit.amount, amount: deposit.amount,
size: demagnify(deposit.amount, asset), size: demagnify(deposit.amount, asset),
value: convertToDisplayAmount( value: convertToDisplayAmount(
{ amount: deposit.amount, denom: deposit.denom }, new BNCoin({ amount: deposit.amount, denom: deposit.denom }),
displayCurrency, displayCurrency,
prices, prices,
).toString(), ).toString(),
@ -58,7 +61,7 @@ export const AccountBalancesTable = (props: Props) => {
amount: lending.amount, amount: lending.amount,
size: demagnify(lending.amount, asset), size: demagnify(lending.amount, asset),
value: convertToDisplayAmount( value: convertToDisplayAmount(
{ amount: lending.amount, denom: lending.denom }, new BNCoin({ amount: lending.amount, denom: lending.denom }),
displayCurrency, displayCurrency,
prices, prices,
).toString(), ).toString(),
@ -89,12 +92,8 @@ export const AccountBalancesTable = (props: Props) => {
accessorKey: 'value', accessorKey: 'value',
id: 'value', id: 'value',
cell: ({ row }) => { cell: ({ row }) => {
return ( const coin = new BNCoin({ denom: row.original.denom, amount: row.original.amount })
<DisplayCurrency return <DisplayCurrency coin={coin} className='text-right text-xs' />
coin={{ denom: row.original.denom, amount: row.original.amount }}
className='text-right text-xs'
/>
)
}, },
}, },
{ {

View File

@ -5,7 +5,9 @@ import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import usePrices from 'hooks/usePrices'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { import {
calculateAccountApr, calculateAccountApr,
calculateAccountBorrowRate, calculateAccountBorrowRate,
@ -30,7 +32,7 @@ interface ItemProps {
} }
export default function AccountComposition(props: Props) { export default function AccountComposition(props: Props) {
const prices = useStore((s) => s.prices) const { data: prices } = usePrices()
const balance = calculateAccountDeposits(props.account, prices) const balance = calculateAccountDeposits(props.account, prices)
const balanceChange = props.change ? calculateAccountDeposits(props.change, prices) : BN(0) const balanceChange = props.change ? calculateAccountDeposits(props.change, prices) : BN(0)
const debtBalance = calculateAccountDebt(props.account, prices) const debtBalance = calculateAccountDebt(props.account, prices)
@ -96,7 +98,7 @@ function Item(props: ItemProps) {
/> />
) : ( ) : (
<DisplayCurrency <DisplayCurrency
coin={{ amount: props.current.toString(), denom: baseCurrency.denom }} coin={new BNCoin({ amount: props.current.toString(), denom: baseCurrency.denom })}
className='text-sm' className='text-sm'
/> />
)} )}
@ -113,7 +115,7 @@ function Item(props: ItemProps) {
/> />
) : ( ) : (
<DisplayCurrency <DisplayCurrency
coin={{ amount: props.change.toString(), denom: baseCurrency.denom }} coin={new BNCoin({ amount: props.change.toString(), denom: baseCurrency.denom })}
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')} className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
/> />
)} )}

View File

@ -3,6 +3,7 @@ import BigNumber from 'bignumber.js'
import AccountHealth from 'components/Account/AccountHealth' import AccountHealth from 'components/Account/AccountHealth'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
interface Props { interface Props {
balance: BigNumber balance: BigNumber
@ -16,7 +17,7 @@ export default function AccountStats(props: Props) {
return ( return (
<div className='w-full flex-wrap'> <div className='w-full flex-wrap'>
<DisplayCurrency <DisplayCurrency
coin={{ amount: props.balance.toString(), denom: baseCurrency.denom }} coin={new BNCoin({ amount: props.balance.toString(), denom: baseCurrency.denom })}
className='w-full text-xl' className='w-full text-xl'
/> />
<div className='mt-1 flex w-full items-center'> <div className='mt-1 flex w-full items-center'>

View File

@ -7,7 +7,9 @@ import DisplayCurrency from 'components/DisplayCurrency'
import { ArrowChartLineUp } from 'components/Icons' import { ArrowChartLineUp } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import useIsOpenArray from 'hooks/useIsOpenArray' import useIsOpenArray from 'hooks/useIsOpenArray'
import usePrices from 'hooks/usePrices'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { calculateAccountDeposits } from 'utils/accounts' import { calculateAccountDeposits } from 'utils/accounts'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
@ -18,8 +20,7 @@ interface Props {
export default function AccountSummary(props: Props) { export default function AccountSummary(props: Props) {
const [isOpen, toggleOpen] = useIsOpenArray(2, true) const [isOpen, toggleOpen] = useIsOpenArray(2, true)
const { data: prices } = usePrices()
const prices = useStore((s) => s.prices)
const baseCurrency = useStore((s) => s.baseCurrency) const baseCurrency = useStore((s) => s.baseCurrency)
const accountBalance = props.account ? calculateAccountDeposits(props.account, prices) : BN(0) const accountBalance = props.account ? calculateAccountDeposits(props.account, prices) : BN(0)
if (!props.account) return null if (!props.account) return null
@ -29,7 +30,7 @@ export default function AccountSummary(props: Props) {
<Card className='mb-4 h-min min-w-fit bg-white/10' contentClassName='flex'> <Card className='mb-4 h-min min-w-fit bg-white/10' contentClassName='flex'>
<Item> <Item>
<DisplayCurrency <DisplayCurrency
coin={{ amount: accountBalance.toString(), denom: baseCurrency.denom }} coin={new BNCoin({ amount: accountBalance.toString(), denom: baseCurrency.denom })}
className='text-sm' className='text-sm'
/> />
</Item> </Item>

View File

@ -16,10 +16,6 @@ export default function AssetExpanded(props: AssetRowProps) {
const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom) const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom)
let isActive: boolean = false let isActive: boolean = false
if ((props.row.original as BorrowAssetActive)?.debt) {
isActive = true
}
if (!asset) return null if (!asset) return null
function borrowHandler() { function borrowHandler() {

View File

@ -1,4 +1,4 @@
import { Suspense } from 'react' import { Suspense, useMemo } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import Card from 'components/Card' import Card from 'components/Card'
@ -18,28 +18,28 @@ function Content(props: Props) {
const marketAssets = getEnabledMarketAssets() const marketAssets = getEnabledMarketAssets()
function getBorrowAssets() { const { available, active } = useMemo(
return marketAssets.reduce( () =>
(prev: { available: BorrowAsset[]; active: BorrowAssetActive[] }, curr) => { marketAssets.reduce(
const borrow = borrowData.find((borrow) => borrow.denom === curr.denom) (prev: { available: BorrowAsset[]; active: BorrowAssetActive[] }, curr) => {
if (borrow) { const borrow = borrowData.find((borrow) => borrow.denom === curr.denom)
const debt = debtData?.find((debt) => debt.denom === curr.denom) if (borrow) {
if (debt) { const debt = debtData?.find((debt) => debt.denom === curr.denom)
prev.active.push({ if (debt) {
...borrow, prev.active.push({
debt: debt.amount, ...borrow,
}) debt: debt.amount,
} else { })
prev.available.push(borrow) } else {
prev.available.push(borrow)
}
} }
} return prev
return prev },
}, { available: [], active: [] },
{ available: [], active: [] }, ),
) [marketAssets, borrowData, debtData],
} )
const { available, active } = getBorrowAssets()
const assets = props.type === 'active' ? active : available const assets = props.type === 'active' ? active : available

View File

@ -43,9 +43,9 @@ export const buttonSizeClasses = {
} }
export const buttonPaddingClasses = { export const buttonPaddingClasses = {
small: 'px-2.5 py-1.5 min-h-[32px]', small: 'px-4 py-1.5 min-h-[32px]',
medium: 'px-3 py-2 min-h-[40px]', medium: 'px-4 py-2 min-h-[40px]',
large: 'px-3.5 py-2.5 min-h-[56px]', large: 'px-4 py-2.5 min-h-[56px]',
} }
export const buttonVariantClasses = { export const buttonVariantClasses = {

View File

@ -1,17 +1,18 @@
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import usePrices from 'hooks/usePrices'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { convertToDisplayAmount } from 'utils/formatters' import { convertToDisplayAmount } from 'utils/formatters'
interface Props { interface Props {
coin: BNCoin | Coin coin: BNCoin
className?: string className?: string
isApproximation?: boolean isApproximation?: boolean
} }
export default function DisplayCurrency(props: Props) { export default function DisplayCurrency(props: Props) {
const displayCurrency = useStore((s) => s.displayCurrency) const displayCurrency = useStore((s) => s.displayCurrency)
const prices = useStore((s) => s.prices) const { data: prices } = usePrices()
return ( return (
<FormattedNumber <FormattedNumber

View File

@ -1,38 +0,0 @@
import { Suspense } from 'react'
import Card from 'components/Card'
import { VaultTable } from 'components/Earn/vault/VaultTable'
import Text from 'components/Text'
import { IS_TESTNET } from 'constants/env'
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import useVaults from 'hooks/useVaults'
function Content() {
const { data: vaults } = useVaults()
if (!vaults.length) return null
return <VaultTable data={vaults} />
}
export default function AvailableVaults() {
return (
<Card title='Available vaults' className='mb-4 h-fit w-full bg-white/5'>
<Suspense fallback={<Fallback />}>
<Content />
</Suspense>
</Card>
)
}
function Fallback() {
// TODO: Replace with loading state of vaulttable
const vaults = IS_TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
return (
<>
{vaults.map((vault) => (
<Text key={vault.address}>{vault.name}</Text>
))}
</>
)
}

View File

@ -58,7 +58,7 @@ export default function VaultCard(props: Props) {
/> />
<TitleAndSubCell <TitleAndSubCell
className='text-xs' className='text-xs'
title={formatValue(props.vault.cap.used || '0', { title={formatValue(props.vault.cap.used.integerValue().toNumber() || '0', {
abbreviated: true, abbreviated: true,
decimals: getAssetByDenom(props.vault.cap.denom)?.decimals, decimals: getAssetByDenom(props.vault.cap.denom)?.decimals,
})} })}
@ -66,7 +66,7 @@ export default function VaultCard(props: Props) {
/> />
<TitleAndSubCell <TitleAndSubCell
className='text-xs' className='text-xs'
title={formatValue(props.vault.cap.max || '0', { title={formatValue(props.vault.cap.max.integerValue().toNumber() || '0', {
abbreviated: true, abbreviated: true,
decimals: getAssetByDenom(props.vault.cap.denom)?.decimals, decimals: getAssetByDenom(props.vault.cap.denom)?.decimals,
})} })}

View File

@ -1,11 +1,11 @@
import { Row } from '@tanstack/react-table' import { Row } from '@tanstack/react-table'
import Button from 'components/Button'
import VaultCard from 'components/Earn/vault/VaultCard' import { LockUnlocked, Plus } from 'components/Icons'
import Text from 'components/Text'
import useStore from 'store' import useStore from 'store'
interface Props { interface Props {
row: Row<Vault> row: Row<Vault | DepositedVault>
resetExpanded: (defaultState?: boolean | undefined) => void resetExpanded: (defaultState?: boolean | undefined) => void
} }
@ -19,6 +19,11 @@ export default function VaultExpanded(props: Props) {
}) })
} }
let isDeposited: boolean = false
if ((props.row.original as DepositedVault)?.amounts) {
isDeposited = true
}
return ( return (
<tr <tr
key={props.row.id} key={props.row.id}
@ -30,27 +35,26 @@ export default function VaultExpanded(props: Props) {
!isExpanded && props.row.toggleExpanded() !isExpanded && props.row.toggleExpanded()
}} }}
> >
<td colSpan={5}> <td colSpan={isDeposited ? 6 : 5}>
<Text className='border-b border-white/10 px-4 py-5 '>Select bonding period</Text> <div className='align-center flex justify-end gap-3 p-4'>
<div className='grid grid-cols-3 md:[&>div:nth-child(3)]:border-none'> {isDeposited ? (
<VaultCard <>
vault={props.row.original} <Button color='secondary' leftIcon={<Plus className='w-3' />}>
title='1 day unbonding' Deposit more
subtitle='$0 deposited' </Button>
unbondingPeriod={1} <Button color='tertiary' leftIcon={<LockUnlocked />}>
/> Unlock to withdraw
<VaultCard </Button>
vault={props.row.original} </>
title='7 day unbonding' ) : (
subtitle='$0 deposited' <Button
unbondingPeriod={7} onClick={enterVaultHandler}
/> color='tertiary'
<VaultCard leftIcon={<Plus className='w-3' />}
vault={props.row.original} >
title='14 day unbonding' Deposit
subtitle='$0 deposited' </Button>
unbondingPeriod={14} )}
/>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -1,10 +1,8 @@
import Image from 'next/image'
import AssetImage from 'components/AssetImage' import AssetImage from 'components/AssetImage'
import { getAssetByDenom } from 'utils/assets' import { getAssetByDenom } from 'utils/assets'
interface Props { interface Props {
vault: Vault vault: VaultMetaData
} }
export default function VaultLogo(props: Props) { export default function VaultLogo(props: Props) {
@ -19,7 +17,7 @@ export default function VaultLogo(props: Props) {
<AssetImage asset={primaryAsset} size={24} /> <AssetImage asset={primaryAsset} size={24} />
</div> </div>
<div className='absolute'> <div className='absolute'>
<AssetImage asset={primaryAsset} size={16} className='ml-5 mt-5' /> <AssetImage asset={secondaryAsset} size={16} className='ml-5 mt-5' />
</div> </div>
</div> </div>
) )

View File

@ -3,6 +3,7 @@ import {
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getSortedRowModel, getSortedRowModel,
Row,
SortingState, SortingState,
useReactTable, useReactTable,
} from '@tanstack/react-table' } from '@tanstack/react-table'
@ -18,27 +19,58 @@ import TitleAndSubCell from 'components/TitleAndSubCell'
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults' import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
import { getAssetByDenom } from 'utils/assets' import { getAssetByDenom } from 'utils/assets'
import { convertPercentage, formatPercent, formatValue } from 'utils/formatters' import { convertPercentage, formatPercent, formatValue } from 'utils/formatters'
import DisplayCurrency from 'components/DisplayCurrency'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import Loading from 'components/Loading'
type Props = { type Props = {
data: Vault[] data: Vault[] | DepositedVault[]
isLoading?: boolean
} }
export const VaultTable = (props: Props) => { export const VaultTable = (props: Props) => {
const [sorting, setSorting] = React.useState<SortingState>([]) const [sorting, setSorting] = React.useState<SortingState>([{ id: 'name', desc: true }])
const columns = React.useMemo<ColumnDef<Vault>[]>( const baseCurrency = useStore((s) => s.baseCurrency)
() => [
const columns = React.useMemo<ColumnDef<Vault | DepositedVault>[]>(() => {
return [
{ {
header: 'Vault', header: 'Vault',
id: 'address', accessorKey: 'name',
cell: ({ row }) => { cell: ({ row }) => {
return <VaultLogo vault={row.original} /> return (
<div className='flex'>
<VaultLogo vault={row.original} />
<TitleAndSubCell
className='ml-2 mr-2 text-left'
title={`${row.original.name} - (${row.original.lockup.duration} ${row.original.lockup.timeframe})`}
sub={row.original.provider}
/>
</div>
)
}, },
}, },
...((props.data[0] as DepositedVault)?.values
? [
{
header: 'Pos. Value',
cell: ({ row }: { row: Row<DepositedVault | Vault> }) => {
const vault = row.original as DepositedVault
const positionValue = vault.values.primary.plus(vault.values.secondary)
const coin = BNCoin.fromDenomAndBigNumber(baseCurrency.denom, positionValue)
return <DisplayCurrency coin={coin} className='text-xs' />
},
},
]
: []),
{ {
accessorKey: 'apy', accessorKey: 'apy',
header: 'APY', header: 'APY',
cell: ({ row }) => { cell: ({ row }) => {
if (row.original.apy === null) return <Loading />
return ( return (
<Text size='xs'>{row.original.apy ? formatPercent(row.original.apy, 2) : '-'}</Text> <Text size='xs'>{row.original.apy ? formatPercent(row.original.apy, 2) : '-'}</Text>
) )
@ -48,29 +80,36 @@ export const VaultTable = (props: Props) => {
accessorKey: 'tvl', accessorKey: 'tvl',
header: 'TVL', header: 'TVL',
cell: ({ row }) => { cell: ({ row }) => {
// TODO: Replace with DisplayCurrency if (props.isLoading) return <Loading />
const symbol = getAssetByDenom(row.original.cap.denom)?.symbol ?? '' const coin = new BNCoin({
return ( denom: row.original.cap.denom,
<Text size='xs'> amount: row.original.cap.used.toString(),
{formatValue(row.original.cap.used, { abbreviated: true, suffix: ` ${symbol}` })} })
</Text>
) return <DisplayCurrency coin={coin} className='text-xs' />
}, },
}, },
{ {
accessorKey: 'cap', accessorKey: 'cap',
header: 'Depo. Cap', header: 'Depo. Cap',
cell: ({ row }) => { cell: ({ row }) => {
if (props.isLoading) return <Loading />
const percent = convertPercentage( const percent = convertPercentage(
(row.original.cap.used / (row.original.cap.max * VAULT_DEPOSIT_BUFFER)) * 100, row.original.cap.used
.div(row.original.cap.max.times(VAULT_DEPOSIT_BUFFER))
.times(100)
.integerValue()
.toNumber(),
) )
const decimals = getAssetByDenom(row.original.cap.denom)?.decimals ?? 6 const decimals = getAssetByDenom(row.original.cap.denom)?.decimals ?? 6
// TODO: Replace with DisplayCurrency
return ( return (
<TitleAndSubCell <TitleAndSubCell
className='text-xs' title={formatValue(row.original.cap.max.integerValue().toNumber(), {
title={formatValue(row.original.cap.max, { abbreviated: true, decimals })} abbreviated: true,
decimals,
})}
sub={`${percent}% Filled`} sub={`${percent}% Filled`}
/> />
) )
@ -80,21 +119,24 @@ export const VaultTable = (props: Props) => {
accessorKey: 'details', accessorKey: 'details',
enableSorting: false, enableSorting: false,
header: 'Details', header: 'Details',
cell: ({ row }) => ( cell: ({ row }) => {
<div className='flex items-center justify-end'> if (props.isLoading) return <Loading />
<div className={classNames('w-4', row.getIsExpanded() && 'rotate-180')}>
<ChevronDown /> return (
<div className='flex items-center justify-end'>
<div className={classNames('w-4', row.getIsExpanded() && 'rotate-180')}>
<ChevronDown />
</div>
</div> </div>
</div> )
), },
}, },
], ]
[], }, [baseCurrency.denom, props.data, props.isLoading])
)
const table = useReactTable({ const table = useReactTable({
data: props.data, data: props.data,
columns, columns: columns,
state: { state: {
sorting, sorting,
}, },
@ -122,7 +164,7 @@ export const VaultTable = (props: Props) => {
<div <div
className={classNames( className={classNames(
'flex', 'flex',
header.id === 'symbol' ? 'justify-start' : 'justify-end', header.id === 'name' ? 'justify-start' : 'justify-end',
'align-center', 'align-center',
)} )}
> >

View File

@ -0,0 +1,78 @@
import { Suspense, useMemo } from 'react'
import { useParams } from 'react-router-dom'
import Card from 'components/Card'
import { VaultTable } from 'components/Earn/vault/VaultTable'
import { IS_TESTNET } from 'constants/env'
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import useVaults from 'hooks/useVaults'
import useDepositedVaults from 'hooks/useDepositedVaults'
import { BN } from 'utils/helpers'
interface Props {
type: 'available' | 'deposited'
}
function Content(props: Props) {
const { accountId } = useParams()
const { data: vaults } = useVaults()
const { data: depositedVaults } = useDepositedVaults(accountId || '')
const vaultsMetaData = IS_TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
const { deposited, available } = useMemo(() => {
return vaultsMetaData.reduce(
(prev: { deposited: DepositedVault[]; available: Vault[] }, curr) => {
const vault = vaults.find((vault) => vault.address === curr.address)
const depositedVault = depositedVaults?.find((vault) => vault.address === curr.address)
if (depositedVault) {
prev.deposited.push(depositedVault)
} else if (vault) {
prev.available.push(vault)
}
return prev
},
{ deposited: [], available: [] },
)
}, [vaults, depositedVaults, vaultsMetaData])
const vaultsToDisplay = props.type === 'available' ? available : deposited
if (!vaultsToDisplay.length) return null
return <VaultTable data={vaultsToDisplay} />
}
export default function Vaults(props: Props) {
return (
<Card
title={props.type === 'available' ? 'Available vaults' : 'Deposited'}
className='mb-4 h-fit w-full bg-white/5'
>
<Suspense fallback={props.type === 'available' ? <Fallback /> : null}>
<Content type={props.type} />
</Suspense>
</Card>
)
}
function Fallback() {
const vaults = IS_TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
const mockVaults: Vault[] = vaults.map((vault) => ({
...vault,
apy: null,
ltv: {
max: 0,
liq: 0,
},
cap: {
denom: 'denom',
used: BN(0),
max: BN(0),
},
}))
return <VaultTable data={mockVaults} isLoading />
}

View File

@ -3,6 +3,7 @@ import TitleAndSubCell from 'components/TitleAndSubCell'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import useAssetIncentivesApy from 'hooks/useAssetIncentiveApy' import useAssetIncentivesApy from 'hooks/useAssetIncentiveApy'
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance' import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
import { BNCoin } from 'types/classes/BNCoin'
interface Props { interface Props {
data: LendingMarketTableData data: LendingMarketTableData
@ -36,7 +37,11 @@ function DetailsHeader({ data }: Props) {
{accountLentAmount && ( {accountLentAmount && (
<> <>
<TitleAndSubCell <TitleAndSubCell
title={<DisplayCurrency coin={{ denom: asset.denom, amount: accountLentAmount }} />} title={
<DisplayCurrency
coin={new BNCoin({ denom: asset.denom, amount: accountLentAmount })}
/>
}
sub={'Deposited'} sub={'Deposited'}
/> />
<div className='h-100 w-[1px] bg-white/10'></div> <div className='h-100 w-[1px] bg-white/10'></div>
@ -44,13 +49,18 @@ function DetailsHeader({ data }: Props) {
)} )}
{balanceInWallet && ( {balanceInWallet && (
<> <>
<TitleAndSubCell title={<DisplayCurrency coin={balanceInWallet} />} sub={'In Wallet'} /> <TitleAndSubCell
title={<DisplayCurrency coin={new BNCoin(balanceInWallet)} />}
sub={'In Wallet'}
/>
<div className='h-100 w-[1px] bg-white/10'></div> <div className='h-100 w-[1px] bg-white/10'></div>
</> </>
)} )}
<TitleAndSubCell <TitleAndSubCell
title={ title={
<DisplayCurrency coin={{ denom: asset.denom, amount: marketDepositCap.toString() }} /> <DisplayCurrency
coin={new BNCoin({ denom: asset.denom, amount: marketDepositCap.toString() })}
/>
} }
sub={'Deposit Cap'} sub={'Deposit Cap'}
/> />

View File

@ -43,8 +43,8 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
const { actions: depositActions, fee: depositFee } = useDepositVault({ const { actions: depositActions, fee: depositFee } = useDepositVault({
vault: props.vault, vault: props.vault,
deposits: props.deposits.filter((borrowing) => borrowing.amount.gt(0)), deposits: props.deposits,
borrowings: props.borrowings.filter((borrowing) => borrowing.amount.gt(0)), borrowings: props.borrowings,
}) })
const primaryValue = useMemo( const primaryValue = useMemo(
@ -189,7 +189,9 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
<div className='flex justify-between'> <div className='flex justify-between'>
<Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Position Value`}</Text> <Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Position Value`}</Text>
<DisplayCurrency coin={{ denom: baseCurrency.denom, amount: totalValue.toString() }} /> <DisplayCurrency
coin={new BNCoin({ denom: baseCurrency.denom, amount: totalValue.toString() })}
/>
</div> </div>
{props.borrowings.map((coin) => { {props.borrowings.map((coin) => {
const asset = getAssetByDenom(coin.denom) const asset = getAssetByDenom(coin.denom)

View File

@ -40,7 +40,7 @@ export default function VaultDepositSubTitle(props: Props) {
<> <>
{` = `} {` = `}
<DisplayCurrency <DisplayCurrency
coin={{ denom: baseCurrency.denom, amount: borrowingValue.toString() }} coin={new BNCoin({ denom: baseCurrency.denom, amount: borrowingValue.toString() })}
/> />
</> </>
)} )}

View File

@ -14,6 +14,7 @@ import { getAmount } from 'utils/accounts'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { Gauge } from 'components/Gauge' import { Gauge } from 'components/Gauge'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
interface Props { interface Props {
primaryAmount: BigNumber primaryAmount: BigNumber
@ -204,7 +205,9 @@ export default function VaultDeposit(props: Props) {
</div> </div>
<div className='flex justify-between'> <div className='flex justify-between'>
<Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Deposit Value`}</Text> <Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Deposit Value`}</Text>
<DisplayCurrency coin={{ denom: baseCurrency.denom, amount: totalValue.toString() }} /> <DisplayCurrency
coin={new BNCoin({ denom: baseCurrency.denom, amount: totalValue.toString() })}
/>
</div> </div>
<Button <Button
onClick={() => props.toggleOpen(1)} onClick={() => props.toggleOpen(1)}

View File

@ -3,6 +3,7 @@ import BigNumber from 'bignumber.js'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import usePrice from 'hooks/usePrice' import usePrice from 'hooks/usePrice'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { formatAmountWithSymbol } from 'utils/formatters' import { formatAmountWithSymbol } from 'utils/formatters'
interface Props { interface Props {
@ -41,7 +42,9 @@ export default function VaultDepositSubTitle(props: Props) {
{(showPrimaryText || showSecondaryText) && ( {(showPrimaryText || showSecondaryText) && (
<> <>
{` = `} {` = `}
<DisplayCurrency coin={{ denom: baseCurrency.denom, amount: positionValue.toString() }} /> <DisplayCurrency
coin={new BNCoin({ denom: baseCurrency.denom, amount: positionValue.toString() })}
/>
</> </>
)} )}
</> </>

View File

@ -1,5 +1,4 @@
import classNames from 'classnames' import classNames from 'classnames'
import Image from 'next/image'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { ChevronDown } from 'components/Icons' import { ChevronDown } from 'components/Icons'
@ -7,6 +6,7 @@ import Text from 'components/Text'
import { ASSETS } from 'constants/assets' import { ASSETS } from 'constants/assets'
import { formatValue } from 'utils/formatters' import { formatValue } from 'utils/formatters'
import AssetImage from 'components/AssetImage' import AssetImage from 'components/AssetImage'
import { BNCoin } from 'types/classes/BNCoin'
interface Props extends Option { interface Props extends Option {
isSelected?: boolean isSelected?: boolean
@ -68,7 +68,7 @@ export default function Option(props: Props) {
{formatValue(5, { maxDecimals: 2, minDecimals: 0, prefix: 'APY ', suffix: '%' })} {formatValue(5, { maxDecimals: 2, minDecimals: 0, prefix: 'APY ', suffix: '%' })}
</Text> </Text>
<Text size='sm' className='col-span-2 text-right text-white/50'> <Text size='sm' className='col-span-2 text-right text-white/50'>
<DisplayCurrency coin={{ denom: asset.denom, amount: balance }} /> <DisplayCurrency coin={new BNCoin({ denom: asset.denom, amount: balance })} />
</Text> </Text>
</div> </div>
) )

View File

@ -12,10 +12,10 @@ interface Props {
export default function TitleAndSubCell(props: Props) { export default function TitleAndSubCell(props: Props) {
return ( return (
<div className={classNames('flex flex-col gap-[0.5]', props.containerClassName)}> <div className={classNames('flex flex-col gap-[0.5]', props.containerClassName)}>
<Text size='sm' className={props.className}> <Text size='xs' className={props.className}>
{props.title} {props.title}
</Text> </Text>
<Text size='sm' className={classNames('text-white/50', props.className)}> <Text size='xs' className={classNames('text-white/50', props.className)}>
{props.sub} {props.sub}
</Text> </Text>
</div> </div>

View File

@ -72,7 +72,7 @@ export default function ConnectedButton() {
const assetDenoms = marketAssets.map((asset) => asset.denom) const assetDenoms = marketAssets.map((asset) => asset.denom)
const balances = walletBalances.filter((coin) => assetDenoms.includes(coin.denom)) const balances = walletBalances.filter((coin) => assetDenoms.includes(coin.denom))
useStore.setState({ balances }) useStore.setState({ balances })
}, [walletBalances, baseAsset.denom, baseAsset.decimals, marketAssets]) }, [walletBalances, baseAsset.denom, baseAsset.decimals, marketAssets, walletAmount])
return ( return (
<div className={'relative'}> <div className={'relative'}>

View File

@ -27,7 +27,13 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
const debouncedGetMinLpToReceive = useMemo(() => debounce(getMinLpToReceive, 500), []) const debouncedGetMinLpToReceive = useMemo(() => debounce(getMinLpToReceive, 500), [])
const { primaryCoin, secondaryCoin, totalValue } = useMemo( const { primaryCoin, secondaryCoin, totalValue } = useMemo(
() => getVaultDepositCoinsAndValue(props.vault, props.deposits, props.borrowings, prices), () =>
getVaultDepositCoinsAndValue(
props.vault,
props.deposits.filter((borrowing) => borrowing.amount.gt(0)),
props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
prices,
),
[props.deposits, props.borrowings, props.vault, prices], [props.deposits, props.borrowings, props.vault, prices],
) )
@ -41,8 +47,8 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
() => () =>
getVaultSwapActions( getVaultSwapActions(
props.vault, props.vault,
props.deposits, props.deposits.filter((borrowing) => borrowing.amount.gt(0)),
props.borrowings, props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
prices, prices,
slippage, slippage,
totalValue, totalValue,
@ -56,9 +62,10 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
const lpAmount = await debouncedGetMinLpToReceive( const lpAmount = await debouncedGetMinLpToReceive(
[secondaryCoin.toCoin(), primaryCoin.toCoin()], [secondaryCoin.toCoin(), primaryCoin.toCoin()],
props.vault.denoms.lp, props.vault.denoms.lp,
slippage,
) )
if (!lpAmount || lpAmount === minLpToReceive) return if (!lpAmount || lpAmount.eq(minLpToReceive)) return
setMinLpToReceive(lpAmount) setMinLpToReceive(lpAmount)
}, [ }, [
primaryCoin, primaryCoin,
@ -66,6 +73,7 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
props.vault.denoms.lp, props.vault.denoms.lp,
debouncedGetMinLpToReceive, debouncedGetMinLpToReceive,
minLpToReceive, minLpToReceive,
slippage,
]) ])
const enterVaultActions: Action[] = useMemo(() => { const enterVaultActions: Action[] = useMemo(() => {

View File

@ -1,14 +0,0 @@
import useSWR from 'swr'
import getMinLpToReceive from 'api/vaults/getMinLpToReceive'
import { BN } from 'utils/helpers'
export default function useMinLpToReceive(coins: Coin[], lpDenom: string) {
return useSWR(
`minLpToReceive-${JSON.stringify(coins)}`,
() => getMinLpToReceive(coins, lpDenom),
{
fallbackData: BN(0),
},
)
}

View File

@ -1,12 +1,10 @@
import useSWR from 'swr' import useSWR from 'swr'
import getPrices from 'api/prices/getPrices' import getPrices from 'api/prices/getPrices'
import useStore from 'store'
export default function usePrices() { export default function usePrices() {
return useSWR('prices', getPrices, { return useSWR('prices', getPrices, {
fallbackData: [], fallbackData: [],
refreshInterval: 30000, refreshInterval: 30000,
onSuccess: (prices) => useStore.setState({ prices }),
}) })
} }

View File

@ -1,13 +1,13 @@
import AvailableVaults from 'components/Earn/vault/AvailableVaults'
import FeaturedVaults from 'components/Earn/vault/FeaturedVaults'
import Tab from 'components/Earn/Tab' import Tab from 'components/Earn/Tab'
import Vaults from 'components/Earn/vault/Vaults'
export default function FarmPage() { export default function FarmPage() {
return ( return (
<> <>
<Tab isFarm /> <Tab isFarm />
<FeaturedVaults /> {/* <FeaturedVaults /> */}
<AvailableVaults /> <Vaults type='deposited' />
<Vaults type='available' />
</> </>
) )
} }

View File

@ -136,20 +136,8 @@ export default function createBroadcastSlice(
}, },
} }
const response = await get().executeMsg({ msg, fee: options.fee }) const response = await get().executeMsg({ msg, fee: options.fee })
if (response.result) {
set({ handleResponseMessages(response, `Deposited into vault`)
toast: {
message: `Deposited into vault`,
},
})
} else {
set({
toast: {
message: response.error ?? `Transaction failed: ${response.error}`,
isError: true,
},
})
}
return !!response.result return !!response.result
}, },
withdraw: async (options: { fee: StdFee; accountId: string; coin: Coin }) => { withdraw: async (options: { fee: StdFee; accountId: string; coin: Coin }) => {

View File

@ -9,6 +9,5 @@ export default function createCurrencySlice(
return { return {
baseCurrency: ASSETS[0], baseCurrency: ASSETS[0],
displayCurrency: ASSETS.find((asset) => asset.denom === ASSETS[0].denom)!, displayCurrency: ASSETS.find((asset) => asset.denom === ASSETS[0].denom)!,
prices: [],
} }
} }

View File

@ -1,5 +1,4 @@
interface CurrencySlice { interface CurrencySlice {
baseCurrency: Asset baseCurrency: Asset
displayCurrency: Asset displayCurrency: Asset
prices: Coin[]
} }

View File

@ -27,8 +27,8 @@ interface VaultInfo {
} }
cap: { cap: {
denom: string denom: string
used: number used: BigNumber
max: number max: BigNumber
} }
} }

View File

@ -7,7 +7,7 @@ export const hardcodedFee = {
amount: '100000', amount: '100000',
}, },
], ],
gas: '5000000', gas: '10000000',
} }
export const SECONDS_IN_A_YEAR = 31540000 export const SECONDS_IN_A_YEAR = 31540000

View File

@ -156,11 +156,7 @@ export function demagnify(amount: number | string | BigNumber, asset: Asset) {
return value.isZero() ? 0 : value.shiftedBy(-1 * asset.decimals).toNumber() return value.isZero() ? 0 : value.shiftedBy(-1 * asset.decimals).toNumber()
} }
export function convertToDisplayAmount( export function convertToDisplayAmount(coin: BNCoin, displayCurrency: Asset, prices: Coin[]) {
coin: BNCoin | Coin,
displayCurrency: Asset,
prices: Coin[],
) {
const price = prices.find((price) => price.denom === coin.denom) const price = prices.find((price) => price.denom === coin.denom)
const asset = getEnabledMarketAssets().find((asset) => asset.denom === coin.denom) const asset = getEnabledMarketAssets().find((asset) => asset.denom === coin.denom)
const displayPrice = prices.find((price) => price.denom === displayCurrency.denom) const displayPrice = prices.find((price) => price.denom === displayCurrency.denom)

View File

@ -1,3 +1,4 @@
import { BNCoin } from 'types/classes/BNCoin'
import { Positions as CreditManagerPosition } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Positions as CreditManagerPosition } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { Market as RedBankMarket } from 'types/generated/mars-mock-red-bank/MarsMockRedBank.types' import { Market as RedBankMarket } from 'types/generated/mars-mock-red-bank/MarsMockRedBank.types'
@ -8,9 +9,9 @@ export function resolvePositionResponses(responses: CreditManagerPosition[]): Ac
export function resolvePositionResponse(response: CreditManagerPosition): Account { export function resolvePositionResponse(response: CreditManagerPosition): Account {
return { return {
id: response.account_id, id: response.account_id,
deposits: response.deposits, debts: response.debts.map((debt) => new BNCoin(debt)),
debts: response.debts, lends: response.lends.map((lend) => new BNCoin(lend)),
lends: response.lends, deposits: response.deposits.map((deposit) => new BNCoin(deposit)),
vaults: response.vaults, vaults: response.vaults,
} }
} }

View File

@ -49,14 +49,14 @@ export function getVaultDepositCoinsAndValue(
return prev.plus(bnCoin.amount.times(price)) return prev.plus(bnCoin.amount.times(price))
}, BN(0)) }, BN(0))
const primaryDepositAmount = getTokenPrice(vault.denoms.primary, prices) const halfValue = totalValue.div(2)
.times(totalValue)
.div(2) const primaryDepositAmount = halfValue
.div(getTokenPrice(vault.denoms.primary, prices))
.integerValue() .integerValue()
const secondaryDepositAmount = getTokenPrice(vault.denoms.secondary, prices) const secondaryDepositAmount = halfValue
.times(totalValue) .div(getTokenPrice(vault.denoms.secondary, prices))
.div(2)
.integerValue() .integerValue()
return { return {