2023-11-28 15:10:39 -08:00
|
|
|
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'
|
2024-04-15 15:05:08 -07:00
|
|
|
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
|
2023-12-29 13:26:20 -08:00
|
|
|
import { useMemo, useState } from 'react'
|
2023-11-28 15:10:39 -08:00
|
|
|
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'
|
2023-11-28 15:10:39 -08:00
|
|
|
|
|
|
|
export async function getStaticProps({ locale }: { locale: string }) {
|
|
|
|
return {
|
|
|
|
props: {
|
|
|
|
...(await serverSideTranslations(locale, [
|
2023-12-06 19:54:53 -08:00
|
|
|
'account',
|
2023-12-02 03:55:19 -08:00
|
|
|
'close-account',
|
2023-11-28 15:10:39 -08:00
|
|
|
'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',
|
|
|
|
}
|
2023-11-28 15:10:39 -08:00
|
|
|
|
|
|
|
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',
|
2023-11-28 15:10:39 -08:00
|
|
|
]),
|
|
|
|
]
|
|
|
|
: []
|
|
|
|
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
|
2023-11-28 15:10:39 -08:00
|
|
|
}
|
|
|
|
| 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,
|
2023-11-28 15:10:39 -08:00
|
|
|
}
|
|
|
|
} 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,
|
2023-11-28 15:10:39 -08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
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) => {
|
2024-04-15 15:05:08 -07:00
|
|
|
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
|
|
|
|
2023-11-28 15:10:39 -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
|
2023-11-28 15:10:39 -08:00
|
|
|
|
2024-01-20 07:00:34 -08:00
|
|
|
const isAboveLiqFee =
|
|
|
|
(hasAssetWeight || borrowsEnabled) &&
|
|
|
|
isNumericValue &&
|
|
|
|
visibleValue > bank.liquidationFee.toNumber() * 100
|
2023-11-28 15:10:39 -08:00
|
|
|
|
2024-01-20 07:00:34 -08:00
|
|
|
const targetAmountVsDeposits =
|
|
|
|
isBid && targetAmount <= notionalDeposits
|
|
|
|
const targetAmountVsBorrows =
|
|
|
|
isAsk && targetAmount <= notionalBorrows
|
2023-11-28 15:10:39 -08:00
|
|
|
|
2024-01-20 07:00:34 -08:00
|
|
|
const targetAmountVsAssetWeightScale =
|
|
|
|
isBid &&
|
|
|
|
collateralEnabled &&
|
|
|
|
uiBorrowWeightScaleStartQuote &&
|
|
|
|
targetAmount <= uiBorrowWeightScaleStartQuote
|
2023-11-28 15:10:39 -08:00
|
|
|
|
2024-01-20 07:00:34 -08:00
|
|
|
const targetAmountVsLiabWeightScale =
|
|
|
|
isAsk &&
|
|
|
|
collateralEnabled &&
|
|
|
|
uiDepositWeightScaleStartQuote &&
|
|
|
|
targetAmount <= uiDepositWeightScaleStartQuote
|
2023-11-28 15:10:39 -08:00
|
|
|
|
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
|
2023-11-28 15:10:39 -08:00
|
|
|
</div>
|
2024-01-17 07:31:49 -08:00
|
|
|
)}
|
2023-11-28 15:10:39 -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'
|
|
|
|
}
|
2023-11-28 15:10:39 -08:00
|
|
|
return val
|
|
|
|
}
|