mango-v4-ui/pages/dashboard/slippage.tsx

448 lines
16 KiB
TypeScript
Raw Normal View History

import useMangoGroup from 'hooks/useMangoGroup'
import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { DashboardNavbar } from '.'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import { PublicKey } from '@solana/web3.js'
import { formatNumericValue } from 'utils/numbers'
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
2023-12-29 13:26:20 -08:00
import { useMemo, useState } from 'react'
import Select from '@components/forms/Select'
import Input from '@components/forms/Input'
2023-12-29 13:26:20 -08:00
import {
LISTING_PRESETS,
getMidPriceImpacts,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
2023-12-02 03:55:19 -08:00
'close-account',
'common',
'notifications',
'onboarding',
'profile',
'search',
'settings',
'token',
'trade',
])),
},
}
}
const formatValue = (val: string | number | PublicKey) => {
if (val instanceof PublicKey || typeof val === 'object') {
return val.toString()
}
if (typeof val === 'string') {
if (val === 'ask') {
return 'Sell'
}
if (val === 'bid') {
return 'Buy'
}
return val
} else {
return formatNumericValue(val)
}
}
const RiskDashboard: NextPage = () => {
const { group } = useMangoGroup()
const [currentFilter, setCurrentFilter] = useState<
'avg_price_impact' | 'p90' | 'p95'
>('avg_price_impact')
const [currentSearch, setCurrentSearch] = useState('')
const filters = ['avg_price_impact', 'p90', 'p95']
2024-01-20 07:00:34 -08:00
const filterLabels = {
avg_price_impact: 'Average Price Impact',
p90: '90th Percentile',
p95: '95th Percentile',
}
const heads = group
? [
...new Set([
'Token',
'Side',
...group.pis.map((x) => formatValue(x.target_amount)),
'Init/Main Weight',
2024-01-04 04:47:46 -08:00
'~Current tier',
2023-12-29 13:26:20 -08:00
'Suggested tier',
]),
]
: []
type FixedProperties = {
symbol: string
side: string
}
type DynamicProperties = {
[key: string]:
| {
avg_price_impact: number
p90: number
p95: number
2023-12-29 13:26:20 -08:00
target_amount: number
}
| string
}
type TransformedPis = FixedProperties & DynamicProperties
const transformedPis = group?.pis
.filter((x) => x.symbol.toLowerCase().includes(currentSearch.toLowerCase()))
.reduce((acc, val) => {
const currentItem = acc.find(
(x) => x.symbol === val.symbol && x.side === val.side,
)
if (currentItem) {
currentItem['amount_' + val.target_amount] = {
avg_price_impact: val.avg_price_impact_percent,
p90: val.p90,
p95: val.p95,
2023-12-29 13:26:20 -08:00
target_amount: val.target_amount,
}
} else {
const newItem = {
symbol: val.symbol,
side: val.side,
['amount_' + val.target_amount]: {
avg_price_impact: val.avg_price_impact_percent,
p90: val.p90,
p95: val.p95,
2023-12-29 13:26:20 -08:00
target_amount: val.target_amount,
},
}
acc.push(newItem)
}
return acc
}, [] as TransformedPis[])
2024-01-04 04:47:46 -08:00
const symbolToSuggestedPresetName = useMemo(
2023-12-29 13:26:20 -08:00
() => (group ? getMidPriceImpacts(group.pis) : []),
[group],
)
.filter((x) => x.avg_price_impact_percent < 1)
.reduce(
(acc, val, index, pisFiltred) => {
const avaPreests = LISTING_PRESETS
2023-12-29 13:26:20 -08:00
if (!acc[val.symbol]) {
acc[val.symbol] =
2024-01-04 04:47:46 -08:00
Object.values(avaPreests).find(
2023-12-29 13:26:20 -08:00
(x) =>
2023-12-31 07:56:37 -08:00
x.preset_target_amount <=
2023-12-29 13:26:20 -08:00
pisFiltred
.filter((pis) => pis.symbol === val.symbol)
.sort(
(a, b) =>
b.avg_price_impact_percent - a.avg_price_impact_percent,
)[0].target_amount,
)?.preset_name || 'C'
}
return acc
},
{} as { [symbol: string]: string },
)
2023-12-31 07:57:45 -08:00
return (
2024-01-20 07:00:34 -08:00
<div className="col-span-12 w-full lg:col-span-8 lg:col-start-3">
2024-01-17 07:31:49 -08:00
<DashboardNavbar />
{group ? (
<div className="mt-4">
<div className="mb-4 ml-20 mr-20">
<p className="flex items-center space-x-4 text-th-fgd-4">
<span>Slippage</span>
<Select
value={currentFilter}
onChange={(filter) => setCurrentFilter(filter)}
className="w-full"
2024-01-20 07:00:34 -08:00
renderValue={(selected) =>
filterLabels[selected as keyof typeof filterLabels]
}
2024-01-17 07:31:49 -08:00
>
2024-01-20 07:00:34 -08:00
{filters.map((filter) => {
return (
<Select.Option key={filter} value={filter}>
<div className="flex w-full items-center justify-between">
{filterLabels[filter as keyof typeof filterLabels]}
</div>
</Select.Option>
)
})}
2024-01-17 07:31:49 -08:00
</Select>
<Input
suffix="Token"
type="text"
2024-01-20 07:00:34 -08:00
heightClass={'h-10'}
2024-01-17 07:31:49 -08:00
value={currentSearch}
onChange={(e) => setCurrentSearch(e.target.value)}
></Input>
</p>
</div>
2024-01-20 07:00:34 -08:00
<div className="w-full overflow-scroll" style={{ maxHeight: '70vh' }}>
<Table className="h-full">
<thead>
<TrHead
style={{ boxShadow: '1px -5px 1px #1d1924', zIndex: 19 }}
className="sticky top-0 border-t bg-th-bkg-2"
>
{heads.map((x, i: number) => (
<Th key={x} xBorder={i != 0} className={`text-left`}>
{x}
</Th>
))}
</TrHead>
</thead>
<tbody>
{transformedPis?.map((row, idx: number) => {
const banks = group?.banksMapByName?.get(
apiNameToBankName(row.symbol),
)
const bank = banks && banks[0]
2024-01-08 08:50:13 -08:00
2024-01-20 07:00:34 -08:00
const borrowsEnabled = bank?.reduceOnly === 0
const hasAssetWeight =
bank &&
(bank.initAssetWeight.toNumber() > 0 ||
bank.maintAssetWeight.toNumber() > 0)
const isBid = row.side === 'bid'
const isAsk = row.side === 'ask'
const collateralEnabled = bank?.maintAssetWeight.isPos()
return (
<TrBody
key={idx}
className="h-10 text-xs md:hover:bg-th-bkg-2"
>
{Object.entries(row).map(([key, val], valIdx) => {
const visibleValue =
typeof val === 'string' ? val : val[currentFilter]
const isNumericValue = typeof visibleValue === 'number'
const targetAmount =
(key.includes('amount_') &&
Number(key.replace('amount_', ''))) ||
0
const uiBorrowWeightScaleStartQuote =
bank &&
toUiDecimals(bank.borrowWeightScaleStartQuote, 6)
const uiDepositWeightScaleStartQuote =
bank &&
toUiDecimals(bank.depositWeightScaleStartQuote, 6)
const notionalDeposits =
(bank && bank!.uiDeposits() * bank!.uiPrice) || 0
const notionalBorrows =
(bank && bank!.uiBorrows() * bank!.uiPrice) || 0
2024-01-20 07:00:34 -08:00
const isAboveLiqFee =
(hasAssetWeight || borrowsEnabled) &&
isNumericValue &&
visibleValue > bank.liquidationFee.toNumber() * 100
2024-01-20 07:00:34 -08:00
const targetAmountVsDeposits =
isBid && targetAmount <= notionalDeposits
const targetAmountVsBorrows =
isAsk && targetAmount <= notionalBorrows
2024-01-20 07:00:34 -08:00
const targetAmountVsAssetWeightScale =
isBid &&
collateralEnabled &&
uiBorrowWeightScaleStartQuote &&
targetAmount <= uiBorrowWeightScaleStartQuote
2024-01-20 07:00:34 -08:00
const targetAmountVsLiabWeightScale =
isAsk &&
collateralEnabled &&
uiDepositWeightScaleStartQuote &&
targetAmount <= uiDepositWeightScaleStartQuote
2024-01-20 07:00:34 -08:00
return (
<Td
xBorder={valIdx != 0}
key={valIdx}
className={`!py-3
${valIdx == 0 ? 'z-1 sticky left-0 bg-th-bkg-2' : ''}
${isAboveLiqFee ? 'text-th-error' : ''}`}
>
<div className="flex">
<div className="mr-2 h-full">
{formatValue(visibleValue)}
2024-01-17 07:31:49 -08:00
</div>
2024-01-20 07:00:34 -08:00
{isNumericValue && (
<div className="ml-auto flex w-4">
{(targetAmountVsBorrows ||
targetAmountVsDeposits) && (
<div className="w-2 bg-[#ffff99]"></div>
)}
{(targetAmountVsAssetWeightScale ||
targetAmountVsLiabWeightScale) && (
<div className="w-2 bg-[#0066ff]"></div>
)}
</div>
)}
</div>
</Td>
)
})}
<Td xBorder>
{isBid &&
collateralEnabled &&
`${
bank &&
formatValue(
bank
?.scaledInitAssetWeight(bank.price)
.toNumber(),
)
} / ${
bank &&
formatValue(bank.maintAssetWeight.toNumber())
}`}
2024-01-17 07:31:49 -08:00
2024-01-20 07:00:34 -08:00
{isAsk &&
borrowsEnabled &&
`${
bank &&
formatValue(
bank?.scaledInitLiabWeight(bank.price).toNumber(),
)
} / ${
bank && formatValue(bank.maintLiabWeight.toNumber())
}`}
</Td>
<Td>
{idx % 2 === 0 && bank
? Object.values(LISTING_PRESETS).find((x) => {
return x.initLiabWeight.toFixed(1) === '1.8'
? x.initLiabWeight.toFixed(1) ===
bank?.initLiabWeight
.toNumber()
.toFixed(1) &&
x.reduceOnly === bank.reduceOnly
: x.initLiabWeight.toFixed(1) ===
bank?.initLiabWeight.toNumber().toFixed(1)
})?.preset_name || ''
: ''}
</Td>
<Td xBorder>
{idx % 2 === 0
2024-01-17 07:31:49 -08:00
? symbolToSuggestedPresetName[row.symbol]
2024-01-20 07:00:34 -08:00
? symbolToSuggestedPresetName[row.symbol]
: 'C'
: ''}
</Td>
</TrBody>
)
})}
</tbody>
</Table>
</div>
<div className="mb-10 ml-20 mr-20 mt-1">
<span>
<h4 className="border-th-bkg-3 px-6 py-4">Annotation Key</h4>
</span>
<Table className="h-full text-xs">
<thead>
<TrHead
style={{ boxShadow: '1px -5px 1px #1d1924', zIndex: 19 }}
className=" bg-th-bkg-2"
>
<Th xBorder className={`text-left`}>
Annotation
</Th>
<Th xBorder className={`text-left`}>
Sell
</Th>
<Th xBorder className={`text-left`}>
Buy
</Th>
</TrHead>
</thead>
<tbody>
<TrBody className="h-10">
<Td>
<div className="flex text-th-error">Red Text</div>
</Td>
<Td>
<div className="flex">
{`liquidation fee < price impact && init or main asset weight > 0`}
</div>
</Td>
<Td>
<div className="flex">
{`liquidation fee < price impact && borrows enabled `}
</div>
</Td>
</TrBody>
<TrBody className="h-10">
<Td>
<div className="flex">
<div className="flex">
<div className="mr-2 h-full"></div>
<div className="ml-auto flex items-center">
<div className="h-5 w-2 bg-[#ffff99]"></div>{' '}
{/* Fixed width and height */}
</div>
</div>
</div>
</Td>
<Td>
<div className="flex">
{`target amount <= notional amount of current deposit`}
</div>
</Td>
<Td>
<div className="flex">
{`target amount <= notional amount of current borrows `}
</div>
</Td>
</TrBody>
<TrBody className="h-10">
<Td>
<div className="flex">
<div className="flex">
<div className="mr-2 h-full"></div>
<div className="ml-auto flex items-center">
<div className="h-5 w-2 bg-[#0066ff]"></div>{' '}
{/* Fixed width and height */}
</div>
</div>
</div>
</Td>
<Td>
<div className="flex">
{`target amount <= ui deposit weight scale start quote && main asset weight > 0`}
</div>
</Td>
<Td>
<div className="flex">
{`target amount <= ui borrows weight scale start quote && main asset weight > 0`}
</div>
</Td>
</TrBody>
</tbody>
</Table>
</div>
2024-01-17 07:31:49 -08:00
</div>
) : (
<div className="mt-8 w-full text-center">
Loading... make take up to 60 seconds
</div>
2024-01-17 07:31:49 -08:00
)}
</div>
)
}
export default RiskDashboard
const apiNameToBankName = (val: string) => {
if (val === 'ETH') {
return 'ETH (Portal)'
}
2024-01-08 08:50:13 -08:00
if (val === '$WIF') {
return 'WIF'
}
return val
}