mango-v4-ui/utils/governance/listingTools.ts

532 lines
15 KiB
TypeScript
Raw Normal View History

import { AnchorProvider, Program } from '@coral-xyz/anchor'
2023-05-22 16:05:43 -07:00
import { PythHttpClient } from '@pythnetwork/client'
import { notify } from 'utils/notifications'
import { MAINNET_PYTH_PROGRAM } from './constants'
import {
Bank,
Group,
I80F48,
OPENBOOK_PROGRAM_ID,
toNative,
toUiDecimals,
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
2023-05-22 16:05:43 -07:00
import { Market } from '@project-serum/serum'
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import EmptyWallet from 'utils/wallet'
export const getOracle = async ({
2023-05-22 16:17:53 -07:00
baseSymbol,
quoteSymbol,
2023-05-22 16:05:43 -07:00
connection,
pythOnly = false,
2023-05-22 16:05:43 -07:00
}: {
2023-05-22 16:17:53 -07:00
baseSymbol: string
quoteSymbol: string
2023-05-22 16:05:43 -07:00
connection: Connection
pythOnly?: boolean
2023-05-22 16:05:43 -07:00
}) => {
try {
let oraclePk = ''
2023-05-22 16:17:53 -07:00
const pythOracle = await getPythOracle({
baseSymbol,
quoteSymbol,
connection,
})
2023-05-22 16:05:43 -07:00
if (pythOracle) {
oraclePk = pythOracle
} else if (!pythOnly) {
2023-05-22 16:05:43 -07:00
const switchBoardOracle = await getSwitchBoardOracle({
2023-05-22 16:17:53 -07:00
baseSymbol,
quoteSymbol,
2023-05-22 16:05:43 -07:00
connection,
})
oraclePk = switchBoardOracle
}
return oraclePk
} catch (e) {
notify({
title: 'Oracle not found',
description: `${e}`,
type: 'error',
})
}
}
export const getPythOracle = async ({
2023-05-22 16:17:53 -07:00
baseSymbol,
quoteSymbol,
2023-05-22 16:05:43 -07:00
connection,
}: {
2023-05-22 16:17:53 -07:00
baseSymbol: string
quoteSymbol: string
2023-05-22 16:05:43 -07:00
connection: Connection
}) => {
try {
const pythClient = new PythHttpClient(connection, MAINNET_PYTH_PROGRAM)
const pythAccounts = await pythClient.getData()
const product = pythAccounts.products.find(
2023-05-22 16:17:53 -07:00
(x) =>
x.base === baseSymbol.toUpperCase() &&
2023-07-21 11:47:53 -07:00
x.quote_currency === quoteSymbol.toUpperCase(),
2023-05-22 16:05:43 -07:00
)
return product?.price_account || ''
} catch (e) {
notify({
title: 'Pyth oracle fetch error',
description: `${e}`,
type: 'error',
})
return ''
}
}
export const getSwitchBoardOracle = async ({
2023-05-22 16:17:53 -07:00
baseSymbol,
quoteSymbol,
2023-05-22 16:05:43 -07:00
connection,
}: {
2023-05-22 16:17:53 -07:00
baseSymbol: string
quoteSymbol: string
2023-05-22 16:05:43 -07:00
connection: Connection
}) => {
try {
const SWITCHBOARD_PROGRAM_ID = 'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
const options = AnchorProvider.defaultOptions()
const provider = new AnchorProvider(
connection,
new EmptyWallet(Keypair.generate()),
2023-07-21 11:47:53 -07:00
options,
2023-05-22 16:05:43 -07:00
)
const idl = await Program.fetchIdl(
new PublicKey(SWITCHBOARD_PROGRAM_ID),
2023-07-21 11:47:53 -07:00
provider,
2023-05-22 16:05:43 -07:00
)
const switchboardProgram = new Program(
idl!,
new PublicKey(SWITCHBOARD_PROGRAM_ID),
2023-07-21 11:47:53 -07:00
provider,
2023-05-22 16:05:43 -07:00
)
const allFeeds =
await switchboardProgram.account.aggregatorAccountData.all()
const feedNames = allFeeds.map((x) =>
2023-07-21 11:47:53 -07:00
String.fromCharCode(
...[...(x.account.name as number[])].filter((x) => x),
),
2023-05-22 16:05:43 -07:00
)
2023-05-22 16:17:53 -07:00
2023-05-22 16:05:43 -07:00
const possibleFeedIndexes = feedNames.reduce(function (r, v, i) {
return r.concat(
2023-05-22 16:17:53 -07:00
v.toLowerCase().includes(baseSymbol.toLowerCase()) &&
v.toLowerCase().includes(quoteSymbol.toLowerCase())
2023-05-22 16:05:43 -07:00
? i
2023-07-21 11:47:53 -07:00
: [],
2023-05-22 16:05:43 -07:00
)
}, [] as number[])
const possibleFeeds = allFeeds.filter(
2023-07-21 11:47:53 -07:00
(x, i) => possibleFeedIndexes.includes(i) && x.account.isLocked,
2023-05-22 16:05:43 -07:00
)
return possibleFeeds.length ? possibleFeeds[0].publicKey.toBase58() : ''
} catch (e) {
notify({
title: 'Switchboard oracle fetch error',
description: `${e}`,
type: 'error',
})
return ''
}
}
export const getBestMarket = async ({
2023-05-22 16:17:53 -07:00
baseMint,
quoteMint,
2023-05-22 16:05:43 -07:00
cluster,
connection,
}: {
2023-05-22 16:17:53 -07:00
baseMint: string
quoteMint: string
2023-05-22 16:05:43 -07:00
cluster: 'devnet' | 'mainnet-beta'
connection: Connection
}) => {
try {
const dexProgramPk = OPENBOOK_PROGRAM_ID[cluster]
const markets = await Market.findAccountsByMints(
connection,
2023-05-22 16:17:53 -07:00
new PublicKey(baseMint),
new PublicKey(quoteMint),
2023-07-21 11:47:53 -07:00
dexProgramPk,
2023-05-22 16:05:43 -07:00
)
2023-05-22 16:05:43 -07:00
if (!markets.length) {
return undefined
}
if (markets.length === 1) {
return markets[0].publicKey
}
const marketsDataJsons = await Promise.all([
...markets.map((x) =>
2023-07-21 11:47:53 -07:00
fetch(`/openSerumApi/market/${x.publicKey.toBase58()}`),
2023-05-22 16:05:43 -07:00
),
])
const marketsData = await Promise.all([
...marketsDataJsons.map((x) => x.json()),
])
const bestMarket = marketsData.sort((a, b) => b.volume24h - a.volume24h)
2023-05-24 07:42:13 -07:00
return bestMarket.length
? new PublicKey(bestMarket[0].id)
: markets[0].publicKey
2023-05-22 16:05:43 -07:00
} catch (e) {
notify({
title: 'Openbook market not found',
description: `${e}`,
type: 'error',
})
}
}
2023-05-23 07:10:22 -07:00
2023-05-24 04:50:23 -07:00
// definitions:
// baseLots = 10 ^ baseLotExponent
// quoteLots = 10 ^ quoteLotExponent
// minOrderSize = 10^(baseLotExponent - baseDecimals)
// minOrderValue = basePrice * minOrderSize
// priceIncrement = 10^(quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals)
2023-05-25 09:09:12 -07:00
// priceIncrementRelative = priceIncrement * quotePrice / basePrice
2023-05-24 04:50:23 -07:00
2023-05-25 07:16:32 -07:00
// derive: baseLotExponent <= min[ basePrice * minOrderSize > 0.05]
// baseLotExponent = 10
// While (baseLotExponent < 10):
2023-05-24 04:50:23 -07:00
// minOrderSize = 10^(baseLotExponent - baseDecimals)
// minOrderValue = basePrice * minOrderSize
2023-05-25 07:16:32 -07:00
// if minOrderValue > 0.05:
2023-05-24 04:50:23 -07:00
// break;
2023-05-25 13:06:22 -07:00
// Derive: quoteLotExponent <= min[ priceIncrement * quotePrice / basePrice > 0.000025 ]
2023-05-25 07:16:32 -07:00
// quoteLotExponent = 0
// While (quoteLotExponent < 10):
2023-05-24 04:50:23 -07:00
// priceIncrement = 10^(quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals)
2023-05-25 09:09:12 -07:00
// priceIncrementRelative = priceIncrement * quotePrice / basePrice
2023-05-25 13:06:22 -07:00
// if priceIncrementRelative > 0.000025:
2023-05-24 04:50:23 -07:00
// break;
export function calculateTradingParameters(
basePrice: number,
2023-05-24 04:50:23 -07:00
quotePrice: number,
baseDecimals: number,
2023-07-21 11:47:53 -07:00
quoteDecimals: number,
2023-05-24 04:50:23 -07:00
) {
2023-05-25 04:39:33 -07:00
const MAX_MIN_ORDER_VALUE = 0.05
2023-05-25 09:09:12 -07:00
const MIN_PRICE_INCREMENT_RELATIVE = 0.000025
2023-05-25 13:06:22 -07:00
const EXPONENT_THRESHOLD = 10
2023-05-24 04:50:23 -07:00
let minOrderSize = 0
let priceIncrement = 0
2023-05-25 04:39:33 -07:00
let baseLotExponent = 0
let quoteLotExponent = 0
2023-05-24 16:22:13 -07:00
let minOrderValue = 0
let priceIncrementRelative = 0
2023-05-24 04:50:23 -07:00
// Calculate minimum order size
do {
minOrderSize = Math.pow(10, baseLotExponent - baseDecimals)
2023-05-24 16:22:13 -07:00
minOrderValue = basePrice * minOrderSize
2023-05-25 04:39:33 -07:00
if (minOrderValue > MAX_MIN_ORDER_VALUE) {
2023-05-24 04:50:23 -07:00
break
}
2023-05-25 04:39:33 -07:00
baseLotExponent++
2023-05-25 13:06:22 -07:00
} while (baseLotExponent < EXPONENT_THRESHOLD)
2023-05-24 04:50:23 -07:00
// Calculate price increment
do {
priceIncrement = Math.pow(
10,
2023-07-21 11:47:53 -07:00
quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals,
2023-05-24 04:50:23 -07:00
)
2023-05-25 09:09:12 -07:00
priceIncrementRelative = (priceIncrement * quotePrice) / basePrice
2023-05-25 04:39:33 -07:00
if (priceIncrementRelative > MIN_PRICE_INCREMENT_RELATIVE) {
2023-05-24 04:50:23 -07:00
break
}
2023-05-23 07:10:22 -07:00
2023-05-25 04:39:33 -07:00
quoteLotExponent++
2023-05-25 13:06:22 -07:00
} while (quoteLotExponent < EXPONENT_THRESHOLD)
2023-05-25 04:39:33 -07:00
2023-05-25 13:06:22 -07:00
//exception override values in that case example eth/btc market
2023-05-25 10:06:37 -07:00
if (
quoteLotExponent === 0 &&
priceIncrementRelative > 0.001 &&
minOrderSize < 1
) {
baseLotExponent = baseLotExponent + 1
minOrderSize = Math.pow(10, baseLotExponent - baseDecimals)
minOrderValue = basePrice * minOrderSize
priceIncrement = Math.pow(
10,
2023-07-21 11:47:53 -07:00
quoteLotExponent + baseDecimals - baseLotExponent - quoteDecimals,
2023-05-25 10:06:37 -07:00
)
priceIncrementRelative = (priceIncrement * quotePrice) / basePrice
}
2023-05-23 07:10:22 -07:00
return {
2023-05-25 13:06:22 -07:00
baseLots: Math.pow(10, baseLotExponent),
quoteLots: Math.pow(10, quoteLotExponent),
2023-05-24 16:22:13 -07:00
minOrderValue: minOrderValue,
baseLotExponent: baseLotExponent,
quoteLotExponent: quoteLotExponent,
2023-05-25 13:06:22 -07:00
minOrderSize: minOrderSize,
priceIncrement: priceIncrement,
2023-05-24 16:22:13 -07:00
priceIncrementRelative: priceIncrementRelative,
2023-05-23 07:10:22 -07:00
}
}
export const getQuoteSymbol = (quoteTokenSymbol: string) => {
if (
quoteTokenSymbol.toLowerCase() === 'usdc' ||
quoteTokenSymbol.toLocaleLowerCase() === 'usdt'
) {
return 'usd'
}
return quoteTokenSymbol
}
const listingBase = {
maxStalenessSlots: 120 as number | null,
oracleConfFilter: 0.1,
adjustmentFactor: 0.004,
util0: 0.5,
rate0: 0.052,
util1: 0.8,
rate1: 0.1446,
maxRate: 1.4456,
loanFeeRate: 0.005,
loanOriginationFeeRate: 0.001,
maintAssetWeight: 0.9,
initAssetWeight: 0.8,
maintLiabWeight: 1.1,
initLiabWeight: 1.2,
liquidationFee: 0.05,
minVaultToDepositsRatio: 0.2,
netBorrowLimitWindowSizeTs: 24 * 60 * 60,
netBorrowLimitPerWindowQuote: toNative(50000, 6).toNumber(),
insuranceFound: true,
borrowWeightScale: toNative(250000, 6).toNumber(),
depositWeightScale: toNative(250000, 6).toNumber(),
preset_key: 'PREMIUM',
preset_name: 'Blue chip',
target_amount: 100000,
}
export type ListingPreset = typeof listingBase
export type LISTING_PRESETS_KEYS =
| 'PREMIUM'
| 'MID'
| 'MEME'
| 'SHIT'
| 'UNTRUSTED'
export const LISTING_PRESETS: {
[key in LISTING_PRESETS_KEYS]: ListingPreset | Record<string, never>
} = {
//Price impact $100,000 < 1%
PREMIUM: {
...listingBase,
},
//Price impact $20,000 < 1%
MID: {
...listingBase,
maintAssetWeight: 0.75,
initAssetWeight: 0.5,
maintLiabWeight: 1.2,
initLiabWeight: 1.4,
liquidationFee: 0.1,
netBorrowLimitPerWindowQuote: toNative(20000, 6).toNumber(),
borrowWeightScale: toNative(50000, 6).toNumber(),
depositWeightScale: toNative(50000, 6).toNumber(),
insuranceFound: false,
preset_key: 'MID',
preset_name: 'Midwit',
target_amount: 20000,
},
//Price impact $5,000 < 1%
MEME: {
...listingBase,
maxStalenessSlots: 800,
loanOriginationFeeRate: 0.002,
maintAssetWeight: 0,
initAssetWeight: 0,
maintLiabWeight: 1.25,
initLiabWeight: 1.5,
liquidationFee: 0.125,
netBorrowLimitPerWindowQuote: toNative(5000, 6).toNumber(),
borrowWeightScale: toNative(20000, 6).toNumber(),
depositWeightScale: toNative(20000, 6).toNumber(),
insuranceFound: false,
preset_key: 'MEME',
preset_name: 'Meme Coin',
target_amount: 5000,
},
//Price impact $1,000 < 1%
SHIT: {
...listingBase,
maxStalenessSlots: 800,
loanOriginationFeeRate: 0.002,
maintAssetWeight: 0,
initAssetWeight: 0,
maintLiabWeight: 1.4,
initLiabWeight: 1.8,
liquidationFee: 0.2,
netBorrowLimitPerWindowQuote: toNative(1000, 6).toNumber(),
borrowWeightScale: toNative(5000, 6).toNumber(),
depositWeightScale: toNative(5000, 6).toNumber(),
insuranceFound: false,
preset_key: 'SHIT',
preset_name: 'Shit Coin',
target_amount: 1000,
},
UNTRUSTED: {},
}
export const coinTiersToNames: {
[key in LISTING_PRESETS_KEYS]: string
} = {
PREMIUM: 'Blue Chip',
MID: 'Mid-wit',
MEME: 'Meme',
SHIT: 'Shit Coin',
UNTRUSTED: 'Untrusted',
}
export const formatSuggestedValues = (
suggestedParams:
| Record<string, never>
| Omit<
typeof listingBase,
'name' | 'netBorrowLimitWindowSizeTs' | 'insuranceFound'
2023-07-21 11:47:53 -07:00
>,
) => {
return {
maxStalenessSlots: suggestedParams.maxStalenessSlots,
oracleConfFilter: (100 * suggestedParams.oracleConfFilter).toFixed(2),
adjustmentFactor: (suggestedParams.adjustmentFactor * 100).toFixed(2),
rate0: (100 * suggestedParams.rate0).toFixed(2),
util0: (100 * suggestedParams.util0).toFixed(),
rate1: (100 * suggestedParams.rate1).toFixed(2),
util1: (100 * suggestedParams.util1).toFixed(),
maxRate: (100 * suggestedParams.maxRate).toFixed(2),
loanFeeRate: (10000 * suggestedParams.loanFeeRate).toFixed(2),
loanOriginationFeeRate: (
10000 * suggestedParams.loanOriginationFeeRate
).toFixed(2),
maintAssetWeight: suggestedParams.maintAssetWeight.toFixed(2),
initAssetWeight: suggestedParams.initAssetWeight.toFixed(2),
maintLiabWeight: suggestedParams.maintLiabWeight.toFixed(2),
initLiabWeight: suggestedParams.initLiabWeight.toFixed(2),
liquidationFee: (suggestedParams.liquidationFee * 100).toFixed(2),
minVaultToDepositsRatio: suggestedParams.minVaultToDepositsRatio * 100,
netBorrowLimitPerWindowQuote: toUiDecimals(
suggestedParams.netBorrowLimitPerWindowQuote,
2023-07-21 11:47:53 -07:00
6,
),
borrowWeightScale: toUiDecimals(suggestedParams.borrowWeightScale, 6),
depositWeightScale: toUiDecimals(suggestedParams.depositWeightScale, 6),
}
}
export const getFormattedBankValues = (group: Group, bank: Bank) => {
return {
...bank,
publicKey: bank.publicKey.toBase58(),
vault: bank.vault.toBase58(),
oracle: bank.oracle.toBase58(),
stablePrice: group.toUiPrice(
I80F48.fromNumber(bank.stablePriceModel.stablePrice),
2023-07-21 11:47:53 -07:00
bank.mintDecimals,
),
maxStalenessSlots: bank.oracleConfig.maxStalenessSlots.toNumber(),
lastStablePriceUpdated: new Date(
2023-07-21 11:47:53 -07:00
1000 * bank.stablePriceModel.lastUpdateTimestamp.toNumber(),
).toUTCString(),
stablePriceGrowthLimitsDelay: (
100 * bank.stablePriceModel.delayGrowthLimit
).toFixed(2),
stablePriceGrowthLimitsStable: (
100 * bank.stablePriceModel.stableGrowthLimit
).toFixed(2),
loanFeeRate: (10000 * bank.loanFeeRate.toNumber()).toFixed(2),
loanOriginationFeeRate: (
10000 * bank.loanOriginationFeeRate.toNumber()
).toFixed(2),
collectedFeesNative: toUiDecimals(
bank.collectedFeesNative.toNumber(),
2023-07-21 11:47:53 -07:00
bank.mintDecimals,
).toFixed(2),
collectedFeesNativePrice: (
toUiDecimals(bank.collectedFeesNative.toNumber(), bank.mintDecimals) *
bank.uiPrice
).toFixed(2),
dust: bank.dust.toNumber(),
deposits: toUiDecimals(
bank.indexedDeposits.mul(bank.depositIndex).toNumber(),
2023-07-21 11:47:53 -07:00
bank.mintDecimals,
),
depositsPrice: (
toUiDecimals(
bank.indexedDeposits.mul(bank.depositIndex).toNumber(),
2023-07-21 11:47:53 -07:00
bank.mintDecimals,
) * bank.uiPrice
).toFixed(2),
borrows: toUiDecimals(
bank.indexedBorrows.mul(bank.borrowIndex).toNumber(),
2023-07-21 11:47:53 -07:00
bank.mintDecimals,
),
borrowsPrice: (
toUiDecimals(
bank.indexedBorrows.mul(bank.borrowIndex).toNumber(),
2023-07-21 11:47:53 -07:00
bank.mintDecimals,
) * bank.uiPrice
).toFixed(2),
avgUtilization: bank.avgUtilization.toNumber() * 100,
maintAssetWeight: bank.maintAssetWeight.toFixed(2),
maintLiabWeight: bank.maintLiabWeight.toFixed(2),
initAssetWeight: bank.initAssetWeight.toFixed(2),
initLiabWeight: bank.initLiabWeight.toFixed(2),
scaledInitAssetWeight: bank.scaledInitAssetWeight(bank.price).toFixed(2),
scaledInitLiabWeight: bank.scaledInitLiabWeight(bank.price).toFixed(2),
depositWeightScale: toUiDecimalsForQuote(bank.depositWeightScaleStartQuote),
borrowWeightScale: toUiDecimalsForQuote(bank.borrowWeightScaleStartQuote),
rate0: (100 * bank.rate0.toNumber()).toFixed(2),
util0: (100 * bank.util0.toNumber()).toFixed(),
rate1: (100 * bank.rate1.toNumber()).toFixed(2),
util1: (100 * bank.util1.toNumber()).toFixed(),
maxRate: (100 * bank.maxRate.toNumber()).toFixed(2),
adjustmentFactor: (bank.adjustmentFactor.toNumber() * 100).toFixed(2),
depositRate: bank.getDepositRateUi(),
borrowRate: bank.getBorrowRateUi(),
lastIndexUpdate: new Date(
2023-07-21 11:47:53 -07:00
1000 * bank.indexLastUpdated.toNumber(),
).toUTCString(),
lastRatesUpdate: new Date(
2023-07-21 11:47:53 -07:00
1000 * bank.bankRateLastUpdated.toNumber(),
).toUTCString(),
oracleConfFilter: (100 * bank.oracleConfig.confFilter.toNumber()).toFixed(
2023-07-21 11:47:53 -07:00
2,
),
minVaultToDepositsRatio: bank.minVaultToDepositsRatio * 100,
netBorrowsInWindow: toUiDecimalsForQuote(
2023-07-21 11:47:53 -07:00
I80F48.fromI64(bank.netBorrowsInWindow).mul(bank.price),
).toFixed(2),
netBorrowLimitPerWindowQuote: toUiDecimals(
bank.netBorrowLimitPerWindowQuote,
2023-07-21 11:47:53 -07:00
6,
),
liquidationFee: (bank.liquidationFee.toNumber() * 100).toFixed(2),
}
}