✨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:
parent
1abaddd4e5
commit
88a7c27a28
42
src/api/swap/getPools.ts
Normal file
42
src/api/swap/getPools.ts
Normal 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
|
||||
}
|
@ -1,24 +1,38 @@
|
||||
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 { CircularProgress } from 'components/CircularProgress'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import Divider from 'components/Divider'
|
||||
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 { formatAmountWithSymbol, formatPercent } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
buyAsset: Asset
|
||||
sellAsset: Asset
|
||||
borrowAmount: BigNumber
|
||||
borrowRate?: number | null
|
||||
buyAction: () => void
|
||||
buyAmount: BigNumber
|
||||
buyAsset: Asset
|
||||
buyButtonDisabled: boolean
|
||||
containerClassName?: string
|
||||
showProgressIndicator: boolean
|
||||
isMargin?: boolean
|
||||
borrowAmount: BigNumber
|
||||
estimatedFee: StdFee
|
||||
buyAction: () => void
|
||||
isMargin?: boolean
|
||||
liquidationPrice: number | null
|
||||
route: Route[]
|
||||
sellAmount: BigNumber
|
||||
sellAsset: Asset
|
||||
showProgressIndicator: boolean
|
||||
}
|
||||
|
||||
const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white'
|
||||
|
||||
export default function TradeSummary(props: Props) {
|
||||
@ -34,7 +48,34 @@ export default function TradeSummary(props: Props) {
|
||||
estimatedFee,
|
||||
showProgressIndicator,
|
||||
route,
|
||||
sellAmount,
|
||||
buyAmount,
|
||||
} = 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(() => {
|
||||
if (!route.length) return '-'
|
||||
@ -59,14 +100,9 @@ export default function TradeSummary(props: Props) {
|
||||
>
|
||||
<div className='flex flex-col flex-1 m-3'>
|
||||
<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 && (
|
||||
<>
|
||||
<div className={infoLineClasses}>
|
||||
<span className='opacity-40'>Borrowing</span>
|
||||
<SummaryLine label='Borrowing'>
|
||||
<FormattedNumber
|
||||
amount={borrowAmount.toNumber()}
|
||||
options={{
|
||||
@ -79,18 +115,45 @@ export default function TradeSummary(props: Props) {
|
||||
}}
|
||||
animate
|
||||
/>
|
||||
</div>
|
||||
<div className={infoLineClasses}>
|
||||
<span className='opacity-40'>Borrow Rate APY</span>
|
||||
</SummaryLine>
|
||||
<SummaryLine label='Borrow Rate APY'>
|
||||
<span>{formatPercent(borrowRate || 0)}</span>
|
||||
</div>
|
||||
</SummaryLine>
|
||||
<Divider className='my-2' />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={infoLineClasses}>
|
||||
<span className='opacity-40'>Route</span>
|
||||
<span>{parsedRoutes}</span>
|
||||
</div>
|
||||
<>
|
||||
<SummaryLine label='Liquidation Price'>
|
||||
<div className='flex h-2'>
|
||||
{isUpdatingLiquidationPrice ? (
|
||||
<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>
|
||||
<ActionButton
|
||||
disabled={buyButtonDisabled}
|
||||
@ -104,3 +167,16 @@ export default function TradeSummary(props: Props) {
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
@ -62,9 +62,9 @@ export default function SwapForm(props: Props) {
|
||||
const { autoLendEnabledAccountIds } = useAutoLend()
|
||||
const isAutoLendEnabled = account ? autoLendEnabledAccountIds.includes(account.id) : false
|
||||
const modal = useStore<string | null>((s) => s.fundAndWithdrawModal)
|
||||
|
||||
const { simulateTrade, removedLends, updatedAccount } = useUpdatedAccount(account)
|
||||
const throttledEstimateExactIn = useMemo(() => asyncThrottle(estimateExactIn, 250), [])
|
||||
const { simulateTrade, removedLends } = useUpdatedAccount(account)
|
||||
const { computeLiquidationPrice } = useHealthComputer(updatedAccount)
|
||||
|
||||
const borrowAsset = useMemo(
|
||||
() => borrowAssets.find(byDenom(sellAsset.denom)),
|
||||
@ -209,6 +209,11 @@ export default function SwapForm(props: Props) {
|
||||
[isRepayable, setAutoRepayChecked],
|
||||
)
|
||||
|
||||
const liquidationPrice = useMemo(
|
||||
() => computeLiquidationPrice(props.buyAsset.denom),
|
||||
[computeLiquidationPrice, props.buyAsset.denom],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setBuyAssetAmount(BN_ZERO)
|
||||
setSellAssetAmount(BN_ZERO)
|
||||
@ -379,6 +384,9 @@ export default function SwapForm(props: Props) {
|
||||
borrowAmount={borrowAmount}
|
||||
estimatedFee={estimatedFee}
|
||||
route={route}
|
||||
liquidationPrice={liquidationPrice}
|
||||
sellAmount={sellAssetAmount}
|
||||
buyAmount={buyAssetAmount}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
|
||||
import useAssetParams from 'hooks/useAssetParams'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
@ -22,6 +23,7 @@ import { SWAP_FEE_BUFFER } from 'utils/constants'
|
||||
import {
|
||||
BorrowTarget,
|
||||
compute_health_js,
|
||||
liquidation_price_js,
|
||||
max_borrow_estimate_js,
|
||||
max_swap_estimate_js,
|
||||
max_withdraw_estimate_js,
|
||||
@ -192,6 +194,25 @@ export default function useHealthComputer(account?: Account) {
|
||||
},
|
||||
[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 convertedHealth = BN(Math.log(healthFactor))
|
||||
.dividedBy(Math.log(3.5))
|
||||
@ -211,5 +232,6 @@ export default function useHealthComputer(account?: Account) {
|
||||
computeMaxBorrowAmount,
|
||||
computeMaxWithdrawAmount,
|
||||
computeMaxSwapAmount,
|
||||
computeLiquidationPrice,
|
||||
}
|
||||
}
|
||||
|
16
src/hooks/useSwapFee.ts
Normal file
16
src/hooks/useSwapFee.ts
Normal 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()
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import useSWR from 'swr'
|
||||
|
||||
import estimateExactIn from 'api/swap/estimateExactIn'
|
||||
@ -7,26 +6,25 @@ import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { SWAP_FEE_BUFFER } from 'utils/constants'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
const amountIn = 1000_000_000
|
||||
|
||||
export default function useSwapValueLoss(denomIn: string, denomOut: string) {
|
||||
export default function useSwapValueLoss(
|
||||
denomIn: string,
|
||||
denomOut: string,
|
||||
amount: number = 1_000_000,
|
||||
) {
|
||||
const denomInPrice = usePrice(denomIn)
|
||||
const denomOutPrice = usePrice(denomOut)
|
||||
|
||||
return useSWR(
|
||||
`swapValueLoss/${denomIn}/${denomOut}`,
|
||||
`swapValueLoss/${denomIn}/${denomOut}/${amount}`,
|
||||
async () => {
|
||||
const valueIn = denomInPrice.times(amountIn)
|
||||
const valueIn = denomInPrice.times(amount)
|
||||
const amountOut = await estimateExactIn(
|
||||
BNCoin.fromDenomAndBigNumber(denomIn, BN(amountIn)).toCoin(),
|
||||
BNCoin.fromDenomAndBigNumber(denomIn, BN(amount)).toCoin(),
|
||||
denomOut,
|
||||
)
|
||||
const valueOut = denomOutPrice.times(amountOut)
|
||||
|
||||
return BigNumber.max(
|
||||
valueIn.minus(valueOut).dividedBy(valueIn).decimalPlaces(6),
|
||||
0,
|
||||
).toNumber()
|
||||
return valueIn.minus(valueOut).dividedBy(valueIn).decimalPlaces(6).toNumber()
|
||||
},
|
||||
{
|
||||
fallbackData: SWAP_FEE_BUFFER,
|
||||
|
8
src/utils/health_computer/index.d.ts
vendored
8
src/utils/health_computer/index.d.ts
vendored
@ -37,6 +37,12 @@ export function max_swap_estimate_js(
|
||||
kind: SwapKind,
|
||||
slippage: Slippage,
|
||||
): string
|
||||
/**
|
||||
* @param {HealthComputer} c
|
||||
* @param {string} denom
|
||||
* @returns {string}
|
||||
*/
|
||||
export function liquidation_price_js(c: HealthComputer, denom: string): string
|
||||
export interface HealthComputer {
|
||||
kind: AccountKind
|
||||
positions: Positions
|
||||
@ -82,9 +88,9 @@ export interface InitOutput {
|
||||
g: number,
|
||||
h: number,
|
||||
) => void
|
||||
readonly liquidation_price_js: (a: number, b: number, c: number, d: number) => void
|
||||
readonly allocate: (a: number) => number
|
||||
readonly deallocate: (a: number) => void
|
||||
readonly requires_stargate: () => void
|
||||
readonly requires_iterator: () => void
|
||||
readonly interface_version_8: () => void
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number
|
||||
|
@ -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) {
|
||||
try {
|
||||
return f.apply(this, args)
|
||||
|
Binary file not shown.
2
src/utils/health_computer/index_bg.wasm.d.ts
vendored
2
src/utils/health_computer/index_bg.wasm.d.ts
vendored
@ -14,9 +14,9 @@ export function max_swap_estimate_js(
|
||||
g: number,
|
||||
h: number,
|
||||
): void
|
||||
export function liquidation_price_js(a: number, b: number, c: number, d: number): void
|
||||
export function allocate(a: number): number
|
||||
export function deallocate(a: number): void
|
||||
export function requires_stargate(): void
|
||||
export function requires_iterator(): void
|
||||
export function interface_version_8(): void
|
||||
export function __wbindgen_malloc(a: number, b: number): number
|
||||
|
Loading…
Reference in New Issue
Block a user