Mp 2949 implemet health computer for borrow (#311)

* implemented max borrow for borrow page

setup basic useHealthComputer

* finish up healthcomputer

* updated tests
This commit is contained in:
Bob van der Helm 2023-07-24 09:44:45 +02:00 committed by GitHub
parent 2d13601365
commit 3413203ca7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1342 additions and 365 deletions

View File

@ -1,26 +1,23 @@
import { render, screen } from '@testing-library/react'
import * as rrd from 'react-router-dom'
import useCurrentAccount from 'hooks/useCurrentAccount'
import AccountDetails from 'components/Account/AccountDetails'
jest.mock('react-router-dom')
const mockedUseParams = rrd.useParams as jest.Mock
jest.mock('hooks/useCurrentAccount', () => jest.fn(() => null))
const mockedUseCurrentAccount = useCurrentAccount as jest.Mock
describe('<AccountDetails />', () => {
afterAll(() => {
mockedUseParams.mockRestore()
})
it('renders account details WHEN accountId specified in the params', () => {
mockedUseParams.mockReturnValue({ accountId: 1 })
it('renders account details WHEN account is selected', () => {
mockedUseCurrentAccount.mockReturnValue({ id: 1 })
render(<AccountDetails />)
const container = screen.queryByTestId('account-details')
expect(container).toBeInTheDocument()
})
it('does not render WHEN accountId is NOT specified in the params', () => {
mockedUseParams.mockReturnValue({ accountId: null })
it('does not render WHEN account is NOT selected', () => {
mockedUseCurrentAccount.mockReturnValue(null)
render(<AccountDetails />)
const container = screen.queryByTestId('account-details')

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'
import LendingDetails from 'components/MarketAssetTable/MarketDetails'
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
import { ASSETS } from 'constants/assets'
import { BN } from 'utils/helpers'
@ -31,7 +31,7 @@ describe('<LendingDetails />', () => {
})
it('should render', () => {
const { container } = render(<LendingDetails data={data} />)
const { container } = render(<MarketDetails type='lend' data={data} />)
expect(container).toBeInTheDocument()
})
})

View File

@ -7,6 +7,7 @@ import DisplayCurrency from 'components/DisplayCurrency'
import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings'
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
import { BNCoin } from 'types/classes/BNCoin'
import { hardcodedFee } from 'utils/constants'
jest.mock('hooks/usePrices', () =>
jest.fn(() => ({
@ -47,9 +48,7 @@ describe('<VaultBorrowings />', () => {
const defaultProps: VaultBorrowingsProps = {
primaryAsset: ASSETS[0],
secondaryAsset: ASSETS[1],
primaryAmount: BN(0),
secondaryAmount: BN(0),
account: {
updatedAccount: {
id: 'test',
deposits: [],
debts: [],
@ -60,6 +59,8 @@ describe('<VaultBorrowings />', () => {
borrowings: [],
deposits: [],
onChangeBorrowings: jest.fn(),
depositActions: [],
depositFee: hardcodedFee,
}
beforeAll(() => {

View File

@ -10,6 +10,11 @@ module.exports = {
'!<rootDir>/*.config.js',
'!<rootDir>/coverage/**',
'!<rootDir>/src/types/**',
'!<rootDir>/src/utils/charting_library/**',
'!<rootDir>/src/utils/datafeeds/**',
'!<rootDir>/public/charting_library/**',
'!<rootDir>/public/datafeeds/**',
'!<rootDir>/src/utils/health_computer/**',
],
moduleNameMapper: {
// Handle CSS imports (with CSS modules)

View File

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

View File

@ -1,12 +0,0 @@
import getAccount from 'api/accounts/getAccount'
import { BNCoin } from 'types/classes/BNCoin'
export default async function getAccountDebts(accountId: string): Promise<BNCoin[]> {
const account = await getAccount(accountId)
if (account) {
return account.debts
}
return new Promise((_, reject) => reject('Account not found'))
}

View File

@ -1,12 +0,0 @@
import getAccount from 'api/accounts/getAccount'
import { BNCoin } from 'types/classes/BNCoin'
export default async function getAccountDeposits(accountId: string): Promise<BNCoin[]> {
const account = await getAccount(accountId)
if (account) {
return account.deposits
}
return new Promise((_, reject) => reject('Account not found'))
}

View File

@ -1,11 +0,0 @@
import getAccount from 'api/accounts/getAccount'
export default async function getAccountDeposits(accountId: string) {
const account = await getAccount(accountId)
if (account) {
return account.vaults
}
return new Promise((_, reject) => reject('Account not found'))
}

View File

@ -0,0 +1,12 @@
import { getParamsQueryClient } from 'api/cosmwasm-client'
import { AssetParamsBaseForAddr } from 'types/generated/mars-params/MarsParams.types'
export default async function getAssetParams(): Promise<AssetParamsBaseForAddr[]> {
try {
const paramsQueryClient = await getParamsQueryClient()
return paramsQueryClient.allAssetParams({})
} catch (ex) {
throw ex
}
}

View File

@ -117,7 +117,7 @@ async function getVaultValuesAndAmounts(
const lpTokensQuery = getLpTokensForVaultPosition(vault, vaultPosition)
const amounts = flatVaultPositionAmount(vaultPosition.amount)
const [[primaryLpToken, secondaryLpToken], [primaryAsset, secondaryAsset]] = await Promise.all([
const [[primaryLpToken, secondaryLpToken], [primaryPrice, secondaryPrice]] = await Promise.all([
lpTokensQuery,
pricesQueries,
])
@ -129,8 +129,8 @@ async function getVaultValuesAndAmounts(
secondary: BN(secondaryLpToken.amount),
},
values: {
primary: BN(primaryLpToken.amount).multipliedBy(primaryAsset),
secondary: BN(secondaryLpToken.amount).multipliedBy(secondaryAsset),
primary: BN(primaryLpToken.amount).multipliedBy(primaryPrice),
secondary: BN(secondaryLpToken.amount).multipliedBy(secondaryPrice),
},
}
} catch (ex) {

View File

@ -0,0 +1,14 @@
import { getVaultQueryClient } from 'api/cosmwasm-client'
export async function getVaultTokenFromLp(
vaultAddress: string,
lpAmount: string,
): Promise<{ vaultAddress: string; amount: string }> {
try {
const client = await getVaultQueryClient(vaultAddress)
return client.previewDeposit({ amount: lpAmount }).then((amount) => ({ vaultAddress, amount }))
} catch (ex) {
throw ex
}
}

View File

@ -1,17 +1,15 @@
import { resolvePositionResponses } from 'utils/resolvers'
import getWalletAccountIds from 'api/wallets/getAccountIds'
import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import getAccount from 'api/accounts/getAccount'
export default async function getAccounts(address: string): Promise<Account[]> {
const accountIds: string[] = await getWalletAccountIds(address)
const creditManagerQueryClient = await getCreditManagerQueryClient()
const $accounts = accountIds.map((accountId) => creditManagerQueryClient.positions({ accountId }))
const $accounts = accountIds.map((accountId) => getAccount(accountId))
const accounts = await Promise.all($accounts).then((accounts) => accounts)
if (accounts) {
return resolvePositionResponses(accounts)
return accounts
}
return new Promise((_, reject) => reject('No data'))

View File

@ -1,15 +1,22 @@
import { useParams } from 'react-router-dom'
import { Gauge } from 'components/Gauge'
import { Heart } from 'components/Icons'
import Text from 'components/Text'
import { isNumber } from 'utils/parsers'
import useCurrentAccount from 'hooks/useCurrentAccount'
export default function AccountDetails() {
const { accountId } = useParams()
const hasAccount = isNumber(accountId)
interface Props {
account: Account
}
return hasAccount ? (
export default function AccountDetailsController() {
const account = useCurrentAccount()
if (!account) return null
return <AccountDetails account={account} />
}
function AccountDetails(props: Props) {
return (
<div
data-testid='account-details'
className='w-16 rounded-base border border-white/20 bg-white/5 backdrop-blur-sticky'
@ -40,5 +47,5 @@ export default function AccountDetails() {
</Text>
</div>
</div>
) : null
)
}

View File

@ -1,17 +0,0 @@
import getAccountDebts from 'api/accounts/getAccountDebts'
interface Props {
accountId: string
}
export async function AccountDebtTable(props: Props) {
const debtData = await getAccountDebts(props.accountId)
return debtData.map((debt) => {
return (
<p key={debt.denom}>
{debt.denom} {debt.amount.toString()}
</p>
)
})
}

View File

@ -19,6 +19,7 @@ import { BNCoin } from 'types/classes/BNCoin'
import { hardcodedFee } from 'utils/constants'
import { formatPercent, formatValue } from 'utils/formatters'
import { BN } from 'utils/helpers'
import useHealthComputer from 'hooks/useHealthComputer'
function getDebtAmount(modal: BorrowModal | null) {
return BN((modal?.marketData as BorrowMarketTableData)?.debt ?? 0).toString()
@ -29,8 +30,18 @@ function getAssetLogo(modal: BorrowModal | null) {
return <AssetImage asset={modal.asset} size={24} />
}
export default function BorrowModal() {
const currentAccount = useCurrentAccount()
interface Props {
account: Account
}
export default function BorrowModalController() {
const account = useCurrentAccount()
if (!account) return null
return <BorrowModal account={account} />
}
function BorrowModal(props: Props) {
const [percentage, setPercentage] = useState(0)
const [amount, setAmount] = useState(BN(0))
const [change, setChange] = useState<AccountChange | undefined>()
@ -41,6 +52,9 @@ export default function BorrowModal() {
const repay = useStore((s) => s.repay)
const asset = modal?.asset ?? ASSETS[0]
const isRepay = modal?.isRepay ?? false
const [max, setMax] = useState(BN(0))
const { computeMaxBorrowAmount } = useHealthComputer(props.account)
function resetState() {
setAmount(BN(0))
@ -49,20 +63,20 @@ export default function BorrowModal() {
}
async function onConfirmClick() {
if (!modal?.asset || !currentAccount) return
if (!modal?.asset) return
setIsConfirming(true)
let result
if (isRepay) {
result = await repay({
fee: hardcodedFee,
accountId: currentAccount.id,
accountId: props.account.id,
coin: BNCoin.fromDenomAndBigNumber(modal.asset.denom, amount),
accountBalance: percentage === 100,
})
} else {
result = await borrow({
fee: hardcodedFee,
accountId: currentAccount.id,
accountId: props.account.id,
coin: { denom: modal.asset.denom, amount: amount.toString() },
borrowToWallet,
})
@ -90,7 +104,16 @@ export default function BorrowModal() {
decimals: 6,
})
const max = BN(isRepay ? getDebtAmount(modal) : modal?.marketData?.liquidity?.amount ?? '0')
useEffect(() => {
if (isRepay) {
setMax(BN(getDebtAmount(modal)))
return
}
computeMaxBorrowAmount(asset.denom).then((maxBorrowAmount) => {
setMax(BN(Math.min(maxBorrowAmount, modal?.marketData?.liquidity?.amount.toNumber() || 0)))
})
}, [isRepay, modal, asset.denom, computeMaxBorrowAmount])
useEffect(() => {
if (!modal?.asset) return
@ -109,7 +132,7 @@ export default function BorrowModal() {
},
],
})
}, [amount, modal?.asset, currentAccount, isRepay])
}, [amount, modal?.asset, props.account, isRepay])
if (!modal) return null
return (
@ -183,7 +206,7 @@ export default function BorrowModal() {
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummary account={currentAccount} change={change} />
<AccountSummary account={props.account} change={change} />
</div>
</Modal>
)

View File

@ -8,53 +8,35 @@ import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
import Slider from 'components/Slider'
import Text from 'components/Text'
import TokenInput from 'components/TokenInput'
import useDepositVault from 'hooks/broadcast/useDepositVault'
import useMarketAssets from 'hooks/useMarketAssets'
import usePrice from 'hooks/usePrice'
import usePrices from 'hooks/usePrices'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { findCoinByDenom, getAssetByDenom } from 'utils/assets'
import { formatPercent } from 'utils/formatters'
import { BN } from 'utils/helpers'
import { calculateMaxBorrowAmounts } from 'utils/vaults'
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
export interface VaultBorrowingsProps {
account: Account
updatedAccount: Account
borrowings: BNCoin[]
primaryAmount: BigNumber
secondaryAmount: BigNumber
deposits: BNCoin[]
primaryAsset: Asset
secondaryAsset: Asset
deposits: BNCoin[]
vault: Vault
depositActions: Action[]
depositFee: StdFee
onChangeBorrowings: (borrowings: BNCoin[]) => void
}
export default function VaultBorrowings(props: VaultBorrowingsProps) {
const { data: marketAssets } = useMarketAssets()
const { data: prices } = usePrices()
const primaryPrice = usePrice(props.primaryAsset.denom)
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 [isConfirming, setIsConfirming] = useState(false)
const { actions: depositActions, fee: depositFee } = useDepositVault({
vault: props.vault,
deposits: props.deposits,
borrowings: props.borrowings,
})
const primaryValue = useMemo(
() => props.primaryAmount.multipliedBy(primaryPrice),
[props.primaryAmount, primaryPrice],
)
const secondaryValue = useMemo(
() => props.secondaryAmount.multipliedBy(secondaryPrice),
[props.secondaryAmount, secondaryPrice],
)
const maxBorrowAmounts: BNCoin[] = []
const borrowingValue = useMemo(() => {
return props.borrowings.reduce((prev, curr) => {
@ -65,10 +47,16 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
}, BN(0) as BigNumber)
}, [props.borrowings, prices])
const totalValue = useMemo(
() => primaryValue.plus(secondaryValue).plus(borrowingValue),
[primaryValue, secondaryValue, borrowingValue],
)
const totalValue = useMemo(() => {
const depositValue = props.deposits.reduce((prev, curr) => {
const price = prices.find((price) => price.denom === curr.denom)?.amount
if (!price) return prev
const value = curr.amount.multipliedBy(price)
return prev.plus(value)
}, BN(0) as BigNumber)
return depositValue.plus(borrowingValue)
}, [props.deposits, borrowingValue, prices])
useEffect(() => {
const selectedBorrowDenoms = vaultModal?.selectedBorrowDenoms || []
@ -89,17 +77,6 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
props.onChangeBorrowings(updatedBorrowings)
}, [vaultModal, props])
const maxAmounts: BNCoin[] = useMemo(
() =>
calculateMaxBorrowAmounts(
props.account,
marketAssets,
prices,
props.borrowings.map((coin) => coin.denom),
),
[props.borrowings, marketAssets, prices, props.account],
)
const [percentage, setPercentage] = useState<number>(0)
function onChangeSlider(value: number) {
@ -107,7 +84,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
const denom = props.borrowings[0].denom
const currentAmount = props.borrowings[0].amount
const maxAmount = maxAmounts.find((coin) => coin.denom === denom)?.amount ?? BN(0)
const maxAmount = maxBorrowAmounts.find((coin) => coin.denom === denom)?.amount ?? BN(0)
const newBorrowings: BNCoin[] = [
new BNCoin({
denom,
@ -152,9 +129,9 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
async function onConfirm() {
setIsConfirming(true)
const isSuccess = await depositIntoVault({
fee: depositFee,
accountId: props.account.id,
actions: depositActions,
fee: props.depositFee,
accountId: props.updatedAccount.id,
actions: props.depositActions,
})
setIsConfirming(false)
if (isSuccess) {
@ -166,7 +143,9 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
<div className='flex flex-1 flex-col gap-4 p-4'>
{props.borrowings.map((coin) => {
const asset = getAssetByDenom(coin.denom)
const maxAmount = maxAmounts.find((maxAmount) => maxAmount.denom === coin.denom)?.amount
const maxAmount = maxBorrowAmounts.find(
(maxAmount) => maxAmount.denom === coin.denom,
)?.amount
if (!asset || !maxAmount)
return <React.Fragment key={`input-${coin.denom}`}></React.Fragment>
return (
@ -222,7 +201,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
text='Deposit'
rightIcon={<ArrowRight />}
showProgressIndicator={isConfirming}
disabled={!depositActions.length}
disabled={!props.depositActions.length}
/>
</div>
)

View File

@ -15,35 +15,44 @@ import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { getAmount } from 'utils/accounts'
import { BN } from 'utils/helpers'
import { findCoinByDenom } from 'utils/assets'
interface Props {
primaryAmount: BigNumber
secondaryAmount: BigNumber
deposits: BNCoin[]
primaryAsset: Asset
secondaryAsset: Asset
account: Account
isCustomRatio: boolean
onChangeDeposits: (deposits: BNCoin[]) => void
onChangeIsCustomRatio: (isCustomRatio: boolean) => void
onChangePrimaryAmount: (amount: BigNumber) => void
onChangeSecondaryAmount: (amount: BigNumber) => void
toggleOpen: (index: number) => void
}
export default function VaultDeposit(props: Props) {
const { deposits, primaryAsset, secondaryAsset, account, onChangeDeposits } = props
const baseCurrency = useStore((s) => s.baseCurrency)
const availablePrimaryAmount = getAmount(primaryAsset.denom, account.deposits)
const availableSecondaryAmount = getAmount(secondaryAsset.denom, account.deposits)
const primaryPrice = usePrice(primaryAsset.denom)
const secondaryPrice = usePrice(secondaryAsset.denom)
const availablePrimaryAmount = getAmount(props.primaryAsset.denom, props.account.deposits)
const availableSecondaryAmount = getAmount(props.secondaryAsset.denom, props.account.deposits)
const primaryPrice = usePrice(props.primaryAsset.denom)
const secondaryPrice = usePrice(props.secondaryAsset.denom)
const primaryCoin = useMemo(() => {
const amount = findCoinByDenom(primaryAsset.denom, deposits)?.amount.toString() || '0'
return new BNCoin({ denom: primaryAsset.denom, amount })
}, [deposits, primaryAsset.denom])
const secondaryCoin = useMemo(() => {
const amount = findCoinByDenom(secondaryAsset.denom, deposits)?.amount.toString() || '0'
return new BNCoin({ denom: secondaryAsset.denom, amount })
}, [deposits, secondaryAsset.denom])
const primaryValue = useMemo(
() => props.primaryAmount.multipliedBy(primaryPrice),
[props.primaryAmount, primaryPrice],
() => primaryCoin.amount.multipliedBy(primaryPrice),
[primaryCoin, primaryPrice],
)
const secondaryValue = useMemo(
() => props.secondaryAmount.multipliedBy(secondaryPrice),
[props.secondaryAmount, secondaryPrice],
() => secondaryCoin.amount.multipliedBy(secondaryPrice),
[secondaryCoin, secondaryPrice],
)
const totalValue = useMemo(
() => primaryValue.plus(secondaryValue),
@ -71,7 +80,9 @@ export default function VaultDeposit(props: Props) {
)
const primaryMax = useMemo(
() =>
props.isCustomRatio ? availablePrimaryAmount : maxAssetValueNonCustom.dividedBy(primaryPrice),
props.isCustomRatio
? availablePrimaryAmount
: maxAssetValueNonCustom.dividedBy(primaryPrice).integerValue(),
[props.isCustomRatio, availablePrimaryAmount, primaryPrice, maxAssetValueNonCustom],
)
const secondaryMax = useMemo(
@ -92,8 +103,9 @@ export default function VaultDeposit(props: Props) {
function handleSwitch() {
const isCustomRatioNew = !props.isCustomRatio
if (!isCustomRatioNew) {
props.onChangePrimaryAmount(BN(0))
props.onChangeSecondaryAmount(BN(0))
primaryCoin.amount = BN(0)
secondaryCoin.amount = BN(0)
onChangeDeposits([primaryCoin, secondaryCoin])
setPercentage(0)
}
props.onChangeIsCustomRatio(isCustomRatioNew)
@ -103,28 +115,33 @@ export default function VaultDeposit(props: Props) {
if (amount.isGreaterThan(primaryMax)) {
amount = primaryMax
}
props.onChangePrimaryAmount(amount)
primaryCoin.amount = amount
setPercentage(amount.dividedBy(primaryMax).multipliedBy(100).decimalPlaces(0).toNumber())
if (!props.isCustomRatio) {
props.onChangeSecondaryAmount(secondaryMax.multipliedBy(amount.dividedBy(primaryMax)))
secondaryCoin.amount = secondaryMax.multipliedBy(amount.dividedBy(primaryMax)).integerValue()
}
onChangeDeposits([primaryCoin, secondaryCoin])
}
function onChangeSecondaryDeposit(amount: BigNumber) {
if (amount.isGreaterThan(secondaryMax)) {
amount = secondaryMax
}
props.onChangeSecondaryAmount(amount)
secondaryCoin.amount = amount
setPercentage(amount.dividedBy(secondaryMax).multipliedBy(100).decimalPlaces(0).toNumber())
if (!props.isCustomRatio) {
props.onChangePrimaryAmount(primaryMax.multipliedBy(amount.dividedBy(secondaryMax)))
primaryCoin.amount = primaryMax.multipliedBy(amount.dividedBy(secondaryMax)).integerValue()
}
onChangeDeposits([primaryCoin, secondaryCoin])
}
function onChangeSlider(value: number) {
setPercentage(value)
props.onChangePrimaryAmount(primaryMax.multipliedBy(value / 100))
props.onChangeSecondaryAmount(secondaryMax.multipliedBy(value / 100))
primaryCoin.amount = primaryMax.multipliedBy(value / 100).integerValue()
secondaryCoin.amount = secondaryMax.multipliedBy(value / 100).integerValue()
onChangeDeposits([primaryCoin, secondaryCoin])
}
function getWarningText(asset: Asset) {
@ -137,7 +154,7 @@ export default function VaultDeposit(props: Props) {
<div className='flex flex-col items-center justify-between gap-1 pb-[30px] pt-2'>
<Gauge
percentage={primaryValuePercentage}
tooltip={`${primaryValuePercentage}% of value is ${props.primaryAsset.symbol}`}
tooltip={`${primaryValuePercentage}% of value is ${primaryAsset.symbol}`}
labelClassName='text-martian-red'
diameter={32}
strokeColor='#FF645F'
@ -146,7 +163,7 @@ export default function VaultDeposit(props: Props) {
<div className='h-full w-[1px] rounded-xl bg-white/10'></div>
<Gauge
percentage={secondaryValuePercentage}
tooltip={`${secondaryValuePercentage}% of value is ${props.secondaryAsset.symbol}`}
tooltip={`${secondaryValuePercentage}% of value is ${secondaryAsset.symbol}`}
labelClassName='text-martian-red'
diameter={32}
strokeColor='#FF645F'
@ -156,13 +173,11 @@ export default function VaultDeposit(props: Props) {
<div className='flex h-full flex-1 flex-col justify-between gap-6'>
<TokenInput
onChange={onChangePrimaryDeposit}
amount={props.primaryAmount}
amount={primaryCoin.amount}
max={availablePrimaryAmount}
maxText='Balance'
asset={props.primaryAsset}
warning={
availablePrimaryAmount.isZero() ? getWarningText(props.primaryAsset) : undefined
}
asset={primaryAsset}
warning={availablePrimaryAmount.isZero() ? getWarningText(primaryAsset) : undefined}
disabled={disableInput}
/>
{!props.isCustomRatio && (
@ -170,13 +185,11 @@ export default function VaultDeposit(props: Props) {
)}
<TokenInput
onChange={onChangeSecondaryDeposit}
amount={props.secondaryAmount}
amount={secondaryCoin.amount}
max={availableSecondaryAmount}
maxText='Balance'
asset={props.secondaryAsset}
warning={
availableSecondaryAmount.isZero() ? getWarningText(props.secondaryAsset) : undefined
}
asset={secondaryAsset}
warning={availableSecondaryAmount.isZero() ? getWarningText(secondaryAsset) : undefined}
disabled={disableInput}
/>
</div>
@ -205,7 +218,7 @@ export default function VaultDeposit(props: Props) {
<Switch checked={props.isCustomRatio} onChange={handleSwitch} name='customRatio' />
</div>
<div className='flex justify-between'>
<Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Deposit Value`}</Text>
<Text className='text-white/50'>{`${primaryAsset.symbol}-${secondaryAsset.symbol} Deposit Value`}</Text>
<DisplayCurrency
coin={new BNCoin({ denom: baseCurrency.denom, amount: totalValue.toString() })}
/>

View File

@ -1,5 +1,5 @@
import BigNumber from 'bignumber.js'
import { useCallback, useMemo, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import Accordion from 'components/Accordion'
import AccountSummary from 'components/Account/AccountSummary'
@ -8,9 +8,9 @@ import VaultBorrowingsSubTitle from 'components/Modals/Vault/VaultBorrowingsSubT
import VaultDeposit from 'components/Modals/Vault/VaultDeposits'
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
import useIsOpenArray from 'hooks/useIsOpenArray'
import useUpdateAccount from 'hooks/useUpdateAccount'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers'
import useDepositVault from 'hooks/broadcast/useDepositVault'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
interface Props {
vault: Vault | DepositedVault
@ -21,35 +21,30 @@ interface Props {
}
export default function VaultModalContent(props: Props) {
const { updatedAccount, onChangeBorrowings, borrowings } = useUpdateAccount(
props.account,
props.vault,
)
const { addDebt, removeDeposits, addedDebt, removedDeposits, updatedAccount, addVaultValues } =
useUpdatedAccount(props.account)
const [isOpen, toggleOpen] = useIsOpenArray(2, false)
const [primaryAmount, setPrimaryAmount] = useState<BigNumber>(BN(0))
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 {
actions: depositActions,
fee: depositFee,
totalValue,
} = useDepositVault({
vault: props.vault,
deposits: removedDeposits,
borrowings: addedDebt,
})
const onChangePrimaryAmount = useCallback(
(amount: BigNumber) => setPrimaryAmount(amount.decimalPlaces(0)),
[setPrimaryAmount],
)
const onChangeSecondaryAmount = useCallback(
(amount: BigNumber) => setSecondaryAmount(amount.decimalPlaces(0)),
[setSecondaryAmount],
)
useEffect(() => {
addVaultValues([
{
address: props.vault.address,
value: totalValue,
},
])
}, [totalValue, addVaultValues, props.vault.address])
const onChangeIsCustomRatio = useCallback(
(isCustomRatio: boolean) => setIsCustomRatio(isCustomRatio),
@ -64,8 +59,12 @@ export default function VaultModalContent(props: Props) {
return (
<VaultDepositSubTitle
primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
primaryAmount={
removedDeposits.find((coin) => coin.denom === props.primaryAsset.denom)?.amount || BN(0)
}
secondaryAmount={
removedDeposits.find((coin) => coin.denom === props.secondaryAsset.denom)?.amount || BN(0)
}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
/>
@ -78,7 +77,7 @@ export default function VaultModalContent(props: Props) {
if (isOpen[1]) return null
return <VaultBorrowingsSubTitle borrowings={borrowings} />
return <VaultBorrowingsSubTitle borrowings={addedDebt} />
}
return (
@ -89,10 +88,8 @@ export default function VaultModalContent(props: Props) {
{
renderContent: () => (
<VaultDeposit
primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
onChangePrimaryAmount={onChangePrimaryAmount}
onChangeSecondaryAmount={onChangeSecondaryAmount}
deposits={removedDeposits}
onChangeDeposits={removeDeposits}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
account={props.account}
@ -109,15 +106,15 @@ export default function VaultModalContent(props: Props) {
{
renderContent: () => (
<VaultBorrowings
account={updatedAccount}
borrowings={borrowings}
primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
updatedAccount={updatedAccount}
borrowings={addedDebt}
deposits={removedDeposits}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
onChangeBorrowings={onChangeBorrowings}
deposits={deposits}
onChangeBorrowings={addDebt}
vault={props.vault}
depositActions={depositActions}
depositFee={depositFee}
/>
),
title: 'Borrow',

View File

@ -108,8 +108,8 @@ export const ASSETS: Asset[] = [
logo: '/tokens/axlusdc.svg',
decimals: 6,
hasOraclePrice: true,
isEnabled: true,
isMarket: true,
isEnabled: !IS_TESTNET,
isMarket: !IS_TESTNET,
isDisplayCurrency: true,
isStable: true,
poolId: 678,
@ -132,4 +132,19 @@ export const ASSETS: Asset[] = [
isStable: true,
pythPriceFeedId: 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a',
},
{
symbol: 'gamm/pool/6',
name: 'OSMO-USDC.n Pool Token',
id: 'gamm/pool/6',
denom: 'gamm/pool/6',
color: '',
logo: '',
decimals: 6,
hasOraclePrice: true,
isEnabled: true,
isMarket: false,
isDisplayCurrency: false,
isStable: false,
forceFetchPrice: true,
},
]

View File

@ -1,3 +1,6 @@
import { VaultStatus } from 'types/enums/vault'
import { BN } from 'utils/helpers'
export const VAULT_DEPOSIT_BUFFER = 0.999
export const TESTNET_VAULTS_META_DATA: VaultMetaData[] = [
@ -13,6 +16,7 @@ export const TESTNET_VAULTS_META_DATA: VaultMetaData[] = [
primary: 'uosmo',
secondary: 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4',
lp: 'gamm/pool/6',
vault: 'factory/osmo1q40xvrzpldwq5he4ftsf7zm2jf80tj373qaven38yqrvhex8r9rs8n94kv/cwVTT',
},
symbols: {
primary: 'OSMO',
@ -32,6 +36,7 @@ export const TESTNET_VAULTS_META_DATA: VaultMetaData[] = [
primary: 'uosmo',
secondary: 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4',
lp: 'gamm/pool/6',
vault: 'factory/osmo14lu7m4ganxs20258dazafrjfaulmfxruq9n0r0th90gs46jk3tuqwfkqwn/cwVTT',
},
symbols: {
primary: 'OSMO',
@ -51,6 +56,7 @@ export const TESTNET_VAULTS_META_DATA: VaultMetaData[] = [
primary: 'uosmo',
secondary: 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4',
lp: 'gamm/pool/6',
vault: 'factory/osmo1fmq9hw224fgz8lk48wyd0gfg028kvvzggt6c3zvnaqkw23x68cws5nd5em/cwVTT',
},
symbols: {
primary: 'OSMO',
@ -74,6 +80,7 @@ export const VAULTS_META_DATA: VaultMetaData[] = [
primary: 'uosmo',
secondary: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
lp: 'gamm/pool/1',
vault: 'factory/osmo1g3kmqpp8608szfp0pdag3r6z85npph7wmccat8lgl3mp407kv73qlj7qwp/cwVTT',
},
symbols: {
primary: 'OSMO',
@ -82,3 +89,28 @@ export const VAULTS_META_DATA: VaultMetaData[] = [
isFeatured: true,
},
]
export const MOCK_DEPOSITED_VAULT_POSITION = {
values: {
primary: BN(0),
secondary: BN(0),
},
amounts: {
primary: BN(0),
secondary: BN(0),
locked: BN(0),
unlocked: BN(0),
unlocking: BN(0),
},
status: VaultStatus.ACTIVE,
apy: null,
ltv: {
liq: 0,
max: 0,
},
cap: {
denom: '',
max: BN(0),
used: BN(0),
},
}

View File

@ -21,7 +21,12 @@ interface Props {
deposits: BNCoin[]
borrowings: BNCoin[]
}
export default function useDepositVault(props: Props): { actions: Action[]; fee: StdFee } {
export default function useDepositVault(props: Props): {
actions: Action[]
fee: StdFee
minLpToReceive: string
totalValue: BigNumber
} {
const [minLpToReceive, setMinLpToReceive] = useState<BigNumber>(BN(0))
const { data: prices } = usePrices()
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
@ -74,7 +79,8 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
])
const enterVaultActions: Action[] = useMemo(() => {
if (primaryCoin.amount.isZero() || secondaryCoin.amount.isZero()) return []
if (primaryCoin.amount.isZero() || secondaryCoin.amount.isZero() || minLpToReceive.isZero())
return []
return getEnterVaultActions(props.vault, primaryCoin, secondaryCoin, minLpToReceive)
}, [props.vault, primaryCoin, secondaryCoin, minLpToReceive])
@ -84,5 +90,10 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
[borrowActions, swapActions, enterVaultActions],
)
return { actions, fee: hardcodedFee }
return {
actions,
fee: hardcodedFee,
minLpToReceive: minLpToReceive.toString(),
totalValue,
}
}

9
src/hooks/useAccount.tsx Normal file
View File

@ -0,0 +1,9 @@
import useSWR from 'swr'
import getAccount from 'api/accounts/getAccount'
export default function useAccounts(accountId?: string) {
return useSWR(`account${accountId}`, () => getAccount(accountId || ''), {
refreshInterval: 30000,
})
}

View File

@ -1,10 +0,0 @@
import useSWR from 'swr'
import getAccountDebts from 'api/accounts/getAccountDebts'
export default function useAccountDebts(accountId?: string) {
return useSWR(`accountDebts${accountId}`, () => getAccountDebts(accountId || ''), {
suspense: true,
isPaused: () => !accountId,
})
}

View File

@ -0,0 +1,9 @@
import useSWR from 'swr'
import getAssetParams from 'api/params/getAssetParams'
export default function useAssetParams() {
return useSWR('assetParams', getAssetParams, {
fallbackData: [],
})
}

View File

@ -0,0 +1,157 @@
import { useCallback, useMemo } from 'react'
import usePrices from 'hooks/usePrices'
import useAssetParams from 'hooks/useAssetParams'
import {
AssetParamsBaseForAddr,
HealthComputer,
} from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
import { VaultConfigBaseForString } from 'types/generated/mars-params/MarsParams.types'
import useVaultConfigs from 'hooks/useVaultConfigs'
import {
compute_health_js,
max_borrow_estimate_js,
max_withdraw_estimate_js,
} from 'utils/health_computer'
import { convertAccountToPositions } from 'utils/accounts'
import { VaultPositionValue } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import useStore from 'store'
export default function useHealthComputer(account: Account) {
const { data: prices } = usePrices()
const { data: assetParams } = useAssetParams()
const { data: vaultConfigs } = useVaultConfigs()
const baseCurrency = useStore((s) => s.baseCurrency)
const positions = useMemo(() => convertAccountToPositions(account), [account])
const baseCurrencyPrice = useMemo(
() => prices.find((price) => price.denom === baseCurrency.denom)?.amount || 0,
[prices, baseCurrency.denom],
)
const vaultPositionValues = useMemo(
() =>
account.vaults.reduce((prev, curr) => {
const baseCoinPrice = prices.find((price) => price.denom === curr.denoms.lp)?.amount || 0
prev[curr.address] = {
base_coin: {
amount: '0', // Not used by healthcomputer
denom: curr.denoms.lp,
value: curr.amounts.unlocking.times(baseCoinPrice).integerValue().toString(),
},
vault_coin: {
amount: '0', // Not used by healthcomputer
denom: curr.denoms.vault,
value: curr.values.primary
.div(baseCurrencyPrice)
.plus(curr.values.secondary.div(baseCurrencyPrice))
.integerValue()
.toString(),
},
}
return prev
}, {} as { [key: string]: VaultPositionValue }),
[account.vaults, prices, baseCurrencyPrice],
)
const priceData = useMemo(() => {
const baseCurrencyPrice =
prices.find((price) => price.denom === baseCurrency.denom)?.amount || 0
return prices.reduce((prev, curr) => {
prev[curr.denom] = curr.amount.div(baseCurrencyPrice).decimalPlaces(18).toString()
return prev
}, {} as { [key: string]: string })
}, [prices, baseCurrency.denom])
const denomsData = useMemo(
() =>
assetParams.reduce((prev, curr) => {
const params: AssetParamsBaseForAddr = {
...curr,
// The following overrides are required as testnet is 'broken' and new contracts are not updated yet
// These overrides are not used by the healthcomputer internally, so they're not important anyways.
protocol_liquidation_fee: '1',
liquidation_bonus: {
max_lb: '1',
min_lb: '1',
slope: '1',
starting_lb: '1',
},
}
prev[params.denom] = params
return prev
}, {} as { [key: string]: AssetParamsBaseForAddr }),
[assetParams],
)
const vaultConfigsData = useMemo(() => {
if (!positions || !vaultConfigs.length) return null
const vaultPositionDenoms = positions.vaults.map((vault) => vault.vault.address)
return vaultConfigs
.filter((config) => vaultPositionDenoms.includes(config.addr))
.reduce((prev, curr) => {
prev[curr.addr] = curr
return prev
}, {} as { [key: string]: VaultConfigBaseForString })
}, [vaultConfigs, positions])
const healthComputer: HealthComputer | null = useMemo(() => {
if (
!positions ||
!vaultPositionValues ||
!vaultConfigsData ||
Object.keys(denomsData).length === 0 ||
Object.keys(priceData).length === 0 ||
positions.vaults.length !== Object.keys(vaultPositionValues).length
)
return null
return {
denoms_data: { params: denomsData, prices: priceData },
vaults_data: {
vault_configs: vaultConfigsData,
vault_values: vaultPositionValues,
},
positions: positions,
kind: 'default',
}
}, [priceData, denomsData, vaultConfigsData, vaultPositionValues, positions])
const computeHealth = useCallback(() => {
async function callComputeHealthWasmFn(): Promise<number> {
if (!healthComputer) return 0
return Number((await compute_health_js(healthComputer)).max_ltv_health_factor) || 0
}
return callComputeHealthWasmFn()
}, [healthComputer])
const computeMaxBorrowAmount = useCallback(
(denom: string) => {
async function callMaxBorrowWasmFn(denom: string): Promise<number> {
if (!healthComputer) return 0
return await max_borrow_estimate_js(healthComputer, denom)
}
return callMaxBorrowWasmFn(denom)
},
[healthComputer],
)
const computeMaxWithdrawAmount = useCallback(
(denom: string) => {
async function callMaxWithdrawWasmFn(denom: string): Promise<number> {
if (!healthComputer) return 0
return await max_withdraw_estimate_js(healthComputer, denom)
}
return callMaxWithdrawWasmFn(denom)
},
[healthComputer],
)
return { computeHealth, computeMaxBorrowAmount, computeMaxWithdrawAmount }
}

View File

@ -1,56 +0,0 @@
import BigNumber from 'bignumber.js'
import { useCallback, useState } from 'react'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers'
export default function useUpdateAccount(account: Account, vault: Vault) {
const [updatedAccount, setUpdatedAccount] = useState<Account>(account)
const [borrowings, setBorrowings] = useState<BNCoin[]>([])
function getCoin(denom: string, amount: BigNumber): Coin {
return {
denom,
amount: amount.decimalPlaces(0).toString(),
}
}
const onChangeBorrowings = useCallback(
(borrowings: BNCoin[]) => {
const debts: Coin[] = [...account.debts]
const deposits: Coin[] = [...account.deposits]
const currentDebtDenoms = debts.map((debt) => debt.denom)
const currentDepositDenoms = deposits.map((deposit) => deposit.denom)
borrowings.map((coin) => {
if (coin.amount.isZero()) return
if (currentDebtDenoms.includes(coin.denom)) {
const index = currentDebtDenoms.indexOf(coin.denom)
const newAmount = BN(debts[index].amount).plus(coin.amount)
debts[index] = getCoin(coin.denom, newAmount)
} else {
debts.push(coin.toCoin())
}
if (currentDepositDenoms.includes(coin.denom)) {
const index = currentDepositDenoms.indexOf(coin.denom)
const newAmount = BN(deposits[index].amount).plus(coin.amount)
deposits[index] = getCoin(coin.denom, newAmount)
} else {
deposits.push(coin.toCoin())
}
})
setBorrowings(borrowings)
setUpdatedAccount({
...account,
debts,
deposits,
})
},
[account],
)
return { borrowings, updatedAccount, onChangeBorrowings }
}

View File

@ -0,0 +1,69 @@
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers'
import { VaultValue } from 'hooks/useUpdatedAccount'
import { getVaultMetaData } from 'utils/vaults'
import { MOCK_DEPOSITED_VAULT_POSITION } from 'constants/vaults'
export function addCoins(additionalCoins: BNCoin[], currentCoins: BNCoin[]) {
const currentDenoms = currentCoins.map((coin) => coin.denom)
additionalCoins.forEach((coin) => {
if (coin.amount.isZero()) return
if (currentDenoms.includes(coin.denom)) {
const index = currentDenoms.indexOf(coin.denom)
currentCoins[index].amount = BN(currentCoins[index].amount).plus(coin.amount)
} else {
currentCoins.push(coin)
}
})
return currentCoins
}
export function removeCoins(coinsToRemove: BNCoin[], currentCoins: BNCoin[]) {
const currentDenoms = currentCoins.map((coin) => coin.denom)
coinsToRemove.forEach((coin) => {
if (coin.amount.isZero()) return
if (!currentDenoms.includes(coin.denom)) return
const index = currentDenoms.indexOf(coin.denom)
currentCoins[index].amount = BN(currentCoins[index].amount).minus(coin.amount)
})
return currentCoins
}
export function addValueToVaults(
vaultValues: VaultValue[],
vaults: DepositedVault[],
): DepositedVault[] {
const currentVaultAddresses = vaults.map((vault) => vault.address)
vaultValues.forEach((vaultValue) => {
if (vaultValue.value.isZero()) return
const halfValue = vaultValue.value.div(2)
if (currentVaultAddresses.includes(vaultValue.address)) {
const index = currentVaultAddresses.indexOf(vaultValue.address)
vaults[index].values.primary = BN(vaults[index].values.primary).plus(halfValue)
vaults[index].values.secondary = BN(vaults[index].values.secondary).plus(halfValue)
} else {
const vaultMetaData = getVaultMetaData(vaultValue.address)
if (!vaultMetaData) return
vaults.push({
...vaultMetaData,
...MOCK_DEPOSITED_VAULT_POSITION,
values: {
primary: halfValue,
secondary: halfValue,
},
})
}
})
return vaults
}

View File

@ -0,0 +1,46 @@
import { useEffect, useState } from 'react'
import { BNCoin } from 'types/classes/BNCoin'
import { addCoins, addValueToVaults, removeCoins } from 'hooks/useUpdatedAccount/functions'
import { cloneAccount } from 'utils/accounts'
export interface VaultValue {
address: string
value: BigNumber
}
export function useUpdatedAccount(account: Account) {
const [updatedAccount, setUpdatedAccount] = useState<Account>(cloneAccount(account))
const [addedDeposits, addDeposits] = useState<BNCoin[]>([])
const [removedDeposits, removeDeposits] = useState<BNCoin[]>([])
const [addedDebt, addDebt] = useState<BNCoin[]>([])
const [removedDebt, removeDebt] = useState<BNCoin[]>([])
const [addedVaultValues, addVaultValues] = useState<VaultValue[]>([])
useEffect(() => {
async function updateAccount() {
const accountCopy = cloneAccount(account)
accountCopy.deposits = addCoins(addedDeposits, [...accountCopy.deposits])
accountCopy.debts = addCoins(addedDebt, [...accountCopy.debts])
accountCopy.vaults = addValueToVaults(addedVaultValues, [...accountCopy.vaults])
accountCopy.deposits = removeCoins(removedDeposits, [...accountCopy.deposits])
accountCopy.debts = removeCoins(removedDebt, [...accountCopy.debts])
setUpdatedAccount(accountCopy)
}
updateAccount()
}, [account, addedDebt, removedDebt, addedDeposits, removedDeposits, addedVaultValues])
return {
updatedAccount,
addDeposits,
removeDeposits,
addDebt,
removeDebt,
addVaultValues,
addedDeposits,
addedDebt,
removedDeposits,
removedDebt,
}
}

View File

@ -0,0 +1,9 @@
import useSWR from 'swr'
import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
export default function useVaultConfigs() {
return useSWR('vaultConfigs', getVaultConfigs, {
fallbackData: [],
})
}

View File

@ -2,6 +2,7 @@ import { AppProps } from 'next/app'
import { useEffect, useState } from 'react'
import DefaultPageHead from 'components/DefaultPageHead'
import init from 'utils/health_computer'
import 'react-toastify/dist/ReactToastify.min.css'
import 'styles/globals.css'
@ -9,6 +10,14 @@ import 'styles/globals.css'
export default function App({ Component, pageProps }: AppProps) {
const PageComponent = Component as any
const [isServer, setIsServer] = useState(true)
useEffect(() => {
const loadHealthComputerWasm = async () => {
await init()
}
loadHealthComputerWasm()
}, [])
useEffect(() => {
setIsServer(false)
}, [])

View File

@ -47,8 +47,9 @@ export interface DenomsData {
export interface AssetParamsBaseForAddr {
credit_manager: CmSettingsForAddr
denom: string
liquidation_bonus: Decimal
liquidation_bonus: LiquidationBonus
liquidation_threshold: Decimal
protocol_liquidation_fee: Decimal
max_loan_to_value: Decimal
red_bank: RedBankSettings
}
@ -128,3 +129,10 @@ export interface CoinValue {
denom: string
value: Uint128
}
export interface LiquidationBonus {
max_lb: Decimal
min_lb: Decimal
slope: Decimal
starting_lb: Decimal
}

View File

@ -3,14 +3,14 @@ interface Account extends AccountChange {
deposits: BNCoin[]
debts: BNCoin[]
lends: BNCoin[]
vaults: import('types/generated/mars-mock-credit-manager/MarsMockCreditManager.types').ArrayOfVaultInfoResponse
vaults: DepositedVault[]
}
interface AccountChange {
deposits?: BNCoin[]
debts?: BNCoin[]
lends?: BNCoin[]
vaults?: import('types/generated/mars-mock-credit-manager/MarsMockCreditManager.types').ArrayOfVaultInfoResponse
vaults?: DepositedVault[]
}
interface AccountBalanceRow {

View File

@ -2,8 +2,26 @@ interface Asset {
color: string
name: string
denom: string
symbol: 'OSMO' | 'ATOM' | 'MARS' | 'stATOM' | 'USDC.axl' | 'USDC.n' | 'WBTC.axl' | 'WETH.axl'
id: 'OSMO' | 'ATOM' | 'MARS' | 'stATOM' | 'axlUSDC' | 'axlWBTC' | 'axlWETH' | 'nUSDC'
symbol:
| 'OSMO'
| 'ATOM'
| 'MARS'
| 'stATOM'
| 'USDC.axl'
| 'USDC.n'
| 'WBTC.axl'
| 'WETH.axl'
| 'gamm/pool/6'
id:
| 'OSMO'
| 'ATOM'
| 'MARS'
| 'stATOM'
| 'axlUSDC'
| 'axlWBTC'
| 'axlWETH'
| 'nUSDC'
| 'gamm/pool/6'
prefix?: string
contract_addr?: string
logo: string

View File

@ -11,6 +11,7 @@ interface VaultMetaData {
primary: string
secondary: string
lp: string
vault: string
}
symbols: {
primary: string

View File

@ -1,8 +1,11 @@
import BigNumber from 'bignumber.js'
import {
Positions,
VaultPosition,
} from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { BN } from 'utils/helpers'
import { BNCoin } from 'types/classes/BNCoin'
import { BN, getApproximateHourlyInterest } from 'utils/helpers'
import { getTokenValue } from 'utils/tokens'
export const calculateAccountBalance = (
account: Account | AccountChange,
@ -25,6 +28,7 @@ export const calculateAccountDeposits = (
return acc.plus(depositValue)
}, BN(0))
}
export const calculateAccountDebt = (
account: Account | AccountChange,
prices: BNCoin[],
@ -63,34 +67,73 @@ export function getAmount(denom: string, coins: Coin[]): BigNumber {
return BN(coins.find((asset) => asset.denom === denom)?.amount ?? 0)
}
export function getNetCollateralValue(account: Account, marketAssets: Market[], prices: BNCoin[]) {
const depositCollateralValue = account.deposits.reduce((acc, coin) => {
const asset = marketAssets.find((asset) => asset.denom === coin.denom)
if (!asset) return acc
const marketValue = BN(getTokenValue(coin, prices))
const collateralValue = marketValue.multipliedBy(asset.maxLtv)
return collateralValue.plus(acc)
}, BN(0))
// Implement Vault Collateral calculation (MP-2915)
const liabilitiesValue = account.debts.reduce((acc, coin) => {
const asset = marketAssets.find((asset) => asset.denom === coin.denom)
if (!asset) return acc
const estimatedInterestAmount = getApproximateHourlyInterest(coin.amount, asset.borrowRate)
const liability = BN(getTokenValue(coin, prices)).plus(estimatedInterestAmount)
return liability.plus(acc)
}, BN(0))
if (liabilitiesValue.isGreaterThan(depositCollateralValue)) {
return BN(0)
export function convertAccountToPositions(account: Account): Positions {
return {
account_id: account.id,
debts: account.debts.map((debt) => ({
shares: '0', // This is not needed, but required by the contract
amount: debt.amount.toString(),
denom: debt.denom,
})),
deposits: account.deposits.map((deposit) => deposit.toCoin()),
lends: account.lends.map((lend) => ({
shares: '0', // This is not needed, but required by the contract
amount: lend.amount.toString(),
denom: lend.denom,
})),
vaults: account.vaults.map(
(vault) =>
({
vault: {
address: vault.address,
},
amount: {
locking: {
locked: vault.amounts.locked.toString(),
unlocking: [
{
id: 0,
coin: { amount: vault.amounts.unlocking.toString(), denom: vault.denoms.lp },
},
],
},
},
} as VaultPosition),
),
}
}
export function cloneAccount(account: Account): Account {
return {
id: account.id,
debts: account.debts.map(
(debt) =>
new BNCoin({
amount: debt.amount.toString(),
denom: debt.denom,
}),
),
deposits: account.deposits.map((deposit) => new BNCoin(deposit.toCoin())),
lends: account.lends.map(
(lend) =>
new BNCoin({
amount: lend.amount.toString(),
denom: lend.denom,
}),
),
vaults: account.vaults.map((vault) => ({
...vault,
amounts: {
locked: BN(vault.amounts.locked),
unlocking: BN(vault.amounts.unlocking),
unlocked: BN(vault.amounts.unlocked),
primary: BN(vault.amounts.primary),
secondary: BN(vault.amounts.secondary),
},
values: {
primary: BN(vault.values.primary),
secondary: BN(vault.values.secondary),
},
})),
}
return depositCollateralValue.minus(liabilitiesValue)
}

60
src/utils/health_computer/index.d.ts vendored Normal file
View File

@ -0,0 +1,60 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param {any} health_computer
* @returns {any}
*/
export function compute_health_js(health_computer: any): any
/**
* @param {any} health_computer
* @param {any} withdraw_denom
* @returns {HealthResponse}
*/
export function max_withdraw_estimate_js(health_computer: any, withdraw_denom: any): any
/**
* @param {any} health_computer
* @param {any} borrow_denom
* @returns {any}
*/
export function max_borrow_estimate_js(health_computer: any, borrow_denom: any): any
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module
export interface InitOutput {
readonly memory: WebAssembly.Memory
readonly compute_health_js: (a: number) => number
readonly max_withdraw_estimate_js: (a: number, b: number) => number
readonly max_borrow_estimate_js: (a: number, b: number) => number
readonly allocate: (a: number) => number
readonly deallocate: (a: number) => void
readonly requires_stargate: () => void
readonly requires_iterator: () => void
readonly interface_version_8: () => void
readonly __wbindgen_malloc: (a: number, b: number) => number
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number
readonly __wbindgen_free: (a: number, b: number, c: number) => void
readonly __wbindgen_exn_store: (a: number) => void
}
export type SyncInitInput = BufferSource | WebAssembly.Module
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {SyncInitInput} module
*
* @returns {InitOutput}
*/
export function initSync(module: SyncInitInput): InitOutput
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init(
module_or_path?: InitInput | Promise<InitInput>,
): Promise<InitOutput>

View File

@ -0,0 +1,568 @@
let wasm
const heap = new Array(128).fill(undefined)
heap.push(undefined, null, true, false)
function getObject(idx) {
return heap[idx]
}
let heap_next = heap.length
function dropObject(idx) {
if (idx < 132) return
heap[idx] = heap_next
heap_next = idx
}
function takeObject(idx) {
const ret = getObject(idx)
dropObject(idx)
return ret
}
let WASM_VECTOR_LEN = 0
let cachedUint8Memory0 = null
function getUint8Memory0() {
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer)
}
return cachedUint8Memory0
}
const cachedTextEncoder =
typeof TextEncoder !== 'undefined'
? new TextEncoder('utf-8')
: {
encode: () => {
throw Error('TextEncoder not available')
},
}
const encodeString =
typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view)
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg)
view.set(buf)
return {
read: arg.length,
written: buf.length,
}
}
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg)
const ptr = malloc(buf.length, 1) >>> 0
getUint8Memory0()
.subarray(ptr, ptr + buf.length)
.set(buf)
WASM_VECTOR_LEN = buf.length
return ptr
}
let len = arg.length
let ptr = malloc(len, 1) >>> 0
const mem = getUint8Memory0()
let offset = 0
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset)
if (code > 0x7f) break
mem[ptr + offset] = code
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset)
}
ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0
const view = getUint8Memory0().subarray(ptr + offset, ptr + len)
const ret = encodeString(arg, view)
offset += ret.written
}
WASM_VECTOR_LEN = offset
return ptr
}
function isLikeNone(x) {
return x === undefined || x === null
}
let cachedInt32Memory0 = null
function getInt32Memory0() {
if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer)
}
return cachedInt32Memory0
}
const cachedTextDecoder =
typeof TextDecoder !== 'undefined'
? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true })
: {
decode: () => {
throw Error('TextDecoder not available')
},
}
if (typeof TextDecoder !== 'undefined') {
cachedTextDecoder.decode()
}
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len))
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1)
const idx = heap_next
heap_next = heap[idx]
heap[idx] = obj
return idx
}
let cachedFloat64Memory0 = null
function getFloat64Memory0() {
if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {
cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer)
}
return cachedFloat64Memory0
}
let cachedBigInt64Memory0 = null
function getBigInt64Memory0() {
if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {
cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer)
}
return cachedBigInt64Memory0
}
function debugString(val) {
// primitive types
const type = typeof val
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`
}
if (type == 'string') {
return `"${val}"`
}
if (type == 'symbol') {
const description = val.description
if (description == null) {
return 'Symbol'
} else {
return `Symbol(${description})`
}
}
if (type == 'function') {
const name = val.name
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`
} else {
return 'Function'
}
}
// objects
if (Array.isArray(val)) {
const length = val.length
let debug = '['
if (length > 0) {
debug += debugString(val[0])
}
for (let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i])
}
debug += ']'
return debug
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val))
let className
if (builtInMatches.length > 1) {
className = builtInMatches[1]
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val)
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')'
} catch (_) {
return 'Object'
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className
}
/**
* @param {any} health_computer
* @returns {any}
*/
export function compute_health_js(health_computer) {
const ret = wasm.compute_health_js(addHeapObject(health_computer))
return takeObject(ret)
}
/**
* @param {any} health_computer
* @param {any} withdraw_denom
* @returns {any}
*/
export function max_withdraw_estimate_js(health_computer, withdraw_denom) {
const ret = wasm.max_withdraw_estimate_js(
addHeapObject(health_computer),
addHeapObject(withdraw_denom),
)
return takeObject(ret)
}
/**
* @param {any} health_computer
* @param {any} borrow_denom
* @returns {any}
*/
export function max_borrow_estimate_js(health_computer, borrow_denom) {
const ret = wasm.max_borrow_estimate_js(
addHeapObject(health_computer),
addHeapObject(borrow_denom),
)
return takeObject(ret)
}
function handleError(f, args) {
try {
return f.apply(this, args)
} catch (e) {
wasm.__wbindgen_exn_store(addHeapObject(e))
}
}
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports)
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn(
'`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n',
e,
)
} else {
throw e
}
}
}
const bytes = await module.arrayBuffer()
return await WebAssembly.instantiate(bytes, imports)
} else {
const instance = await WebAssembly.instantiate(module, imports)
if (instance instanceof WebAssembly.Instance) {
return { instance, module }
} else {
return instance
}
}
}
function __wbg_get_imports() {
const imports = {}
imports.wbg = {}
imports.wbg.__wbindgen_object_drop_ref = function (arg0) {
takeObject(arg0)
}
imports.wbg.__wbindgen_is_object = function (arg0) {
const val = getObject(arg0)
const ret = typeof val === 'object' && val !== null
return ret
}
imports.wbg.__wbindgen_is_undefined = function (arg0) {
const ret = getObject(arg0) === undefined
return ret
}
imports.wbg.__wbindgen_in = function (arg0, arg1) {
const ret = getObject(arg0) in getObject(arg1)
return ret
}
imports.wbg.__wbindgen_string_get = function (arg0, arg1) {
const obj = getObject(arg1)
const ret = typeof obj === 'string' ? obj : undefined
var ptr1 = isLikeNone(ret)
? 0
: passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
var len1 = WASM_VECTOR_LEN
getInt32Memory0()[arg0 / 4 + 1] = len1
getInt32Memory0()[arg0 / 4 + 0] = ptr1
}
imports.wbg.__wbindgen_error_new = function (arg0, arg1) {
const ret = new Error(getStringFromWasm0(arg0, arg1))
return addHeapObject(ret)
}
imports.wbg.__wbindgen_is_string = function (arg0) {
const ret = typeof getObject(arg0) === 'string'
return ret
}
imports.wbg.__wbindgen_boolean_get = function (arg0) {
const v = getObject(arg0)
const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2
return ret
}
imports.wbg.__wbindgen_is_bigint = function (arg0) {
const ret = typeof getObject(arg0) === 'bigint'
return ret
}
imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) {
const ret = BigInt.asUintN(64, arg0)
return addHeapObject(ret)
}
imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) {
const ret = getObject(arg0) === getObject(arg1)
return ret
}
imports.wbg.__wbg_new_abda76e883ba8a5f = function () {
const ret = new Error()
return addHeapObject(ret)
}
imports.wbg.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) {
const ret = getObject(arg1).stack
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
const len1 = WASM_VECTOR_LEN
getInt32Memory0()[arg0 / 4 + 1] = len1
getInt32Memory0()[arg0 / 4 + 0] = ptr1
}
imports.wbg.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) {
let deferred0_0
let deferred0_1
try {
deferred0_0 = arg0
deferred0_1 = arg1
console.error(getStringFromWasm0(arg0, arg1))
} finally {
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1)
}
}
imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) {
const ret = getObject(arg0) == getObject(arg1)
return ret
}
imports.wbg.__wbindgen_number_get = function (arg0, arg1) {
const obj = getObject(arg1)
const ret = typeof obj === 'number' ? obj : undefined
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret)
}
imports.wbg.__wbindgen_object_clone_ref = function (arg0) {
const ret = getObject(arg0)
return addHeapObject(ret)
}
imports.wbg.__wbindgen_string_new = function (arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1)
return addHeapObject(ret)
}
imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function (arg0, arg1) {
const ret = getObject(arg0)[getObject(arg1)]
return addHeapObject(ret)
}
imports.wbg.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) {
getObject(arg0)[takeObject(arg1)] = takeObject(arg2)
}
imports.wbg.__wbg_get_44be0491f933a435 = function (arg0, arg1) {
const ret = getObject(arg0)[arg1 >>> 0]
return addHeapObject(ret)
}
imports.wbg.__wbg_length_fff51ee6522a1a18 = function (arg0) {
const ret = getObject(arg0).length
return ret
}
imports.wbg.__wbindgen_is_function = function (arg0) {
const ret = typeof getObject(arg0) === 'function'
return ret
}
imports.wbg.__wbg_next_526fc47e980da008 = function (arg0) {
const ret = getObject(arg0).next
return addHeapObject(ret)
}
imports.wbg.__wbg_next_ddb3312ca1c4e32a = function () {
return handleError(function (arg0) {
const ret = getObject(arg0).next()
return addHeapObject(ret)
}, arguments)
}
imports.wbg.__wbg_done_5c1f01fb660d73b5 = function (arg0) {
const ret = getObject(arg0).done
return ret
}
imports.wbg.__wbg_value_1695675138684bd5 = function (arg0) {
const ret = getObject(arg0).value
return addHeapObject(ret)
}
imports.wbg.__wbg_iterator_97f0c81209c6c35a = function () {
const ret = Symbol.iterator
return addHeapObject(ret)
}
imports.wbg.__wbg_get_97b561fb56f034b5 = function () {
return handleError(function (arg0, arg1) {
const ret = Reflect.get(getObject(arg0), getObject(arg1))
return addHeapObject(ret)
}, arguments)
}
imports.wbg.__wbg_call_cb65541d95d71282 = function () {
return handleError(function (arg0, arg1) {
const ret = getObject(arg0).call(getObject(arg1))
return addHeapObject(ret)
}, arguments)
}
imports.wbg.__wbg_new_b51585de1b234aff = function () {
const ret = new Object()
return addHeapObject(ret)
}
imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function (arg0) {
const ret = Array.isArray(getObject(arg0))
return ret
}
imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function (arg0) {
let result
try {
result = getObject(arg0) instanceof ArrayBuffer
} catch {
result = false
}
const ret = result
return ret
}
imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function (arg0) {
const ret = Number.isSafeInteger(getObject(arg0))
return ret
}
imports.wbg.__wbg_entries_e51f29c7bba0c054 = function (arg0) {
const ret = Object.entries(getObject(arg0))
return addHeapObject(ret)
}
imports.wbg.__wbg_buffer_085ec1f694018c4f = function (arg0) {
const ret = getObject(arg0).buffer
return addHeapObject(ret)
}
imports.wbg.__wbg_new_8125e318e6245eed = function (arg0) {
const ret = new Uint8Array(getObject(arg0))
return addHeapObject(ret)
}
imports.wbg.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) {
getObject(arg0).set(getObject(arg1), arg2 >>> 0)
}
imports.wbg.__wbg_length_72e2208bbc0efc61 = function (arg0) {
const ret = getObject(arg0).length
return ret
}
imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function (arg0) {
let result
try {
result = getObject(arg0) instanceof Uint8Array
} catch {
result = false
}
const ret = result
return ret
}
imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) {
const v = getObject(arg1)
const ret = typeof v === 'bigint' ? v : undefined
getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret)
}
imports.wbg.__wbindgen_debug_string = function (arg0, arg1) {
const ret = debugString(getObject(arg1))
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
const len1 = WASM_VECTOR_LEN
getInt32Memory0()[arg0 / 4 + 1] = len1
getInt32Memory0()[arg0 / 4 + 0] = ptr1
}
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1))
}
imports.wbg.__wbindgen_memory = function () {
const ret = wasm.memory
return addHeapObject(ret)
}
return imports
}
function __wbg_init_memory(imports, maybe_memory) {}
function __wbg_finalize_init(instance, module) {
wasm = instance.exports
__wbg_init.__wbindgen_wasm_module = module
cachedBigInt64Memory0 = null
cachedFloat64Memory0 = null
cachedInt32Memory0 = null
cachedUint8Memory0 = null
return wasm
}
function initSync(module) {
if (wasm !== undefined) return wasm
const imports = __wbg_get_imports()
__wbg_init_memory(imports)
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module)
}
const instance = new WebAssembly.Instance(module, imports)
return __wbg_finalize_init(instance, module)
}
async function __wbg_init(input) {
if (wasm !== undefined) return wasm
if (typeof input === 'undefined') {
input = new URL('index_bg.wasm', import.meta.url)
}
const imports = __wbg_get_imports()
if (
typeof input === 'string' ||
(typeof Request === 'function' && input instanceof Request) ||
(typeof URL === 'function' && input instanceof URL)
) {
input = fetch(input)
}
__wbg_init_memory(imports)
const { instance, module } = await __wbg_load(await input, imports)
return __wbg_finalize_init(instance, module)
}
export { initSync }
export default __wbg_init

Binary file not shown.

View File

@ -0,0 +1,15 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory
export function compute_health_js(a: number): number
export function max_withdraw_estimate_js(a: number, b: number): number
export function max_borrow_estimate_js(a: number, b: number): number
export function allocate(a: number): number
export function deallocate(a: number): void
export function requires_stargate(): void
export function requires_iterator(): void
export function interface_version_8(): void
export function __wbindgen_malloc(a: number, b: number): number
export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number
export function __wbindgen_free(a: number, b: number, c: number): void
export function __wbindgen_exn_store(a: number): void

View File

@ -1,21 +1,5 @@
import { BNCoin } from 'types/classes/BNCoin'
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'
export function resolvePositionResponses(responses: CreditManagerPosition[]): Account[] {
return responses.map(resolvePositionResponse)
}
export function resolvePositionResponse(response: CreditManagerPosition): Account {
return {
id: response.account_id,
debts: response.debts.map((debt) => new BNCoin(debt)),
lends: response.lends.map((lend) => new BNCoin(lend)),
deposits: response.deposits.map((deposit) => new BNCoin(deposit)),
vaults: response.vaults,
}
}
export function resolveMarketResponses(responses: RedBankMarket[]): Market[] {
return responses.map(resolveMarketResponse)
}

View File

@ -2,40 +2,18 @@ import { IS_TESTNET } from 'constants/env'
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import { BNCoin } from 'types/classes/BNCoin'
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { getNetCollateralValue } from 'utils/accounts'
import { BN } from 'utils/helpers'
import { getTokenPrice, getTokenValue } from 'utils/tokens'
export function getVaultsMetaData() {
return IS_TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
}
export function getVaultMetaData(address: string) {
const vaults = IS_TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
return vaults.find((vault) => vault.address === address)
}
// This should be replaced when the calculation is made part of the Health Computer (MP-2877)
export function calculateMaxBorrowAmounts(
account: Account,
marketAssets: Market[],
prices: BNCoin[],
denoms: string[],
): BNCoin[] {
const maxAmounts: BNCoin[] = []
const collateralValue = getNetCollateralValue(account, marketAssets, prices)
for (const denom of denoms) {
const borrowAsset = marketAssets.find((asset) => asset.denom === denom)
const borrowAssetPrice = prices.find((price) => price.denom === denom)?.amount
if (!borrowAssetPrice || !borrowAsset) continue
const borrowValue = BN(1).minus(borrowAsset.maxLtv).multipliedBy(borrowAssetPrice)
const amount = collateralValue.dividedBy(borrowValue).decimalPlaces(0)
maxAmounts.push(new BNCoin({ denom, amount: amount.toString() }))
}
return maxAmounts
}
export function getVaultDepositCoinsAndValue(
vault: Vault,
deposits: BNCoin[],
@ -68,7 +46,7 @@ export function getVaultDepositCoinsAndValue(
denom: vault.denoms.secondary,
amount: secondaryDepositAmount.toString(),
}),
totalValue,
totalValue: totalValue.integerValue(),
}
}