From d2afe06b16c31d046d554fd09e84be0e2a5e9a95 Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:01:15 +0100 Subject: [PATCH] Mp 3367 staking interactions (#613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ Refactor borrowRate to be in full numbers * ✨Enter into HLS Staking strategy * ✨HLS Staking deposited table + Portfolio pages * tidy: refactored the masks for HealthBar --------- Co-authored-by: Linkie Link --- src/api/accounts/getAccount.ts | 14 +- src/api/hls/getHLSStakingAccounts.ts | 41 +++++ src/api/hls/getHLSStakingAssets.ts | 8 +- src/api/vaults/getVaults.ts | 2 +- src/api/wallets/getAccounts.ts | 2 +- .../useAccountBalanceData.tsx | 2 +- .../Account/AccountList/AccountStats.tsx | 2 +- src/components/Account/Health/HealthBar.tsx | 162 +++++++++++------- src/components/Account/Health/HealthIcon.tsx | 2 +- .../Borrow/Table/Columns/BorrowRate.tsx | 2 +- src/components/DepositCapCell.tsx | 38 ++++ .../Earn/Farm/Table/Columns/DepositCap.tsx | 33 +--- src/components/Earn/Farm/VaultCard.tsx | 2 +- .../Earn/Lend/Table/Columns/Apy.tsx | 2 +- .../Earn/Lend/Table/Columns/DepositCap.tsx | 2 +- src/components/HLS/HLSTag.tsx | 11 ++ .../HLS/Staking/ActiveStakingAccounts.tsx | 24 +++ .../HLS/Staking/AvailableHLSStakingAssets.tsx | 2 +- .../HLS/Staking/Table/Columns/Account.tsx | 30 ++++ .../HLS/Staking/Table/Columns/ActiveApy.tsx | 22 +++ .../HLS/Staking/Table/Columns/DebtValue.tsx | 25 +++ .../HLS/Staking/Table/Columns/DepositCap.tsx | 23 +++ .../HLS/Staking/Table/Columns/Leverage.tsx | 25 +++ .../HLS/Staking/Table/Columns/Manage.tsx | 14 ++ .../HLS/Staking/Table/Columns/Name.tsx | 6 +- .../HLS/Staking/Table/Columns/NetValue.tsx | 25 +++ .../Staking/Table/Columns/PositionValue.tsx | 25 +++ .../Table/Columns/useDepositedColumns.tsx | 84 +++++++++ .../AssetsSelect/useAssetTableColumns.tsx | 2 +- src/components/Modals/HLS/ChooseAccount.tsx | 17 -- .../Modals/HLS/Content/useAccordionItems.tsx | 3 +- .../HLS/Content/useStakingController.tsx | 71 +++++++- src/components/Modals/HLS/Header.tsx | 5 +- src/components/Modals/HLS/Leverage.tsx | 3 +- src/components/Modals/HLS/LeverageSummary.tsx | 13 +- .../Modals/HLS/Summary/YourPosition.tsx | 8 + src/components/Portfolio/Account/Balances.tsx | 2 +- src/components/Portfolio/Account/Summary.tsx | 10 +- src/components/Portfolio/Card/Skeleton.tsx | 16 +- src/components/Portfolio/Card/index.tsx | 3 +- src/components/Portfolio/Overview/Summary.tsx | 2 +- src/components/Portfolio/SummarySkeleton.tsx | 9 +- src/components/Select/Option.tsx | 2 +- src/components/Table/Row.tsx | 2 +- src/components/Table/index.tsx | 8 +- src/constants/assets.ts | 1 + src/hooks/useAccount.tsx | 5 +- src/hooks/useHLSStakingAccounts.tsx | 11 ++ src/pages/HLSStakingPage.tsx | 2 + src/store/slices/broadcast.ts | 59 ++++++- src/types/interfaces/account.d.ts | 10 ++ src/types/interfaces/asset.d.ts | 1 + src/types/interfaces/store/broadcast.d.ts | 7 + src/utils/accounts.ts | 14 +- src/utils/assets.ts | 4 + src/utils/formatters.ts | 5 +- src/utils/parsers.ts | 4 +- src/utils/resolvers.ts | 8 +- 58 files changed, 743 insertions(+), 194 deletions(-) create mode 100644 src/api/hls/getHLSStakingAccounts.ts create mode 100644 src/components/DepositCapCell.tsx create mode 100644 src/components/HLS/HLSTag.tsx create mode 100644 src/components/HLS/Staking/ActiveStakingAccounts.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/Account.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/ActiveApy.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/DebtValue.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/DepositCap.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/Leverage.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/Manage.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/NetValue.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/PositionValue.tsx create mode 100644 src/components/HLS/Staking/Table/Columns/useDepositedColumns.tsx delete mode 100644 src/components/Modals/HLS/ChooseAccount.tsx create mode 100644 src/hooks/useHLSStakingAccounts.tsx diff --git a/src/api/accounts/getAccount.ts b/src/api/accounts/getAccount.ts index 1d8ea24a..b73f7a89 100644 --- a/src/api/accounts/getAccount.ts +++ b/src/api/accounts/getAccount.ts @@ -4,16 +4,20 @@ import getDepositedVaults from 'api/vaults/getDepositedVaults' import { BNCoin } from 'types/classes/BNCoin' import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types' -export default async function getAccount(accountIdAndKind: AccountIdAndKind): Promise { +export default async function getAccount(accountId?: string): Promise { + if (!accountId) return new Promise((_, reject) => reject('No account ID found')) + const creditManagerQueryClient = await getCreditManagerQueryClient() const accountPosition: Positions = await cacheFn( - () => creditManagerQueryClient.positions({ accountId: accountIdAndKind.id }), + () => creditManagerQueryClient.positions({ accountId: accountId }), positionsCache, - `account/${accountIdAndKind.id}`, + `account/${accountId}`, ) - const depositedVaults = await getDepositedVaults(accountIdAndKind.id, accountPosition) + const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId }) + + const depositedVaults = await getDepositedVaults(accountId, accountPosition) if (accountPosition) { return { @@ -22,7 +26,7 @@ export default async function getAccount(accountIdAndKind: AccountIdAndKind): Pr lends: accountPosition.lends.map((lend) => new BNCoin(lend)), deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)), vaults: depositedVaults, - kind: accountIdAndKind.kind, + kind: accountKind, } } diff --git a/src/api/hls/getHLSStakingAccounts.ts b/src/api/hls/getHLSStakingAccounts.ts new file mode 100644 index 00000000..180e049f --- /dev/null +++ b/src/api/hls/getHLSStakingAccounts.ts @@ -0,0 +1,41 @@ +import getHLSStakingAssets from 'api/hls/getHLSStakingAssets' +import getPrices from 'api/prices/getPrices' +import getAccounts from 'api/wallets/getAccounts' +import { calculateAccountLeverage, getAccountPositionValues, isAccountEmpty } from 'utils/accounts' + +export default async function getHLSStakingAccounts( + address?: string, +): Promise { + const accounts = await getAccounts('high_levered_strategy', address) + const activeAccounts = accounts.filter((account) => !isAccountEmpty(account)) + const hlsStrategies = await getHLSStakingAssets() + const prices = await getPrices() + const hlsAccountsWithStrategy: HLSAccountWithStrategy[] = [] + + activeAccounts.forEach((account) => { + if (account.deposits.length === 0 || account.debts.length === 0) return + + const strategy = hlsStrategies.find( + (strategy) => + strategy.denoms.deposit === account.deposits.at(0).denom && + strategy.denoms.borrow === account.debts.at(0).denom, + ) + + if (!strategy) return + + const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices) + + hlsAccountsWithStrategy.push({ + ...account, + strategy, + values: { + net: deposits.minus(debts), + debt: debts, + total: deposits, + }, + leverage: calculateAccountLeverage(account, prices).toNumber(), + }) + }) + + return hlsAccountsWithStrategy +} diff --git a/src/api/hls/getHLSStakingAssets.ts b/src/api/hls/getHLSStakingAssets.ts index ea2a73b5..8f2ad39a 100644 --- a/src/api/hls/getHLSStakingAssets.ts +++ b/src/api/hls/getHLSStakingAssets.ts @@ -1,14 +1,18 @@ import { getParamsQueryClient } from 'api/cosmwasm-client' import getAssetParams from 'api/params/getAssetParams' +import { getStakingAssets } from 'utils/assets' import { BN } from 'utils/helpers' import { resolveHLSStrategies } from 'utils/resolvers' export default async function getHLSStakingAssets() { + const stakingAssetDenoms = getStakingAssets().map((asset) => asset.denom) const assetParams = await getAssetParams() - const client = await getParamsQueryClient() - const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls) + const HLSAssets = assetParams + .filter((asset) => stakingAssetDenoms.includes(asset.denom)) + .filter((asset) => asset.credit_manager.hls) const strategies = resolveHLSStrategies('coin', HLSAssets) + const client = await getParamsQueryClient() const depositCaps$ = strategies.map((strategy) => client.totalDeposit({ denom: strategy.denoms.deposit }), ) diff --git a/src/api/vaults/getVaults.ts b/src/api/vaults/getVaults.ts index 2a145777..b0af108f 100644 --- a/src/api/vaults/getVaults.ts +++ b/src/api/vaults/getVaults.ts @@ -41,7 +41,7 @@ export default async function getVaults(): Promise { ), }, apy: apr ? convertAprToApy(apr.apr, 365) : null, - apr: apr ? apr.apr / 100 : null, + apr: apr ? apr.apr : null, ltv: { max: Number(vaultConfig.max_loan_to_value), liq: Number(vaultConfig.liquidation_threshold), diff --git a/src/api/wallets/getAccounts.ts b/src/api/wallets/getAccounts.ts index 5218e8da..cbb58e61 100644 --- a/src/api/wallets/getAccounts.ts +++ b/src/api/wallets/getAccounts.ts @@ -8,7 +8,7 @@ export default async function getAccounts(kind: AccountKind, address?: string): const $accounts = accountIdsAndKinds .filter((a) => a.kind === kind) - .map((account) => getAccount(account)) + .map((account) => getAccount(account.id)) const accounts = await Promise.all($accounts).then((accounts) => accounts) diff --git a/src/components/Account/AccountBalancesTable/useAccountBalanceData.tsx b/src/components/Account/AccountBalancesTable/useAccountBalanceData.tsx index 54a17471..31eddade 100644 --- a/src/components/Account/AccountBalancesTable/useAccountBalanceData.tsx +++ b/src/components/Account/AccountBalancesTable/useAccountBalanceData.tsx @@ -65,7 +65,7 @@ export default function useAccountBalanceData(props: Props) { const prevDebt = updatedAccount ? account?.debts.find((position) => position.denom === debt.denom) : debt - return getAssetAccountBalanceRow('borrowing', asset, prices, debt, apy * -100, prevDebt) + return getAssetAccountBalanceRow('borrowing', asset, prices, debt, apy, prevDebt) }) return [...deposits, ...lends, ...vaults, ...debts] }, [prices, account, updatedAccount, borrowingData, lendingData]) diff --git a/src/components/Account/AccountList/AccountStats.tsx b/src/components/Account/AccountList/AccountStats.tsx index d680a7d4..01fbd9f3 100644 --- a/src/components/Account/AccountList/AccountStats.tsx +++ b/src/components/Account/AccountList/AccountStats.tsx @@ -21,7 +21,7 @@ interface Props { export default function AccountStats(props: Props) { const { accountId, isActive, setShowMenu } = props - const { data: account } = useAccount('default', accountId) + const { data: account } = useAccount(accountId) const { data: prices } = usePrices() const positionBalance = useMemo( () => (!account ? null : calculateAccountBalanceValue(account, prices)), diff --git a/src/components/Account/Health/HealthBar.tsx b/src/components/Account/Health/HealthBar.tsx index 0245bddb..90117462 100644 --- a/src/components/Account/Health/HealthBar.tsx +++ b/src/components/Account/Health/HealthBar.tsx @@ -1,6 +1,7 @@ import classNames from 'classnames' import { useMemo } from 'react' +import HealthIcon from 'components/Account/Health/HealthIcon' import HealthTooltip from 'components/Account/Health/HealthTooltip' import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { LocalStorageKeys } from 'constants/localStorageKeys' @@ -9,12 +10,15 @@ import useLocalStorage from 'hooks/useLocalStorage' import { getHealthIndicatorColors } from 'utils/healthIndicator' interface Props { + className?: string + hasLabel?: boolean health: number healthFactor: number + height?: string + iconClassName?: string updatedHealth?: number updatedHealthFactor?: number - hasLabel?: boolean - className?: string + showIcon?: boolean } function calculateHealth(health: number): number { @@ -36,6 +40,9 @@ export default function HealthBar({ healthFactor = 0, updatedHealthFactor = 0, className, + height = '4', + iconClassName = 'w-5', + showIcon = false, }: Props) { const [reduceMotion] = useLocalStorage( LocalStorageKeys.REDUCE_MOTION, @@ -57,74 +64,97 @@ export default function HealthBar({ health={isUpdated ? updatedHealth : health} healthFactor={isUpdated ? updatedHealthFactor : healthFactor} > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {showIcon && ( + - {isUpdated && ( + )} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - )} - -
+ + {isUpdated && ( + + )} + +
+ ) } diff --git a/src/components/Account/Health/HealthIcon.tsx b/src/components/Account/Health/HealthIcon.tsx index 8712f73d..0a6e6631 100644 --- a/src/components/Account/Health/HealthIcon.tsx +++ b/src/components/Account/Health/HealthIcon.tsx @@ -16,7 +16,7 @@ export default function HealthIcon(props: Props) { return ( <> {!isLoading && health === 0 ? ( - + ) : ( diff --git a/src/components/DepositCapCell.tsx b/src/components/DepositCapCell.tsx new file mode 100644 index 00000000..51ba0edb --- /dev/null +++ b/src/components/DepositCapCell.tsx @@ -0,0 +1,38 @@ +import { FormattedNumber } from 'components/FormattedNumber' +import TitleAndSubCell from 'components/TitleAndSubCell' +import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults' +import { getAssetByDenom } from 'utils/assets' + +interface Props { + depositCap: DepositCap +} + +export default function DepositCapCell(props: Props) { + const percent = props.depositCap.used + .dividedBy(props.depositCap.max.multipliedBy(VAULT_DEPOSIT_BUFFER)) + .multipliedBy(100) + .integerValue() + + const decimals = getAssetByDenom(props.depositCap.denom)?.decimals ?? 6 + + return ( + + } + sub={ + + } + /> + ) +} diff --git a/src/components/Earn/Farm/Table/Columns/DepositCap.tsx b/src/components/Earn/Farm/Table/Columns/DepositCap.tsx index 37ca70b9..e12c47ff 100644 --- a/src/components/Earn/Farm/Table/Columns/DepositCap.tsx +++ b/src/components/Earn/Farm/Table/Columns/DepositCap.tsx @@ -1,10 +1,7 @@ import { Row } from '@tanstack/react-table' -import { FormattedNumber } from 'components/FormattedNumber' +import DepositCapCell from 'components/DepositCapCell' import Loading from 'components/Loading' -import TitleAndSubCell from 'components/TitleAndSubCell' -import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults' -import { getAssetByDenom } from 'utils/assets' export const DEPOSIT_CAP_META = { accessorKey: 'cap', header: 'Deposit Cap' } @@ -27,31 +24,5 @@ export default function DepositCap(props: Props) { if (props.isLoading) return - const percent = vault.cap.used - .dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER)) - .multipliedBy(100) - .integerValue() - - const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6 - - return ( - - } - sub={ - - } - /> - ) + return } diff --git a/src/components/Earn/Farm/VaultCard.tsx b/src/components/Earn/Farm/VaultCard.tsx index 54570018..a0864693 100644 --- a/src/components/Earn/Farm/VaultCard.tsx +++ b/src/components/Earn/Farm/VaultCard.tsx @@ -74,7 +74,7 @@ export default function VaultCard(props: Props) { abbreviated: true, decimals: getAssetByDenom(props.vault.cap.denom)?.decimals, })} - sub={'Depo. Cap'} + sub={'Deposit Cap'} /> diff --git a/src/components/Earn/Lend/Table/Columns/Apy.tsx b/src/components/Earn/Lend/Table/Columns/Apy.tsx index 11996422..62c9ab5e 100644 --- a/src/components/Earn/Lend/Table/Columns/Apy.tsx +++ b/src/components/Earn/Lend/Table/Columns/Apy.tsx @@ -14,7 +14,7 @@ export default function Apr(props: Props) { return ( + HLS + + ) +} diff --git a/src/components/HLS/Staking/ActiveStakingAccounts.tsx b/src/components/HLS/Staking/ActiveStakingAccounts.tsx new file mode 100644 index 00000000..5a52b011 --- /dev/null +++ b/src/components/HLS/Staking/ActiveStakingAccounts.tsx @@ -0,0 +1,24 @@ +import { NAME_META } from 'components/HLS/Farm/Table/Columns/Name' +import useDepositedColumns from 'components/HLS/Staking/Table/Columns/useDepositedColumns' +import Table from 'components/Table' +import useHLSStakingAccounts from 'hooks/useHLSStakingAccounts' +import useStore from 'store' + +const title = 'Active Strategies' + +export default function ActiveStakingAccounts() { + const address = useStore((s) => s.address) + const columns = useDepositedColumns({ isLoading: false }) + const { data: hlsStakingAccounts } = useHLSStakingAccounts(address) + + if (!hlsStakingAccounts.length) return null + + return ( + + ) +} diff --git a/src/components/HLS/Staking/AvailableHLSStakingAssets.tsx b/src/components/HLS/Staking/AvailableHLSStakingAssets.tsx index ba42a01c..c74b1d3c 100644 --- a/src/components/HLS/Staking/AvailableHLSStakingAssets.tsx +++ b/src/components/HLS/Staking/AvailableHLSStakingAssets.tsx @@ -6,7 +6,7 @@ import Table from 'components/Table' import useHLSStakingAssets from 'hooks/useHLSStakingAssets' import { getEnabledMarketAssets } from 'utils/assets' -const title = 'Available HLS Staking' +const title = 'Available Strategies' function Content() { const assets = getEnabledMarketAssets() diff --git a/src/components/HLS/Staking/Table/Columns/Account.tsx b/src/components/HLS/Staking/Table/Columns/Account.tsx new file mode 100644 index 00000000..73e475eb --- /dev/null +++ b/src/components/HLS/Staking/Table/Columns/Account.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +import HealthBar from 'components/Account/Health/HealthBar' +import TitleAndSubCell from 'components/TitleAndSubCell' +import useHealthComputer from 'hooks/useHealthComputer' + +export const ACCOUNT_META = { id: 'account', header: 'Account', accessorKey: 'id' } +interface Props { + account: HLSAccountWithStrategy +} + +export default function Name(props: Props) { + const { health, healthFactor } = useHealthComputer(props.account) + return ( + + } + /> + ) +} diff --git a/src/components/HLS/Staking/Table/Columns/ActiveApy.tsx b/src/components/HLS/Staking/Table/Columns/ActiveApy.tsx new file mode 100644 index 00000000..ce64cee1 --- /dev/null +++ b/src/components/HLS/Staking/Table/Columns/ActiveApy.tsx @@ -0,0 +1,22 @@ +import { Row } from '@tanstack/react-table' +import React from 'react' + +import TitleAndSubCell from 'components/TitleAndSubCell' + +export const ACTIVE_APY_META = { header: 'APY', accessorKey: 'strategy' } + +export const activeApySortingFn = ( + a: Row, + b: Row, +): number => { + // TODO: Properly implement this + return 0 +} + +interface Props { + account: HLSAccountWithStrategy +} + +export default function ActiveAPY(props: Props) { + return +} diff --git a/src/components/HLS/Staking/Table/Columns/DebtValue.tsx b/src/components/HLS/Staking/Table/Columns/DebtValue.tsx new file mode 100644 index 00000000..1f3771a9 --- /dev/null +++ b/src/components/HLS/Staking/Table/Columns/DebtValue.tsx @@ -0,0 +1,25 @@ +import { Row } from '@tanstack/react-table' + +import DisplayCurrency from 'components/DisplayCurrency' +import { BNCoin } from 'types/classes/BNCoin' + +export const DEBT_VAL_META = { header: 'Debt Value', accessorKey: 'values.debt' } +interface Props { + account: HLSAccountWithStrategy +} + +export function debtValueSorting( + a: Row, + b: Row, +): number { + return a.original.values.debt.minus(b.original.values.debt).toNumber() +} + +export default function DebtValue(props: Props) { + return ( + + ) +} diff --git a/src/components/HLS/Staking/Table/Columns/DepositCap.tsx b/src/components/HLS/Staking/Table/Columns/DepositCap.tsx new file mode 100644 index 00000000..cea2b6d7 --- /dev/null +++ b/src/components/HLS/Staking/Table/Columns/DepositCap.tsx @@ -0,0 +1,23 @@ +import { Row } from '@tanstack/react-table' +import React from 'react' + +import DepositCapCell from 'components/DepositCapCell' + +export const CAP_META = { header: 'Cap', accessorKey: 'strategy.depositCap' } + +export const depositCapSortingFn = ( + a: Row, + b: Row, +): number => { + const depositCapA = a.original.strategy.depositCap.max + const depositCapB = b.original.strategy.depositCap.max + return depositCapA.minus(depositCapB).toNumber() +} + +interface Props { + account: HLSAccountWithStrategy +} + +export default function Name(props: Props) { + return +} diff --git a/src/components/HLS/Staking/Table/Columns/Leverage.tsx b/src/components/HLS/Staking/Table/Columns/Leverage.tsx new file mode 100644 index 00000000..f8f4a686 --- /dev/null +++ b/src/components/HLS/Staking/Table/Columns/Leverage.tsx @@ -0,0 +1,25 @@ +import { Row } from '@tanstack/react-table' +import React from 'react' + +import { FormattedNumber } from 'components/FormattedNumber' + +export const LEV_META = { accessorKey: 'leverage ', header: 'Leverage' } + +interface Props { + account: HLSAccountWithStrategy +} + +export function leverageSortingFn(a: Row, b: Row) { + return a.original.leverage - b.original.leverage +} + +export default function MaxLeverage(props: Props) { + return ( + + ) +} diff --git a/src/components/HLS/Staking/Table/Columns/Manage.tsx b/src/components/HLS/Staking/Table/Columns/Manage.tsx new file mode 100644 index 00000000..e12df380 --- /dev/null +++ b/src/components/HLS/Staking/Table/Columns/Manage.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +import Button from 'components/Button' + +export const MANAGE_META = { id: 'manage' } + +interface Props { + account: HLSAccountWithStrategy +} + +export default function Manage(props: Props) { + // TODO: Impelement dropdown + return