finish adding liquidation price (#649)

* finish adding liquidation price

* fix minor issues

* v2.0.6 and use feature flag for auto repay (#650)

* 🐛incorrect deposit cap utilization (#647)

* Initialize perps (#648)

* setup routing

* add basic perps interface

* small fix

* feat: listed dydx and AKT (#652)

* Deposit Cap and Utilization Fix (#654)

* fix: fixed the deposit cap and total supplied / utilization rate

* fix: fixed build

* fix: fixed build

* fix: avoid deposit cap usage over 100%

* refactor market data apy/ltv

* fix: fixed the withdraw from vaults modal

* tidy: refactor

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>

* stATOM and stOSMO TradingView support (#653)

* feat: listed dydx and AKT

* fix: removed theGraph support for now

* adjust liq price loading and null/0 values

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
Bob van der Helm 2023-11-27 12:07:00 +01:00 committed by GitHub
parent 1abaddd4e5
commit 88a7c27a28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 229 additions and 37 deletions

42
src/api/swap/getPools.ts Normal file
View File

@ -0,0 +1,42 @@
import { ENV } from 'constants/env'
const url = `${ENV.URL_REST}osmosis/gamm/v1beta1/pools/`
export default async function getPools(poolIds: string[]): Promise<Pool[]> {
const promises = poolIds.map((poolId) => fetch(url + poolId))
const responses = await Promise.all(promises)
return await Promise.all(responses.map(async (pool) => (await pool.json()).pool as Pool))
}
interface Pool {
'@type': string
address: string
future_pool_governor: string
id: string
pool_assets?: PoolAsset[]
pool_liquidity?: PoolLiquidity[]
pool_params: PoolParams
total_shares: TotalShares
total_weight: string
}
interface PoolAsset {
token: TotalShares
weight: string
}
interface PoolLiquidity {
amount: string
denom: string
}
interface TotalShares {
amount: string
denom: string
}
interface PoolParams {
exit_fee: string
smooth_weight_change_params: null
swap_fee: string
}

View File

@ -1,24 +1,38 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useMemo } from 'react' import debounce from 'lodash.debounce'
import React, { useEffect, useMemo, useState } from 'react'
import ActionButton from 'components/Button/ActionButton' import ActionButton from 'components/Button/ActionButton'
import { CircularProgress } from 'components/CircularProgress'
import DisplayCurrency from 'components/DisplayCurrency'
import Divider from 'components/Divider'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrice from 'hooks/usePrice'
import useSwapFee from 'hooks/useSwapFee'
import { BNCoin } from 'types/classes/BNCoin'
import { getAssetByDenom } from 'utils/assets' import { getAssetByDenom } from 'utils/assets'
import { formatAmountWithSymbol, formatPercent } from 'utils/formatters' import { formatAmountWithSymbol, formatPercent } from 'utils/formatters'
interface Props { interface Props {
buyAsset: Asset borrowAmount: BigNumber
sellAsset: Asset
borrowRate?: number | null borrowRate?: number | null
buyAction: () => void
buyAmount: BigNumber
buyAsset: Asset
buyButtonDisabled: boolean buyButtonDisabled: boolean
containerClassName?: string containerClassName?: string
showProgressIndicator: boolean
isMargin?: boolean
borrowAmount: BigNumber
estimatedFee: StdFee estimatedFee: StdFee
buyAction: () => void isMargin?: boolean
liquidationPrice: number | null
route: Route[] route: Route[]
sellAmount: BigNumber
sellAsset: Asset
showProgressIndicator: boolean
} }
const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white' const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white'
export default function TradeSummary(props: Props) { export default function TradeSummary(props: Props) {
@ -34,7 +48,34 @@ export default function TradeSummary(props: Props) {
estimatedFee, estimatedFee,
showProgressIndicator, showProgressIndicator,
route, route,
sellAmount,
buyAmount,
} = props } = props
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
const sellAssetPrice = usePrice(sellAsset.denom)
const swapFee = useSwapFee(route.map((r) => r.pool_id))
const [liquidationPrice, setLiquidationPrice] = useState<number | null>(null)
const [isUpdatingLiquidationPrice, setIsUpdatingLiquidationPrice] = useState(false)
const debouncedSetLiqPrice = useMemo(
() => debounce(setLiquidationPrice, 1000, { leading: false }),
[],
)
const minReceive = useMemo(() => {
return buyAmount.times(1 - swapFee).times(1 - slippage)
}, [buyAmount, slippage, swapFee])
useEffect(() => {
setIsUpdatingLiquidationPrice(true)
debouncedSetLiqPrice(props.liquidationPrice)
}, [debouncedSetLiqPrice, props.liquidationPrice])
useEffect(() => setIsUpdatingLiquidationPrice(false), [liquidationPrice])
const swapFeeValue = useMemo(() => {
return sellAssetPrice.times(swapFee).times(sellAmount)
}, [sellAmount, sellAssetPrice, swapFee])
const parsedRoutes = useMemo(() => { const parsedRoutes = useMemo(() => {
if (!route.length) return '-' if (!route.length) return '-'
@ -59,14 +100,9 @@ export default function TradeSummary(props: Props) {
> >
<div className='flex flex-col flex-1 m-3'> <div className='flex flex-col flex-1 m-3'>
<span className='mb-2 text-xs font-bold'>Summary</span> <span className='mb-2 text-xs font-bold'>Summary</span>
<div className={infoLineClasses}>
<span className='opacity-40'>Fees</span>
<span>{formatAmountWithSymbol(estimatedFee.amount[0])}</span>
</div>
{isMargin && ( {isMargin && (
<> <>
<div className={infoLineClasses}> <SummaryLine label='Borrowing'>
<span className='opacity-40'>Borrowing</span>
<FormattedNumber <FormattedNumber
amount={borrowAmount.toNumber()} amount={borrowAmount.toNumber()}
options={{ options={{
@ -79,18 +115,45 @@ export default function TradeSummary(props: Props) {
}} }}
animate animate
/> />
</div> </SummaryLine>
<div className={infoLineClasses}> <SummaryLine label='Borrow Rate APY'>
<span className='opacity-40'>Borrow Rate APY</span>
<span>{formatPercent(borrowRate || 0)}</span> <span>{formatPercent(borrowRate || 0)}</span>
</div> </SummaryLine>
<Divider className='my-2' />
</> </>
)} )}
<>
<div className={infoLineClasses}> <SummaryLine label='Liquidation Price'>
<span className='opacity-40'>Route</span> <div className='flex h-2'>
<span>{parsedRoutes}</span> {isUpdatingLiquidationPrice ? (
</div> <CircularProgress className='opacity-50' />
) : liquidationPrice === null || liquidationPrice === 0 ? (
'-'
) : (
<FormattedNumber
className='inline'
amount={liquidationPrice}
options={{ abbreviated: true, prefix: `${props.buyAsset.symbol} = $ ` }}
/>
)}
</div>
</SummaryLine>
<Divider className='my-2' />
</>
<SummaryLine label={`Swap fees (${(swapFee || 0.002) * 100}%)`}>
<DisplayCurrency coin={BNCoin.fromDenomAndBigNumber(sellAsset.denom, swapFeeValue)} />
</SummaryLine>
<SummaryLine label='Transaction fees'>
<span>{formatAmountWithSymbol(estimatedFee.amount[0])}</span>
</SummaryLine>
<SummaryLine label={`Min receive (${slippage * 100}% slippage)`}>
<FormattedNumber
amount={minReceive.toNumber()}
options={{ decimals: buyAsset.decimals, suffix: ` ${buyAsset.symbol}`, maxDecimals: 6 }}
/>
</SummaryLine>
<Divider className='my-2' />
<SummaryLine label='Route'>{parsedRoutes}</SummaryLine>
</div> </div>
<ActionButton <ActionButton
disabled={buyButtonDisabled} disabled={buyButtonDisabled}
@ -104,3 +167,16 @@ export default function TradeSummary(props: Props) {
</div> </div>
) )
} }
interface SummaryLineProps {
children: React.ReactNode
label: string
}
function SummaryLine(props: SummaryLineProps) {
return (
<div className={infoLineClasses}>
<span className='opacity-40'>{props.label}</span>
<span>{props.children}</span>
</div>
)
}

View File

@ -62,9 +62,9 @@ export default function SwapForm(props: Props) {
const { autoLendEnabledAccountIds } = useAutoLend() const { autoLendEnabledAccountIds } = useAutoLend()
const isAutoLendEnabled = account ? autoLendEnabledAccountIds.includes(account.id) : false const isAutoLendEnabled = account ? autoLendEnabledAccountIds.includes(account.id) : false
const modal = useStore<string | null>((s) => s.fundAndWithdrawModal) const modal = useStore<string | null>((s) => s.fundAndWithdrawModal)
const { simulateTrade, removedLends, updatedAccount } = useUpdatedAccount(account)
const throttledEstimateExactIn = useMemo(() => asyncThrottle(estimateExactIn, 250), []) const throttledEstimateExactIn = useMemo(() => asyncThrottle(estimateExactIn, 250), [])
const { simulateTrade, removedLends } = useUpdatedAccount(account) const { computeLiquidationPrice } = useHealthComputer(updatedAccount)
const borrowAsset = useMemo( const borrowAsset = useMemo(
() => borrowAssets.find(byDenom(sellAsset.denom)), () => borrowAssets.find(byDenom(sellAsset.denom)),
@ -209,6 +209,11 @@ export default function SwapForm(props: Props) {
[isRepayable, setAutoRepayChecked], [isRepayable, setAutoRepayChecked],
) )
const liquidationPrice = useMemo(
() => computeLiquidationPrice(props.buyAsset.denom),
[computeLiquidationPrice, props.buyAsset.denom],
)
useEffect(() => { useEffect(() => {
setBuyAssetAmount(BN_ZERO) setBuyAssetAmount(BN_ZERO)
setSellAssetAmount(BN_ZERO) setSellAssetAmount(BN_ZERO)
@ -379,6 +384,9 @@ export default function SwapForm(props: Props) {
borrowAmount={borrowAmount} borrowAmount={borrowAmount}
estimatedFee={estimatedFee} estimatedFee={estimatedFee}
route={route} route={route}
liquidationPrice={liquidationPrice}
sellAmount={sellAssetAmount}
buyAmount={buyAssetAmount}
/> />
</div> </div>
</> </>

View File

@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import useAssetParams from 'hooks/useAssetParams' import useAssetParams from 'hooks/useAssetParams'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
@ -22,6 +23,7 @@ import { SWAP_FEE_BUFFER } from 'utils/constants'
import { import {
BorrowTarget, BorrowTarget,
compute_health_js, compute_health_js,
liquidation_price_js,
max_borrow_estimate_js, max_borrow_estimate_js,
max_swap_estimate_js, max_swap_estimate_js,
max_withdraw_estimate_js, max_withdraw_estimate_js,
@ -192,6 +194,25 @@ export default function useHealthComputer(account?: Account) {
}, },
[healthComputer, slippage], [healthComputer, slippage],
) )
const computeLiquidationPrice = useCallback(
(denom: string) => {
if (!healthComputer) return null
try {
const asset = getAssetByDenom(denom)
if (!asset) return null
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
return BN(liquidation_price_js(healthComputer, denom))
.shiftedBy(-VALUE_SCALE_FACTOR)
.shiftedBy(decimalDiff)
.toNumber()
} catch (e) {
return null
}
},
[healthComputer],
)
const health = useMemo(() => { const health = useMemo(() => {
const convertedHealth = BN(Math.log(healthFactor)) const convertedHealth = BN(Math.log(healthFactor))
.dividedBy(Math.log(3.5)) .dividedBy(Math.log(3.5))
@ -211,5 +232,6 @@ export default function useHealthComputer(account?: Account) {
computeMaxBorrowAmount, computeMaxBorrowAmount,
computeMaxWithdrawAmount, computeMaxWithdrawAmount,
computeMaxSwapAmount, computeMaxSwapAmount,
computeLiquidationPrice,
} }
} }

16
src/hooks/useSwapFee.ts Normal file
View File

@ -0,0 +1,16 @@
import useSWR from 'swr'
import getSwapFees from 'api/swap/getPools'
import { BN_ZERO } from 'constants/math'
const STANDARD_SWAP_FEE = 0.002
export default function useSwapFee(poolIds: string[]) {
const { data: pools } = useSWR(`swapFees/${poolIds.join(',')}`, () => getSwapFees(poolIds))
if (!pools?.length) return STANDARD_SWAP_FEE
return pools
.reduce((acc, pool) => acc.plus(pool?.pool_params?.swap_fee || STANDARD_SWAP_FEE), BN_ZERO)
.toNumber()
}

View File

@ -1,4 +1,3 @@
import BigNumber from 'bignumber.js'
import useSWR from 'swr' import useSWR from 'swr'
import estimateExactIn from 'api/swap/estimateExactIn' import estimateExactIn from 'api/swap/estimateExactIn'
@ -7,26 +6,25 @@ import { BNCoin } from 'types/classes/BNCoin'
import { SWAP_FEE_BUFFER } from 'utils/constants' import { SWAP_FEE_BUFFER } from 'utils/constants'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
const amountIn = 1000_000_000 export default function useSwapValueLoss(
denomIn: string,
export default function useSwapValueLoss(denomIn: string, denomOut: string) { denomOut: string,
amount: number = 1_000_000,
) {
const denomInPrice = usePrice(denomIn) const denomInPrice = usePrice(denomIn)
const denomOutPrice = usePrice(denomOut) const denomOutPrice = usePrice(denomOut)
return useSWR( return useSWR(
`swapValueLoss/${denomIn}/${denomOut}`, `swapValueLoss/${denomIn}/${denomOut}/${amount}`,
async () => { async () => {
const valueIn = denomInPrice.times(amountIn) const valueIn = denomInPrice.times(amount)
const amountOut = await estimateExactIn( const amountOut = await estimateExactIn(
BNCoin.fromDenomAndBigNumber(denomIn, BN(amountIn)).toCoin(), BNCoin.fromDenomAndBigNumber(denomIn, BN(amount)).toCoin(),
denomOut, denomOut,
) )
const valueOut = denomOutPrice.times(amountOut) const valueOut = denomOutPrice.times(amountOut)
return BigNumber.max( return valueIn.minus(valueOut).dividedBy(valueIn).decimalPlaces(6).toNumber()
valueIn.minus(valueOut).dividedBy(valueIn).decimalPlaces(6),
0,
).toNumber()
}, },
{ {
fallbackData: SWAP_FEE_BUFFER, fallbackData: SWAP_FEE_BUFFER,

View File

@ -37,6 +37,12 @@ export function max_swap_estimate_js(
kind: SwapKind, kind: SwapKind,
slippage: Slippage, slippage: Slippage,
): string ): string
/**
* @param {HealthComputer} c
* @param {string} denom
* @returns {string}
*/
export function liquidation_price_js(c: HealthComputer, denom: string): string
export interface HealthComputer { export interface HealthComputer {
kind: AccountKind kind: AccountKind
positions: Positions positions: Positions
@ -82,9 +88,9 @@ export interface InitOutput {
g: number, g: number,
h: number, h: number,
) => void ) => void
readonly liquidation_price_js: (a: number, b: number, c: number, d: number) => void
readonly allocate: (a: number) => number readonly allocate: (a: number) => number
readonly deallocate: (a: number) => void readonly deallocate: (a: number) => void
readonly requires_stargate: () => void
readonly requires_iterator: () => void readonly requires_iterator: () => void
readonly interface_version_8: () => void readonly interface_version_8: () => void
readonly __wbindgen_malloc: (a: number, b: number) => number readonly __wbindgen_malloc: (a: number, b: number) => number

View File

@ -230,6 +230,30 @@ export function max_swap_estimate_js(c, from_denom, to_denom, kind, slippage) {
} }
} }
/**
* @param {HealthComputer} c
* @param {string} denom
* @returns {string}
*/
export function liquidation_price_js(c, denom) {
let deferred2_0
let deferred2_1
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16)
const ptr0 = passStringToWasm0(denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
const len0 = WASM_VECTOR_LEN
wasm.liquidation_price_js(retptr, addHeapObject(c), ptr0, len0)
var r0 = getInt32Memory0()[retptr / 4 + 0]
var r1 = getInt32Memory0()[retptr / 4 + 1]
deferred2_0 = r0
deferred2_1 = r1
return getStringFromWasm0(r0, r1)
} finally {
wasm.__wbindgen_add_to_stack_pointer(16)
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1)
}
}
function handleError(f, args) { function handleError(f, args) {
try { try {
return f.apply(this, args) return f.apply(this, args)

View File

@ -14,9 +14,9 @@ export function max_swap_estimate_js(
g: number, g: number,
h: number, h: number,
): void ): void
export function liquidation_price_js(a: number, b: number, c: number, d: number): void
export function allocate(a: number): number export function allocate(a: number): number
export function deallocate(a: number): void export function deallocate(a: number): void
export function requires_stargate(): void
export function requires_iterator(): void export function requires_iterator(): void
export function interface_version_8(): void export function interface_version_8(): void
export function __wbindgen_malloc(a: number, b: number): number export function __wbindgen_malloc(a: number, b: number): number