token tiers adjustments + refactor (#356)

* fix

* fix

* add simulation

* fix

* improve decode logic for oderbook
This commit is contained in:
Adrian Brzeziński 2023-12-28 19:39:46 +01:00 committed by GitHub
parent 60f2815569
commit 6fb99db6ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 279 additions and 261 deletions

View File

@ -10,11 +10,10 @@ import {
JUPITER_REFERRAL_PK,
USDC_MINT,
} from 'utils/constants'
import { PublicKey, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'
import { PublicKey, SYSVAR_RENT_PUBKEY, Transaction } from '@solana/web3.js'
import { useWallet } from '@solana/wallet-adapter-react'
import { OPENBOOK_PROGRAM_ID, toNative } from '@blockworks-foundation/mango-v4'
import {
MANGO_DAO_FAST_LISTING_GOVERNANCE,
MANGO_DAO_FAST_LISTING_WALLET,
MANGO_DAO_WALLET,
MANGO_DAO_WALLET_GOVERNANCE,
@ -44,11 +43,12 @@ import CreateOpenbookMarketModal from '@components/modals/CreateOpenbookMarketMo
import useJupiterMints from 'hooks/useJupiterMints'
import CreateSwitchboardOracleModal from '@components/modals/CreateSwitchboardOracleModal'
import {
LISTING_PRESETS_KEYS,
LISTING_PRESETS,
coinTiersToNames,
calculateMarketTradingParams,
LISTING_PRESETS_PYTH,
getSwitchBoardPresets,
getPythPresets,
LISTING_PRESET,
getPresetWithAdjustedDepositLimit,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
import Checkbox from '@components/forms/Checkbox'
import { ReferralProvider } from '@jup-ag/referral-sdk'
@ -125,22 +125,24 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
const [orcaPoolAddress, setOrcaPoolAddress] = useState('')
const [raydiumPoolAddress, setRaydiumPoolAddress] = useState('')
const [oracleModalOpen, setOracleModalOpen] = useState(false)
const [liqudityTier, setLiqudityTier] = useState<LISTING_PRESETS_KEYS | ''>(
'',
)
const [isPyth, setIsPyth] = useState(false)
const tierLowerThenCurrent =
liqudityTier === 'ULTRA_PREMIUM' || liqudityTier === 'PREMIUM'
? 'MID'
: liqudityTier === 'MID'
? 'MEME'
: liqudityTier
const isPythRecommended =
liqudityTier === 'MID' ||
liqudityTier === 'PREMIUM' ||
liqudityTier === 'ULTRA_PREMIUM'
const listingTier =
isPythRecommended && !isPyth ? tierLowerThenCurrent : liqudityTier
const presets = useMemo(() => {
return !isPyth
? getSwitchBoardPresets(LISTING_PRESETS)
: getPythPresets(LISTING_PRESETS)
}, [isPyth])
const [proposedPresetTargetAmount, setProposedProposedTargetAmount] =
useState(0)
const preset: LISTING_PRESET =
Object.values(presets).find(
(x) => x.preset_target_amount === proposedPresetTargetAmount,
) || presets.UNTRUSTED
const proposedPreset = getPresetWithAdjustedDepositLimit(
preset,
baseTokenPrice,
currentTokenInfo?.decimals || 0,
)
const quoteBank = group?.getFirstBankByMint(new PublicKey(USDC_MINT))
const minVoterWeight = useMemo(
@ -174,13 +176,6 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
priceIncrementRelative: 0,
}
}, [quoteBank, currentTokenInfo, baseTokenPrice])
const tierPreset = useMemo(() => {
return listingTier
? isPyth
? LISTING_PRESETS_PYTH[listingTier]
: LISTING_PRESETS[listingTier]
: {}
}, [listingTier])
const handleSetAdvForm = (
propertyName: string,
@ -191,14 +186,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
}
const getListingParams = useCallback(
async (tokenInfo: Token, tier: LISTING_PRESETS_KEYS) => {
async (tokenInfo: Token, targetAmount: number) => {
setLoadingListingParams(true)
const [{ oraclePk, isPyth }, marketPk] = await Promise.all([
getOracle({
baseSymbol: tokenInfo.symbol,
quoteSymbol: 'usd',
connection,
tier: tier,
targetAmount: targetAmount,
}),
getBestMarket({
baseMint: mint,
@ -235,7 +230,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
marketIndex: index,
openBookMarketExternalPk: marketPk?.toBase58() || '',
proposalTitle: `List ${tokenInfo.symbol} on Mango-v4`,
listForSwapOnly: tier === 'UNTRUSTED',
listForSwapOnly: false,
})
setLoadingListingParams(false)
setIsPyth(isPyth)
@ -272,31 +267,32 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
[wallet.publicKey],
)
const handleLiqudityCheck = useCallback(
const handleLiquidityCheck = useCallback(
async (tokenMint: PublicKey) => {
try {
const TIERS: LISTING_PRESETS_KEYS[] = [
'ULTRA_PREMIUM',
'PREMIUM',
'MID',
'MEME',
'SHIT',
const targetAmounts = [
...new Set([
...Object.values(presets).map((x) => x.preset_target_amount),
]),
]
const swaps = await Promise.all([
handleGetRoutesWithFixedArgs(250000, tokenMint, 'ExactIn'),
handleGetRoutesWithFixedArgs(100000, tokenMint, 'ExactIn'),
handleGetRoutesWithFixedArgs(20000, tokenMint, 'ExactIn'),
handleGetRoutesWithFixedArgs(10000, tokenMint, 'ExactIn'),
handleGetRoutesWithFixedArgs(5000, tokenMint, 'ExactIn'),
handleGetRoutesWithFixedArgs(1000, tokenMint, 'ExactIn'),
handleGetRoutesWithFixedArgs(250000, tokenMint, 'ExactOut'),
handleGetRoutesWithFixedArgs(100000, tokenMint, 'ExactOut'),
handleGetRoutesWithFixedArgs(20000, tokenMint, 'ExactOut'),
handleGetRoutesWithFixedArgs(10000, tokenMint, 'ExactOut'),
handleGetRoutesWithFixedArgs(5000, tokenMint, 'ExactOut'),
handleGetRoutesWithFixedArgs(1000, tokenMint, 'ExactOut'),
])
const bestRoutesSwaps = swaps
.filter((x) => x.bestRoute)
.map((x) => x.bestRoute!)
const averageSwaps = bestRoutesSwaps.reduce(
(acc: { amount: string; priceImpactPct: number }[], val) => {
if (val.swapMode === 'ExactIn') {
@ -319,43 +315,33 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
const midTierCheck = averageSwaps.find(
(x) => x.amount === TWENTY_K_USDC_BASE,
)
const indexForTierFromSwaps = averageSwaps.findIndex(
const indexForTargetAmount = averageSwaps.findIndex(
(x) => x?.priceImpactPct && x?.priceImpactPct * 100 < 1,
)
const tier =
indexForTierFromSwaps > -1
? TIERS[indexForTierFromSwaps]
: 'UNTRUSTED'
setLiqudityTier(tier)
const targetAmount =
indexForTargetAmount > -1 ? targetAmounts[indexForTargetAmount] : 0
setProposedProposedTargetAmount(targetAmount)
setPriceImpact(midTierCheck ? midTierCheck.priceImpactPct * 100 : 100)
handleGetPoolParams(tier, tokenMint)
return tier
handleGetPoolParams(targetAmount, tokenMint)
return targetAmount
} catch (e) {
notify({
title: t('liquidity-check-error'),
description: `${e}`,
type: 'error',
})
return 'UNTRUSTED'
return 0
}
},
[t, handleGetRoutesWithFixedArgs],
)
const handleGetPoolParams = async (
tier: LISTING_PRESETS_KEYS,
targetAmount: number,
tokenMint: PublicKey,
) => {
const tierToSwapValue: { [key: string]: number } = {
ULTRA_PREMIUM: 250000,
PREMIUM: 100000,
MID: 20000,
MEME: 5000,
SHIT: 1000,
UNTRUSTED: 100,
}
const swaps = await handleGetRoutesWithFixedArgs(
tierToSwapValue[tier],
targetAmount ? targetAmount : 100,
tokenMint,
'ExactIn',
true,
@ -393,10 +379,10 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
setBaseTokenPrice(priceInfo.data[mint]?.price || 0)
setCurrentTokenInfo(tokenInfo)
if (tokenInfo) {
const tier = await handleLiqudityCheck(new PublicKey(mint))
getListingParams(tokenInfo, tier)
const targetAmount = await handleLiquidityCheck(new PublicKey(mint))
getListingParams(tokenInfo, targetAmount)
}
}, [getListingParams, handleLiqudityCheck, jupiterTokens, mint, t])
}, [getListingParams, handleLiquidityCheck, jupiterTokens, mint, t])
const cancel = () => {
setCurrentTokenInfo(null)
@ -405,7 +391,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
setProposalPk(null)
setOrcaPoolAddress('')
setRaydiumPoolAddress('')
setLiqudityTier('')
setProposedProposedTargetAmount(0)
setBaseTokenPrice(0)
}
@ -472,46 +458,46 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
const mint = new PublicKey(advForm.mintPk)
const proposalTx = []
if (Object.keys(tierPreset).length) {
if (Object.keys(proposedPreset).length) {
const registerTokenIx = await client!.program.methods
.tokenRegister(
Number(advForm.tokenIndex),
advForm.name,
{
confFilter: Number(tierPreset.oracleConfFilter),
maxStalenessSlots: tierPreset.maxStalenessSlots,
confFilter: Number(proposedPreset.oracleConfFilter),
maxStalenessSlots: proposedPreset.maxStalenessSlots,
},
{
adjustmentFactor: Number(tierPreset.adjustmentFactor),
util0: Number(tierPreset.util0),
rate0: Number(tierPreset.rate0),
util1: Number(tierPreset.util1),
rate1: Number(tierPreset.rate1),
maxRate: Number(tierPreset.maxRate),
adjustmentFactor: Number(proposedPreset.adjustmentFactor),
util0: Number(proposedPreset.util0),
rate0: Number(proposedPreset.rate0),
util1: Number(proposedPreset.util1),
rate1: Number(proposedPreset.rate1),
maxRate: Number(proposedPreset.maxRate),
},
Number(tierPreset.loanFeeRate),
Number(tierPreset.loanOriginationFeeRate),
Number(tierPreset.maintAssetWeight),
Number(tierPreset.initAssetWeight),
Number(tierPreset.maintLiabWeight),
Number(tierPreset.initLiabWeight),
Number(tierPreset.liquidationFee),
Number(tierPreset.stablePriceDelayIntervalSeconds),
Number(tierPreset.stablePriceDelayGrowthLimit),
Number(tierPreset.stablePriceGrowthLimit),
Number(tierPreset.minVaultToDepositsRatio),
new BN(tierPreset.netBorrowLimitWindowSizeTs),
new BN(tierPreset.netBorrowLimitPerWindowQuote),
Number(tierPreset.borrowWeightScaleStartQuote),
Number(tierPreset.depositWeightScaleStartQuote),
Number(tierPreset.reduceOnly),
Number(tierPreset.tokenConditionalSwapTakerFeeRate),
Number(tierPreset.tokenConditionalSwapMakerFeeRate),
Number(tierPreset.flashLoanSwapFeeRate),
Number(tierPreset.interestCurveScaling),
Number(tierPreset.interestTargetUtilization),
tierPreset.groupInsuranceFund,
new BN(tierPreset.depositLimit),
Number(proposedPreset.loanFeeRate),
Number(proposedPreset.loanOriginationFeeRate),
Number(proposedPreset.maintAssetWeight),
Number(proposedPreset.initAssetWeight),
Number(proposedPreset.maintLiabWeight),
Number(proposedPreset.initLiabWeight),
Number(proposedPreset.liquidationFee),
Number(proposedPreset.stablePriceDelayIntervalSeconds),
Number(proposedPreset.stablePriceDelayGrowthLimit),
Number(proposedPreset.stablePriceGrowthLimit),
Number(proposedPreset.minVaultToDepositsRatio),
new BN(proposedPreset.netBorrowLimitWindowSizeTs),
new BN(proposedPreset.netBorrowLimitPerWindowQuote),
Number(proposedPreset.borrowWeightScaleStartQuote),
Number(proposedPreset.depositWeightScaleStartQuote),
Number(proposedPreset.reduceOnly),
Number(proposedPreset.tokenConditionalSwapTakerFeeRate),
Number(proposedPreset.tokenConditionalSwapMakerFeeRate),
Number(proposedPreset.flashLoanSwapFeeRate),
Number(proposedPreset.interestCurveScaling),
Number(proposedPreset.interestTargetUtilization),
proposedPreset.groupInsuranceFund,
new BN(proposedPreset.depositLimit.toString()),
)
.accounts({
admin: MANGO_DAO_WALLET,
@ -539,12 +525,12 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
proposalTx.push(trustlessIx)
}
if (listingTier !== 'UNTRUSTED' && !advForm.listForSwapOnly) {
if (!advForm.listForSwapOnly) {
const registerMarketix = await client!.program.methods
.serum3RegisterMarket(
Number(advForm.marketIndex),
advForm.marketName,
tierPreset.oraclePriceBand,
proposedPreset.oraclePriceBand,
)
.accounts({
group: group!.publicKey,
@ -561,10 +547,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
const rp = new ReferralProvider(connection)
const tx = await rp.initializeReferralTokenAccount({
payerPubKey:
listingTier === 'UNTRUSTED'
? MANGO_DAO_FAST_LISTING_WALLET
: MANGO_DAO_WALLET,
payerPubKey: MANGO_DAO_WALLET,
referralAccountPubKey: JUPITER_REFERRAL_PK,
mint: mint,
})
@ -579,22 +562,29 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
setCreatingProposal(true)
try {
const proposalAddress = await createProposal(
connection,
walletSigner,
listingTier === 'UNTRUSTED'
? MANGO_DAO_FAST_LISTING_GOVERNANCE
: MANGO_DAO_WALLET_GOVERNANCE,
voter.tokenOwnerRecord!,
advForm.proposalTitle,
advForm.proposalDescription,
advForm.tokenIndex,
proposalTx,
vsrClient,
fee,
)
setProposalPk(proposalAddress.toBase58())
const simTransaction = new Transaction({ feePayer: wallet.publicKey })
simTransaction.add(...proposalTx)
const simulation = await connection.simulateTransaction(simTransaction)
if (!simulation.value.err) {
const proposalAddress = await createProposal(
connection,
walletSigner,
MANGO_DAO_WALLET_GOVERNANCE,
voter.tokenOwnerRecord!,
advForm.proposalTitle,
advForm.proposalDescription,
advForm.tokenIndex,
proposalTx,
vsrClient,
fee,
)
setProposalPk(proposalAddress.toBase58())
} else {
throw simulation.value.logs
}
} catch (e) {
console.log(e)
notify({
title: t('error-proposal-creation'),
description: `${e}`,
@ -604,34 +594,34 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
setCreatingProposal(false)
}, [
listingTier,
isFormValid,
advForm,
client,
connection,
wallet,
vsrClient,
connectionContext,
getCurrentVotingPower,
group,
isFormValid,
minVoterWeight,
mintVoterWeightNumber,
t,
tierPreset,
voter.tokenOwnerRecord,
voter.voteWeight,
vsrClient,
wallet,
voter.tokenOwnerRecord,
minVoterWeight,
proposedPreset,
connection,
t,
mintVoterWeightNumber,
client,
group,
fee,
])
const closeCreateOpenBookMarketModal = () => {
setCreateOpenbookMarket(false)
if (currentTokenInfo && liqudityTier) {
getListingParams(currentTokenInfo, liqudityTier)
if (currentTokenInfo && proposedPresetTargetAmount) {
getListingParams(currentTokenInfo, proposedPresetTargetAmount)
}
}
const closeCreateOracleModal = () => {
setOracleModalOpen(false)
if (currentTokenInfo && liqudityTier) {
getListingParams(currentTokenInfo, liqudityTier)
if (currentTokenInfo && proposedPresetTargetAmount) {
getListingParams(currentTokenInfo, proposedPresetTargetAmount)
}
}
@ -701,17 +691,8 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
</div>
<div className="mb-2 flex items-center justify-between">
<p>{t('tier')}</p>
<p className="text-th-fgd-2">
{listingTier && coinTiersToNames[listingTier]}
</p>
<p className="text-th-fgd-2">{proposedPreset.preset_name}</p>
</div>
{isPythRecommended && !isPyth && (
<div className="mb-2 flex items-center justify-end">
<p className="text-th-warning">
Pyth oracle needed for higher tier
</p>
</div>
)}
<div className="flex items-center justify-between">
<p>{t('mint')}</p>
<p className="flex items-center">
@ -822,30 +803,28 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
)}
</div>
)}
{listingTier !== 'UNTRUSTED' && (
<div>
<Label text={t('list-for-swap-only')} />
<Checkbox
checked={advForm.listForSwapOnly}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleSetAdvForm(
'listForSwapOnly',
e.target.checked,
)
}
>
<></>
</Checkbox>
{formErrors.openBookMarketExternalPk && (
<div className="mt-1.5 flex items-center space-x-1">
<ExclamationCircleIcon className="h-4 w-4 text-th-down" />
<p className="mb-0 text-xs text-th-down">
{formErrors.openBookMarketExternalPk}
</p>
</div>
)}
</div>
)}
<div>
<Label text={t('list-for-swap-only')} />
<Checkbox
checked={advForm.listForSwapOnly}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleSetAdvForm(
'listForSwapOnly',
e.target.checked,
)
}
>
<></>
</Checkbox>
{formErrors.openBookMarketExternalPk && (
<div className="mt-1.5 flex items-center space-x-1">
<ExclamationCircleIcon className="h-4 w-4 text-th-down" />
<p className="mb-0 text-xs text-th-down">
{formErrors.openBookMarketExternalPk}
</p>
</div>
)}
</div>
<div>
<Label text={t('base-bank')} />
<Input
@ -982,7 +961,6 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
<ol className="list-decimal pl-4">
{!advForm.openBookMarketExternalPk &&
!advForm.listForSwapOnly &&
listingTier &&
!loadingListingParams ? (
<li className="pl-2">
<div className="mb-4">
@ -1013,7 +991,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
) : null}
</li>
) : null}
{!advForm.oraclePk && listingTier && !loadingListingParams ? (
{!advForm.oraclePk && !loadingListingParams ? (
<li
className={`my-4 pl-2 ${
!advForm.openBookMarketExternalPk &&
@ -1036,7 +1014,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
type="error"
/>
<CreateSwitchboardOracleModal
tier={listingTier}
tierKey={proposedPreset.preset_key}
orcaPoolAddress={orcaPoolAddress}
raydiumPoolAddress={raydiumPoolAddress}
baseTokenName={currentTokenInfo.symbol}

View File

@ -21,8 +21,10 @@ import Loading from '@components/shared/Loading'
import { WhirlpoolContext, buildWhirlpoolClient } from '@orca-so/whirlpools-sdk'
import { LIQUIDITY_STATE_LAYOUT_V4 } from '@raydium-io/raydium-sdk'
import { createComputeBudgetIx } from '@blockworks-foundation/mango-v4'
import { LISTING_PRESETS_KEY } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
const poolAddressError = 'no-pool-address-found'
const wrongTierPassedForCreation = 'Wrong tier passed for creation of oracle'
const SWITCHBOARD_PERMISSIONLESS_QUE =
'5JYwqvKkqp35w8Nq3ba4z1WYUeJQ1rB36V8XvaGp6zn1'
@ -33,7 +35,7 @@ type BaseProps = ModalProps & {
openbookMarketPk: string
baseTokenPk: string
baseTokenName: string
tier: string
tierKey: LISTING_PRESETS_KEY
}
type RaydiumProps = BaseProps & {
@ -53,7 +55,7 @@ const CreateSwitchboardOracleModal = ({
baseTokenName,
raydiumPoolAddress,
orcaPoolAddress,
tier,
tierKey,
}: RaydiumProps | OrcaProps) => {
const { t } = useTranslation(['governance'])
const connection = mangoStore((s) => s.connection)
@ -61,41 +63,41 @@ const CreateSwitchboardOracleModal = ({
const wallet = useWallet()
const quoteTokenName = 'USD'
const pythUsdOracle = 'Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD'
const tierToSwapValue: { [key: string]: string } = {
PREMIUM: '10000',
MID: '2000',
MEME: '500',
SHIT: '100',
const tierToSwapValue: { [key in LISTING_PRESETS_KEY]?: string } = {
asset_100: '10000',
asset_20: '2000',
liab_5: '500',
liab_1: '100',
UNTRUSTED: '100',
}
const tierSettings: {
[key: string]: {
[key in LISTING_PRESETS_KEY]?: {
fundAmount: number
batchSize: number
minRequiredOracleResults: number
minUpdateDelaySeconds: number
}
} = {
PREMIUM: {
asset_100: {
fundAmount: 5,
minRequiredOracleResults: 3,
minUpdateDelaySeconds: 6,
batchSize: 5,
},
MID: {
asset_20: {
fundAmount: 5,
minRequiredOracleResults: 1,
minUpdateDelaySeconds: 6,
batchSize: 2,
},
MEME: {
liab_5: {
fundAmount: 2,
minRequiredOracleResults: 1,
minUpdateDelaySeconds: 20,
batchSize: 2,
},
SHIT: {
liab_1: {
fundAmount: 2,
batchSize: 2,
minRequiredOracleResults: 1,
@ -132,7 +134,7 @@ const CreateSwitchboardOracleModal = ({
const create = useCallback(async () => {
try {
const swapValue = tierToSwapValue[tier]
const swapValue = tierToSwapValue[tierKey]
setCreatingOracle(true)
const payer = wallet!.publicKey!
if (!orcaPoolAddress && !raydiumPoolAddress) {
@ -185,20 +187,23 @@ const CreateSwitchboardOracleModal = ({
},
]
}
const settingFromLib = tierSettings[tierKey]
if (!settingFromLib) {
throw wrongTierPassedForCreation
}
const [aggregatorAccount, txArray1] =
await queueAccount.createFeedInstructions(payer, {
name: `${baseTokenName}/${quoteTokenName}`,
batchSize: tierSettings[tier].batchSize,
minRequiredOracleResults: tierSettings[tier].minRequiredOracleResults,
batchSize: settingFromLib.batchSize,
minRequiredOracleResults: settingFromLib.minRequiredOracleResults,
minRequiredJobResults: 2,
minUpdateDelaySeconds: tierSettings[tier].minUpdateDelaySeconds,
minUpdateDelaySeconds: settingFromLib.minUpdateDelaySeconds,
forceReportPeriod: 60 * 60,
withdrawAuthority: MANGO_DAO_WALLET,
authority: payer,
crankDataBuffer: crankAccount.dataBuffer?.publicKey,
crankPubkey: crankAccount.publicKey,
fundAmount: tierSettings[tier].fundAmount,
fundAmount: settingFromLib.fundAmount,
slidingWindow: true,
disableCrank: false,
maxPriorityFeeMultiplier: 5,
@ -364,6 +369,12 @@ const CreateSwitchboardOracleModal = ({
description: 'No orca or raydium pool found for oracle',
type: 'error',
})
} else if (e === wrongTierPassedForCreation) {
notify({
title: 'Transaction failed',
description: 'Wrong tier passed for oracle creation',
type: 'error',
})
} else {
if (!isMangoError(e)) return
notify({
@ -381,7 +392,7 @@ const CreateSwitchboardOracleModal = ({
onClose,
orcaPoolAddress,
raydiumPoolAddress,
tier,
tierKey,
tierToSwapValue,
wallet,
])
@ -393,7 +404,7 @@ const CreateSwitchboardOracleModal = ({
{t('create-switch-oracle')} {baseTokenName}/USD
</p>
<p>
{t('estimated-oracle-cost')} {tierSettings[tier].fundAmount} SOL
{t('estimated-oracle-cost')} {tierSettings[tierKey]?.fundAmount} SOL
</p>
</div>

View File

@ -14,7 +14,7 @@ import {
OracleProvider,
PriceImpact,
} from '@blockworks-foundation/mango-v4'
import { AccountMeta } from '@solana/web3.js'
import { AccountMeta, Transaction } from '@solana/web3.js'
import { BN } from '@project-serum/anchor'
import {
MANGO_DAO_WALLET,
@ -26,16 +26,19 @@ import Button from '@components/shared/Button'
import { compareObjectsAndGetDifferentKeys } from 'utils/governance/tools'
import { Disclosure } from '@headlessui/react'
import {
LISTING_PRESET,
LISTING_PRESETS,
LISTING_PRESETS_KEYS,
LISTING_PRESETS_PYTH,
ListingPreset,
LISTING_PRESETS_KEY,
MidPriceImpact,
getMidPriceImpacts,
getProposedTier,
getTierWithAdjustedNetBorrows,
getPresetWithAdjustedDepositLimit,
getPresetWithAdjustedNetBorrows,
getProposedKey,
getPythPresets,
getSwitchBoardPresets,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
import Select from '@components/forms/Select'
import Loading from '@components/shared/Loading'
const DashboardSuggestedValues = ({
isOpen,
@ -58,11 +61,12 @@ const DashboardSuggestedValues = ({
const proposals = GovernanceStore((s) => s.proposals)
const PRESETS =
bank?.oracleProvider === OracleProvider.Pyth
? LISTING_PRESETS_PYTH
: LISTING_PRESETS
? getPythPresets(LISTING_PRESETS)
: getSwitchBoardPresets(LISTING_PRESETS)
const [suggestedTier, setSuggestedTier] =
useState<LISTING_PRESETS_KEYS>('SHIT')
useState<LISTING_PRESETS_KEY>('liab_1')
const [proposing, setProposing] = useState(false)
const getApiTokenName = (bankName: string) => {
if (bankName === 'ETH (Portal)') {
@ -93,25 +97,19 @@ const DashboardSuggestedValues = ({
}, {})
const priceImpact = filteredResp[getApiTokenName(bank.name)]
const suggestedTier = getProposedTier(
PRESETS,
const suggestedTier = getProposedKey(
priceImpact?.target_amount,
bank.oracleProvider === OracleProvider.Pyth,
)
setSuggestedTier(suggestedTier as LISTING_PRESETS_KEYS)
}, [
PRESETS,
bank.name,
bank.oracleProvider,
JSON.stringify(priceImpactsFiltered),
])
setSuggestedTier(suggestedTier)
}, [bank.name, bank.oracleProvider, priceImpactsFiltered])
const proposeNewSuggestedValues = useCallback(
async (
bank: Bank,
invalidFieldsKeys: string[],
tokenTier: LISTING_PRESETS_KEYS,
tokenTier: LISTING_PRESETS_KEY,
) => {
const proposalTx = []
const mintInfo = group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)!
@ -228,7 +226,7 @@ const DashboardSuggestedValues = ({
false,
false,
getNullOrVal(fieldsToChange.depositLimit)
? new BN(fieldsToChange.depositLimit!)
? new BN(fieldsToChange.depositLimit!.toString())
: null,
)
.accounts({
@ -248,24 +246,34 @@ const DashboardSuggestedValues = ({
proposalTx.push(ix)
const walletSigner = wallet as never
try {
const index = proposals ? Object.values(proposals).length : 0
const proposalAddress = await createProposal(
connection,
walletSigner,
MANGO_DAO_WALLET_GOVERNANCE,
voter.tokenOwnerRecord!,
`Edit token ${bank.name}`,
'Adjust settings to current liquidity',
index,
proposalTx,
vsrClient!,
fee,
)
window.open(
`https://dao.mango.markets/dao/MNGO/proposal/${proposalAddress.toBase58()}`,
'_blank',
)
setProposing(true)
const simTransaction = new Transaction({ feePayer: wallet.publicKey })
simTransaction.add(...proposalTx)
const simulation = await connection.simulateTransaction(simTransaction)
if (!simulation.value.err) {
const index = proposals ? Object.values(proposals).length : 0
const proposalAddress = await createProposal(
connection,
walletSigner,
MANGO_DAO_WALLET_GOVERNANCE,
voter.tokenOwnerRecord!,
`Edit token ${bank.name}`,
'Adjust settings to current liquidity',
index,
proposalTx,
vsrClient!,
fee,
)
window.open(
`https://dao.mango.markets/dao/MNGO/proposal/${proposalAddress.toBase58()}`,
'_blank',
)
} else {
throw simulation.value.logs
}
} catch (e) {
notify({
title: 'Error during proposal creation',
@ -273,11 +281,13 @@ const DashboardSuggestedValues = ({
type: 'error',
})
}
setProposing(false)
},
[
PRESETS,
client,
connection,
fee,
group,
proposals,
voter.tokenOwnerRecord,
@ -294,16 +304,21 @@ const DashboardSuggestedValues = ({
const formattedBankValues = getFormattedBankValues(group, bank)
const suggestedVaules = getTierWithAdjustedNetBorrows(
PRESETS[suggestedTier as LISTING_PRESETS_KEYS] as ListingPreset,
bank.nativeDeposits().mul(bank.price).toNumber(),
const suggestedValues = getPresetWithAdjustedDepositLimit(
getPresetWithAdjustedNetBorrows(
PRESETS[suggestedTier as LISTING_PRESETS_KEY] as LISTING_PRESET,
bank.nativeDeposits().mul(bank.price).toNumber(),
),
bank.uiPrice,
bank.mintDecimals,
)
const suggestedFormattedPreset = formatSuggestedValues(suggestedVaules)
const suggestedFormattedPreset = formatSuggestedValues(suggestedValues)
type SuggestedFormattedPreset = typeof suggestedFormattedPreset
const invalidKeys: (keyof SuggestedFormattedPreset)[] = Object.keys(
suggestedVaules,
suggestedValues,
).length
? compareObjectsAndGetDifferentKeys<SuggestedFormattedPreset>(
formattedBankValues,
@ -339,7 +354,7 @@ const DashboardSuggestedValues = ({
onChange={(tier) => setSuggestedTier(tier)}
className="w-full"
>
{Object.keys(LISTING_PRESETS)
{Object.keys(PRESETS)
.filter((x) => x !== 'UNTRUSTED')
.map((name) => (
<Select.Option key={name} value={name}>
@ -630,12 +645,12 @@ const DashboardSuggestedValues = ({
proposeNewSuggestedValues(
bank,
invalidKeys,
suggestedTier as LISTING_PRESETS_KEYS,
suggestedTier as LISTING_PRESETS_KEY,
)
}
disabled={!wallet.connected}
disabled={!wallet.connected || proposing}
>
Propose new suggested values
{proposing ? <Loading></Loading> : 'Propose new suggested values'}
</Button>
</div>
)}

View File

@ -316,11 +316,11 @@ const DepthChart = () => {
</div>
</div>
<div
className={
className={`${
increaseHeight ? 'h-[570px]' : isTablet ? 'h-[538px]' : 'h-[482px]'
}
}`}
>
<ResponsiveContainer width="100%" height="100%">
<ResponsiveContainer width="100" height="100%">
<AreaChart
data={chartData}
layout="vertical"

View File

@ -304,7 +304,7 @@ const Orderbook = () => {
connection
.getAccountInfoAndContext(bidsPk)
.then(({ context, value: info }) => {
if (!info) return
if (!info || !isMarketReadyForDecode(market)) return
const decodedBook = decodeBook(client, market, info, 'bids')
set((state) => {
state.selectedMarket.lastSeenSlot.bids = context.slot
@ -319,8 +319,8 @@ const Orderbook = () => {
mangoStore.getState().selectedMarket.lastSeenSlot.bids
if (context.slot > lastSeenSlot) {
const market = getMarket()
if (!market) return
const decodedBook = decodeBook(client, market, info, 'bids')
if (!isMarketReadyForDecode(market)) return
const decodedBook = decodeBook(client, market!, info, 'bids')
if (decodedBook instanceof BookSide) {
updatePerpMarketOnGroup(decodedBook, 'bids')
}
@ -340,7 +340,7 @@ const Orderbook = () => {
connection
.getAccountInfoAndContext(asksPk)
.then(({ context, value: info }) => {
if (!info) return
if (!info || !isMarketReadyForDecode(market)) return
const decodedBook = decodeBook(client, market, info, 'asks')
set((state) => {
state.selectedMarket.asksAccount = decodedBook
@ -355,8 +355,8 @@ const Orderbook = () => {
mangoStore.getState().selectedMarket.lastSeenSlot.asks
if (context.slot > lastSeenSlot) {
const market = getMarket()
if (!market) return
const decodedBook = decodeBook(client, market, info, 'asks')
if (!isMarketReadyForDecode(market)) return
const decodedBook = decodeBook(client, market!, info, 'asks')
if (decodedBook instanceof BookSide) {
updatePerpMarketOnGroup(decodedBook, 'asks')
}
@ -812,3 +812,14 @@ function usersOpenOrderPrices(market: Market | PerpMarket | null) {
}
return usersOpenOrderPrices
}
const isMarketReadyForDecode = (market: PerpMarket | Market | undefined) => {
if (
!market ||
(market instanceof Market &&
(!market.decoded.accountFlags.initialized ||
!(market.decoded.accountFlags.bids ^ market.decoded.accountFlags.asks)))
)
return false
else return true
}

View File

@ -23,8 +23,8 @@
"dependencies": {
"@blockworks-foundation/mango-feeds": "0.1.7",
"@blockworks-foundation/mango-mints-redemption": "^0.0.10",
"@blockworks-foundation/mango-v4": "0.21.3",
"@blockworks-foundation/mango-v4-settings": "0.3.3",
"@blockworks-foundation/mango-v4": "0.21.5",
"@blockworks-foundation/mango-v4-settings": "0.4.3",
"@blockworks-foundation/mangolana": "0.0.1-beta.15",
"@headlessui/react": "1.6.6",
"@heroicons/react": "2.0.18",

View File

@ -953,7 +953,9 @@ const VaultData = ({ bank }: { bank: Bank }) => {
<KeyValuePair
label="Vault balance"
value={
vault ? toUiDecimals(vault.amount.toNumber(), bank.mintDecimals) : '...'
vault
? toUiDecimals(new BN(vault.amount.toString()), bank.mintDecimals)
: '...'
}
/>
)

View File

@ -141,12 +141,14 @@ export const createProposal = async (
tx.feePayer = payer
transactions.push(tx)
}
const signedTransactions = await wallet.signAllTransactions(transactions)
for (const tx of signedTransactions) {
const rawTransaction = tx.serialize()
const address = await connection.sendRawTransaction(rawTransaction, {
skipPreflight: true,
})
await connection.confirmTransaction({
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,

View File

@ -15,21 +15,18 @@ import { Market } from '@project-serum/serum'
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import EmptyWallet from 'utils/wallet'
import dayjs from 'dayjs'
import {
LISTING_PRESETS_KEYS,
ListingPreset,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
import { LISTING_PRESET } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
export const getOracle = async ({
baseSymbol,
quoteSymbol,
connection,
tier,
targetAmount,
}: {
baseSymbol: string
quoteSymbol: string
connection: Connection
tier: LISTING_PRESETS_KEYS
targetAmount: number
}) => {
try {
let oraclePk = ''
@ -38,6 +35,7 @@ export const getOracle = async ({
quoteSymbol,
connection,
})
if (pythOracle) {
oraclePk = pythOracle
} else {
@ -45,13 +43,14 @@ export const getOracle = async ({
baseSymbol,
quoteSymbol,
connection,
noLock: tier === 'SHIT' || tier === 'UNTRUSTED',
noLock: targetAmount === 0 || targetAmount === 1000,
})
oraclePk = switchBoardOracle
}
return { oraclePk, isPyth: !!pythOracle }
} catch (e) {
console.log(e)
notify({
title: 'Oracle not found',
description: `${e}`,
@ -268,7 +267,7 @@ export const getQuoteSymbol = (quoteTokenSymbol: string) => {
}
export const formatSuggestedValues = (
suggestedParams: Record<string, never> | Omit<ListingPreset, 'name'>,
suggestedParams: Record<string, never> | Omit<LISTING_PRESET, 'name'>,
) => {
return {
maxStalenessSlots: suggestedParams.maxStalenessSlots,

View File

@ -71,7 +71,7 @@ export function notify(newNotification: {
}
if (
newNotif.txid &&
!newNotif.txid ||
!notifications.find(
(n) => n.txid == newNotif.txid && n.type == newNotif.type,
)

View File

@ -42,10 +42,10 @@
keccak256 "^1.0.6"
merkletreejs "^0.3.11"
"@blockworks-foundation/mango-v4-settings@0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.3.3.tgz#84f37cb02dfe6371b30b6051f76fa11eef6c0b47"
integrity sha512-eIQK0593IOcoN3xITpKCU7toq8xG1x5yQL1pVgaZLEdBLv0M2QlUimBg9OVJ04t7Ch2SHDMcL+QMbqi8NKqUYg==
"@blockworks-foundation/mango-v4-settings@0.4.3":
version "0.4.3"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.4.3.tgz#03a77096e2802479d284af21d8c90ca6e7f61ddb"
integrity sha512-95uEKS0rC06P+YK6uYLzlznfA95B2Vs7zpM0gVp6Q8oftaVMeIHg/BHadhESHLrAi3AzasuLwn4IzcKVOtX3Kw==
dependencies:
bn.js "^5.2.1"
eslint-config-prettier "^9.0.0"
@ -58,10 +58,10 @@
bn.js "^5.2.1"
eslint-config-prettier "^9.0.0"
"@blockworks-foundation/mango-v4@0.21.3":
version "0.21.3"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.21.3.tgz#906800f63b4e847264444e13a628356b2afd6fbe"
integrity sha512-LBc5QjUrJwWcrUSnzW0KvXwfjDQMrg9gRujyHI6eV/BMLBhqw+NqrNMBDjdxgpcAnAna+WoX9aAsEPV9rmi7tQ==
"@blockworks-foundation/mango-v4@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.21.5.tgz#5e3b3257de0a0efc98c1f9057982e434c1665120"
integrity sha512-tCBFb+eBltsJbFkcBPBgfk5Wzmwa+9MfNU7FTWX8vMycl+ck4svE8K05HEO25dWRSJmZbwx8osGxaqCsricAbQ==
dependencies:
"@blockworks-foundation/mango-v4-settings" "^0.2.16"
"@coral-xyz/anchor" "^0.28.1-beta.2"