mango-v4-ui/pages/api/tokentiers.ts

243 lines
7.6 KiB
TypeScript

import type { NextApiRequest, NextApiResponse } from 'next'
import { MANGO_V4_ID, MangoClient } from '@blockworks-foundation/mango-v4'
import { AnchorProvider, Wallet } from '@coral-xyz/anchor'
import { Connection, PublicKey, Keypair } from '@solana/web3.js'
import {
LISTING_PRESETS,
getMidPriceImpacts,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
interface TokenDetails {
reduceOnly?: number
maxBelow1Percent: { amount: number; preset: string }
maxBelow2Percent: { amount: number; preset: string }
initAssetWeight?: string
maintAssetWeight?: string
maintLiabWeight?: string
initLiabWeight?: string
mint?: string
currentTier?: string
collateralFeesPerDay?: number
}
interface CurrentTier {
name: string
reduceOnly?: number
maxBelow1PercentAmount?: number
maxBelow1PercentPreset?: string
maxBelow2PercentAmount?: number
maxBelow2PercentPreset?: string
initAssetWeight?: string
maintAssetWeight?: string
maintLiabWeight?: string
initLiabWeight?: string
mint?: string
currentTier?: string
collateralFeesPerDay?: number
}
export const TRITON_DEDICATED_URL = process.env.NEXT_PUBLIC_TRITON_TOKEN
? `https://mango.rpcpool.com/${process.env.NEXT_PUBLIC_TRITON_TOKEN}`
: 'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88'
type CurrentTiersResponse = CurrentTier[]
async function buildClient(rpcUrl: string): Promise<MangoClient | undefined> {
try {
const clientKeypair = new Keypair()
const options = AnchorProvider.defaultOptions()
const connection = new Connection(rpcUrl, options)
const clientWallet = new Wallet(clientKeypair)
const clientProvider = new AnchorProvider(connection, clientWallet, options)
return MangoClient.connect(
clientProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{
idsSource: 'api',
},
)
} catch (e) {
console.log(e)
}
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<
CurrentTiersResponse | { error: string; details: string }
>,
) {
const { body } = req
console.log(body)
if (!('rpcUrl' in body)) {
throw new Error(`${body} should contain rpcUrl!`)
}
try {
const client = await buildClient(body['rpcUrl'])
if (!client) {
console.log('Client build failed')
throw 'Client build failed'
}
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
)
const banks = Array.from(group.banksMapByTokenIndex.values())
.map((bank) => bank[0])
.sort((a, b) => a.name.localeCompare(b.name))
const priceImpacts = group?.pis || []
const midPriceImpacts = getMidPriceImpacts(
priceImpacts.length ? priceImpacts : [],
)
const tokenThresholds: {
[symbol: string]: { below1Percent: number; below2Percent: number }
} = {}
for (const impact of midPriceImpacts) {
if (!tokenThresholds[impact.symbol]) {
tokenThresholds[impact.symbol] = { below1Percent: 0, below2Percent: 0 }
}
if (
impact.avg_price_impact_percent < 1 &&
impact.target_amount > tokenThresholds[impact.symbol].below1Percent
) {
tokenThresholds[impact.symbol].below1Percent = impact.target_amount
}
if (
impact.avg_price_impact_percent < 2 &&
impact.target_amount > tokenThresholds[impact.symbol].below2Percent
) {
tokenThresholds[impact.symbol].below2Percent = impact.target_amount
}
}
const newRankings: { [symbol: string]: TokenDetails } = {}
for (const [, preset] of Object.entries(LISTING_PRESETS)) {
for (const [symbol, thresholds] of Object.entries(tokenThresholds)) {
if (!newRankings[symbol]) {
newRankings[symbol] = {
maxBelow1Percent: { amount: 0, preset: 'C' },
maxBelow2Percent: { amount: 0, preset: 'C' },
}
}
if (preset.preset_target_amount <= thresholds.below1Percent) {
if (
preset.preset_target_amount >=
newRankings[symbol].maxBelow1Percent.amount
) {
newRankings[symbol].maxBelow1Percent = {
amount: preset.preset_target_amount,
preset: preset.preset_name,
}
}
}
if (preset.preset_target_amount <= thresholds.below2Percent) {
if (
preset.preset_target_amount >=
newRankings[symbol].maxBelow2Percent.amount
) {
newRankings[symbol].maxBelow2Percent = {
amount: preset.preset_target_amount,
preset: preset.preset_name,
}
}
}
}
}
await Promise.all(
banks.map(async (bank) => {
const currentTier = Object.values(LISTING_PRESETS).find((x) => {
if (bank?.platformLiquidationFee.toNumber() === 0) {
return (
x.platformLiquidationFee.toFixed(2) ===
bank?.platformLiquidationFee.toNumber().toFixed(2)
)
}
if (bank?.initAssetWeight.toNumber() === 0) {
return (
x.maintLiabWeight.toFixed(2) ===
bank?.maintLiabWeight.toNumber().toFixed(2)
)
}
if (bank?.depositWeightScaleStartQuote !== 20000000000) {
return (
x.depositWeightScaleStartQuote ===
bank?.depositWeightScaleStartQuote
)
} else {
return (
Math.abs(
x.loanOriginationFeeRate -
bank?.loanOriginationFeeRate.toNumber(),
) < 1e-8
)
}
})
let bankName = bank?.name
if (bankName === 'WIF') bankName = '$WIF'
if (bankName === 'ETH (Portal)') bankName = 'ETH'
if (bank?.mint && newRankings[bankName]) {
newRankings[bankName].reduceOnly = bank?.reduceOnly
newRankings[bankName].currentTier = currentTier?.preset_name || 'S'
newRankings[bankName].mint = bank?.mint.toString()
newRankings[bankName].initAssetWeight = bank?.initAssetWeight
.toNumber()
.toFixed(2)
newRankings[bankName].maintAssetWeight = bank?.maintAssetWeight
.toNumber()
.toFixed(2)
newRankings[bankName].maintLiabWeight = bank?.maintLiabWeight
.toNumber()
.toFixed(2)
newRankings[bankName].initLiabWeight = bank?.initLiabWeight
.toNumber()
.toFixed(2)
newRankings[bankName].collateralFeesPerDay = bank?.collateralFeePerDay
}
}),
)
const currentTiers: CurrentTiersResponse = Object.entries(newRankings).map(
([name, details]) => ({
name,
reduceOnly: details.reduceOnly,
maxBelow1PercentAmount: details.maxBelow1Percent.amount,
maxBelow1PercentPreset: details.maxBelow1Percent.preset,
maxBelow2PercentAmount: details.maxBelow2Percent.amount,
maxBelow2PercentPreset: details.maxBelow2Percent.preset,
initAssetWeight: details.initAssetWeight,
maintAssetWeight: details.maintAssetWeight,
maintLiabWeight: details.maintLiabWeight,
initLiabWeight: details.initLiabWeight,
mint: details.mint,
currentTier: details.currentTier,
collateralFeesPerDay: details.collateralFeesPerDay,
}),
)
res.status(200).json(currentTiers)
} catch (error: unknown) {
let errorMessage = 'An unexpected error occurred'
if (error instanceof Error) {
errorMessage = error.message
}
console.error('Failed to fetch tokens:', error)
res
.status(500)
.json({ error: 'Failed to fetch tokens', details: errorMessage })
}
}