From 3413203ca76f7030dde390da65bc44c9bbee9bd4 Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Mon, 24 Jul 2023 09:44:45 +0200 Subject: [PATCH] Mp 2949 implemet health computer for borrow (#311) * implemented max borrow for borrow page setup basic useHealthComputer * finish up healthcomputer * updated tests --- .../Account/AccountDetails.test.tsx | 19 +- .../MarketAssetTable/MarketDetails.test.tsx | 4 +- .../Modals/vault/VaultBorrowings.test.tsx | 7 +- jest.config.js | 5 + src/api/accounts/getAccount.ts | 14 +- src/api/accounts/getAccountDebts.ts | 12 - src/api/accounts/getAccountDeposits.ts | 12 - src/api/accounts/getAccountVaults.ts | 11 - src/api/params/getAssetParams.ts | 12 + src/api/vaults/getDepositedVaults.ts | 6 +- src/api/vaults/getVaultTokenFromLp.ts | 14 + src/api/wallets/getAccounts.ts | 8 +- src/components/Account/AccountDetails.tsx | 23 +- src/components/AccountDebtTable.tsx | 17 - src/components/Modals/BorrowModal.tsx | 39 +- .../Modals/Vault/VaultBorrowings.tsx | 69 +-- src/components/Modals/Vault/VaultDeposits.tsx | 81 +-- .../Modals/Vault/VaultModalContent.tsx | 79 ++- src/constants/assets.ts | 19 +- src/constants/vaults.ts | 32 + src/hooks/broadcast/useDepositVault.ts | 17 +- src/hooks/useAccount.tsx | 9 + src/hooks/useAccountDebts.tsx | 10 - src/hooks/useAssetParams.tsx | 9 + src/hooks/useHealthComputer.tsx | 157 +++++ src/hooks/useUpdateAccount.tsx | 56 -- src/hooks/useUpdatedAccount/functions.ts | 69 +++ src/hooks/useUpdatedAccount/index.ts | 46 ++ src/hooks/useVaultConfigs.tsx | 9 + src/pages/_app.tsx | 9 + .../MarsRoverHealthComputer.types.ts | 10 +- src/types/interfaces/account.d.ts | 4 +- src/types/interfaces/asset.d.ts | 22 +- src/types/interfaces/vaults.d.ts | 1 + src/utils/accounts.ts | 105 +++- src/utils/health_computer/index.d.ts | 60 ++ src/utils/health_computer/index.js | 568 ++++++++++++++++++ src/utils/health_computer/index_bg.wasm | Bin 0 -> 174668 bytes src/utils/health_computer/index_bg.wasm.d.ts | 15 + src/utils/resolvers.ts | 16 - src/utils/vaults.ts | 32 +- 41 files changed, 1342 insertions(+), 365 deletions(-) delete mode 100644 src/api/accounts/getAccountDebts.ts delete mode 100644 src/api/accounts/getAccountDeposits.ts delete mode 100644 src/api/accounts/getAccountVaults.ts create mode 100644 src/api/params/getAssetParams.ts create mode 100644 src/api/vaults/getVaultTokenFromLp.ts delete mode 100644 src/components/AccountDebtTable.tsx create mode 100644 src/hooks/useAccount.tsx delete mode 100644 src/hooks/useAccountDebts.tsx create mode 100644 src/hooks/useAssetParams.tsx create mode 100644 src/hooks/useHealthComputer.tsx delete mode 100644 src/hooks/useUpdateAccount.tsx create mode 100644 src/hooks/useUpdatedAccount/functions.ts create mode 100644 src/hooks/useUpdatedAccount/index.ts create mode 100644 src/hooks/useVaultConfigs.tsx create mode 100644 src/utils/health_computer/index.d.ts create mode 100644 src/utils/health_computer/index.js create mode 100644 src/utils/health_computer/index_bg.wasm create mode 100644 src/utils/health_computer/index_bg.wasm.d.ts 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 0000000000000000000000000000000000000000..aa04b916ec6649bf09fed7f6de0da449d99a0d80 GIT binary patch literal 174668 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 bOexyfWu(!vwau-o(O0I#wEnG(jSK$=_hBoB literal 0 HcmV?d00001 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(), } }