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' import { useMemo, useState } from 'react' import Select from '@components/forms/Select' import Input from '@components/forms/Input' 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', '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'] 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', '~Current tier', 'Suggested tier', ]), ] : [] type FixedProperties = { symbol: string side: string } type DynamicProperties = { [key: string]: | { avg_price_impact: number p90: number p95: number 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, 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, target_amount: val.target_amount, }, } acc.push(newItem) } return acc }, [] as TransformedPis[]) const symbolToSuggestedPresetName = useMemo( () => (group ? getMidPriceImpacts(group.pis) : []), [group], ) .filter((x) => x.avg_price_impact_percent < 1) .reduce( (acc, val, index, pisFiltred) => { const avaPreests = LISTING_PRESETS if (!acc[val.symbol]) { acc[val.symbol] = Object.values(avaPreests).find( (x) => x.preset_target_amount <= 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 }, ) return (
{group ? (

Slippage setCurrentSearch(e.target.value)} >

{heads.map((x, i: number) => ( ))} {transformedPis?.map((row, idx: number) => { const banks = group?.banksMapByName?.get( apiNameToBankName(row.symbol), ) const bank = banks && banks[0] 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 ( {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 const isAboveLiqFee = (hasAssetWeight || borrowsEnabled) && isNumericValue && visibleValue > bank.liquidationFee.toNumber() * 100 const targetAmountVsDeposits = isBid && targetAmount <= notionalDeposits const targetAmountVsBorrows = isAsk && targetAmount <= notionalBorrows const targetAmountVsAssetWeightScale = isBid && collateralEnabled && uiBorrowWeightScaleStartQuote && targetAmount <= uiBorrowWeightScaleStartQuote const targetAmountVsLiabWeightScale = isAsk && collateralEnabled && uiDepositWeightScaleStartQuote && targetAmount <= uiDepositWeightScaleStartQuote return ( ) })} ) })}
{x}
{formatValue(visibleValue)}
{isNumericValue && (
{(targetAmountVsBorrows || targetAmountVsDeposits) && (
)} {(targetAmountVsAssetWeightScale || targetAmountVsLiabWeightScale) && (
)}
)}
{isBid && collateralEnabled && `${ bank && formatValue( bank ?.scaledInitAssetWeight(bank.price) .toNumber(), ) } / ${ bank && formatValue(bank.maintAssetWeight.toNumber()) }`} {isAsk && borrowsEnabled && `${ bank && formatValue( bank?.scaledInitLiabWeight(bank.price).toNumber(), ) } / ${ bank && formatValue(bank.maintLiabWeight.toNumber()) }`} {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 || '' : ''} {idx % 2 === 0 ? symbolToSuggestedPresetName[row.symbol] ? symbolToSuggestedPresetName[row.symbol] : 'C' : ''}

Annotation Key

Annotation Sell Buy
Red Text
{`liquidation fee < price impact && init or main asset weight > 0`}
{`liquidation fee < price impact && borrows enabled `}
{' '} {/* Fixed width and height */}
{`target amount <= notional amount of current deposit`}
{`target amount <= notional amount of current borrows `}
{' '} {/* Fixed width and height */}
{`target amount <= ui deposit weight scale start quote && main asset weight > 0`}
{`target amount <= ui borrows weight scale start quote && main asset weight > 0`}
) : (
Loading... make take up to 60 seconds
)}
) } export default RiskDashboard const apiNameToBankName = (val: string) => { if (val === 'ETH') { return 'ETH (Portal)' } if (val === '$WIF') { return 'WIF' } return val }