From 9d36f50856a3fa60d937b945c938c19aeadc39e8 Mon Sep 17 00:00:00 2001 From: Linkie Link Date: Wed, 7 Jun 2023 10:32:21 +0200 Subject: [PATCH] v1.4.7 --- next.config.js | 8 +- package.json | 2 +- .../common/Containers/CommonContainer.tsx | 4 +- src/components/common/Layout/Layout.tsx | 9 +- .../TermsOfService/TermsOfService.module.scss | 44 ++ .../common/TermsOfService/TermsOfService.tsx | 60 +++ src/components/common/Tooltip/Apy.module.scss | 17 + src/components/common/Tooltip/Apy.tsx | 56 ++- src/components/common/index.ts | 1 + .../ActionsTooltip/components/EditContent.tsx | 5 + .../components/RepayContent.tsx | 2 + .../components/UnlockContent.tsx | 3 + .../ActiveVaultsTable/ActiveVaultsTable.tsx | 6 +- .../ActiveVaultsTableMobile.tsx | 6 +- .../useActiveVaultsColumns.tsx | 16 +- .../AvailableVaultsTableMobile.tsx | 18 +- .../useAvailableVaultsColumns.tsx | 30 +- .../BreakdownTable/BreakdownTable.tsx | 17 +- .../LiquidationNotification.tsx | 11 +- .../TokenInput/TokenInput.module.scss | 1 + .../PositionInput/TokenInput/TokenInput.tsx | 5 +- .../UnlockedNotification.tsx | 4 +- src/configs/osmo-test-5.ts | 25 +- src/configs/osmosis-1.ts | 21 +- src/constants/appConstants.ts | 1 + src/constants/defaults.ts | 7 +- .../fields/getTokenValueFromCoins.ts | 2 +- src/functions/queries/getDebtQuery.ts | 7 - src/functions/queries/getRedbankQuery.ts | 9 - .../queries/getUserCollateralQuery.ts | 7 - src/functions/queries/index.ts | 2 - src/hooks/data/useActiveVault.tsx | 6 +- src/hooks/data/usePrice.tsx | 4 +- src/hooks/queries/index.ts | 2 +- src/hooks/queries/useUserCollaterals.tsx | 64 +++ src/hooks/queries/useUserDebt.tsx | 80 ++-- src/hooks/queries/useUserDeposit.tsx | 55 --- src/mocks/position.ts | 7 +- src/mocks/redBankData.ts | 20 - src/mocks/vault.ts | 7 +- .../{ => account/[id]}/close/index.tsx | 3 +- .../[id]}/edit/EditVault.module.scss | 0 .../{ => account/[id]}/edit/EditVault.tsx | 5 +- .../{ => account/[id]}/edit/index.tsx | 4 +- .../[id]}/repay/RepayVault.module.scss | 0 .../{ => account/[id]}/repay/RepayVault.tsx | 0 .../{ => account/[id]}/repay/index.tsx | 4 +- .../[id]}/unlock/UnlockDisclaimer.module.scss | 0 .../{ => account/[id]}/unlock/index.tsx | 4 +- src/store/interfaces/common.interface.ts | 1 + src/store/interfaces/redBank.interface.ts | 6 - src/store/interfaces/vaults.interface..ts | 6 +- src/store/slices/common.ts | 11 +- src/store/slices/redBank.ts | 27 -- src/store/slices/vaults.ts | 424 ++++++++++-------- src/types/enums/queryKeys.ts | 1 + src/types/interfaces/fields.d.ts | 38 +- src/types/interfaces/redbank.d.ts | 1 - 58 files changed, 705 insertions(+), 481 deletions(-) create mode 100644 src/components/common/TermsOfService/TermsOfService.module.scss create mode 100644 src/components/common/TermsOfService/TermsOfService.tsx delete mode 100644 src/functions/queries/getDebtQuery.ts delete mode 100644 src/functions/queries/getUserCollateralQuery.ts create mode 100644 src/hooks/queries/useUserCollaterals.tsx delete mode 100644 src/hooks/queries/useUserDeposit.tsx rename src/pages/farm/vault/[address]/{ => account/[id]}/close/index.tsx (94%) rename src/pages/farm/vault/[address]/{ => account/[id]}/edit/EditVault.module.scss (100%) rename src/pages/farm/vault/[address]/{ => account/[id]}/edit/EditVault.tsx (98%) rename src/pages/farm/vault/[address]/{ => account/[id]}/edit/index.tsx (74%) rename src/pages/farm/vault/[address]/{ => account/[id]}/repay/RepayVault.module.scss (100%) rename src/pages/farm/vault/[address]/{ => account/[id]}/repay/RepayVault.tsx (100%) rename src/pages/farm/vault/[address]/{ => account/[id]}/repay/index.tsx (75%) rename src/pages/farm/vault/[address]/{ => account/[id]}/unlock/UnlockDisclaimer.module.scss (100%) rename src/pages/farm/vault/[address]/{ => account/[id]}/unlock/index.tsx (96%) diff --git a/next.config.js b/next.config.js index e9f9259..0f9327d 100644 --- a/next.config.js +++ b/next.config.js @@ -26,22 +26,22 @@ const moduleExports = { permanent: true, }, { - source: '/farm/vault/:address/edit', + source: '/farm/vault/:address/account/:id/edit', destination: '/farm/', permanent: true, }, { - source: '/farm/vault/:address/unlock', + source: '/farm/vault/:address/account/:id/unlock', destination: '/farm', permanent: true, }, { - source: '/farm/vault/:address/close', + source: '/farm/vault/:address/account/:id/close', destination: '/farm', permanent: true, }, { - source: '/farm/vault/:address/repay', + source: '/farm/vault/:address/account/:id/repay', destination: '/farm', permanent: true, }, diff --git a/package.json b/package.json index 36b7256..828c4e0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mars", "homepage": "./", - "version": "1.4.6", + "version": "1.4.7", "license": "SEE LICENSE IN LICENSE FILE", "private": false, "scripts": { diff --git a/src/components/common/Containers/CommonContainer.tsx b/src/components/common/Containers/CommonContainer.tsx index 4e0cb84..4251189 100644 --- a/src/components/common/Containers/CommonContainer.tsx +++ b/src/components/common/Containers/CommonContainer.tsx @@ -18,10 +18,10 @@ import { useUsdPrice, useUserBalance, useUserDebt, - useUserDeposit, useUserIcns, } from 'hooks/queries' import { useSpotPrice } from 'hooks/queries/useSpotPrice' +import { useUserCollaterals } from 'hooks/queries/useUserCollaterals' import { ReactNode, useEffect, useState } from 'react' import useStore from 'store' import { State } from 'types/enums' @@ -155,8 +155,8 @@ export const CommonContainer = ({ children }: CommonContainerProps) => { useRedBank() useUserBalance() useUserIcns() - useUserDeposit() useUserDebt() + useUserCollaterals() useMarsOracle() useSpotPrice(MARS_SYMBOL) useUsdPrice() diff --git a/src/components/common/Layout/Layout.tsx b/src/components/common/Layout/Layout.tsx index d67f27e..818e0bc 100644 --- a/src/components/common/Layout/Layout.tsx +++ b/src/components/common/Layout/Layout.tsx @@ -1,9 +1,9 @@ import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector' import classNames from 'classnames' -import { Footer, Header, MobileNav } from 'components/common' +import { Footer, Header, MobileNav, TermsOfService } from 'components/common' import { FieldsNotConnected } from 'components/fields' import { RedbankNotConnected } from 'components/redbank' -import { SESSION_WALLET_KEY } from 'constants/appConstants' +import { SESSION_WALLET_KEY, TERMS_OF_SERVICE } from 'constants/appConstants' import { useAnimations } from 'hooks/data' import { useRouter } from 'next/router' import React, { useEffect } from 'react' @@ -14,6 +14,9 @@ type Props = { } export const Layout = ({ children }: Props) => { + const alreadyAcceptedTOS = localStorage.getItem(TERMS_OF_SERVICE) + const currentlyAcceptedROS = useStore((s) => s.acceptedTermsOfService) + const router = useRouter() const { status } = useWalletManager() useAnimations() @@ -35,6 +38,8 @@ export const Layout = ({ children }: Props) => { return (
+ {alreadyAcceptedTOS || currentlyAcceptedROS ? null : } +
diff --git a/src/components/common/TermsOfService/TermsOfService.module.scss b/src/components/common/TermsOfService/TermsOfService.module.scss new file mode 100644 index 0000000..accf301 --- /dev/null +++ b/src/components/common/TermsOfService/TermsOfService.module.scss @@ -0,0 +1,44 @@ +@import 'src/styles/master'; + +.container { + backdrop-filter: blur(rem-calc(50)); + background-color: rgba(0, 0, 0, 0.1); + position: fixed; + left: 0; + top: 0; + z-index: 1; + right: 0; + bottom: 0; + display: grid; + place-items: center; + + .card { + max-width: min(rem-calc(500), 95vw); + @include padding(2, 4, 4, 4); + + .subtitle { + text-align: center; + @include padding(4, 0, 0, 0); + } + + .checkbox { + display: grid; + grid-template-columns: auto auto; + align-items: baseline; + @include margin(4, 0); + } + + .btn { + @include margin(6, 0, 0); + width: 100%; + } + } +} + +@media only screen and (min-width: $bpXSmallLow) { + .container { + .card { + @include padding(2, 12, 12, 12); + } + } +} diff --git a/src/components/common/TermsOfService/TermsOfService.tsx b/src/components/common/TermsOfService/TermsOfService.tsx new file mode 100644 index 0000000..ca7b514 --- /dev/null +++ b/src/components/common/TermsOfService/TermsOfService.tsx @@ -0,0 +1,60 @@ +import classNames from 'classnames' +import { Button, Card, Checkbox } from 'components/common' +import { TERMS_OF_SERVICE } from 'constants/appConstants' +import { useState } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import useStore from 'store' +import { DocURL } from 'types/enums/docURL' + +import styles from './TermsOfService.module.scss' + +export const TermsOfService = () => { + const { t } = useTranslation() + const [isFirstAccepted, setIsFirstAccepted] = useState(false) + const [isSecondAccepted, setIsSecondAccepted] = useState(false) + + const onTermsConfirmed = () => { + if (!isFirstAccepted && !isSecondAccepted) return + + localStorage.setItem(TERMS_OF_SERVICE, 'true') + useStore.setState({ acceptedTermsOfService: true }) + } + + return ( +
+ +
+ + + Please check the boxes below to confirm your agreement to the{' '} + + + Mars Protocol Terms and Conditions + + +
+ setIsFirstAccepted(isChecked)} + text={t('common.termsOfService.term1')} + /> + setIsSecondAccepted(isChecked)} + text={t('common.termsOfService.term2')} + /> +
+ ) +} diff --git a/src/components/common/Tooltip/Apy.module.scss b/src/components/common/Tooltip/Apy.module.scss index c4e9970..7cd85d6 100644 --- a/src/components/common/Tooltip/Apy.module.scss +++ b/src/components/common/Tooltip/Apy.module.scss @@ -6,6 +6,23 @@ @include layoutTooltip; min-width: rem-calc(280); + .border { + @include devider20; + } + + .list { + padding-inline-start: 1rem; + font-style: italic; + @include padding(0, 0, 1); + @include typoXS; + + .listItem { + list-style-type: circle; + display: flex; + justify-content: space-between; + } + } + .item { @include padding(1, 0); display: flex; diff --git a/src/components/common/Tooltip/Apy.tsx b/src/components/common/Tooltip/Apy.tsx index 1725e0c..bf3f5d7 100644 --- a/src/components/common/Tooltip/Apy.tsx +++ b/src/components/common/Tooltip/Apy.tsx @@ -5,33 +5,45 @@ import { useTranslation } from 'react-i18next' import styles from './Apy.module.scss' -interface VaultRate { - total: number - borrow: number -} - interface Props { - apyData: VaultRate + apyData: ApyBreakdown | PositionApyBreakdown + borrowRate: number leverage: number } -export const Apy = ({ apyData, leverage }: Props) => { +export const Apy = ({ apyData, leverage, borrowRate }: Props) => { const { t } = useTranslation() - const totalApy = useMemo(() => apyData.total * leverage - apyData.borrow, [apyData, leverage]) - const leveragedApy = useMemo(() => apyData.total * leverage, [apyData, leverage]) + const totalApy = useMemo( + () => (apyData.total ?? 0) * leverage - borrowRate ?? 0, + [apyData, leverage], + ) + const leveragedApy = useMemo(() => (apyData.total ?? 0) * leverage, [apyData, leverage]) + const performanceFee = apyData.fees && apyData.fees[0].value > 0 ? apyData.fees[0] : null return (

{t('fields.apyBreakdown')}

+
+
+ {t('fields.vaultApy')} + + {formatValue(apyData.total ?? 0, 2, 2, true, false, '%', true)} + +
+
    + {apyData.apys + ?.filter((apy) => apy.value > 0.009) + .map((item, index) => ( +
  • + - {item.type} + {formatValue(item.value, 2, 2, true, false, '%', true)} +
  • + ))} +
+
{leverage > 1 && ( <> -
- {t('fields.vaultApy')} - - {formatValue(apyData.total, 2, 2, true, false, '%', true)} - -
{t('fields.leveragedApy', { @@ -42,11 +54,19 @@ export const Apy = ({ apyData, leverage }: Props) => { {formatValue(leveragedApy, 2, 2, true, false, '%', true)}
- {apyData.borrow > 0 && ( -
+ {borrowRate > 0 && ( +
{t('fields.borrowRateApy')} - {formatValue(apyData.borrow, 2, 2, true, '-', '%', true)} + {formatValue(borrowRate, 2, 2, true, '-', '%', true)} + +
+ )} + {performanceFee && ( +
+ {performanceFee.type} + + {formatValue(performanceFee.value, 2, 2, true, '-', '%', true)}
)} diff --git a/src/components/common/index.ts b/src/components/common/index.ts index e87dc89..49daa18 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -30,6 +30,7 @@ export { MobileNav } from './MobileNav/MobileNav' export { Notification } from './Notification/Notification' export { NumberInput } from './NumberInput/NumberInput' export { SVG } from './SVG/SVG' +export { TermsOfService } from './TermsOfService/TermsOfService' export { TextTooltip } from './TextTooltip/TextTooltip' export { Title } from './Title/Title' export { Toggle } from './Toggle/Toggle' diff --git a/src/components/fields/ActionsTooltip/components/EditContent.tsx b/src/components/fields/ActionsTooltip/components/EditContent.tsx index b9dcce7..ec8fcef 100644 --- a/src/components/fields/ActionsTooltip/components/EditContent.tsx +++ b/src/components/fields/ActionsTooltip/components/EditContent.tsx @@ -32,6 +32,7 @@ export const EditContent = (props: Props) => { denom: props.vault.denoms.primary, amount: primaryAmount.toString(), }} + maxDecimals={6} className={styles.marginRight} showSymbol /> @@ -43,6 +44,7 @@ export const EditContent = (props: Props) => { denom: props.vault.denoms.secondary, amount: secondaryAmount.toString(), }} + maxDecimals={6} className={styles.marginRight} showSymbol /> @@ -54,6 +56,7 @@ export const EditContent = (props: Props) => { denom: props.position.borrowDenom || props.vault.denoms.secondary, amount: borrowedAmount.toString(), }} + maxDecimals={6} className={styles.marginRight} showSymbol /> @@ -113,6 +116,7 @@ export const EditContent = (props: Props) => { amount: (difference / 2).toString(), }).toString(), }} + maxDecimals={6} className={styles.marginRight} showSymbol /> @@ -125,6 +129,7 @@ export const EditContent = (props: Props) => { amount: (difference / 2).toString(), }).toString(), }} + maxDecimals={6} className={styles.marginRight} showSymbol /> diff --git a/src/components/fields/ActionsTooltip/components/RepayContent.tsx b/src/components/fields/ActionsTooltip/components/RepayContent.tsx index 6d161be..93ced59 100644 --- a/src/components/fields/ActionsTooltip/components/RepayContent.tsx +++ b/src/components/fields/ActionsTooltip/components/RepayContent.tsx @@ -21,6 +21,7 @@ export const RepayContent = (props: Props) => { denom: props.vault.denoms.secondary, amount: props.repayAmount.toString(), }} + maxDecimals={6} showSymbol /> @@ -31,6 +32,7 @@ export const RepayContent = (props: Props) => { denom: props.vault.denoms.secondary, amount: props.repayAmount.toString(), }} + maxDecimals={6} showSymbol /> diff --git a/src/components/fields/ActionsTooltip/components/UnlockContent.tsx b/src/components/fields/ActionsTooltip/components/UnlockContent.tsx index 4ec4fe4..6cb4a50 100644 --- a/src/components/fields/ActionsTooltip/components/UnlockContent.tsx +++ b/src/components/fields/ActionsTooltip/components/UnlockContent.tsx @@ -26,6 +26,7 @@ export const UnlockContent = (props: Props) => { props.position.amounts.borrowedSecondary, ).toString(), }} + maxDecimals={6} showSymbol /> @@ -38,6 +39,7 @@ export const UnlockContent = (props: Props) => { denom: props.vault.denoms.primary, amount: props.position.amounts.primary.toString(), }} + maxDecimals={6} className={styles.marginRight} showSymbol /> @@ -49,6 +51,7 @@ export const UnlockContent = (props: Props) => { denom: props.vault.denoms.secondary, amount: props.position.amounts.secondary.toString(), }} + maxDecimals={6} showSymbol /> diff --git a/src/components/fields/ActiveVaultsTable/ActiveVaultsTable.tsx b/src/components/fields/ActiveVaultsTable/ActiveVaultsTable.tsx index 2114231..f67e7dd 100644 --- a/src/components/fields/ActiveVaultsTable/ActiveVaultsTable.tsx +++ b/src/components/fields/ActiveVaultsTable/ActiveVaultsTable.tsx @@ -52,13 +52,13 @@ export const ActiveVaultsTable = () => { const handleRowClick = (vault: ActiveVault) => { switch (vault.position.status) { case 'active': - router.push(`/farm/vault/${vault.address}/edit`) + router.push(`/farm/vault/${vault.address}/account/${vault.position.accountId}/edit`) return case 'unlocked': - router.push(`/farm/vault/${vault.address}/close`) + router.push(`/farm/vault/${vault.address}/account/${vault.position.accountId}/close`) return case 'unlocking': - router.push(`/farm/vault/${vault.address}/repay`) + router.push(`/farm/vault/${vault.address}/account/${vault.position.accountId}/repay`) return } } diff --git a/src/components/fields/ActiveVaultsTable/ActiveVaultsTableMobile.tsx b/src/components/fields/ActiveVaultsTable/ActiveVaultsTableMobile.tsx index f79a9e5..76cded5 100644 --- a/src/components/fields/ActiveVaultsTable/ActiveVaultsTableMobile.tsx +++ b/src/components/fields/ActiveVaultsTable/ActiveVaultsTableMobile.tsx @@ -88,10 +88,8 @@ export const ActiveVaultsTableMobile = () => { } tooltip={ } diff --git a/src/components/fields/ActiveVaultsTable/useActiveVaultsColumns.tsx b/src/components/fields/ActiveVaultsTable/useActiveVaultsColumns.tsx index 185951f..e01bf1b 100644 --- a/src/components/fields/ActiveVaultsTable/useActiveVaultsColumns.tsx +++ b/src/components/fields/ActiveVaultsTable/useActiveVaultsColumns.tsx @@ -259,10 +259,6 @@ export const useActiveVaultsColumns = () => { if (row.original.position.apy?.net !== null) { const apy = new BigNumber(row.original.position.apy.net).toNumber() - const apyData = { - total: row.original.apy || 0, - borrow: row.original.position.apy.borrow, - } return ( <> { } tooltip={ - + } /> @@ -374,7 +374,11 @@ export const useActiveVaultsColumns = () => { return ( diff --git a/src/components/fields/PositionInput/TokenInput/TokenInput.module.scss b/src/components/fields/PositionInput/TokenInput/TokenInput.module.scss index e2c3fd9..c0a07df 100644 --- a/src/components/fields/PositionInput/TokenInput/TokenInput.module.scss +++ b/src/components/fields/PositionInput/TokenInput/TokenInput.module.scss @@ -18,6 +18,7 @@ @include typoXXS; border: none; min-height: unset; + white-space: nowrap; &:hover, &:focus, diff --git a/src/components/fields/PositionInput/TokenInput/TokenInput.tsx b/src/components/fields/PositionInput/TokenInput/TokenInput.tsx index 1f6a673..e458b7d 100644 --- a/src/components/fields/PositionInput/TokenInput/TokenInput.tsx +++ b/src/components/fields/PositionInput/TokenInput/TokenInput.tsx @@ -1,4 +1,5 @@ import { Coin } from '@cosmjs/proto-signing' +import BigNumber from 'bignumber.js' import classNames from 'classnames' import { Button, DisplayCurrency, NumberInput } from 'components/common' import { findByDenom } from 'functions' @@ -9,6 +10,8 @@ import useStore from 'store' import styles from './TokenInput.module.scss' +BigNumber.config({ EXPONENTIAL_AT: [-24, 20] }) + interface Props { tokens: string[] input: Input @@ -70,7 +73,7 @@ export const TokenInput = (props: Props) => { color='quaternary' className={`xxsCaps faded ${styles.maxBtn}`} onClick={() => onValueEntered(maxAmount)} - text={`${props.maxAmountLabel}: ${maxAmount / 10 ** asset.decimals}`} + text={`${props.maxAmountLabel}: ${new BigNumber(maxAmount).shiftedBy(-1 * asset.decimals)}`} variant='transparent' />
diff --git a/src/components/fields/UnlockedNotification/UnlockedNotification.tsx b/src/components/fields/UnlockedNotification/UnlockedNotification.tsx index f65807e..a314d2c 100644 --- a/src/components/fields/UnlockedNotification/UnlockedNotification.tsx +++ b/src/components/fields/UnlockedNotification/UnlockedNotification.tsx @@ -20,7 +20,9 @@ export const UnlockedNotification = () => { if (!vaultsUnlocked.length) return null const exitVaultHandler = () => { - router.push(`/farm/vault/${vaultsUnlocked[0].address}/close`) + router.push( + `/farm/vault/${vaultsUnlocked[0].address}/account/${vaultsUnlocked[0].position.accountId}/close`, + ) } const unlockedContent = () => { diff --git a/src/configs/osmo-test-5.ts b/src/configs/osmo-test-5.ts index c4962ac..4ec4dd0 100644 --- a/src/configs/osmo-test-5.ts +++ b/src/configs/osmo-test-5.ts @@ -125,7 +125,7 @@ export const VAULT_CONFIGS: Vault[] = [ secondary: 'USDC.n', }, color: colors.usdc, - lockup: 86400 * 14, + lockup: 86400 * 1, provider: 'Apollo vault', description: { maxLeverage: 1.43, lpName: 'OSMO-USDC.n' }, ltv: { @@ -133,7 +133,12 @@ export const VAULT_CONFIGS: Vault[] = [ contract: 0.3, liq: 0.4, }, - apy: 0, + apy: { + apys: null, + fees: null, + total: null, + vaultAddress: '', + }, }, { address: 'osmo14lu7m4ganxs20258dazafrjfaulmfxruq9n0r0th90gs46jk3tuqwfkqwn', @@ -148,7 +153,7 @@ export const VAULT_CONFIGS: Vault[] = [ secondary: 'USDC.n', }, color: colors.usdc, - lockup: 86400 * 14, + lockup: 86400 * 7, provider: 'Apollo vault', description: { maxLeverage: 1.43, lpName: 'OSMO-USDC.n' }, ltv: { @@ -156,7 +161,12 @@ export const VAULT_CONFIGS: Vault[] = [ contract: 0.3, liq: 0.4, }, - apy: 0, + apy: { + apys: null, + fees: null, + total: null, + vaultAddress: '', + }, }, { address: 'osmo1fmq9hw224fgz8lk48wyd0gfg028kvvzggt6c3zvnaqkw23x68cws5nd5em', @@ -179,6 +189,11 @@ export const VAULT_CONFIGS: Vault[] = [ contract: 0.3, liq: 0.4, }, - apy: 0, + apy: { + apys: null, + fees: null, + total: null, + vaultAddress: '', + }, }, ] diff --git a/src/configs/osmosis-1.ts b/src/configs/osmosis-1.ts index 9f1192a..19f1f16 100644 --- a/src/configs/osmosis-1.ts +++ b/src/configs/osmosis-1.ts @@ -171,7 +171,12 @@ export const VAULT_CONFIGS: Vault[] = [ contract: 0.63, liq: 0.65, }, - apy: 0, + apy: { + apys: null, + fees: null, + total: null, + vaultAddress: '', + }, }, { address: 'osmo1jfmwayj8jqp9tfy4v4eks5c2jpnqdumn8x8xvfllng0wfes770qqp7jl4j', @@ -194,7 +199,12 @@ export const VAULT_CONFIGS: Vault[] = [ contract: 0.65, liq: 0.66, }, - apy: 0, + apy: { + apys: null, + fees: null, + total: null, + vaultAddress: '', + }, }, { address: 'osmo1a6tcf60pyz8qq2n532dzcs7s7sj8klcmra04tvaqympzcvxqg9esn7xz7l', @@ -217,6 +227,11 @@ export const VAULT_CONFIGS: Vault[] = [ contract: 0.61, liq: 0.625, }, - apy: 0, + apy: { + apys: null, + fees: null, + total: null, + vaultAddress: '', + }, }, ] diff --git a/src/constants/appConstants.ts b/src/constants/appConstants.ts index c48951d..a4fc8a3 100644 --- a/src/constants/appConstants.ts +++ b/src/constants/appConstants.ts @@ -37,3 +37,4 @@ export const FIELDS_TUTORIAL_KEY = 'fieldsHideTutorial' export const RED_BANK_TUTORIAL_KEY = 'redbankHideTutorial' export const DISPLAY_CURRENCY_KEY = 'displayCurrency' export const ENABLE_ANIMATIONS_KEY = 'enableAnimations' +export const TERMS_OF_SERVICE = 'termsOfService' diff --git a/src/constants/defaults.ts b/src/constants/defaults.ts index 91238ab..e98a95e 100644 --- a/src/constants/defaults.ts +++ b/src/constants/defaults.ts @@ -22,9 +22,12 @@ export const DEFAULT_POSITION: Position = { net: 0, }, apy: { - borrow: 5.2, - net: 7.7, + apys: null, + fees: null, total: 19, + borrow: 5.2, + net: 13.8, + vaultAddress: '', }, ltv: 0.5, currentLeverage: 1, diff --git a/src/functions/fields/getTokenValueFromCoins.ts b/src/functions/fields/getTokenValueFromCoins.ts index f5f9ecf..18db90b 100644 --- a/src/functions/fields/getTokenValueFromCoins.ts +++ b/src/functions/fields/getTokenValueFromCoins.ts @@ -12,7 +12,7 @@ export const getTokenValueFromCoins = (assets: Asset[], coins: Coin[] = []) => { return formatValue( convertedValue, 2, - 2, + asset.decimals, true, convertedValue >= 0.01 ? false : '>', ` ${asset.symbol}`, diff --git a/src/functions/queries/getDebtQuery.ts b/src/functions/queries/getDebtQuery.ts deleted file mode 100644 index 71ddd6e..0000000 --- a/src/functions/queries/getDebtQuery.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const getDebtQuery = (address: string) => { - return ` - { - user_debts: { user: "${address}" } - }` -} -1 diff --git a/src/functions/queries/getRedbankQuery.ts b/src/functions/queries/getRedbankQuery.ts index 0f28ceb..94e1836 100644 --- a/src/functions/queries/getRedbankQuery.ts +++ b/src/functions/queries/getRedbankQuery.ts @@ -3,7 +3,6 @@ import { getContractQuery, getIncentiveQuery, getMarketQuery, - getUserCollateralQuery, getUserIncentivesQuery, } from '.' @@ -47,14 +46,6 @@ export const getRedbankQuery = ( return `query RedbankQuery { ${REDBANK_WASM_KEY}: wasm { ${wasmQueries} - ${ - address && - getContractQuery( - 'collateral', - redBankContractAddress || '', - getUserCollateralQuery(address), - ) - } ${ address && getContractQuery( diff --git a/src/functions/queries/getUserCollateralQuery.ts b/src/functions/queries/getUserCollateralQuery.ts deleted file mode 100644 index 7e6aad9..0000000 --- a/src/functions/queries/getUserCollateralQuery.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const getUserCollateralQuery = (address: string) => { - return `{ - user_collaterals: { - user: "${address}" - } - }` -} diff --git a/src/functions/queries/index.ts b/src/functions/queries/index.ts index bd6b37d..33e399a 100644 --- a/src/functions/queries/index.ts +++ b/src/functions/queries/index.ts @@ -2,7 +2,6 @@ export { getBalanceQuery } from './getBalanceQuery' export { getConfigQuery } from './getConfigQuery' export { getContractQuery } from './getContractQuery' -export { getDebtQuery } from './getDebtQuery' export { getDepositDebtQuery } from './getDepositDebtQuery' export { getDepositsQuery } from './getDepositsQuery' export { getGlobalStateQuery } from './getGlobalStateQuery' @@ -13,6 +12,5 @@ export { getRedbankQuery } from './getRedbankQuery' export { getSnapshotQuery } from './getSnapshotQuery' export { getStateQuery } from './getStateQuery' export { getUncollaterisedLoanLimitQuery } from './getUncollaterisedLoanLimitQuery' -export { getUserCollateralQuery } from './getUserCollateralQuery' export { getUserIncentivesQuery } from './getUserIncentivesQuery' // @endindex diff --git a/src/hooks/data/useActiveVault.tsx b/src/hooks/data/useActiveVault.tsx index 11203b4..7a5a420 100644 --- a/src/hooks/data/useActiveVault.tsx +++ b/src/hooks/data/useActiveVault.tsx @@ -1,11 +1,11 @@ import { useMemo } from 'react' import useStore from 'store' -export const useActiveVault = (address: string) => { +export const useActiveVault = (accountId: string) => { const activeVaults = useStore((s) => s.activeVaults) return useMemo(() => { if (!activeVaults?.length) return - return activeVaults.find((activeVault) => activeVault.address === address) - }, [activeVaults, address]) + return activeVaults.find((activeVault) => activeVault.position.accountId === accountId) + }, [activeVaults, accountId]) } diff --git a/src/hooks/data/usePrice.tsx b/src/hooks/data/usePrice.tsx index 5e5108e..26cf132 100644 --- a/src/hooks/data/usePrice.tsx +++ b/src/hooks/data/usePrice.tsx @@ -1,6 +1,6 @@ import useStore from 'store' export const usePrice = (denom: string): number => { - const convertToBaseCurrency = useStore((s) => s.convertToBaseCurrency) - return convertToBaseCurrency({ denom, amount: '1' }) + const getExchangeRate = useStore((s) => s.getExchangeRate) + return getExchangeRate(denom) } diff --git a/src/hooks/queries/index.ts b/src/hooks/queries/index.ts index e5a2ae1..9cde6c1 100644 --- a/src/hooks/queries/index.ts +++ b/src/hooks/queries/index.ts @@ -14,7 +14,7 @@ export { useSpotPrice } from './useSpotPrice' export { useUnlockMessages } from './useUnlockMessages' export { useUsdPrice } from './useUsdPrice' export { useUserBalance } from './useUserBalance' +export { useUserCollaterals } from './useUserCollaterals' export { useUserDebt } from './useUserDebt' -export { useUserDeposit } from './useUserDeposit' export { useUserIcns } from './useUserIcns' // @endindex diff --git a/src/hooks/queries/useUserCollaterals.tsx b/src/hooks/queries/useUserCollaterals.tsx new file mode 100644 index 0000000..e8d1ca2 --- /dev/null +++ b/src/hooks/queries/useUserCollaterals.tsx @@ -0,0 +1,64 @@ +import { Coin } from '@cosmjs/stargate' +import { useQuery } from '@tanstack/react-query' +import useStore from 'store' +import { QUERY_KEYS } from 'types/enums/queryKeys' + +const QUERY_LIMIT = 10 + +export const useUserCollaterals = () => { + const userWalletAddress = useStore((s) => s.userWalletAddress) + const redbankContractAddress = useStore((s) => s.networkConfig?.contracts.redBank) + const client = useStore((s) => s.client) + + const resolveUserDeposits = (collaterals: UserCollateral[]): Coin[] => { + return collaterals.map((collateral) => { + return { + denom: collateral.denom, + amount: collateral.amount, + } + }) + } + + const getCollaterals = async ( + contract: string, + startAfter?: string, + ): Promise => { + if (!client) return [] + return client.cosmWasmClient.queryContractSmart(contract, { + user_collaterals: { + user: userWalletAddress, + limit: QUERY_LIMIT, + start_after: startAfter, + }, + }) + } + + useQuery( + [QUERY_KEYS.USER_COLLATERAL], + async () => { + let userCollateral: UserCollateral[] = [] + if (!redbankContractAddress) return userCollateral + + let isMoreCollaterals = true + + while (isMoreCollaterals) { + const collateral = await getCollaterals( + redbankContractAddress, + userCollateral[userCollateral.length - 1]?.denom || '', + ) + userCollateral = userCollateral.concat(collateral) + + if (collateral.length < QUERY_LIMIT) isMoreCollaterals = false + } + + const userDeposits: Coin[] = resolveUserDeposits(userCollateral) + useStore.setState({ userCollateral: userCollateral, userDeposits: userDeposits }) + return userCollateral + }, + { + enabled: !!redbankContractAddress && !!userWalletAddress && !!client, + staleTime: 30000, + refetchInterval: 30000, + }, + ) +} diff --git a/src/hooks/queries/useUserDebt.tsx b/src/hooks/queries/useUserDebt.tsx index 6f36cba..564c8ae 100644 --- a/src/hooks/queries/useUserDebt.tsx +++ b/src/hooks/queries/useUserDebt.tsx @@ -1,55 +1,67 @@ +import { Coin } from '@cosmjs/stargate' import { useQuery } from '@tanstack/react-query' -import { getContractQuery, getDebtQuery } from 'functions/queries' -import { gql, request } from 'graphql-request' import useStore from 'store' import { QUERY_KEYS } from 'types/enums/queryKeys' +const QUERY_LIMIT = 10 + export interface UserDebtData { - debts: { - debts: [ - { - denom: string - amount_scaled: string - amount: string - enabled: boolean - }, - ] - } + denom: string + amount_scaled: string + amount: string + enabled: boolean } -// ! Implement pagination. Currently there is a limit of 5 assets from the SC export const useUserDebt = () => { - const hiveUrl = useStore((s) => s.networkConfig?.hiveUrl) const userWalletAddress = useStore((s) => s.userWalletAddress) const redbankContractAddress = useStore((s) => s.networkConfig?.contracts.redBank) - const processUserDebtQuery = useStore((s) => s.processUserDebtQuery) + const client = useStore((s) => s.client) - const debtsQuery = getContractQuery( - 'debts', - redbankContractAddress || '', - getDebtQuery(userWalletAddress), - ) + const resolveDebtResponse = (debts: UserDebtData[]): Coin[] => { + return debts.map((debt) => { + return { + denom: debt.denom, + amount: debt.amount, + } + }) + } - useQuery( + const getDebts = async (contract: string, startAfter?: string): Promise => { + if (!client) return [] + return client.cosmWasmClient.queryContractSmart(contract, { + user_debts: { + user: userWalletAddress, + limit: QUERY_LIMIT, + start_after: startAfter, + }, + }) + } + + useQuery( [QUERY_KEYS.USER_DEBT], async () => { - return await request( - hiveUrl!, - gql` - query UserDebtQuery { - debts: wasm { - ${debtsQuery} - } - } - `, - ) + let userDebts: Coin[] = [] + if (!redbankContractAddress) return userDebts + + let isMoreDebts = true + + while (isMoreDebts) { + const debts = await getDebts( + redbankContractAddress, + userDebts[userDebts.length - 1]?.denom || '', + ) + userDebts = userDebts.concat(resolveDebtResponse(debts)) + + if (debts.length < QUERY_LIMIT) isMoreDebts = false + } + + useStore.setState({ userDebts: userDebts }) + return userDebts }, { - enabled: - !!hiveUrl && !!redbankContractAddress && !!processUserDebtQuery && !!userWalletAddress, + enabled: !!redbankContractAddress && !!userWalletAddress && !!client, staleTime: 30000, refetchInterval: 30000, - onSuccess: processUserDebtQuery, }, ) } diff --git a/src/hooks/queries/useUserDeposit.tsx b/src/hooks/queries/useUserDeposit.tsx deleted file mode 100644 index aa2ecb8..0000000 --- a/src/hooks/queries/useUserDeposit.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { getContractQuery, getDepositsQuery } from 'functions/queries' -import { gql, request } from 'graphql-request' -import useStore from 'store' -import { QUERY_KEYS } from 'types/enums/queryKeys' - -export interface UserDepositData { - deposits: { - deposits: - | [ - { - denom: string - amount_scaled: string - amount: string - enabled: boolean - }, - ] - } -} - -// ! Implement pagination. Currently there is a limit of 5 assets from the SC - -export const useUserDeposit = () => { - const hiveUrl = useStore((s) => s.networkConfig?.hiveUrl) - const userWalletAddress = useStore((s) => s.userWalletAddress) - const whitelistedAssets = useStore((s) => s.whitelistedAssets) - const redbankContractAddress = useStore((s) => s.networkConfig?.contracts.redBank) - const processUserDepositQuery = useStore((s) => s.processUserDepositQuery) - - useQuery( - [QUERY_KEYS.USER_DEPOSIT], - async () => { - return await request( - hiveUrl!, - gql` - query UserDepositQuery { - deposits: wasm { - ${getContractQuery( - 'deposits', - redbankContractAddress || '', - getDepositsQuery(userWalletAddress), - )} - } - } - `, - ) - }, - { - enabled: !!hiveUrl && !!redbankContractAddress && !!whitelistedAssets && !!userWalletAddress, - staleTime: 30000, - refetchInterval: 30000, - onSuccess: processUserDepositQuery, - }, - ) -} diff --git a/src/mocks/position.ts b/src/mocks/position.ts index 150cae0..cc80e85 100644 --- a/src/mocks/position.ts +++ b/src/mocks/position.ts @@ -22,9 +22,12 @@ export const position: Position = { net: 0, }, apy: { - borrow: 5.2, - net: 7.7, + apys: null, + fees: null, total: 19, + borrow: 5.2, + net: 13.8, + vaultAddress: '', }, ltv: 0.5, currentLeverage: 1, diff --git a/src/mocks/redBankData.ts b/src/mocks/redBankData.ts index f67c239..5800b8a 100644 --- a/src/mocks/redBankData.ts +++ b/src/mocks/redBankData.ts @@ -125,26 +125,6 @@ export const redBankData: RedBankData = { index: '0.000000143742920378', last_updated: 1678369620, }, - collateral: [ - { - denom: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', - amount_scaled: '2559593418324', - amount: '2564911', - enabled: true, - }, - { - denom: 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858', - amount_scaled: '20000000000000', - amount: '20105061', - enabled: true, - }, - { - denom: 'uosmo', - amount_scaled: '48000037051741', - amount: '48107901', - enabled: true, - }, - ], unclaimedRewards: '4679062', }, } diff --git a/src/mocks/vault.ts b/src/mocks/vault.ts index adeafec..164e3f2 100644 --- a/src/mocks/vault.ts +++ b/src/mocks/vault.ts @@ -1,6 +1,11 @@ export const vault: Vault = { address: 'test', - apy: 10, + apy: { + apys: null, + fees: null, + total: null, + vaultAddress: '', + }, color: 'test', denoms: { primary: 'OSMO', diff --git a/src/pages/farm/vault/[address]/close/index.tsx b/src/pages/farm/vault/[address]/account/[id]/close/index.tsx similarity index 94% rename from src/pages/farm/vault/[address]/close/index.tsx rename to src/pages/farm/vault/[address]/account/[id]/close/index.tsx index cb36860..3aee63e 100644 --- a/src/pages/farm/vault/[address]/close/index.tsx +++ b/src/pages/farm/vault/[address]/account/[id]/close/index.tsx @@ -11,7 +11,8 @@ const CloseVaultPosition = () => { const vaultConfigs = useStore((s) => s.vaultConfigs) const { mutate, data, isLoading, error } = useUpdateAccount() const vaultAddress = String(router.query.address) - const activeVault = useActiveVault(vaultAddress) + const accountId = String(router.query.id) + const activeVault = useActiveVault(accountId) const { closeActions, closeFee } = useClosePosition({ activeVault, isLoading: isLoading || !!data || !!error, diff --git a/src/pages/farm/vault/[address]/edit/EditVault.module.scss b/src/pages/farm/vault/[address]/account/[id]/edit/EditVault.module.scss similarity index 100% rename from src/pages/farm/vault/[address]/edit/EditVault.module.scss rename to src/pages/farm/vault/[address]/account/[id]/edit/EditVault.module.scss diff --git a/src/pages/farm/vault/[address]/edit/EditVault.tsx b/src/pages/farm/vault/[address]/account/[id]/edit/EditVault.tsx similarity index 98% rename from src/pages/farm/vault/[address]/edit/EditVault.tsx rename to src/pages/farm/vault/[address]/account/[id]/edit/EditVault.tsx index d3dbcf8..a5dfcf4 100644 --- a/src/pages/farm/vault/[address]/edit/EditVault.tsx +++ b/src/pages/farm/vault/[address]/account/[id]/edit/EditVault.tsx @@ -113,7 +113,9 @@ const EditVault = (props: Props) => { const handleUnlockClick = useCallback(() => { if (!unlockFee) return if (showDisclaimer) { - router.push(`/farm/vault/${props.activeVault.address}/unlock`) + router.push( + `/farm/vault/${props.activeVault.address}/account/${props.activeVault.position.accountId}/unlock`, + ) return } @@ -125,6 +127,7 @@ const EditVault = (props: Props) => { }) }, [ props.activeVault.address, + props.activeVault.position.accountId, showDisclaimer, router, unlockFee, diff --git a/src/pages/farm/vault/[address]/edit/index.tsx b/src/pages/farm/vault/[address]/account/[id]/edit/index.tsx similarity index 74% rename from src/pages/farm/vault/[address]/edit/index.tsx rename to src/pages/farm/vault/[address]/account/[id]/edit/index.tsx index dddc151..a866e03 100644 --- a/src/pages/farm/vault/[address]/edit/index.tsx +++ b/src/pages/farm/vault/[address]/account/[id]/edit/index.tsx @@ -7,8 +7,8 @@ import EditVault from './EditVault' const Edit = () => { const router = useRouter() - const vaultAddress = String(router.query.address) - const activeVault = useActiveVault(vaultAddress) + const accountId = String(router.query.id) + const activeVault = useActiveVault(accountId) if (!activeVault) return <> diff --git a/src/pages/farm/vault/[address]/repay/RepayVault.module.scss b/src/pages/farm/vault/[address]/account/[id]/repay/RepayVault.module.scss similarity index 100% rename from src/pages/farm/vault/[address]/repay/RepayVault.module.scss rename to src/pages/farm/vault/[address]/account/[id]/repay/RepayVault.module.scss diff --git a/src/pages/farm/vault/[address]/repay/RepayVault.tsx b/src/pages/farm/vault/[address]/account/[id]/repay/RepayVault.tsx similarity index 100% rename from src/pages/farm/vault/[address]/repay/RepayVault.tsx rename to src/pages/farm/vault/[address]/account/[id]/repay/RepayVault.tsx diff --git a/src/pages/farm/vault/[address]/repay/index.tsx b/src/pages/farm/vault/[address]/account/[id]/repay/index.tsx similarity index 75% rename from src/pages/farm/vault/[address]/repay/index.tsx rename to src/pages/farm/vault/[address]/account/[id]/repay/index.tsx index 442bf7a..f84c732 100644 --- a/src/pages/farm/vault/[address]/repay/index.tsx +++ b/src/pages/farm/vault/[address]/account/[id]/repay/index.tsx @@ -7,8 +7,8 @@ import RepayVault from './RepayVault' const Repay = () => { const router = useRouter() - const vaultAddress = String(router.query.address) - const activeVault = useActiveVault(vaultAddress) + const accountId = String(router.query.id) + const activeVault = useActiveVault(accountId) if (!activeVault) return <> diff --git a/src/pages/farm/vault/[address]/unlock/UnlockDisclaimer.module.scss b/src/pages/farm/vault/[address]/account/[id]/unlock/UnlockDisclaimer.module.scss similarity index 100% rename from src/pages/farm/vault/[address]/unlock/UnlockDisclaimer.module.scss rename to src/pages/farm/vault/[address]/account/[id]/unlock/UnlockDisclaimer.module.scss diff --git a/src/pages/farm/vault/[address]/unlock/index.tsx b/src/pages/farm/vault/[address]/account/[id]/unlock/index.tsx similarity index 96% rename from src/pages/farm/vault/[address]/unlock/index.tsx rename to src/pages/farm/vault/[address]/account/[id]/unlock/index.tsx index 65d98ec..021527c 100644 --- a/src/pages/farm/vault/[address]/unlock/index.tsx +++ b/src/pages/farm/vault/[address]/account/[id]/unlock/index.tsx @@ -12,8 +12,8 @@ import styles from './UnlockDisclaimer.module.scss' const Unlock = () => { const { t } = useTranslation() const router = useRouter() - const address = String(router.query.address) - const activeVault = useActiveVault(address) + const accountId = String(router.query.id) + const activeVault = useActiveVault(accountId) const { mutate: requestUnlock, data: unlockData, diff --git a/src/store/interfaces/common.interface.ts b/src/store/interfaces/common.interface.ts index c586fe6..11b48cf 100644 --- a/src/store/interfaces/common.interface.ts +++ b/src/store/interfaces/common.interface.ts @@ -43,6 +43,7 @@ export interface CommonSlice { networkConfig?: NetworkConfig otherAssets: Asset[] queryErrors: string[] + acceptedTermsOfService: boolean slippage: number tutorialSteps: { redbank: number; fields: number } userBalances: Coin[] diff --git a/src/store/interfaces/redBank.interface.ts b/src/store/interfaces/redBank.interface.ts index ebc3bcc..95d7580 100644 --- a/src/store/interfaces/redBank.interface.ts +++ b/src/store/interfaces/redBank.interface.ts @@ -1,6 +1,4 @@ import { Coin } from '@cosmjs/stargate' -import { UserDebtData } from 'hooks/queries/useUserDebt' -import { UserDepositData } from 'hooks/queries/useUserDeposit' import { State } from 'types/enums' export interface RedBankSlice { @@ -38,9 +36,5 @@ export interface RedBankSlice { // QUERY RELATED // ------------------ previousRedBankQueryData?: RedBankData - previousUserDebtQueryData?: UserDebtData - previousUserDepositQueryData?: UserDepositData processRedBankQuery: (data: RedBankData, whitelistedAssets: Asset[]) => void - processUserDebtQuery: (data: UserDebtData) => void - processUserDepositQuery: (data: UserDepositData) => void } diff --git a/src/store/interfaces/vaults.interface..ts b/src/store/interfaces/vaults.interface..ts index b13eb28..b808c6a 100644 --- a/src/store/interfaces/vaults.interface..ts +++ b/src/store/interfaces/vaults.interface..ts @@ -5,14 +5,14 @@ export interface VaultsSlice { availableVaults: Vault[] activeVaults: ActiveVault[] creditAccounts?: Positions[] - addAprToVaults: (aprs: AprData[]) => void + addApyToVaults: (apys: ApyBreakdown[]) => void getCreditAccounts: (options?: Options) => Promise vaultAssets?: VaultCoinsWithAddress[] getVaultAssets: (options?: Options) => Promise unlockTimes?: UnlockTimeWithAddress[] getUnlockTimes: (options?: Options) => Promise - aprs?: AprData[] | null - getAprs: (options?: Options) => Promise + apys?: ApyBreakdown[] | null + getApys: (options?: Options) => Promise caps?: VaultCapData[] getCaps: (options?: Options) => Promise lpTokens?: LpTokenWithAddress[] diff --git a/src/store/slices/common.ts b/src/store/slices/common.ts index 323bcb9..2c5c5f2 100644 --- a/src/store/slices/common.ts +++ b/src/store/slices/common.ts @@ -45,6 +45,7 @@ const commonSlice = ( marketDebts: [], otherAssets: [], queryErrors: [], + acceptedTermsOfService: false, slippage: 0.02, tutorialSteps: { redbank: 1, fields: 1 }, userBalances: [], @@ -65,8 +66,16 @@ const commonSlice = ( (exchangeRate) => exchangeRate.denom === coin.denom, )?.amount if (!exchangeRate) return 0 + const assets = [...get().whitelistedAssets, ...get().otherAssets] + const baseDecimals = get().baseAsset?.decimals ?? 0 + const coinDecimals = assets.find((currency) => currency.denom === coin.denom)?.decimals ?? 0 - return new BigNumber(coin.amount).times(exchangeRate).toNumber() + const additionalDecimals = coinDecimals - baseDecimals + + return new BigNumber(coin.amount) + .times(exchangeRate) + .shiftedBy(-1 * additionalDecimals) + .toNumber() }, convertValueToAmount: (coin: Coin) => { const exchangeRates = get().exchangeRates diff --git a/src/store/slices/redBank.ts b/src/store/slices/redBank.ts index 5fc1da5..e7378b6 100644 --- a/src/store/slices/redBank.ts +++ b/src/store/slices/redBank.ts @@ -2,8 +2,6 @@ import { Coin } from '@cosmjs/stargate' import { MARS_SYMBOL } from 'constants/appConstants' import { SECONDS_IN_YEAR } from 'constants/timeConstants' import { findByDenom } from 'functions' -import { UserDebtData } from 'hooks/queries/useUserDebt' -import { UserDepositData } from 'hooks/queries/useUserDeposit' import { demagnify, lookupDenomBySymbol } from 'libs/parse' import isEqual from 'lodash.isequal' import { RedBankSlice } from 'store/interfaces/redBank.interface' @@ -166,7 +164,6 @@ const redBankSlice = (set: NamedSet, get: GetState): RedBankSlice marketInfo, marketIncentiveInfo, previousRedBankQueryData: data, - userCollateral: data.rbwasmkey.collateral, userUnclaimedRewards, redBankState: State.READY, }) @@ -174,30 +171,6 @@ const redBankSlice = (set: NamedSet, get: GetState): RedBankSlice findCollateral: (denom: string) => { return get().userCollateral && get().userCollateral!.find((item) => item.denom === denom) }, - processUserDebtQuery: (data: UserDebtData) => { - if (isEqual(data, get().previousUserDebtQueryData)) return - - const debtsResponse = data.debts.debts - set({ - previousUserDebtQueryData: data, - userDebts: debtsResponse.map((debt) => { - return { denom: debt.denom, amount: debt.amount } - }), - }) - }, - processUserDepositQuery: (data: UserDepositData) => { - if (isEqual(data, get().previousUserDepositQueryData)) return - - const deposits = data.deposits.deposits - - if (!deposits) return - - const userDeposits = deposits.map((deposit) => ({ - denom: deposit.denom, - amount: deposit.amount, - })) - set({ previousUserDepositQueryData: data, userDeposits }) - }, }) export default redBankSlice diff --git a/src/store/slices/vaults.ts b/src/store/slices/vaults.ts index f1bf7f1..7ae5e2f 100644 --- a/src/store/slices/vaults.ts +++ b/src/store/slices/vaults.ts @@ -19,21 +19,29 @@ export const vaultsSlice = (set: NamedSet, get: GetState): VaultsS isLoading: false, availableVaults: [], activeVaults: [], - addAprToVaults: (aprs: AprData[]) => { + addApyToVaults: (apys: ApyBreakdown[]) => { const updatedAvailableVaults = get().availableVaults.map((availableVault) => { - const apr = - (aprs?.find((apr) => apr.contractAddress === availableVault.address)?.apr || 0) * 100 - availableVault.apy = convertAprToApy(apr, 365) + const apy = apys?.find((apy) => apy.vaultAddress === availableVault.address) + + if (!apy) return availableVault + availableVault.apy = apy return availableVault }) const updatedActiveVaults = get().activeVaults.map((activeVault) => { - const apr = (aprs?.find((apr) => apr.contractAddress === activeVault.address)?.apr || 0) * 100 - const apy = convertAprToApy(apr, 365) + const apy = apys?.find((apy) => apy.vaultAddress === activeVault.address) + + if (!apy) return activeVault + activeVault.apy = apy - activeVault.position.apy.total = apy - activeVault.position.apy.net = - apy * activeVault.position.currentLeverage - activeVault.position.apy.borrow + activeVault.position.apy.borrow + activeVault.position.apy = { + ...apy, + borrow: activeVault.position.apy.borrow, + net: + (apy.total || 0) * activeVault.position.currentLeverage - activeVault.position.apy.borrow, + } + return activeVault }) @@ -93,6 +101,7 @@ export const vaultsSlice = (set: NamedSet, get: GetState): VaultsS }, }), vaultAddress: lpToken.vaultAddress, + accountId: lpToken.accountId, } }) @@ -133,6 +142,7 @@ export const vaultsSlice = (set: NamedSet, get: GetState): VaultsS ) / 1e6, ), vaultAddress: creditAccount.vaults[0].vault.address, + accountId: creditAccount.account_id, } }) @@ -144,13 +154,14 @@ export const vaultsSlice = (set: NamedSet, get: GetState): VaultsS return newUnlockTimes }, - getAprs: async (options?: Options) => { - const aprs = get().aprs - if (aprs && !options?.refetch) { - get().addAprToVaults(aprs) + getApys: async (options?: Options) => { + const apys = get().apys + if (apys && !options?.refetch) { + get().addApyToVaults(apys) return null } + const vaultAddresses = get().vaultConfigs.map((vault) => vault.address) const networkConfig = get().networkConfig if (!networkConfig) return null @@ -158,31 +169,40 @@ export const vaultsSlice = (set: NamedSet, get: GetState): VaultsS const response = await fetch(networkConfig!.apolloAprUrl) if (response.ok) { - const data: FlatApr[] | NestedApr[] = await response.json() + const data: ApolloAprResponse[] = await response.json() - const newAprs = data.map((aprData) => { - try { - const apr = aprData as FlatApr - const aprTotal = apr.apr.reduce((prev, curr) => Number(curr.value) + prev, 0) - const feeTotal = apr.fees.reduce((prev, curr) => Number(curr.value) + prev, 0) + const filteredData = data.filter((aprData) => + vaultAddresses.includes(aprData.contract_address), + ) - const finalApr = aprTotal + feeTotal + const newApys: ApyBreakdown[] = filteredData.map((aprData) => { + const aprTotal = aprData.apr.aprs.reduce((prev, curr) => Number(curr.value) + prev, 0) + const feeTotal = aprData.apr.fees.reduce((prev, curr) => Number(curr.value) + prev, 0) + const finalApr = (aprTotal - feeTotal) * 100 + const finalApy = convertAprToApy(finalApr, 365) - return { contractAddress: aprData.contract_address, apr: finalApr } - } catch { - const apr = aprData as NestedApr - const aprTotal = apr.apr.aprs.reduce((prev, curr) => Number(curr.value) + prev, 0) - const feeTotal = apr.apr.fees.reduce((prev, curr) => Number(curr.value) + prev, 0) + const apys = aprData.apr.aprs.map((apr) => ({ + type: apr.type, + value: new BigNumber(apr.value).dividedBy(aprTotal).multipliedBy(finalApy).toNumber(), + })) - const finalApr = aprTotal + feeTotal - return { contractAddress: aprData.contract_address, apr: finalApr } + const fees = aprData.apr.fees.map((fee) => ({ + type: fee.type, + value: new BigNumber(fee.value).dividedBy(feeTotal).multipliedBy(finalApy).toNumber(), + })) + + return { + vaultAddress: aprData.contract_address, + total: finalApy, + apys, + fees, } }) set({ - aprs: newAprs, + apys: newApys, }) - get().addAprToVaults(newAprs) + get().addApyToVaults(newApys) } return null @@ -256,6 +276,7 @@ export const vaultsSlice = (set: NamedSet, get: GetState): VaultsS unlocked: amounts.unlocked, denom: vault?.denoms.lpToken || '', vaultAddress: creditAccount.vaults[0].vault.address, + accountId: creditAccount.account_id, } }) @@ -275,199 +296,218 @@ export const vaultsSlice = (set: NamedSet, get: GetState): VaultsS return Promise.all([vaultAssets, unlockTimes, caps]).then( ([vaultAssets, unlockTimes, caps]) => { const { activeVaults, availableVaults } = get().vaultConfigs.reduce( - (prev, curr) => { + (prev, vaultConfig) => { const lpTokens = get().lpTokens const creditAccounts = get().creditAccounts - const creditAccountPosition = creditAccounts?.find( - (position) => position.vaults[0].vault.address === curr.address, + const creditAccountPositions = creditAccounts?.filter( + (position) => position.vaults[0].vault.address === vaultConfig.address, ) - curr.apy = null + vaultConfig.apy = { + apys: null, + fees: null, + total: null, + vaultAddress: vaultConfig.address, + } - curr.vaultCap = caps?.find((cap) => cap.address === curr.address)?.vaultCap + vaultConfig.vaultCap = caps?.find( + (cap) => cap.address === vaultConfig.address, + )?.vaultCap // No position = available vault - if (!creditAccountPosition) { - prev.availableVaults.push(curr) + if (!creditAccountPositions?.length) { + prev.availableVaults.push(vaultConfig) return prev } - // Position = active vault - const primaryAndSecondaryAmount = vaultAssets.find( - (vaultAsset) => vaultAsset.vaultAddress === curr.address, - ) + creditAccountPositions.forEach((creditAccountPosition) => { + const primaryAndSecondaryAmount = vaultAssets.find( + (vaultAsset) => vaultAsset.accountId === creditAccountPosition.account_id, + ) - const vaultTokenAmounts = getAmountsFromActiveVault( - creditAccountPosition.vaults[0].amount, - ) + const vaultTokenAmounts = getAmountsFromActiveVault( + creditAccountPosition.vaults[0].amount, + ) - const lpTokenAmounts = lpTokens?.find( - (lpToken) => lpToken.vaultAddress === curr.address, - ) + const lpTokenAmounts = lpTokens?.find( + (lpToken) => lpToken.accountId === creditAccountPosition.account_id, + ) - if (!primaryAndSecondaryAmount || !vaultTokenAmounts || !lpTokenAmounts) { - prev.availableVaults.push(curr) - return prev - } - - let id: number | undefined - try { - id = (creditAccountPosition.vaults[0].amount as { locking: LockingVaultAmount }) - .locking.unlocking[0].id - } catch { - id = undefined - } - - // Should already filter out null values - const unlockTime = unlockTimes.find( - (unlockTime) => unlockTime?.vaultAddress === curr.address, - )?.unlockAtTimestamp - - const primaryAmount = Number( - findByDenom(primaryAndSecondaryAmount.coins, curr.denoms.primary)?.amount || 0, - ) - const secondaryAmount = Number( - findByDenom(primaryAndSecondaryAmount.coins, curr.denoms.secondary)?.amount || 0, - ) - - let primarySupplyAmount = 0 - let secondarySupplyAmount = 0 - let borrowedPrimaryAmount = 0 - let borrowedSecondaryAmount = 0 - - const debt = creditAccountPosition.debts[0] - - if (debt) { - if (debt.denom === curr.denoms.primary) { - borrowedPrimaryAmount = Number(debt.amount) - } else { - borrowedSecondaryAmount = Number(debt.amount) + if (!primaryAndSecondaryAmount || !vaultTokenAmounts || !lpTokenAmounts) { + prev.availableVaults.push(vaultConfig) + return prev } - } - const borrowedDenom = debt?.denom || '' + let id: number | undefined + try { + id = (creditAccountPosition.vaults[0].amount as { locking: LockingVaultAmount }) + .locking.unlocking[0].id + } catch { + id = undefined + } - if (borrowedDenom === curr.denoms.primary) { - if (borrowedPrimaryAmount > primaryAmount) { - const swapped = Math.round( - get().convertToBaseCurrency({ - denom: borrowedDenom, - amount: (borrowedPrimaryAmount - primaryAmount).toString(), - }), - ) + // Should already filter out null values + const unlockTime = unlockTimes.find( + (unlockTime) => unlockTime?.accountId === creditAccountPosition.account_id, + )?.unlockAtTimestamp - const rate = Number( - get().exchangeRates?.find((coin) => coin.denom === curr.denoms.secondary) - ?.amount ?? 0, - ) - primarySupplyAmount = 0 - secondarySupplyAmount = Math.floor(secondaryAmount - swapped / rate) + const primaryAmount = Number( + findByDenom(primaryAndSecondaryAmount.coins, vaultConfig.denoms.primary)?.amount || + 0, + ) + const secondaryAmount = Number( + findByDenom(primaryAndSecondaryAmount.coins, vaultConfig.denoms.secondary) + ?.amount || 0, + ) + + let primarySupplyAmount = 0 + let secondarySupplyAmount = 0 + let borrowedPrimaryAmount = 0 + let borrowedSecondaryAmount = 0 + + const debt = creditAccountPosition.debts[0] + + if (debt) { + if (debt.denom === vaultConfig.denoms.primary) { + borrowedPrimaryAmount = Number(debt.amount) + } else { + borrowedSecondaryAmount = Number(debt.amount) + } + } + + const borrowedDenom = debt?.denom || '' + + if (borrowedDenom === vaultConfig.denoms.primary) { + if (borrowedPrimaryAmount > primaryAmount) { + const swapped = Math.round( + get().convertToBaseCurrency({ + denom: borrowedDenom, + amount: (borrowedPrimaryAmount - primaryAmount).toString(), + }), + ) + + const rate = Number( + get().exchangeRates?.find((coin) => coin.denom === vaultConfig.denoms.secondary) + ?.amount ?? 0, + ) + primarySupplyAmount = 0 + secondarySupplyAmount = Math.floor(secondaryAmount - swapped / rate) + } else { + primarySupplyAmount = primaryAmount - borrowedPrimaryAmount + secondarySupplyAmount = secondaryAmount + } + } else if (borrowedDenom === vaultConfig.denoms.secondary) { + if (borrowedSecondaryAmount > secondaryAmount) { + const swapped = Math.round( + get().convertToBaseCurrency({ + denom: borrowedDenom, + amount: (borrowedSecondaryAmount - secondaryAmount).toString(), + }), + ) + const rate = Number( + get().exchangeRates?.find((coin) => coin.denom === vaultConfig.denoms.primary) + ?.amount ?? 0, + ) + secondarySupplyAmount = 0 + primarySupplyAmount = Math.floor(primaryAmount - swapped / rate) + } else { + secondarySupplyAmount = secondaryAmount - borrowedSecondaryAmount + primarySupplyAmount = primaryAmount + } } else { - primarySupplyAmount = primaryAmount - borrowedPrimaryAmount + primarySupplyAmount = primaryAmount secondarySupplyAmount = secondaryAmount } - } else if (borrowedDenom === curr.denoms.secondary) { - if (borrowedSecondaryAmount > secondaryAmount) { - const swapped = Math.round( - get().convertToBaseCurrency({ - denom: borrowedDenom, - amount: (borrowedSecondaryAmount - secondaryAmount).toString(), - }), - ) - const rate = Number( - get().exchangeRates?.find((coin) => coin.denom === curr.denoms.primary)?.amount ?? - 0, - ) - secondarySupplyAmount = 0 - primarySupplyAmount = Math.floor(primaryAmount - swapped / rate) - } else { - secondarySupplyAmount = secondaryAmount - borrowedSecondaryAmount - primarySupplyAmount = primaryAmount + + const borrowedAmount = Math.max(borrowedPrimaryAmount, borrowedSecondaryAmount) + + const convertToBaseCurrency = get().convertToBaseCurrency + const redBankAssets = get().redBankAssets + const primarySupplyValue = convertToBaseCurrency({ + denom: vaultConfig.denoms.primary, + amount: primarySupplyAmount.toString(), + }) + + const secondarySupplyValue = convertToBaseCurrency({ + denom: vaultConfig.denoms.secondary, + amount: secondarySupplyAmount.toString(), + }) + + const borrowedValue = convertToBaseCurrency({ + denom: borrowedDenom, + amount: borrowedAmount.toString(), + }) + + const values = { + primary: primarySupplyValue, + secondary: secondarySupplyValue, + borrowedPrimary: borrowedDenom === vaultConfig.denoms.primary ? borrowedValue : 0, + borrowedSecondary: + borrowedDenom === vaultConfig.denoms.secondary ? borrowedValue : 0, + net: primarySupplyValue + secondarySupplyValue, + total: primarySupplyValue + secondarySupplyValue + borrowedValue, } - } - const borrowedAmount = Math.max(borrowedPrimaryAmount, borrowedSecondaryAmount) + const leverage = getLeverageFromValues(values) - const convertToBaseCurrency = get().convertToBaseCurrency - const redBankAssets = get().redBankAssets - const primarySupplyValue = convertToBaseCurrency({ - denom: curr.denoms.primary, - amount: primarySupplyAmount.toString(), - }) + const borrowRate = + redBankAssets.find((asset) => asset.denom === borrowedDenom)?.borrowRate || 0 - const secondarySupplyValue = convertToBaseCurrency({ - denom: curr.denoms.secondary, - amount: secondarySupplyAmount.toString(), - }) + const trueBorrowRate = (leverage - 1) * borrowRate - const borrowedValue = convertToBaseCurrency({ - denom: borrowedDenom, - amount: borrowedAmount.toString(), - }) + const getPositionStatus = (unlockTime?: number) => { + if (!unlockTime) return 'active' - const values = { - primary: primarySupplyValue, - secondary: secondarySupplyValue, - borrowedPrimary: borrowedDenom === curr.denoms.primary ? borrowedValue : 0, - borrowedSecondary: borrowedDenom === curr.denoms.secondary ? borrowedValue : 0, - net: primarySupplyValue + secondarySupplyValue, - total: primarySupplyValue + secondarySupplyValue + borrowedValue, - } + const isUnlocked = moment(unlockTime).isBefore(new Date()) + if (isUnlocked) return 'unlocked' - const leverage = getLeverageFromValues(values) + return 'unlocking' + } - const borrowRate = - redBankAssets.find((asset) => asset.denom === borrowedDenom)?.borrowRate || 0 - - const trueBorrowRate = (leverage - 1) * borrowRate - - const getPositionStatus = (unlockTime?: number) => { - if (!unlockTime) return 'active' - - const isUnlocked = moment(unlockTime).isBefore(new Date()) - if (isUnlocked) return 'unlocked' - - return 'unlocking' - } - - const position: Position = { - id: id, - accountId: creditAccountPosition.account_id, - amounts: { - primary: primarySupplyAmount, - secondary: secondarySupplyAmount, - borrowedPrimary: borrowedDenom === curr.denoms.primary ? borrowedAmount : 0, - borrowedSecondary: borrowedDenom === curr.denoms.secondary ? borrowedAmount : 0, - lp: { - amount: vaultTokenAmounts.unlocking, - primary: Number( - primaryAndSecondaryAmount.coins.find( - (coin) => coin.denom === curr.denoms.primary, - )?.amount || 0, - ), - secondary: Number( - primaryAndSecondaryAmount.coins.find( - (coin) => coin.denom === curr.denoms.secondary, - )?.amount || 0, - ), + const position: Position = { + id: id, + accountId: creditAccountPosition.account_id, + amounts: { + primary: primarySupplyAmount, + secondary: secondarySupplyAmount, + borrowedPrimary: + borrowedDenom === vaultConfig.denoms.primary ? borrowedAmount : 0, + borrowedSecondary: + borrowedDenom === vaultConfig.denoms.secondary ? borrowedAmount : 0, + lp: { + amount: vaultTokenAmounts.unlocking, + primary: Number( + primaryAndSecondaryAmount.coins.find( + (coin) => coin.denom === vaultConfig.denoms.primary, + )?.amount || 0, + ), + secondary: Number( + primaryAndSecondaryAmount.coins.find( + (coin) => coin.denom === vaultConfig.denoms.secondary, + )?.amount || 0, + ), + }, + vault: vaultTokenAmounts.locked, }, - vault: vaultTokenAmounts.locked, - }, - values, - apy: { - total: null, - borrow: trueBorrowRate, - net: null, - }, - currentLeverage: leverage, - ltv: leverageToLtv(leverage), - ...(unlockTime ? { unlockAtTimestamp: unlockTime } : {}), - status: getPositionStatus(unlockTime), - borrowDenom: borrowedDenom, - } + values, + apy: { + vaultAddress: vaultConfig.address, + borrow: trueBorrowRate, + total: null, + net: null, + apys: null, + fees: null, + }, + currentLeverage: leverage, + ltv: leverageToLtv(leverage), + ...(unlockTime ? { unlockAtTimestamp: unlockTime } : {}), + status: getPositionStatus(unlockTime), + borrowDenom: borrowedDenom, + } - prev.activeVaults.push({ ...curr, position }) + prev.activeVaults.push({ ...vaultConfig, position }) + }) return prev }, @@ -478,7 +518,7 @@ export const vaultsSlice = (set: NamedSet, get: GetState): VaultsS ) set({ activeVaults, availableVaults, isLoading: false }) - get().getAprs(options) + get().getApys(options) }, ) }, diff --git a/src/types/enums/queryKeys.ts b/src/types/enums/queryKeys.ts index 1e902c3..94627e8 100644 --- a/src/types/enums/queryKeys.ts +++ b/src/types/enums/queryKeys.ts @@ -16,4 +16,5 @@ export enum QUERY_KEYS { PROVIDE_LIQUIDITY = 'provideLiquidity', UNLOCK_MESSAGE = 'unlockMessage', USD_PRICE = 'usdPrice', + USER_COLLATERAL = 'userCollateral', } diff --git a/src/types/interfaces/fields.d.ts b/src/types/interfaces/fields.d.ts index 0ff44c5..217354e 100644 --- a/src/types/interfaces/fields.d.ts +++ b/src/types/interfaces/fields.d.ts @@ -35,7 +35,7 @@ interface Vault { used: number max: number } - apy: number | null + apy: ApyBreakdown } interface Position { @@ -62,11 +62,7 @@ interface Position { total: number net: number } - apy: { - total: number | null - borrow: number - net: number | null - } + apy: PositionApyBreakdown ltv: number currentLeverage: number unlockAtTimestamp?: number @@ -88,16 +84,19 @@ interface LpTokenWithAddress { unlocked: string denom: string vaultAddress: string + accountId: string } interface VaultCoinsWithAddress { coins: Coin[] vaultAddress: string + accountId: string } interface UnlockTimeWithAddress { unlockAtTimestamp: number vaultAddress: string + accountId: string } interface FieldsAction { @@ -105,23 +104,26 @@ interface FieldsAction { values: string[] } -interface AprData { - contractAddress: string - apr: number +interface PositionApyBreakdown extends ApyBreakdown { + borrow: number + net: number | null } -interface FlatApr { - contract_address: string - apr: { type: string; value: number | string }[] - fees: { type: string; value: number | string }[] +interface ApyBreakdown { + vaultAddress: string + apys: { type: string; value: number }[] | null + fees: { type: string; value: number }[] | null + total: number | null } -interface NestedApr { +interface ApolloAprResponse { contract_address: string - apr: { - aprs: { type: string; value: number | string }[] - fees: { type: string; value: number | string }[] - } + apr: AprBreakdown +} + +interface AprBreakdown { + aprs: { type: string; value: number }[] + fees: { type: string; value: string | number }[] } interface VaultCapData { diff --git a/src/types/interfaces/redbank.d.ts b/src/types/interfaces/redbank.d.ts index f27c251..91069c9 100644 --- a/src/types/interfaces/redbank.d.ts +++ b/src/types/interfaces/redbank.d.ts @@ -21,7 +21,6 @@ interface RedBankData { stATOMMarketIncentive: MarketIncentive nUSDCMarket: Market nUSDCMarketIncentive: MarketIncentive - collateral: UserCollateral[] unclaimedRewards: string } }