Mp 2548 deposit into vault (#264)
* Implement vault deposit message * Merge custom Coin into BNCoin * Fix build errors * fixed tests
This commit is contained in:
parent
c69461b95d
commit
415da05b8d
@ -5,6 +5,7 @@ import { BN } from 'utils/helpers'
|
||||
import useStore from 'store'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings'
|
||||
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
|
||||
|
||||
jest.mock('hooks/usePrices', () =>
|
||||
jest.fn(() => ({
|
||||
@ -20,11 +21,27 @@ jest.mock('hooks/useMarketAssets', () =>
|
||||
})),
|
||||
)
|
||||
|
||||
jest.mock('hooks/broadcast/useDepositVault', () => jest.fn(() => ({})))
|
||||
|
||||
jest.mock('components/DisplayCurrency')
|
||||
|
||||
const mockedDisplayCurrency = jest
|
||||
.mocked(DisplayCurrency)
|
||||
.mockImplementation(() => <div>Display currency</div>)
|
||||
|
||||
const mockedVault: Vault = {
|
||||
...TESTNET_VAULTS_META_DATA[0],
|
||||
apy: 0,
|
||||
ltv: {
|
||||
liq: 0.2,
|
||||
max: 0.1,
|
||||
},
|
||||
cap: {
|
||||
denom: 'test',
|
||||
max: 10,
|
||||
used: 2,
|
||||
},
|
||||
}
|
||||
describe('<VaultBorrowings />', () => {
|
||||
const defaultProps: VaultBorrowingsProps = {
|
||||
primaryAsset: ASSETS[0],
|
||||
@ -38,7 +55,9 @@ describe('<VaultBorrowings />', () => {
|
||||
vaults: [],
|
||||
lends: [],
|
||||
},
|
||||
vault: mockedVault,
|
||||
borrowings: [],
|
||||
deposits: [],
|
||||
onChangeBorrowings: jest.fn(),
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"bignumber.js": "^9.1.1",
|
||||
"classnames": "^2.3.2",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"moment": "^2.29.4",
|
||||
"next": "^13.4.7",
|
||||
"react": "^18.2.0",
|
||||
@ -41,6 +42,7 @@
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/debounce-promise": "^3.1.6",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/react": "18.2.14",
|
||||
"@types/react-dom": "18.2.4",
|
||||
|
@ -1,10 +1,11 @@
|
||||
import getAccount from 'api/accounts/getAccount'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default async function getAccountDebts(accountId: string): Promise<Coin[]> {
|
||||
export default async function getAccountDebts(accountId: string): Promise<BNCoin[]> {
|
||||
const account = await getAccount(accountId)
|
||||
|
||||
if (account) {
|
||||
return account.debts
|
||||
return account.debts.map((coin) => new BNCoin(coin))
|
||||
}
|
||||
|
||||
return new Promise((_, reject) => reject('Account not found'))
|
||||
|
@ -1,10 +1,11 @@
|
||||
import getAccount from 'api/accounts/getAccount'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default async function getAccountDeposits(accountId: string) {
|
||||
export default async function getAccountDeposits(accountId: string): Promise<BNCoin[]> {
|
||||
const account = await getAccount(accountId)
|
||||
|
||||
if (account) {
|
||||
return account.deposits
|
||||
return account.deposits.map((coin) => new BNCoin(coin))
|
||||
}
|
||||
|
||||
return new Promise((_, reject) => reject('Account not found'))
|
||||
|
@ -8,7 +8,9 @@ import getMarsPrice from 'api/prices/getMarsPrice'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { byDenom } from 'utils/array'
|
||||
|
||||
export default async function calculateAssetIncentivesApy(denom: string): Promise<number | null> {
|
||||
export default async function calculateAssetIncentivesApy(
|
||||
denom: string,
|
||||
): Promise<BigNumber | null> {
|
||||
try {
|
||||
const [assetIncentive, market] = await Promise.all([getAssetIncentive(denom), getMarket(denom)])
|
||||
|
||||
@ -38,7 +40,7 @@ export default async function calculateAssetIncentivesApy(denom: string): Promis
|
||||
const totalAnnualReturnsValue = annualEmission.plus(marketReturns)
|
||||
const incentivesApy = totalAnnualReturnsValue.dividedBy(marketLiquidityValue).multipliedBy(100)
|
||||
|
||||
return incentivesApy.toNumber()
|
||||
return incentivesApy
|
||||
} catch (ex) {
|
||||
console.error(ex)
|
||||
return null
|
||||
|
@ -19,8 +19,8 @@ export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
|
||||
...asset,
|
||||
borrowRate: market.borrowRate ?? 0,
|
||||
liquidity: {
|
||||
amount: amount,
|
||||
value: BN(amount).times(price).toString(),
|
||||
amount: BN(amount),
|
||||
value: BN(amount).times(price),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,8 @@
|
||||
import getMarkets from 'api/markets/getMarkets'
|
||||
import { getRedBankQueryClient } from 'api/cosmwasm-client'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default async function getMarketDebts(): Promise<Coin[]> {
|
||||
export default async function getMarketDebts(): Promise<BNCoin[]> {
|
||||
try {
|
||||
const markets: Market[] = await getMarkets()
|
||||
const redBankQueryClient = await getRedBankQueryClient()
|
||||
@ -14,7 +15,9 @@ export default async function getMarketDebts(): Promise<Coin[]> {
|
||||
)
|
||||
const debtsResults = await Promise.all(debtQueries)
|
||||
|
||||
return debtsResults.map<Coin>((debt, index) => ({ denom: markets[index].denom, amount: debt }))
|
||||
return debtsResults.map<BNCoin>(
|
||||
(debt, index) => new BNCoin({ denom: markets[index].denom, amount: debt }),
|
||||
)
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
}
|
||||
|
@ -1,16 +1,20 @@
|
||||
import getMarkets from 'api/markets/getMarkets'
|
||||
import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default async function getMarketDeposits(): Promise<Coin[]> {
|
||||
export default async function getMarketDeposits(): Promise<BNCoin[]> {
|
||||
try {
|
||||
const markets: Market[] = await getMarkets()
|
||||
const depositQueries = markets.map(getUnderlyingLiquidityAmount)
|
||||
const depositsResults = await Promise.all(depositQueries)
|
||||
|
||||
return depositsResults.map<Coin>((deposit, index) => ({
|
||||
denom: markets[index].denom,
|
||||
amount: deposit,
|
||||
}))
|
||||
return depositsResults.map<BNCoin>(
|
||||
(deposit, index) =>
|
||||
new BNCoin({
|
||||
denom: markets[index].denom,
|
||||
amount: deposit,
|
||||
}),
|
||||
)
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
}
|
||||
|
@ -1,25 +1,26 @@
|
||||
import { BN } from 'utils/helpers'
|
||||
import getMarketDeposits from 'api/markets/getMarketDeposits'
|
||||
import getMarketDebts from 'api/markets/getMarketDebts'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default async function getMarketLiquidities(): Promise<Coin[]> {
|
||||
export default async function getMarketLiquidities(): Promise<BNCoin[]> {
|
||||
const deposits = await getMarketDeposits()
|
||||
const debts = await getMarketDebts()
|
||||
|
||||
const liquidity: Coin[] = deposits.map((deposit) => {
|
||||
const liquidity: BNCoin[] = deposits.map((deposit) => {
|
||||
const debt = debts.find((debt) => debt.denom === deposit.denom)
|
||||
|
||||
if (debt) {
|
||||
return {
|
||||
return new BNCoin({
|
||||
denom: deposit.denom,
|
||||
amount: BN(deposit.amount).minus(debt.amount).toString(),
|
||||
}
|
||||
amount: deposit.amount.minus(debt.amount).toString(),
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
return new BNCoin({
|
||||
denom: deposit.denom,
|
||||
amount: '0',
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (liquidity) {
|
||||
|
19
src/api/vaults/getMinLpToReceive.ts
Normal file
19
src/api/vaults/getMinLpToReceive.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
|
||||
import { ENV } from 'constants/env'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default async function getVaultConfigs(coins: Coin[], lpDenom: string): Promise<BigNumber> {
|
||||
if (!ENV.ADDRESS_CREDIT_MANAGER) return BN(0)
|
||||
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient()
|
||||
try {
|
||||
return BN(
|
||||
await creditManagerQueryClient.estimateProvideLiquidity({
|
||||
coinsIn: coins,
|
||||
lpTokenOut: lpDenom,
|
||||
}),
|
||||
)
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import Text from 'components/Text'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import useStore from 'store'
|
||||
import { convertToDisplayAmount, demagnify } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
data: Account
|
||||
@ -43,7 +44,7 @@ export const AccountBalancesTable = (props: Props) => {
|
||||
{ amount: deposit.amount, denom: deposit.denom },
|
||||
displayCurrency,
|
||||
prices,
|
||||
),
|
||||
).toString(),
|
||||
apy,
|
||||
}
|
||||
})
|
||||
@ -60,7 +61,7 @@ export const AccountBalancesTable = (props: Props) => {
|
||||
{ amount: lending.amount, denom: lending.denom },
|
||||
displayCurrency,
|
||||
prices,
|
||||
),
|
||||
).toString(),
|
||||
apy,
|
||||
}
|
||||
})
|
||||
@ -104,9 +105,11 @@ export const AccountBalancesTable = (props: Props) => {
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-right text-xs'
|
||||
amount={demagnify(
|
||||
row.original.amount,
|
||||
ASSETS.find((asset) => asset.denom === row.original.denom) ?? ASSETS[0],
|
||||
amount={BN(
|
||||
demagnify(
|
||||
row.original.amount,
|
||||
ASSETS.find((asset) => asset.denom === row.original.denom) ?? ASSETS[0],
|
||||
),
|
||||
)}
|
||||
options={{ maxDecimals: 4 }}
|
||||
/>
|
||||
@ -121,7 +124,7 @@ export const AccountBalancesTable = (props: Props) => {
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-xs'
|
||||
amount={row.original.apy}
|
||||
amount={BN(row.original.apy)}
|
||||
options={{ maxDecimals: 2, minDecimals: 2, suffix: '%' }}
|
||||
/>
|
||||
)
|
||||
|
@ -90,7 +90,7 @@ function Item(props: ItemProps) {
|
||||
<div className='flex flex-grow items-center justify-end gap-2'>
|
||||
{props.isPercentage ? (
|
||||
<FormattedNumber
|
||||
amount={props.current.toString()}
|
||||
amount={props.current}
|
||||
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||
className='text-sm'
|
||||
/>
|
||||
@ -107,7 +107,7 @@ function Item(props: ItemProps) {
|
||||
</span>
|
||||
{props.isPercentage ? (
|
||||
<FormattedNumber
|
||||
amount={props.change.toString()}
|
||||
amount={props.change}
|
||||
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
|
||||
/>
|
||||
|
@ -13,17 +13,18 @@ import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export const RiskChart = ({ data }: RiskChartProps) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const accountStats = null
|
||||
const currentRisk = 0
|
||||
const currentRisk = BN(0)
|
||||
|
||||
return (
|
||||
<div className='flex w-full flex-wrap overflow-hidden py-2'>
|
||||
<FormattedNumber
|
||||
className='px-3 pb-2 text-lg'
|
||||
amount={currentRisk * 100}
|
||||
amount={currentRisk.times(100)}
|
||||
options={{
|
||||
maxDecimals: 0,
|
||||
minDecimals: 0,
|
||||
|
@ -10,7 +10,7 @@ export async function AccountDebtTable(props: Props) {
|
||||
return debtData.map((debt) => {
|
||||
return (
|
||||
<p key={debt.denom}>
|
||||
{debt.denom} {debt.amount}
|
||||
{debt.denom} {debt.amount.toString()}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
interface Props {
|
||||
asset: Asset
|
||||
amount: string
|
||||
amount: BigNumber
|
||||
}
|
||||
|
||||
export default function AmountAndValue(props: Props) {
|
||||
@ -16,7 +17,11 @@ export default function AmountAndValue(props: Props) {
|
||||
options={{ decimals: props.asset.decimals, abbreviated: true }}
|
||||
/>
|
||||
}
|
||||
sub={<DisplayCurrency coin={{ amount: props.amount, denom: props.asset.denom }} />}
|
||||
sub={
|
||||
<DisplayCurrency
|
||||
coin={new BNCoin({ amount: props.amount.toString(), denom: props.asset.denom })}
|
||||
/>
|
||||
}
|
||||
className='justify-end'
|
||||
/>
|
||||
)
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import classNames from 'classnames'
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
|
||||
import AmountAndValue from 'components/AmountAndValue'
|
||||
|
@ -5,6 +5,7 @@ import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import useStore from 'store'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
balance: number
|
||||
@ -68,7 +69,7 @@ export const BorrowCapacity = ({
|
||||
limitPercentOfMax ? 'opacity-50' : 'opacity-0',
|
||||
)}
|
||||
>
|
||||
<FormattedNumber animate amount={limit} />
|
||||
<FormattedNumber animate amount={BN(limit)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -126,7 +127,7 @@ export const BorrowCapacity = ({
|
||||
maxDecimals: decimals,
|
||||
suffix: '%',
|
||||
}}
|
||||
amount={percentOfMaxRound}
|
||||
amount={BN(percentOfMaxRound)}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
@ -138,9 +139,9 @@ export const BorrowCapacity = ({
|
||||
</Tooltip>
|
||||
{!hideValues && (
|
||||
<div className='mt-2 flex opacity-50 text-3xs-caps'>
|
||||
<FormattedNumber animate amount={balance} className='mr-1' />
|
||||
<FormattedNumber animate amount={BN(balance)} className='mr-1' />
|
||||
<span className='mr-1'>of</span>
|
||||
<FormattedNumber animate amount={max} />
|
||||
<FormattedNumber animate amount={BN(max)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { convertToDisplayAmount } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
coin: Coin
|
||||
coin: BNCoin | Coin
|
||||
className?: string
|
||||
isApproximation?: boolean
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ function LendingMarketsTable(props: Props) {
|
||||
accessorKey: 'accountDepositValue',
|
||||
header: 'Deposited',
|
||||
cell: ({ row }) => {
|
||||
const accountDepositValue = (row.original.accountLentValue as BigNumber).toNumber()
|
||||
const accountDepositValue = row.original.accountLentValue as BigNumber
|
||||
|
||||
return (
|
||||
<FormattedNumber
|
||||
|
@ -4,9 +4,10 @@ import { animated, useSpring } from 'react-spring'
|
||||
|
||||
import useStore from 'store'
|
||||
import { FormatOptions, formatValue } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
amount: number | string
|
||||
amount: BigNumber
|
||||
options?: FormatOptions
|
||||
className?: string
|
||||
animate?: boolean
|
||||
@ -14,23 +15,23 @@ interface Props {
|
||||
|
||||
export const FormattedNumber = React.memo((props: Props) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const prevAmountRef = useRef<number>(0)
|
||||
const prevAmountRef = useRef<BigNumber>(BN(0))
|
||||
|
||||
useEffect(() => {
|
||||
if (prevAmountRef.current !== Number(props.amount)) prevAmountRef.current = Number(props.amount)
|
||||
if (!prevAmountRef.current.eq(props.amount)) prevAmountRef.current = props.amount
|
||||
}, [props.amount])
|
||||
|
||||
const springAmount = useSpring({
|
||||
number: Number(props.amount),
|
||||
from: { number: prevAmountRef.current },
|
||||
number: props.amount.toNumber(),
|
||||
from: { number: prevAmountRef.current.toNumber() },
|
||||
config: { duration: 1000 },
|
||||
})
|
||||
|
||||
return (prevAmountRef.current === props.amount && props.amount === 0) ||
|
||||
return (prevAmountRef.current.eq(props.amount) && props.amount.isZero()) ||
|
||||
!props.animate ||
|
||||
!enableAnimations ? (
|
||||
<span className={classNames('number', props.className)}>
|
||||
{formatValue(props.amount, {
|
||||
{formatValue(props.amount.toString(), {
|
||||
minDecimals: props.options?.minDecimals,
|
||||
maxDecimals: props.options?.maxDecimals,
|
||||
thousandSeparator: props.options?.thousandSeparator,
|
||||
|
@ -4,6 +4,7 @@ import { ReactElement, ReactNode } from 'react'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import useStore from 'store'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
tooltip: string | ReactNode
|
||||
@ -87,7 +88,7 @@ export const Gauge = ({
|
||||
)}
|
||||
<FormattedNumber
|
||||
className={classNames(labelClassName, 'text-2xs')}
|
||||
amount={Math.round(percentage)}
|
||||
amount={BN(Math.round(percentage))}
|
||||
options={{ maxDecimals: 0, minDecimals: 0 }}
|
||||
animate
|
||||
/>
|
||||
|
@ -2,6 +2,7 @@ import classNames from 'classnames'
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface ValueData extends FormattedNumberProps {
|
||||
format?: 'number' | 'string'
|
||||
@ -19,7 +20,11 @@ export const LabelValuePair = ({ label, value, className }: Props) => (
|
||||
{label}
|
||||
</Text>
|
||||
<Text size='xs' className='text-white/60'>
|
||||
{value.format === 'number' ? <FormattedNumber animate {...value} /> : value.amount || ''}
|
||||
{value.format === 'number' ? (
|
||||
<FormattedNumber animate {...value} amount={BN(value.amount)} />
|
||||
) : (
|
||||
value.amount || ''
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
|
@ -6,27 +6,28 @@ import {
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import useAddVaultAssetTableColumns from 'components/Modals/AddVaultAssets/useAddVaultAssetTableColumns'
|
||||
|
||||
interface Props {
|
||||
assets: BorrowAsset[]
|
||||
selectedDenoms: string[]
|
||||
onChangeSelected: (denoms: string[]) => void
|
||||
}
|
||||
|
||||
export default function AddVaultAssetTable(props: Props) {
|
||||
const selectedDenoms = useStore((s) => s.addVaultBorrowingsModal?.selectedDenoms) || []
|
||||
const defaultSelected = props.assets.reduce((acc, asset, index) => {
|
||||
if (selectedDenoms.includes(asset.denom)) {
|
||||
acc[index] = true
|
||||
}
|
||||
return acc
|
||||
}, {} as { [key: number]: boolean })
|
||||
const defaultSelected = useMemo(() => {
|
||||
return props.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)
|
||||
@ -46,11 +47,16 @@ export default function AddVaultAssetTable(props: Props) {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const selectedDenoms = props.assets
|
||||
const newSelectedDenoms = props.assets
|
||||
.filter((_, index) => selected[index])
|
||||
.map((asset) => asset.denom)
|
||||
|
||||
props.onChangeSelected(selectedDenoms)
|
||||
if (
|
||||
props.selectedDenoms.length === newSelectedDenoms.length &&
|
||||
newSelectedDenoms.every((denom) => props.selectedDenoms.includes(denom))
|
||||
)
|
||||
return
|
||||
props.onChangeSelected(newSelectedDenoms)
|
||||
}, [selected, props])
|
||||
|
||||
return (
|
||||
|
@ -4,6 +4,7 @@ import SearchBar from 'components/SearchBar'
|
||||
import Text from 'components/Text'
|
||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||
import AddVaultAssetTable from 'components/Modals/AddVaultAssets/AddVaultAssetTable'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
vault: Vault
|
||||
@ -14,8 +15,6 @@ interface Props {
|
||||
export default function AddVaultAssetsModalContent(props: Props) {
|
||||
const [searchString, setSearchString] = useState<string>('')
|
||||
const { data: borrowAssets } = useMarketBorrowings()
|
||||
const [selectedPoolDenoms, setSelectedPoolDenoms] = useState<string[]>([])
|
||||
const [selectedOtherDenoms, setSelectedOtherDenoms] = useState<string[]>([])
|
||||
|
||||
const filteredBorrowAssets: BorrowAsset[] = useMemo(() => {
|
||||
return borrowAssets.filter(
|
||||
@ -49,6 +48,15 @@ export default function AddVaultAssetsModalContent(props: Props) {
|
||||
[filteredBorrowAssets, props.vault.denoms.primary, props.vault.denoms.secondary],
|
||||
)
|
||||
|
||||
const selectedDenoms = useStore((s) => s.addVaultBorrowingsModal?.selectedDenoms)
|
||||
const [selectedPoolDenoms, setSelectedPoolDenoms] = useState<string[]>(
|
||||
selectedDenoms?.filter((denom) => poolAssets.map((asset) => asset.denom).includes(denom)) || [],
|
||||
)
|
||||
const [selectedOtherDenoms, setSelectedOtherDenoms] = useState<string[]>(
|
||||
selectedDenoms?.filter((denom) => stableAssets.map((asset) => asset.denom).includes(denom)) ||
|
||||
[],
|
||||
)
|
||||
|
||||
const onChangePoolDenoms = useCallback(
|
||||
(denoms: string[]) => {
|
||||
setSelectedPoolDenoms(denoms)
|
||||
@ -81,7 +89,11 @@ export default function AddVaultAssetsModalContent(props: Props) {
|
||||
Leverage will be set at 50% for both assets by default
|
||||
</Text>
|
||||
</div>
|
||||
<AddVaultAssetTable assets={poolAssets} onChangeSelected={onChangePoolDenoms} />
|
||||
<AddVaultAssetTable
|
||||
assets={poolAssets}
|
||||
onChangeSelected={onChangePoolDenoms}
|
||||
selectedDenoms={selectedPoolDenoms}
|
||||
/>
|
||||
<div className='p-4'>
|
||||
<Text>Assets not in the liquidity pool</Text>
|
||||
<Text size='xs' className='mt-1 text-white/60'>
|
||||
@ -89,7 +101,11 @@ export default function AddVaultAssetsModalContent(props: Props) {
|
||||
these assets below.
|
||||
</Text>
|
||||
</div>
|
||||
<AddVaultAssetTable assets={stableAssets} onChangeSelected={onChangeOtherDenoms} />
|
||||
<AddVaultAssetTable
|
||||
assets={stableAssets}
|
||||
onChangeSelected={onChangeOtherDenoms}
|
||||
selectedDenoms={selectedOtherDenoms}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
@ -83,12 +83,12 @@ export default function BorrowModal() {
|
||||
useStore.setState({ borrowModal: null })
|
||||
}
|
||||
|
||||
const liquidityAmountString = formatValue(modal?.marketData?.liquidity?.amount || 0, {
|
||||
const liquidityAmountString = formatValue(modal?.marketData?.liquidity?.amount.toString() || 0, {
|
||||
abbreviated: true,
|
||||
decimals: 6,
|
||||
})
|
||||
|
||||
const liquidityValueString = formatValue(modal?.marketData?.liquidity?.value || 0, {
|
||||
const liquidityValueString = formatValue(modal?.marketData?.liquidity?.value.toString() || 0, {
|
||||
abbreviated: true,
|
||||
decimals: 6,
|
||||
})
|
||||
|
@ -23,7 +23,7 @@ function DetailsHeader({ data }: Props) {
|
||||
<FormattedNumber amount={assetApy} options={{ suffix: '%' }} />
|
||||
<FormattedNumber
|
||||
className='ml-2 text-xs'
|
||||
amount={assetApy / 365}
|
||||
amount={assetApy.div(365)}
|
||||
options={{ suffix: '%/day' }}
|
||||
/>
|
||||
</>
|
||||
|
@ -7,7 +7,7 @@ import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
|
||||
import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader'
|
||||
import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal'
|
||||
import AccountBalanceSettableCoin from 'types/classes/AccountBalanceSettableCoin'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
function LendAndReclaimModalController() {
|
||||
const currentAccount = useCurrentAccount()
|
||||
@ -48,15 +48,12 @@ function LendAndReclaimModal({ currentAccount, config }: Props) {
|
||||
async (value: BigNumber, isMax: boolean) => {
|
||||
setIsConfirming(true)
|
||||
|
||||
const coin = new AccountBalanceSettableCoin(
|
||||
asset.denom,
|
||||
value.integerValue().toString(),
|
||||
isMax,
|
||||
)
|
||||
const coin = BNCoin.fromDenomAndBigNumber(asset.denom, value)
|
||||
const options = {
|
||||
fee: hardcodedFee,
|
||||
accountId: currentAccount.id,
|
||||
coin,
|
||||
isMax,
|
||||
}
|
||||
await (isLendAction ? lend : reclaim)(options)
|
||||
|
||||
|
@ -18,6 +18,7 @@ import useStore from 'store'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import usePrice from 'hooks/usePrice'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import useDepositVault from 'hooks/broadcast/useDepositVault'
|
||||
|
||||
export interface VaultBorrowingsProps {
|
||||
account: Account
|
||||
@ -26,6 +27,8 @@ export interface VaultBorrowingsProps {
|
||||
secondaryAmount: BigNumber
|
||||
primaryAsset: Asset
|
||||
secondaryAsset: Asset
|
||||
deposits: BNCoin[]
|
||||
vault: Vault
|
||||
onChangeBorrowings: (borrowings: BNCoin[]) => void
|
||||
}
|
||||
|
||||
@ -36,6 +39,13 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
||||
const secondaryPrice = usePrice(props.secondaryAsset.denom)
|
||||
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||
const vaultModal = useStore((s) => s.vaultModal)
|
||||
const depositIntoVault = useStore((s) => s.depositIntoVault)
|
||||
|
||||
const { actions: depositActions, fee: depositFee } = useDepositVault({
|
||||
vault: props.vault,
|
||||
deposits: props.deposits.filter((borrowing) => borrowing.amount.gt(0)),
|
||||
borrowings: props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
|
||||
})
|
||||
|
||||
const primaryValue = useMemo(
|
||||
() => props.primaryAmount.times(primaryPrice),
|
||||
@ -139,6 +149,10 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
||||
})
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
depositIntoVault({ fee: depositFee, accountId: props.account.id, actions: depositActions })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-grow flex-col gap-4 p-4'>
|
||||
{props.borrowings.map((coin) => {
|
||||
@ -191,7 +205,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<Button color='primary' text='Deposit' rightIcon={<ArrowRight />} />
|
||||
<Button onClick={onConfirm} color='primary' text='Deposit' rightIcon={<ArrowRight />} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ export default function VaultDeposit(props: Props) {
|
||||
() =>
|
||||
props.isCustomRatio
|
||||
? availableSecondaryAmount
|
||||
: maxAssetValueNonCustom.dividedBy(secondaryPrice),
|
||||
: maxAssetValueNonCustom.dividedBy(secondaryPrice).decimalPlaces(0),
|
||||
[props.isCustomRatio, availableSecondaryAmount, secondaryPrice, maxAssetValueNonCustom],
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
|
||||
import Accordion from 'components/Accordion'
|
||||
import AccountSummary from 'components/Account/AccountSummary'
|
||||
@ -10,6 +10,7 @@ import VaultBorrowingsSubTitle from 'components/Modals/Vault/VaultBorrowingsSubT
|
||||
import VaultDeposit from 'components/Modals/Vault/VaultDeposits'
|
||||
import VaultBorrowings from 'components/Modals/Vault/VaultBorrowings'
|
||||
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
interface Props {
|
||||
vault: Vault
|
||||
@ -28,12 +29,24 @@ export default function VaultModalContent(props: Props) {
|
||||
const [secondaryAmount, setSecondaryAmount] = useState<BigNumber>(BN(0))
|
||||
const [isCustomRatio, setIsCustomRatio] = useState(false)
|
||||
|
||||
const deposits: BNCoin[] = useMemo(() => {
|
||||
const primaryBNCoin = new BNCoin({
|
||||
denom: props.vault.denoms.primary,
|
||||
amount: primaryAmount.toString(),
|
||||
})
|
||||
const secondaryBNCoin = new BNCoin({
|
||||
denom: props.vault.denoms.secondary,
|
||||
amount: secondaryAmount.toString(),
|
||||
})
|
||||
return [primaryBNCoin, secondaryBNCoin]
|
||||
}, [primaryAmount, secondaryAmount, props.vault.denoms.primary, props.vault.denoms.secondary])
|
||||
|
||||
const onChangePrimaryAmount = useCallback(
|
||||
(amount: BigNumber) => setPrimaryAmount(amount),
|
||||
(amount: BigNumber) => setPrimaryAmount(amount.decimalPlaces(0)),
|
||||
[setPrimaryAmount],
|
||||
)
|
||||
const onChangeSecondaryAmount = useCallback(
|
||||
(amount: BigNumber) => setSecondaryAmount(amount),
|
||||
(amount: BigNumber) => setSecondaryAmount(amount.decimalPlaces(0)),
|
||||
[setSecondaryAmount],
|
||||
)
|
||||
|
||||
@ -84,6 +97,8 @@ export default function VaultModalContent(props: Props) {
|
||||
primaryAsset={props.primaryAsset}
|
||||
secondaryAsset={props.secondaryAsset}
|
||||
onChangeBorrowings={onChangeBorrowings}
|
||||
deposits={deposits}
|
||||
vault={props.vault}
|
||||
/>
|
||||
),
|
||||
title: 'Borrow',
|
||||
|
@ -14,6 +14,7 @@ import Button from 'components/Button'
|
||||
import { ExclamationMarkTriangle, TrashBin } from 'components/Icons'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
interface Props {
|
||||
amount: BigNumber
|
||||
@ -108,7 +109,7 @@ export default function TokenInput(props: Props) {
|
||||
</Text>
|
||||
<FormattedNumber
|
||||
className='mr-1 text-xs text-white/50'
|
||||
amount={props.max.toNumber()}
|
||||
amount={props.max}
|
||||
options={{ decimals: props.asset.decimals }}
|
||||
/>
|
||||
<Button
|
||||
@ -127,8 +128,8 @@ export default function TokenInput(props: Props) {
|
||||
<div className='flex'>
|
||||
<DisplayCurrency
|
||||
isApproximation
|
||||
className='inline pl-0.5 text-xs text-white/50'
|
||||
coin={{ denom: props.asset.denom, amount: props.amount.toString() }}
|
||||
className='inline pl-1 text-xs text-white/50'
|
||||
coin={new BNCoin({ denom: props.asset.denom, amount: props.amount.toString() })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,6 +21,7 @@ import useStore from 'store'
|
||||
import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
|
||||
import { formatValue, truncate } from 'utils/formatters'
|
||||
import useWalletBalances from 'hooks/useWalletBalances'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default function ConnectedButton() {
|
||||
// ---------------
|
||||
@ -38,7 +39,7 @@ export default function ConnectedButton() {
|
||||
// LOCAL STATE
|
||||
// ---------------
|
||||
const [showDetails, setShowDetails] = useToggle()
|
||||
const [walletAmount, setWalletAmount] = useState(0)
|
||||
const [walletAmount, setWalletAmount] = useState(BN(0))
|
||||
const [isCopied, setCopied] = useClipboard(address || '', {
|
||||
successDuration: 1000 * 5,
|
||||
})
|
||||
@ -62,9 +63,9 @@ export default function ConnectedButton() {
|
||||
useEffect(() => {
|
||||
if (!walletBalances || walletBalances.length === 0) return
|
||||
setWalletAmount(
|
||||
BigNumber(walletBalances?.find((coin: Coin) => coin.denom === baseAsset.denom)?.amount ?? 0)
|
||||
.div(10 ** baseAsset.decimals)
|
||||
.toNumber(),
|
||||
BigNumber(
|
||||
walletBalances?.find((coin: Coin) => coin.denom === baseAsset.denom)?.amount ?? 0,
|
||||
).div(10 ** baseAsset.decimals),
|
||||
)
|
||||
|
||||
const assetDenoms = marketAssets.map((asset) => asset.denom)
|
||||
@ -103,7 +104,7 @@ export default function ConnectedButton() {
|
||||
{isLoading ? (
|
||||
<CircularProgress size={12} />
|
||||
) : (
|
||||
`${formatValue(walletAmount, { suffix: ` ${baseAsset.symbol}` })}`
|
||||
`${formatValue(walletAmount.toString(), { suffix: ` ${baseAsset.symbol}` })}`
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
|
81
src/hooks/broadcast/useDepositVault.ts
Normal file
81
src/hooks/broadcast/useDepositVault.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import debounce from 'debounce-promise'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import getMinLpToReceive from 'api/vaults/getMinLpToReceive'
|
||||
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 {
|
||||
getEnterVaultActions,
|
||||
getVaultDepositCoinsAndValue,
|
||||
getVaultSwapActions,
|
||||
} from 'utils/vaults'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
vault: Vault
|
||||
deposits: BNCoin[]
|
||||
borrowings: BNCoin[]
|
||||
}
|
||||
export default function useDepositVault(props: Props): { actions: Action[]; fee: StdFee } {
|
||||
const [minLpToReceive, setMinLpToReceive] = useState<BigNumber>(BN(0))
|
||||
const { data: prices } = usePrices()
|
||||
const slippage = useStore((s) => s.slippage)
|
||||
|
||||
const debouncedGetMinLpToReceive = useMemo(() => debounce(getMinLpToReceive, 500), [])
|
||||
|
||||
const { primaryCoin, secondaryCoin, totalValue } = useMemo(
|
||||
() => getVaultDepositCoinsAndValue(props.vault, props.deposits, props.borrowings, prices),
|
||||
[props.deposits, props.borrowings, props.vault, prices],
|
||||
)
|
||||
|
||||
const borrowActions: Action[] = useMemo(() => {
|
||||
return props.borrowings.map((bnCoin) => ({
|
||||
borrow: bnCoin.toCoin(),
|
||||
}))
|
||||
}, [props.borrowings])
|
||||
|
||||
const swapActions: Action[] = useMemo(
|
||||
() =>
|
||||
getVaultSwapActions(
|
||||
props.vault,
|
||||
props.deposits,
|
||||
props.borrowings,
|
||||
prices,
|
||||
slippage,
|
||||
totalValue,
|
||||
),
|
||||
[totalValue, prices, props.vault, props.deposits, props.borrowings, slippage],
|
||||
)
|
||||
|
||||
useMemo(async () => {
|
||||
if (primaryCoin.amount.isZero() || secondaryCoin.amount.isZero()) return
|
||||
|
||||
const lpAmount = await debouncedGetMinLpToReceive(
|
||||
[secondaryCoin.toCoin(), primaryCoin.toCoin()],
|
||||
props.vault.denoms.lp,
|
||||
)
|
||||
|
||||
if (!lpAmount || lpAmount === minLpToReceive) return
|
||||
setMinLpToReceive(lpAmount)
|
||||
}, [
|
||||
primaryCoin,
|
||||
secondaryCoin,
|
||||
props.vault.denoms.lp,
|
||||
debouncedGetMinLpToReceive,
|
||||
minLpToReceive,
|
||||
])
|
||||
|
||||
const enterVaultActions: Action[] = useMemo(() => {
|
||||
return getEnterVaultActions(props.vault, primaryCoin, secondaryCoin, minLpToReceive)
|
||||
}, [props.vault, primaryCoin, secondaryCoin, minLpToReceive])
|
||||
|
||||
const actions = useMemo(
|
||||
() => [...borrowActions, ...swapActions, ...enterVaultActions],
|
||||
[borrowActions, swapActions, enterVaultActions],
|
||||
)
|
||||
|
||||
return { actions, fee: hardcodedFee }
|
||||
}
|
14
src/hooks/useMinLpToReceive.tsx
Normal file
14
src/hooks/useMinLpToReceive.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
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),
|
||||
},
|
||||
)
|
||||
}
|
@ -6,7 +6,8 @@ import { ENV } from 'constants/env'
|
||||
import { Store } from 'store'
|
||||
import { getSingleValueFromBroadcastResult } from 'utils/broadcast'
|
||||
import { formatAmountWithSymbol } from 'utils/formatters'
|
||||
import AccountBalanceSettableCoin from 'types/classes/AccountBalanceSettableCoin'
|
||||
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default function createBroadcastSlice(
|
||||
set: SetState<Store>,
|
||||
@ -127,6 +128,30 @@ export default function createBroadcastSlice(
|
||||
handleResponseMessages(response, `Requested unlock for ${options.vault.name}`)
|
||||
return !!response.result
|
||||
},
|
||||
depositIntoVault: async (options: { fee: StdFee; accountId: string; actions: Action[] }) => {
|
||||
const msg = {
|
||||
update_credit_account: {
|
||||
account_id: options.accountId,
|
||||
actions: options.actions,
|
||||
},
|
||||
}
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
if (response.result) {
|
||||
set({
|
||||
toast: {
|
||||
message: `Deposited into vault`,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
set({
|
||||
toast: {
|
||||
message: response.error ?? `Transaction failed: ${response.error}`,
|
||||
isError: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
return !!response.result
|
||||
},
|
||||
withdraw: async (options: { fee: StdFee; accountId: string; coin: Coin }) => {
|
||||
const msg = {
|
||||
update_credit_account: {
|
||||
@ -213,13 +238,13 @@ export default function createBroadcastSlice(
|
||||
)
|
||||
return !!response.result
|
||||
},
|
||||
lend: async (options: { fee: StdFee; accountId: string; coin: AccountBalanceSettableCoin }) => {
|
||||
lend: async (options: { fee: StdFee; accountId: string; coin: BNCoin; isMax?: boolean }) => {
|
||||
const msg = {
|
||||
update_credit_account: {
|
||||
account_id: options.accountId,
|
||||
actions: [
|
||||
{
|
||||
lend: options.coin,
|
||||
lend: options.coin.toActionCoin(options.isMax),
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -229,21 +254,17 @@ export default function createBroadcastSlice(
|
||||
|
||||
handleResponseMessages(
|
||||
response,
|
||||
`Successfully deposited ${formatAmountWithSymbol(options.coin)}`,
|
||||
`Successfully deposited ${formatAmountWithSymbol(options.coin.toCoin())}`,
|
||||
)
|
||||
return !!response.result
|
||||
},
|
||||
reclaim: async (options: {
|
||||
fee: StdFee
|
||||
accountId: string
|
||||
coin: AccountBalanceSettableCoin
|
||||
}) => {
|
||||
reclaim: async (options: { fee: StdFee; accountId: string; coin: BNCoin; isMax?: boolean }) => {
|
||||
const msg = {
|
||||
update_credit_account: {
|
||||
account_id: options.accountId,
|
||||
actions: [
|
||||
{
|
||||
reclaim: options.coin.toActionCoin(),
|
||||
reclaim: options.coin.toActionCoin(options.isMax),
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -253,7 +274,7 @@ export default function createBroadcastSlice(
|
||||
|
||||
handleResponseMessages(
|
||||
response,
|
||||
`Successfully withdrew ${formatAmountWithSymbol(options.coin)}`,
|
||||
`Successfully withdrew ${formatAmountWithSymbol(options.coin.toCoin())}`,
|
||||
)
|
||||
return !!response.result
|
||||
},
|
||||
|
@ -9,6 +9,7 @@ export default function createCommonSlice(set: SetState<CommonSlice>, get: GetSt
|
||||
enableAnimations: true,
|
||||
isOpen: true,
|
||||
selectedAccount: null,
|
||||
slippage: 0.02,
|
||||
status: WalletConnectionStatus.Unconnected,
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { ActionCoin } from 'types/generated'
|
||||
|
||||
class AccountBalanceSettableCoin implements Coin {
|
||||
public denom: string
|
||||
public amount: string
|
||||
public setAccountBalance: boolean
|
||||
|
||||
constructor(denom: string, amount: string, setAccountBalance: boolean) {
|
||||
this.denom = denom
|
||||
this.amount = amount
|
||||
this.setAccountBalance = setAccountBalance
|
||||
}
|
||||
|
||||
toActionCoin(): ActionCoin {
|
||||
return {
|
||||
denom: this.denom,
|
||||
amount: this.setAccountBalance ? 'account_balance' : { exact: this.amount },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AccountBalanceSettableCoin
|
@ -1,3 +1,4 @@
|
||||
import { ActionCoin } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export class BNCoin {
|
||||
@ -9,10 +10,25 @@ export class BNCoin {
|
||||
this.amount = BN(coin.amount)
|
||||
}
|
||||
|
||||
static fromDenomAndBigNumber(denom: string, amount: BigNumber) {
|
||||
return new BNCoin({ denom, amount: amount.toString() })
|
||||
}
|
||||
|
||||
toCoin(): Coin {
|
||||
return {
|
||||
denom: this.denom,
|
||||
amount: this.amount.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
toActionCoin(max?: boolean): ActionCoin {
|
||||
return {
|
||||
denom: this.denom,
|
||||
amount: max
|
||||
? 'account_balance'
|
||||
: {
|
||||
exact: this.amount.toString(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,19 +5,20 @@
|
||||
* and run the @cosmwasm/ts-codegen generate command to regenerate this file.
|
||||
*/
|
||||
|
||||
import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate'
|
||||
import { CosmWasmClient, ExecuteResult, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { Coin, StdFee } from '@cosmjs/amino'
|
||||
|
||||
import {
|
||||
InstantiateMsg,
|
||||
ExecuteMsg,
|
||||
Uint128,
|
||||
Addr,
|
||||
ArrayOfAssetIncentiveResponse,
|
||||
AssetIncentiveResponse,
|
||||
ConfigResponse,
|
||||
Decimal,
|
||||
ExecuteMsg,
|
||||
InstantiateMsg,
|
||||
OwnerUpdate,
|
||||
QueryMsg,
|
||||
Decimal,
|
||||
AssetIncentiveResponse,
|
||||
ArrayOfAssetIncentiveResponse,
|
||||
ConfigResponse,
|
||||
Uint128,
|
||||
} from './MarsIncentives.types'
|
||||
export interface MarsIncentivesReadOnlyInterface {
|
||||
contractAddress: string
|
||||
|
@ -5,22 +5,23 @@
|
||||
* and run the @cosmwasm/ts-codegen generate command to regenerate this file.
|
||||
*/
|
||||
|
||||
import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query'
|
||||
import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
|
||||
import { StdFee, Coin } from '@cosmjs/amino'
|
||||
import { Coin, StdFee } from '@cosmjs/amino'
|
||||
|
||||
import {
|
||||
InstantiateMsg,
|
||||
ExecuteMsg,
|
||||
Uint128,
|
||||
Addr,
|
||||
ArrayOfAssetIncentiveResponse,
|
||||
AssetIncentiveResponse,
|
||||
ConfigResponse,
|
||||
Decimal,
|
||||
ExecuteMsg,
|
||||
InstantiateMsg,
|
||||
OwnerUpdate,
|
||||
QueryMsg,
|
||||
Decimal,
|
||||
AssetIncentiveResponse,
|
||||
ArrayOfAssetIncentiveResponse,
|
||||
ConfigResponse,
|
||||
Uint128,
|
||||
} from './MarsIncentives.types'
|
||||
import { MarsIncentivesQueryClient, MarsIncentivesClient } from './MarsIncentives.client'
|
||||
import { MarsIncentivesClient, MarsIncentivesQueryClient } from './MarsIncentives.client'
|
||||
export const marsIncentivesQueryKeys = {
|
||||
contract: [
|
||||
{
|
||||
|
12
src/types/interfaces/account.d.ts
vendored
12
src/types/interfaces/account.d.ts
vendored
@ -1,15 +1,15 @@
|
||||
interface Account extends AccountChange {
|
||||
id: string
|
||||
deposits: Coin[]
|
||||
debts: Coin[]
|
||||
lends: Coin[]
|
||||
deposits: BNCoin[]
|
||||
debts: BNCoin[]
|
||||
lends: BNCoin[]
|
||||
vaults: import('types/generated/mars-mock-credit-manager/MarsMockCreditManager.types').ArrayOfVaultInfoResponse
|
||||
}
|
||||
|
||||
interface AccountChange {
|
||||
deposits?: Coin[]
|
||||
debts?: Coin[]
|
||||
lends?: Coin[]
|
||||
deposits?: BNCoin[]
|
||||
debts?: BNCoin[]
|
||||
lends?: BNCoin[]
|
||||
vaults?: import('types/generated/mars-mock-credit-manager/MarsMockCreditManager.types').ArrayOfVaultInfoResponse
|
||||
}
|
||||
|
||||
|
6
src/types/interfaces/asset.d.ts
vendored
6
src/types/interfaces/asset.d.ts
vendored
@ -23,13 +23,13 @@ interface OtherAsset extends Omit<Asset, 'symbol'> {
|
||||
interface BorrowAsset extends Asset {
|
||||
borrowRate: number | null
|
||||
liquidity: {
|
||||
amount: string
|
||||
value: string
|
||||
amount: BigNumber
|
||||
value: BigNumber
|
||||
} | null
|
||||
}
|
||||
|
||||
interface BorrowAssetActive extends BorrowAsset {
|
||||
debt: string
|
||||
debt: BigNumber
|
||||
}
|
||||
|
||||
interface BigNumberCoin {
|
||||
|
13
src/types/interfaces/store/broadcast.d.ts
vendored
13
src/types/interfaces/store/broadcast.d.ts
vendored
@ -1,5 +1,3 @@
|
||||
type AccountBalanceSettableCoin = import('types/classes/AccountBalanceSettableCoin')
|
||||
|
||||
interface BroadcastResult {
|
||||
result?: import('@marsprotocol/wallet-connector').TxBroadcastResult
|
||||
error?: string
|
||||
@ -17,16 +15,23 @@ interface BroadcastSlice {
|
||||
deleteAccount: (options: { fee: StdFee; accountId: string }) => Promise<boolean>
|
||||
deposit: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise<boolean>
|
||||
unlock: (options: { fee: StdFee; vault: Vault; amount: string }) => Promise<boolean>
|
||||
depositIntoVault: (options: {
|
||||
fee: StdFee
|
||||
accountId: string
|
||||
actions: Action[]
|
||||
}) => Promise<boolean>
|
||||
withdraw: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise<boolean>
|
||||
lend: (options: {
|
||||
fee: StdFee
|
||||
accountId: string
|
||||
coin: AccountBalanceSettableCoin
|
||||
coin: BNCoin
|
||||
isMax?: boolean
|
||||
}) => Promise<boolean>
|
||||
reclaim: (options: {
|
||||
fee: StdFee
|
||||
accountId: string
|
||||
coin: AccountBalanceSettableCoin
|
||||
coin: BNCoin
|
||||
isMax?: boolean
|
||||
}) => Promise<boolean>
|
||||
repay: (options: {
|
||||
fee: StdFee
|
||||
|
1
src/types/interfaces/store/common.d.ts
vendored
1
src/types/interfaces/store/common.d.ts
vendored
@ -6,5 +6,6 @@ interface CommonSlice {
|
||||
enableAnimations: boolean
|
||||
isOpen: boolean
|
||||
selectedAccount: string | null
|
||||
slippage: number
|
||||
status: import('@marsprotocol/wallet-connector').WalletConnectionStatus
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ export const calculateAccountDebt = (
|
||||
if (!account.debts) return BN(0)
|
||||
return account.debts.reduce((acc, debt) => {
|
||||
const price = prices.find((price) => price.denom === debt.denom)?.amount ?? 0
|
||||
const debtValue = BN(debt.amount).multipliedBy(price)
|
||||
const debtValue = debt.amount.multipliedBy(price)
|
||||
return acc.plus(debtValue)
|
||||
}, BN(0))
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
@ -155,18 +156,21 @@ export function demagnify(amount: number | string | BigNumber, asset: Asset) {
|
||||
return value.isZero() ? 0 : value.shiftedBy(-1 * asset.decimals).toNumber()
|
||||
}
|
||||
|
||||
export function convertToDisplayAmount(coin: Coin, displayCurrency: Asset, prices: Coin[]) {
|
||||
export function convertToDisplayAmount(
|
||||
coin: BNCoin | Coin,
|
||||
displayCurrency: Asset,
|
||||
prices: Coin[],
|
||||
) {
|
||||
const price = prices.find((price) => price.denom === coin.denom)
|
||||
const asset = getEnabledMarketAssets().find((asset) => asset.denom === coin.denom)
|
||||
const displayPrice = prices.find((price) => price.denom === displayCurrency.denom)
|
||||
|
||||
if (!price || !asset || !displayPrice) return '0'
|
||||
if (!price || !asset || !displayPrice) return BN(0)
|
||||
|
||||
return BN(coin.amount)
|
||||
.shiftedBy(-1 * asset.decimals)
|
||||
.times(price.amount)
|
||||
.div(displayPrice.amount)
|
||||
.toNumber()
|
||||
}
|
||||
|
||||
export function convertLiquidityRateToAPR(rate: number) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { getBaseAsset } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
@ -15,7 +16,12 @@ export const getTokenIcon = (denom: string, marketAssets: Asset[]) =>
|
||||
export const getTokenInfo = (denom: string, marketAssets: Asset[]) =>
|
||||
marketAssets.find((asset) => asset.denom.toLowerCase() === denom.toLowerCase()) || getBaseAsset()
|
||||
|
||||
export function getTokenValue(coin: Coin, prices: Coin[]): BigNumber {
|
||||
export function getTokenValue(coin: BNCoin, prices: Coin[]): BigNumber {
|
||||
const price = prices.find((price) => price.denom === coin.denom)?.amount || '0'
|
||||
return BN(price).times(coin.amount)
|
||||
return BN(price).times(coin.amount).decimalPlaces(0)
|
||||
}
|
||||
|
||||
export function getTokenPrice(denom: string, prices: Coin[]): BigNumber {
|
||||
const price = prices.find((price) => price.denom === denom)?.amount || '0'
|
||||
return BN(price)
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { getNetCollateralValue } from 'utils/accounts'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||
import { getTokenPrice, getTokenValue } from 'utils/tokens'
|
||||
|
||||
export function getVaultMetaData(address: string) {
|
||||
const vaults = IS_TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
|
||||
@ -33,3 +35,160 @@ export function calculateMaxBorrowAmounts(
|
||||
|
||||
return maxAmounts
|
||||
}
|
||||
|
||||
export function getVaultDepositCoinsAndValue(
|
||||
vault: Vault,
|
||||
deposits: BNCoin[],
|
||||
borrowings: BNCoin[],
|
||||
prices: Coin[],
|
||||
) {
|
||||
const totalValue = [...deposits, ...borrowings].reduce((prev, bnCoin) => {
|
||||
const price = prices.find((coin) => coin.denom === bnCoin.denom)?.amount
|
||||
if (!price) return prev
|
||||
|
||||
return prev.plus(bnCoin.amount.times(price))
|
||||
}, BN(0))
|
||||
|
||||
const primaryDepositAmount = getTokenPrice(vault.denoms.primary, prices)
|
||||
.times(totalValue)
|
||||
.div(2)
|
||||
.integerValue()
|
||||
|
||||
const secondaryDepositAmount = getTokenPrice(vault.denoms.secondary, prices)
|
||||
.times(totalValue)
|
||||
.div(2)
|
||||
.integerValue()
|
||||
|
||||
return {
|
||||
primaryCoin: new BNCoin({
|
||||
denom: vault.denoms.primary,
|
||||
amount: primaryDepositAmount.toString(),
|
||||
}),
|
||||
secondaryCoin: new BNCoin({
|
||||
denom: vault.denoms.secondary,
|
||||
amount: secondaryDepositAmount.toString(),
|
||||
}),
|
||||
totalValue,
|
||||
}
|
||||
}
|
||||
|
||||
export function getVaultSwapActions(
|
||||
vault: Vault,
|
||||
deposits: BNCoin[],
|
||||
borrowings: BNCoin[],
|
||||
prices: Coin[],
|
||||
slippage: number,
|
||||
totalValue: BigNumber,
|
||||
): Action[] {
|
||||
const swapActions: Action[] = []
|
||||
const coins = [...deposits, ...borrowings]
|
||||
|
||||
let primaryLeftoverValue = totalValue.div(2).integerValue()
|
||||
let secondaryLeftoverValue = totalValue.div(2).integerValue()
|
||||
|
||||
const [primaryCoins, secondaryCoins, otherCoins] = coins.reduce(
|
||||
(prev, bnCoin) => {
|
||||
switch (bnCoin.denom) {
|
||||
case vault.denoms.primary:
|
||||
prev[0].push(bnCoin)
|
||||
break
|
||||
case vault.denoms.secondary:
|
||||
prev[1].push(bnCoin)
|
||||
break
|
||||
default:
|
||||
prev[2].push(bnCoin)
|
||||
}
|
||||
return prev
|
||||
},
|
||||
[[], [], []] as [BNCoin[], BNCoin[], BNCoin[]],
|
||||
)
|
||||
|
||||
primaryCoins.forEach((bnCoin) => {
|
||||
let value = getTokenValue(bnCoin, prices)
|
||||
if (value.isLessThanOrEqualTo(primaryLeftoverValue)) {
|
||||
primaryLeftoverValue = primaryLeftoverValue.minus(value)
|
||||
} else {
|
||||
value = value.minus(primaryLeftoverValue)
|
||||
primaryLeftoverValue = primaryLeftoverValue.minus(primaryLeftoverValue)
|
||||
otherCoins.push(new BNCoin({ denom: bnCoin.denom, amount: value.toString() }))
|
||||
}
|
||||
})
|
||||
|
||||
secondaryCoins.forEach((bnCoin) => {
|
||||
let value = getTokenValue(bnCoin, prices)
|
||||
if (value.isLessThanOrEqualTo(secondaryLeftoverValue)) {
|
||||
secondaryLeftoverValue = secondaryLeftoverValue.minus(value)
|
||||
} else {
|
||||
value = value.minus(secondaryLeftoverValue)
|
||||
secondaryLeftoverValue = secondaryLeftoverValue.minus(secondaryLeftoverValue)
|
||||
otherCoins.push(new BNCoin({ denom: bnCoin.denom, amount: value.toString() }))
|
||||
}
|
||||
})
|
||||
|
||||
otherCoins.forEach((bnCoin) => {
|
||||
let value = getTokenValue(bnCoin, prices)
|
||||
let amount = bnCoin.amount
|
||||
|
||||
if (primaryLeftoverValue.isGreaterThan(0)) {
|
||||
const swapValue = value.isLessThan(primaryLeftoverValue) ? value : primaryLeftoverValue
|
||||
const swapAmount = swapValue
|
||||
.dividedBy(prices.find((coin) => coin.denom === bnCoin.denom)?.amount || 1)
|
||||
.integerValue()
|
||||
value = value.minus(swapValue)
|
||||
amount = amount.minus(swapAmount)
|
||||
primaryLeftoverValue = primaryLeftoverValue.minus(swapValue)
|
||||
swapActions.push(getSwapAction(bnCoin.denom, vault.denoms.primary, swapAmount, slippage))
|
||||
}
|
||||
|
||||
if (secondaryLeftoverValue.isGreaterThan(0)) {
|
||||
secondaryLeftoverValue = secondaryLeftoverValue.minus(value)
|
||||
swapActions.push(getSwapAction(bnCoin.denom, vault.denoms.secondary, amount, slippage))
|
||||
}
|
||||
})
|
||||
|
||||
return swapActions
|
||||
}
|
||||
|
||||
export function getEnterVaultActions(
|
||||
vault: Vault,
|
||||
primaryCoin: BNCoin,
|
||||
secondaryCoin: BNCoin,
|
||||
minLpToReceive: BigNumber,
|
||||
): Action[] {
|
||||
return [
|
||||
{
|
||||
provide_liquidity: {
|
||||
// Smart Contact demands that secondary coin is first
|
||||
coins_in: [secondaryCoin.toActionCoin(), primaryCoin.toActionCoin()],
|
||||
lp_token_out: vault.denoms.lp,
|
||||
minimum_receive: minLpToReceive.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
enter_vault: {
|
||||
coin: {
|
||||
denom: vault.denoms.lp,
|
||||
amount: 'account_balance',
|
||||
},
|
||||
vault: {
|
||||
address: vault.address,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function getSwapAction(denomIn: string, denomOut: string, amount: BigNumber, slippage: number) {
|
||||
return {
|
||||
swap_exact_in: {
|
||||
coin_in: {
|
||||
denom: denomIn,
|
||||
amount: {
|
||||
exact: amount.toString(),
|
||||
},
|
||||
},
|
||||
denom_out: denomOut,
|
||||
slippage: slippage.toString(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
12
yarn.lock
12
yarn.lock
@ -3408,6 +3408,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz"
|
||||
integrity sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==
|
||||
|
||||
"@types/debounce-promise@^3.1.6":
|
||||
version "3.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/debounce-promise/-/debounce-promise-3.1.6.tgz#873e838574011095ed0debf73eed3538e1261d75"
|
||||
integrity sha512-DowqK95aku+OxMCeG2EQSeXeGeE8OCwLpMsUfIbP7hMF8Otj8eQXnzpwdtIKV+UqQBtkMcF6vbi4Otbh8P/wmg==
|
||||
|
||||
"@types/estree@*", "@types/estree@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz"
|
||||
@ -4795,6 +4800,11 @@ data-urls@^3.0.2:
|
||||
whatwg-mimetype "^3.0.0"
|
||||
whatwg-url "^11.0.0"
|
||||
|
||||
debounce-promise@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/debounce-promise/-/debounce-promise-3.1.2.tgz#320fb8c7d15a344455cd33cee5ab63530b6dc7c5"
|
||||
integrity sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||
@ -7029,7 +7039,7 @@ locate-path@^6.0.0:
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
|
Loading…
Reference in New Issue
Block a user