This commit is contained in:
Linkie Link 2023-06-07 10:32:21 +02:00
parent e3d0e194ce
commit 9d36f50856
No known key found for this signature in database
GPG Key ID: 5318B0F2564D38EA
58 changed files with 705 additions and 481 deletions

View File

@ -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,
},

View File

@ -1,7 +1,7 @@
{
"name": "mars",
"homepage": "./",
"version": "1.4.6",
"version": "1.4.7",
"license": "SEE LICENSE IN LICENSE FILE",
"private": false,
"scripts": {

View File

@ -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()

View File

@ -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 (
<div className={classNames('app', !enableAnimations && 'no-motion')}>
{alreadyAcceptedTOS || currentlyAcceptedROS ? null : <TermsOfService />}
<div className={backgroundClasses} id='bg' />
<Header />
<div className='appContainer'>

View File

@ -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);
}
}
}

View File

@ -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 (
<div className={styles.container}>
<Card title='Disclaimer' className={styles.card}>
<div className={classNames('xs', styles.subtitle)}>
<Trans i18nKey='common.termsOfService.intro'>
<span className={classNames('faded xs')}>
Please check the boxes below to confirm your agreement to the{' '}
</span>
<a
href={DocURL.TERMS_OF_SERVICE_URL}
rel='noreferrer'
target='_blank'
className={classNames('xs')}
>
Mars Protocol Terms and Conditions
</a>
</Trans>
</div>
<Checkbox
className={styles.checkbox}
onChecked={(isChecked) => setIsFirstAccepted(isChecked)}
text={t('common.termsOfService.term1')}
/>
<Checkbox
className={styles.checkbox}
onChecked={(isChecked) => setIsSecondAccepted(isChecked)}
text={t('common.termsOfService.term2')}
/>
<Button
onClick={onTermsConfirmed}
text={t('common.confirm')}
disabled={!isFirstAccepted || !isSecondAccepted}
className={styles.btn}
/>
</Card>
</div>
)
}

View File

@ -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;

View File

@ -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 (
<div className={styles.container}>
<p className='sub2'>{t('fields.apyBreakdown')}</p>
<div className={classNames(styles.border)}>
<div className={classNames(styles.item, styles.total)}>
<span className={styles.label}>{t('fields.vaultApy')}</span>
<span className={styles.value}>
{formatValue(apyData.total ?? 0, 2, 2, true, false, '%', true)}
</span>
</div>
<ul className={styles.list}>
{apyData.apys
?.filter((apy) => apy.value > 0.009)
.map((item, index) => (
<li className={styles.listItem} key={index}>
<span>- {item.type}</span>
<span>{formatValue(item.value, 2, 2, true, false, '%', true)}</span>
</li>
))}
</ul>
</div>
{leverage > 1 && (
<>
<div className={classNames(styles.item, styles.total, styles.border)}>
<span className={styles.label}>{t('fields.vaultApy')}</span>
<span className={styles.value}>
{formatValue(apyData.total, 2, 2, true, false, '%', true)}
</span>
</div>
<div className={styles.item}>
<span className={styles.label}>
{t('fields.leveragedApy', {
@ -42,11 +54,19 @@ export const Apy = ({ apyData, leverage }: Props) => {
{formatValue(leveragedApy, 2, 2, true, false, '%', true)}
</span>
</div>
{apyData.borrow > 0 && (
<div className={classNames(styles.item, styles.border)}>
{borrowRate > 0 && (
<div className={classNames(styles.item, !performanceFee && styles.border)}>
<span className={styles.label}>{t('fields.borrowRateApy')}</span>
<span className={styles.value}>
{formatValue(apyData.borrow, 2, 2, true, '-', '%', true)}
{formatValue(borrowRate, 2, 2, true, '-', '%', true)}
</span>
</div>
)}
{performanceFee && (
<div className={classNames(styles.item, styles.border)}>
<span className={styles.label}>{performanceFee.type}</span>
<span className={styles.value}>
{formatValue(performanceFee.value, 2, 2, true, '-', '%', true)}
</span>
</div>
)}

View File

@ -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'

View File

@ -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
/>

View File

@ -21,6 +21,7 @@ export const RepayContent = (props: Props) => {
denom: props.vault.denoms.secondary,
amount: props.repayAmount.toString(),
}}
maxDecimals={6}
showSymbol
/>
</li>
@ -31,6 +32,7 @@ export const RepayContent = (props: Props) => {
denom: props.vault.denoms.secondary,
amount: props.repayAmount.toString(),
}}
maxDecimals={6}
showSymbol
/>
</li>

View File

@ -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
/>
</>

View File

@ -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
}
}

View File

@ -88,10 +88,8 @@ export const ActiveVaultsTableMobile = () => {
}
tooltip={
<Apy
apyData={{
borrow: vault.position.apy.borrow,
total: vault.position.apy.total,
}}
apyData={vault.position.apy}
borrowRate={vault.position.apy.borrow}
leverage={vault.position.currentLeverage}
/>
}

View File

@ -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 (
<>
<TextTooltip
@ -276,7 +272,11 @@ export const useActiveVaultsColumns = () => {
</>
}
tooltip={
<Apy apyData={apyData} leverage={row.original.position.currentLeverage} />
<Apy
apyData={row.original.apy}
borrowRate={row.original.position.apy.borrow}
leverage={row.original.position.currentLeverage}
/>
}
/>
</>
@ -374,7 +374,11 @@ export const useActiveVaultsColumns = () => {
return (
<Button
onClick={() => router.push(`/farm/vault/${row.original.address}/${route}`)}
onClick={() =>
router.push(
`/farm/vault/${row.original.address}/account/${row.original.position.accountId}/${route}`,
)
}
color='quaternary'
prefix={prefix}
variant='round'

View File

@ -30,17 +30,16 @@ export const AvailableVaultsTableMobile = () => {
(asset) => asset.denom === vault.denoms.secondary,
)
const borrowRate = Math.min(
Number(primaryBorrowAsset?.borrowRate ?? 0),
Number(secondaryBorrowAsset?.borrowRate ?? 0),
Number(primaryBorrowAsset?.borrowRate || 1000),
Number(secondaryBorrowAsset?.borrowRate || 1000),
)
const maxBorrowRate = borrowRate * (ltvToLeverage(vault.ltv.contract) - 1)
const minAPY = new BigNumber(vault.apy || 0).toNumber()
const minAPY = new BigNumber(vault.apy.total || 0).toNumber()
const leverage = ltvToLeverage(vault.ltv.contract)
const maxAPY =
new BigNumber(minAPY).times(leverage).decimalPlaces(2).toNumber() - maxBorrowRate
const apyDataNoLev = { total: vault.apy || 0, borrow: 0 }
const apyDataLev = { total: vault.apy || 0, borrow: maxBorrowRate }
return (
<Link
key={`${vault.address}-${i}`}
@ -61,15 +60,16 @@ export const AvailableVaultsTableMobile = () => {
<span>
<TextTooltip
hideStyling
text={<AnimatedNumber amount={minAPY} suffix=' - ' />}
tooltip={<Apy apyData={apyDataNoLev} leverage={1} />}
text={<AnimatedNumber amount={Math.min(minAPY, maxAPY)} suffix=' - ' />}
tooltip={<Apy apyData={vault.apy} borrowRate={0} leverage={1} />}
/>
<TextTooltip
hideStyling
text={<AnimatedNumber amount={maxAPY} suffix='%' />}
text={<AnimatedNumber amount={Math.max(minAPY, maxAPY)} suffix='%' />}
tooltip={
<Apy
apyData={apyDataLev}
apyData={vault.apy}
borrowRate={maxBorrowRate}
leverage={ltvToLeverage(vault.ltv.contract)}
/>
}

View File

@ -103,18 +103,19 @@ export const useAvailableVaultsColumns = () => {
const maxBorrowRate = borrowRate * (ltvToLeverage(row.original.ltv.contract) - 1)
const minAPY = new BigNumber(row.original.apy).toNumber()
const minAPY = row.original.apy.total ?? 0
const maxAPY = new BigNumber(minAPY).times(maxLeverage).toNumber() - maxBorrowRate
const minDailyAPY = new BigNumber(convertApyToDailyApy(row.original.apy))
const minDailyAPY = new BigNumber(convertApyToDailyApy(minAPY))
.decimalPlaces(2)
.toNumber()
const maxDailyAPY = new BigNumber(minDailyAPY)
.times(maxLeverage)
.decimalPlaces(2)
.toNumber()
const apyDataNoLev = { total: row.original.apy || 0, borrow: 0 }
const apyDataLev = { total: row.original.apy || 0, borrow: maxBorrowRate }
const apyDataNoLev = { ...row.original.apy }
const apyDataLev = { ...row.original.apy }
return (
<>
@ -122,26 +123,37 @@ export const useAvailableVaultsColumns = () => {
hideStyling
text={
<AnimatedNumber
amount={Number(formatValue(minAPY, 2, 2, true, false, false, true))}
amount={Number(
formatValue(Math.min(minAPY, maxAPY), 2, 2, true, false, false, true),
)}
/>
}
tooltip={<Apy apyData={apyDataNoLev} leverage={1} />}
tooltip={<Apy apyData={apyDataNoLev} borrowRate={0} leverage={1} />}
/>
<span> - </span>
<TextTooltip
hideStyling
text={
<AnimatedNumber
amount={Number(formatValue(maxAPY, 2, 2, true, false, false, true))}
amount={Number(
formatValue(Math.max(minAPY, maxAPY), 2, 2, true, false, false, true),
)}
suffix='%'
/>
}
tooltip={
<Apy apyData={apyDataLev} leverage={ltvToLeverage(row.original.ltv.contract)} />
<Apy
apyData={apyDataLev}
borrowRate={maxBorrowRate}
leverage={ltvToLeverage(row.original.ltv.contract)}
/>
}
/>
<p className='s faded'>{`${minDailyAPY} - ${maxDailyAPY}%/${t('common.day')}`}</p>
<p className='s faded'>{`${Math.min(minDailyAPY, maxDailyAPY)} - ${Math.max(
minDailyAPY,
maxDailyAPY,
)}%/${t('common.day')}`}</p>
</>
)
},

View File

@ -97,7 +97,7 @@ export const BreakdownTable = (props: Props) => {
denom,
amount: amount.toString(),
}}
maxDecimals={2}
maxDecimals={6}
/>
)
}
@ -225,12 +225,7 @@ export const BreakdownTable = (props: Props) => {
)
const trueBorrowRate = borrowRate * (Number(currentLeverage) - 1)
const apy = (props.vault.apy || 0) * currentLeverage - trueBorrowRate
const apyData = {
total: props.vault.apy || 0,
borrow: trueBorrowRate,
}
const apy = (props.vault.apy.total || 0) * currentLeverage - trueBorrowRate
return (
<div className={containerClasses}>
@ -251,7 +246,13 @@ export const BreakdownTable = (props: Props) => {
abbreviated={false}
/>
}
tooltip={<Apy apyData={apyData} leverage={currentLeverage} />}
tooltip={
<Apy
apyData={props.vault.apy}
borrowRate={trueBorrowRate}
leverage={currentLeverage}
/>
}
/>
) : (
<Loading />

View File

@ -13,8 +13,8 @@ export const LiquidationNotification = () => {
const elligiblePositions = activeVaults.filter((vault) => vault.position.ltv > vault.ltv.contract)
const repayHandler = (address: string) => {
router.push(`/farm/vault/${address}/repay`)
const repayHandler = (address: string, accountId: string) => {
router.push(`/farm/vault/${address}/account/${accountId}/repay`)
return
}
@ -33,7 +33,12 @@ export const LiquidationNotification = () => {
})}
</span>
<Button
onClick={() => repayHandler(elligiblePositions[0].address)}
onClick={() =>
repayHandler(
elligiblePositions[0].address,
elligiblePositions[0].position.accountId,
)
}
color='tertiary'
text={t('fields.notifications.liquidation.single.button')}
></Button>

View File

@ -18,6 +18,7 @@
@include typoXXS;
border: none;
min-height: unset;
white-space: nowrap;
&:hover,
&:focus,

View File

@ -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'
/>
<div className={styles.input}>

View File

@ -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 = () => {

View File

@ -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: '',
},
},
]

View File

@ -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: '',
},
},
]

View File

@ -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'

View File

@ -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,

View File

@ -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}`,

View File

@ -1,7 +0,0 @@
export const getDebtQuery = (address: string) => {
return `
{
user_debts: { user: "${address}" }
}`
}
1

View File

@ -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(

View File

@ -1,7 +0,0 @@
export const getUserCollateralQuery = (address: string) => {
return `{
user_collaterals: {
user: "${address}"
}
}`
}

View File

@ -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

View File

@ -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])
}

View File

@ -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)
}

View File

@ -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

View File

@ -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<UserCollateral[]> => {
if (!client) return []
return client.cosmWasmClient.queryContractSmart(contract, {
user_collaterals: {
user: userWalletAddress,
limit: QUERY_LIMIT,
start_after: startAfter,
},
})
}
useQuery<UserCollateral[]>(
[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,
},
)
}

View File

@ -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<UserDebtData>(
const getDebts = async (contract: string, startAfter?: string): Promise<UserDebtData[]> => {
if (!client) return []
return client.cosmWasmClient.queryContractSmart(contract, {
user_debts: {
user: userWalletAddress,
limit: QUERY_LIMIT,
start_after: startAfter,
},
})
}
useQuery<Coin[]>(
[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,
},
)
}

View File

@ -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<UserDepositData>(
[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,
},
)
}

View File

@ -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,

View File

@ -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',
},
}

View File

@ -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',

View File

@ -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,

View File

@ -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,

View File

@ -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 <></>

View File

@ -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 <></>

View File

@ -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,

View File

@ -43,6 +43,7 @@ export interface CommonSlice {
networkConfig?: NetworkConfig
otherAssets: Asset[]
queryErrors: string[]
acceptedTermsOfService: boolean
slippage: number
tutorialSteps: { redbank: number; fields: number }
userBalances: Coin[]

View File

@ -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
}

View File

@ -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<Positions[]>
vaultAssets?: VaultCoinsWithAddress[]
getVaultAssets: (options?: Options) => Promise<VaultCoinsWithAddress[]>
unlockTimes?: UnlockTimeWithAddress[]
getUnlockTimes: (options?: Options) => Promise<UnlockTimeWithAddress[]>
aprs?: AprData[] | null
getAprs: (options?: Options) => Promise<null>
apys?: ApyBreakdown[] | null
getApys: (options?: Options) => Promise<null>
caps?: VaultCapData[]
getCaps: (options?: Options) => Promise<VaultCapData[]>
lpTokens?: LpTokenWithAddress[]

View File

@ -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

View File

@ -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<Store>, get: GetState<Store>): RedBankSlice
marketInfo,
marketIncentiveInfo,
previousRedBankQueryData: data,
userCollateral: data.rbwasmkey.collateral,
userUnclaimedRewards,
redBankState: State.READY,
})
@ -174,30 +171,6 @@ const redBankSlice = (set: NamedSet<Store>, get: GetState<Store>): 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

View File

@ -19,21 +19,29 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): 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<Store>, get: GetState<Store>): VaultsS
},
}),
vaultAddress: lpToken.vaultAddress,
accountId: lpToken.accountId,
}
})
@ -133,6 +142,7 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): VaultsS
) / 1e6,
),
vaultAddress: creditAccount.vaults[0].vault.address,
accountId: creditAccount.account_id,
}
})
@ -144,13 +154,14 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): 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<Store>, get: GetState<Store>): 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<Store>, get: GetState<Store>): 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<Store>, get: GetState<Store>): 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<Store>, get: GetState<Store>): VaultsS
)
set({ activeVaults, availableVaults, isLoading: false })
get().getAprs(options)
get().getApys(options)
},
)
},

View File

@ -16,4 +16,5 @@ export enum QUERY_KEYS {
PROVIDE_LIQUIDITY = 'provideLiquidity',
UNLOCK_MESSAGE = 'unlockMessage',
USD_PRICE = 'usdPrice',
USER_COLLATERAL = 'userCollateral',
}

View File

@ -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 {

View File

@ -21,7 +21,6 @@ interface RedBankData {
stATOMMarketIncentive: MarketIncentive
nUSDCMarket: Market
nUSDCMarketIncentive: MarketIncentive
collateral: UserCollateral[]
unclaimedRewards: string
}
}