diff --git a/__tests__/components/Account/AccountDetails.test.tsx b/__tests__/components/Account/AccountDetails.test.tsx index 4fcbaf83..d988e409 100644 --- a/__tests__/components/Account/AccountDetails.test.tsx +++ b/__tests__/components/Account/AccountDetails.test.tsx @@ -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('', () => { - 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() 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() const container = screen.queryByTestId('account-details') diff --git a/__tests__/components/MarketAssetTable/MarketDetails.test.tsx b/__tests__/components/MarketAssetTable/MarketDetails.test.tsx index 4cd4fded..9f35977b 100644 --- a/__tests__/components/MarketAssetTable/MarketDetails.test.tsx +++ b/__tests__/components/MarketAssetTable/MarketDetails.test.tsx @@ -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('', () => { }) it('should render', () => { - const { container } = render() + const { container } = render() expect(container).toBeInTheDocument() }) }) diff --git a/__tests__/components/Modals/vault/VaultBorrowings.test.tsx b/__tests__/components/Modals/vault/VaultBorrowings.test.tsx index f5dc2ada..24f7c801 100644 --- a/__tests__/components/Modals/vault/VaultBorrowings.test.tsx +++ b/__tests__/components/Modals/vault/VaultBorrowings.test.tsx @@ -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('', () => { 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('', () => { borrowings: [], deposits: [], onChangeBorrowings: jest.fn(), + depositActions: [], + depositFee: hardcodedFee, } beforeAll(() => { diff --git a/jest.config.js b/jest.config.js index ff3d3456..377aee24 100644 --- a/jest.config.js +++ b/jest.config.js @@ -10,6 +10,11 @@ module.exports = { '!/*.config.js', '!/coverage/**', '!/src/types/**', + '!/src/utils/charting_library/**', + '!/src/utils/datafeeds/**', + '!/public/charting_library/**', + '!/public/datafeeds/**', + '!/src/utils/health_computer/**', ], moduleNameMapper: { // Handle CSS imports (with CSS modules) diff --git a/src/api/accounts/getAccount.ts b/src/api/accounts/getAccount.ts index b57bcbf2..2546a1d2 100644 --- a/src/api/accounts/getAccount.ts +++ b/src/api/accounts/getAccount.ts @@ -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 { 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')) diff --git a/src/api/accounts/getAccountDebts.ts b/src/api/accounts/getAccountDebts.ts deleted file mode 100644 index 037c0b2a..00000000 --- a/src/api/accounts/getAccountDebts.ts +++ /dev/null @@ -1,12 +0,0 @@ -import getAccount from 'api/accounts/getAccount' -import { BNCoin } from 'types/classes/BNCoin' - -export default async function getAccountDebts(accountId: string): Promise { - const account = await getAccount(accountId) - - if (account) { - return account.debts - } - - return new Promise((_, reject) => reject('Account not found')) -} diff --git a/src/api/accounts/getAccountDeposits.ts b/src/api/accounts/getAccountDeposits.ts deleted file mode 100644 index a476290c..00000000 --- a/src/api/accounts/getAccountDeposits.ts +++ /dev/null @@ -1,12 +0,0 @@ -import getAccount from 'api/accounts/getAccount' -import { BNCoin } from 'types/classes/BNCoin' - -export default async function getAccountDeposits(accountId: string): Promise { - const account = await getAccount(accountId) - - if (account) { - return account.deposits - } - - return new Promise((_, reject) => reject('Account not found')) -} diff --git a/src/api/accounts/getAccountVaults.ts b/src/api/accounts/getAccountVaults.ts deleted file mode 100644 index da24f005..00000000 --- a/src/api/accounts/getAccountVaults.ts +++ /dev/null @@ -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')) -} diff --git a/src/api/params/getAssetParams.ts b/src/api/params/getAssetParams.ts new file mode 100644 index 00000000..3fd81b56 --- /dev/null +++ b/src/api/params/getAssetParams.ts @@ -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 { + try { + const paramsQueryClient = await getParamsQueryClient() + + return paramsQueryClient.allAssetParams({}) + } catch (ex) { + throw ex + } +} diff --git a/src/api/vaults/getDepositedVaults.ts b/src/api/vaults/getDepositedVaults.ts index 1cc35834..970db893 100644 --- a/src/api/vaults/getDepositedVaults.ts +++ b/src/api/vaults/getDepositedVaults.ts @@ -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) { diff --git a/src/api/vaults/getVaultTokenFromLp.ts b/src/api/vaults/getVaultTokenFromLp.ts new file mode 100644 index 00000000..e4ed9886 --- /dev/null +++ b/src/api/vaults/getVaultTokenFromLp.ts @@ -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 + } +} diff --git a/src/api/wallets/getAccounts.ts b/src/api/wallets/getAccounts.ts index 3e0c1914..ab20f299 100644 --- a/src/api/wallets/getAccounts.ts +++ b/src/api/wallets/getAccounts.ts @@ -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 { 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')) diff --git a/src/components/Account/AccountDetails.tsx b/src/components/Account/AccountDetails.tsx index 101481ad..1db54a2a 100644 --- a/src/components/Account/AccountDetails.tsx +++ b/src/components/Account/AccountDetails.tsx @@ -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 +} + +function AccountDetails(props: Props) { + return (
- ) : null + ) } diff --git a/src/components/AccountDebtTable.tsx b/src/components/AccountDebtTable.tsx deleted file mode 100644 index f7f119de..00000000 --- a/src/components/AccountDebtTable.tsx +++ /dev/null @@ -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 ( -

- {debt.denom} {debt.amount.toString()} -

- ) - }) -} diff --git a/src/components/Modals/BorrowModal.tsx b/src/components/Modals/BorrowModal.tsx index 1d407df3..d6cce4e5 100644 --- a/src/components/Modals/BorrowModal.tsx +++ b/src/components/Modals/BorrowModal.tsx @@ -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 } -export default function BorrowModal() { - const currentAccount = useCurrentAccount() +interface Props { + account: Account +} + +export default function BorrowModalController() { + const account = useCurrentAccount() + if (!account) return null + + return +} + +function BorrowModal(props: Props) { const [percentage, setPercentage] = useState(0) const [amount, setAmount] = useState(BN(0)) const [change, setChange] = useState() @@ -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={} /> - + ) diff --git a/src/components/Modals/Vault/VaultBorrowings.tsx b/src/components/Modals/Vault/VaultBorrowings.tsx index 54c82483..79a94a15 100644 --- a/src/components/Modals/Vault/VaultBorrowings.tsx +++ b/src/components/Modals/Vault/VaultBorrowings.tsx @@ -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(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) {
{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 return ( @@ -222,7 +201,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) { text='Deposit' rightIcon={} showProgressIndicator={isConfirming} - disabled={!depositActions.length} + disabled={!props.depositActions.length} />
) diff --git a/src/components/Modals/Vault/VaultDeposits.tsx b/src/components/Modals/Vault/VaultDeposits.tsx index 38203b6c..59f21b05 100644 --- a/src/components/Modals/Vault/VaultDeposits.tsx +++ b/src/components/Modals/Vault/VaultDeposits.tsx @@ -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) {
{!props.isCustomRatio && ( @@ -170,13 +185,11 @@ export default function VaultDeposit(props: Props) { )} @@ -205,7 +218,7 @@ export default function VaultDeposit(props: Props) {
- {`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Deposit Value`} + {`${primaryAsset.symbol}-${secondaryAsset.symbol} Deposit Value`} diff --git a/src/components/Modals/Vault/VaultModalContent.tsx b/src/components/Modals/Vault/VaultModalContent.tsx index 65d7bfea..327811e3 100644 --- a/src/components/Modals/Vault/VaultModalContent.tsx +++ b/src/components/Modals/Vault/VaultModalContent.tsx @@ -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(BN(0)) - const [secondaryAmount, setSecondaryAmount] = useState(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 ( 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 + return } return ( @@ -89,10 +88,8 @@ export default function VaultModalContent(props: Props) { { renderContent: () => ( ( ), title: 'Borrow', diff --git a/src/constants/assets.ts b/src/constants/assets.ts index 39613183..9abae6fa 100644 --- a/src/constants/assets.ts +++ b/src/constants/assets.ts @@ -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, + }, ] diff --git a/src/constants/vaults.ts b/src/constants/vaults.ts index edf5263f..6a7e2094 100644 --- a/src/constants/vaults.ts +++ b/src/constants/vaults.ts @@ -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), + }, +} diff --git a/src/hooks/broadcast/useDepositVault.ts b/src/hooks/broadcast/useDepositVault.ts index 8ab77041..761aa478 100644 --- a/src/hooks/broadcast/useDepositVault.ts +++ b/src/hooks/broadcast/useDepositVault.ts @@ -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(BN(0)) const { data: prices } = usePrices() const [slippage] = useLocalStorage(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, + } } diff --git a/src/hooks/useAccount.tsx b/src/hooks/useAccount.tsx new file mode 100644 index 00000000..0f2e0259 --- /dev/null +++ b/src/hooks/useAccount.tsx @@ -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, + }) +} diff --git a/src/hooks/useAccountDebts.tsx b/src/hooks/useAccountDebts.tsx deleted file mode 100644 index 63826155..00000000 --- a/src/hooks/useAccountDebts.tsx +++ /dev/null @@ -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, - }) -} diff --git a/src/hooks/useAssetParams.tsx b/src/hooks/useAssetParams.tsx new file mode 100644 index 00000000..6e991ca9 --- /dev/null +++ b/src/hooks/useAssetParams.tsx @@ -0,0 +1,9 @@ +import useSWR from 'swr' + +import getAssetParams from 'api/params/getAssetParams' + +export default function useAssetParams() { + return useSWR('assetParams', getAssetParams, { + fallbackData: [], + }) +} diff --git a/src/hooks/useHealthComputer.tsx b/src/hooks/useHealthComputer.tsx new file mode 100644 index 00000000..64ca92a6 --- /dev/null +++ b/src/hooks/useHealthComputer.tsx @@ -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 { + 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 { + 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 { + if (!healthComputer) return 0 + return await max_withdraw_estimate_js(healthComputer, denom) + } + + return callMaxWithdrawWasmFn(denom) + }, + [healthComputer], + ) + + return { computeHealth, computeMaxBorrowAmount, computeMaxWithdrawAmount } +} diff --git a/src/hooks/useUpdateAccount.tsx b/src/hooks/useUpdateAccount.tsx deleted file mode 100644 index 37eccb04..00000000 --- a/src/hooks/useUpdateAccount.tsx +++ /dev/null @@ -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) - const [borrowings, setBorrowings] = useState([]) - - 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 } -} diff --git a/src/hooks/useUpdatedAccount/functions.ts b/src/hooks/useUpdatedAccount/functions.ts new file mode 100644 index 00000000..adfb9c42 --- /dev/null +++ b/src/hooks/useUpdatedAccount/functions.ts @@ -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 +} diff --git a/src/hooks/useUpdatedAccount/index.ts b/src/hooks/useUpdatedAccount/index.ts new file mode 100644 index 00000000..fa50d7b5 --- /dev/null +++ b/src/hooks/useUpdatedAccount/index.ts @@ -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(cloneAccount(account)) + const [addedDeposits, addDeposits] = useState([]) + const [removedDeposits, removeDeposits] = useState([]) + const [addedDebt, addDebt] = useState([]) + const [removedDebt, removeDebt] = useState([]) + const [addedVaultValues, addVaultValues] = useState([]) + + 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, + } +} diff --git a/src/hooks/useVaultConfigs.tsx b/src/hooks/useVaultConfigs.tsx new file mode 100644 index 00000000..500623c5 --- /dev/null +++ b/src/hooks/useVaultConfigs.tsx @@ -0,0 +1,9 @@ +import useSWR from 'swr' + +import { getVaultConfigs } from 'api/vaults/getVaultConfigs' + +export default function useVaultConfigs() { + return useSWR('vaultConfigs', getVaultConfigs, { + fallbackData: [], + }) +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index c47f4fa4..e0426492 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -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) }, []) diff --git a/src/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts b/src/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts index 4227dd32..98f84b76 100644 --- a/src/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts +++ b/src/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts @@ -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 +} diff --git a/src/types/interfaces/account.d.ts b/src/types/interfaces/account.d.ts index 4104ea84..f4ca6903 100644 --- a/src/types/interfaces/account.d.ts +++ b/src/types/interfaces/account.d.ts @@ -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 { diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts index 32af095c..aa2c6505 100644 --- a/src/types/interfaces/asset.d.ts +++ b/src/types/interfaces/asset.d.ts @@ -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 diff --git a/src/types/interfaces/vaults.d.ts b/src/types/interfaces/vaults.d.ts index 5f01ad81..7713ec1e 100644 --- a/src/types/interfaces/vaults.d.ts +++ b/src/types/interfaces/vaults.d.ts @@ -11,6 +11,7 @@ interface VaultMetaData { primary: string secondary: string lp: string + vault: string } symbols: { primary: string diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 3a82a931..55503f6e 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -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) } diff --git a/src/utils/health_computer/index.d.ts b/src/utils/health_computer/index.d.ts new file mode 100644 index 00000000..febd00d0 --- /dev/null +++ b/src/utils/health_computer/index.d.ts @@ -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} module_or_path + * + * @returns {Promise} + */ +export default function __wbg_init( + module_or_path?: InitInput | Promise, +): Promise diff --git a/src/utils/health_computer/index.js b/src/utils/health_computer/index.js new file mode 100644 index 00000000..5b2b264f --- /dev/null +++ b/src/utils/health_computer/index.js @@ -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 diff --git a/src/utils/health_computer/index_bg.wasm b/src/utils/health_computer/index_bg.wasm new file mode 100644 index 00000000..aa04b916 Binary files /dev/null and b/src/utils/health_computer/index_bg.wasm differ diff --git a/src/utils/health_computer/index_bg.wasm.d.ts b/src/utils/health_computer/index_bg.wasm.d.ts new file mode 100644 index 00000000..da007578 --- /dev/null +++ b/src/utils/health_computer/index_bg.wasm.d.ts @@ -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 diff --git a/src/utils/resolvers.ts b/src/utils/resolvers.ts index 047d724b..3a9ee39e 100644 --- a/src/utils/resolvers.ts +++ b/src/utils/resolvers.ts @@ -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) } diff --git a/src/utils/vaults.ts b/src/utils/vaults.ts index 79bdc738..9bd44636 100644 --- a/src/utils/vaults.ts +++ b/src/utils/vaults.ts @@ -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(), } }