import { ReactNode, useCallback, useEffect, useState } from 'react' import { ModalProps } from '../../types/modal' import Modal from '../shared/Modal' import mangoStore from '@store/mangoStore' import { useWallet } from '@solana/wallet-adapter-react' import GovernanceStore from '@store/governanceStore' import { formatSuggestedValues, getApiTokenName, getFormattedBankValues, } from 'utils/governance/listingTools' import { Bank, Group, OracleProvider, toUiDecimals, } from '@blockworks-foundation/mango-v4' import { AccountMeta, Transaction } from '@solana/web3.js' import { BN } from '@project-serum/anchor' import { MANGO_DAO_WALLET, MANGO_DAO_WALLET_GOVERNANCE, } from 'utils/governance/constants' import { createProposal } from 'utils/governance/instructions/createProposal' import { notify } from 'utils/notifications' import Button from '@components/shared/Button' import { compareObjectsAndGetDifferentKeys, tryGetPubKey, } from 'utils/governance/tools' import { Disclosure } from '@headlessui/react' import { LISTING_PRESET, LISTING_PRESETS, LISTING_PRESETS_KEY, MidPriceImpact, getPresetWithAdjustedDepositLimit, getPresetWithAdjustedNetBorrows, } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools' import Select from '@components/forms/Select' import Loading from '@components/shared/Loading' import Label from '@components/forms/Label' import Input from '@components/forms/Input' import Switch from '@components/forms/Switch' import { PublicKey } from '@metaplex-foundation/js' //import useBanks from 'hooks/useBanks' const DashboardSuggestedValues = ({ isOpen, onClose, bank, group, suggestedTierKey, currentTier, midPriceImp, }: ModalProps & { bank: Bank group: Group suggestedTierKey: LISTING_PRESETS_KEY currentTier: LISTING_PRESET | undefined midPriceImp: MidPriceImpact[] }) => { const client = mangoStore((s) => s.client) //do not deconstruct wallet is used for anchor to sign const wallet = useWallet() //const { banks } = useBanks() const connection = mangoStore((s) => s.connection) const fee = mangoStore((s) => s.priorityFee) const voter = GovernanceStore((s) => s.voter) const vsrClient = GovernanceStore((s) => s.vsrClient) const proposals = GovernanceStore((s) => s.proposals) const [oracle, setOracle] = useState('') const [forcePythOracle, setForcePythOracle] = useState(false) const PRESETS = LISTING_PRESETS const [proposedTier, setProposedTier] = useState(suggestedTierKey) const [proposing, setProposing] = useState(false) useEffect(() => { setForcePythOracle(bank?.oracleProvider === OracleProvider.Pyth) }, [bank.oracleProvider]) // const proposeMultipleEdit = async () => { // const toPropose = banks.filter( // (x) => // x.oracleProvider === OracleProvider.Switchboard && x.reduceOnly === 0, // ) // const walletSigner = wallet as never // const proposalInstructions = [] // for (const bank of toPropose) { // const ix = await client!.program.methods // .tokenEdit( // null, // null, // null, // null, // null, // null, // null, // 0, // null, // null, // null, // null, // null, // null, // null, // null, // null, // null, // null, // false, // false, // 2, // null, // true, // null, // null, // null, // null, // null, // null, // null, // null, // null, // false, // false, // null, // null, // null, // null, // null, // null, // null, // ) // .accounts({ // group: group!.publicKey, // fallbackOracle: // bank.fallbackOracle && tryGetPubKey(bank.fallbackOracle.toBase58()) // ? bank.fallbackOracle // : PublicKey.default, // oracle: bank.oracle, // admin: MANGO_DAO_WALLET, // mintInfo: group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)! // .publicKey, // }) // .remainingAccounts([ // { // pubkey: bank.publicKey, // isWritable: true, // isSigner: false, // } as AccountMeta, // ]) // .instruction() // proposalInstructions.push(ix) // } // const index = proposals ? Object.values(proposals).length : 0 // const proposalAddress = await createProposal( // connection, // client, // walletSigner, // MANGO_DAO_WALLET_GOVERNANCE, // voter.tokenOwnerRecord!, // `Edit switchboard oracle tokens`, // 'Adjust settings to current liquidity', // index, // proposalInstructions, // vsrClient!, // fee, // ) // window.open( // `https://dao.mango.markets/dao/MNGO/proposal/${proposalAddress.toBase58()}`, // '_blank', // ) // } const proposeNewSuggestedValues = useCallback( async ( bank: Bank, invalidFieldsKeys: string[], tokenTier: LISTING_PRESETS_KEY, ) => { const proposalTx = [] const mintInfo = group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)! const preset = getPresetWithAdjustedDepositLimit( getPresetWithAdjustedNetBorrows( PRESETS[tokenTier], bank.uiDeposits(), bank.uiPrice, toUiDecimals(PRESETS[tokenTier].netBorrowLimitPerWindowQuote, 6), ), bank.uiPrice, bank.mintDecimals, ) const fieldsToChange = invalidFieldsKeys.reduce( (obj, key) => ({ ...obj, [key]: preset[key as keyof typeof preset] }), {}, ) as Partial const oracleConfFilter = fieldsToChange.oracleConfFilter === undefined ? null : fieldsToChange.oracleConfFilter const maxStalenessSlots = (fieldsToChange.maxStalenessSlots as number | string) === '' || fieldsToChange.maxStalenessSlots === -1 ? null : fieldsToChange.maxStalenessSlots const isThereNeedOfSendingOracleConfig = bank.oracleConfig.confFilter.toNumber() !== oracleConfFilter || bank.oracleConfig.maxStalenessSlots.toNumber() !== maxStalenessSlots const rateConfigs = { adjustmentFactor: getNullOrVal(fieldsToChange.adjustmentFactor), util0: getNullOrVal(fieldsToChange.util0), rate0: getNullOrVal(fieldsToChange.rate0), util1: getNullOrVal(fieldsToChange.util1), rate1: getNullOrVal(fieldsToChange.rate1), maxRate: getNullOrVal(fieldsToChange.maxRate), } const isThereNeedOfSendingRateConfigs = Object.values(rateConfigs).filter( (x) => x !== null, ).length const ix = await client!.program.methods .tokenEdit( oracle ? tryGetPubKey(oracle) : null, isThereNeedOfSendingOracleConfig ? { confFilter: fieldsToChange.oracleConfFilter === undefined ? bank.oracleConfig.confFilter.toNumber() : fieldsToChange.oracleConfFilter, maxStalenessSlots: fieldsToChange.maxStalenessSlots === undefined ? bank.oracleConfig.maxStalenessSlots.toNumber() === -1 ? null : bank.oracleConfig.maxStalenessSlots.toNumber() : fieldsToChange.maxStalenessSlots === -1 ? null : fieldsToChange.maxStalenessSlots, } : null, fieldsToChange.groupInsuranceFund === undefined ? null : fieldsToChange.groupInsuranceFund, isThereNeedOfSendingRateConfigs ? { adjustmentFactor: fieldsToChange.adjustmentFactor || bank.adjustmentFactor.toNumber(), util0: fieldsToChange.util0 || bank.util0.toNumber(), rate0: fieldsToChange.rate0 || bank.rate0.toNumber(), util1: fieldsToChange.util1 || bank.util1.toNumber(), rate1: fieldsToChange.rate1 || bank.rate1.toNumber(), maxRate: fieldsToChange.maxRate || bank.maxRate.toNumber(), } : null, getNullOrVal(fieldsToChange.loanFeeRate), getNullOrVal(fieldsToChange.loanOriginationFeeRate), getNullOrVal(fieldsToChange.maintAssetWeight), getNullOrVal(fieldsToChange.initAssetWeight), getNullOrVal(fieldsToChange.maintLiabWeight), getNullOrVal(fieldsToChange.initLiabWeight), getNullOrVal(fieldsToChange.liquidationFee), null, null, null, getNullOrVal(fieldsToChange.minVaultToDepositsRatio), getNullOrVal(fieldsToChange.netBorrowLimitPerWindowQuote) ? new BN(fieldsToChange.netBorrowLimitPerWindowQuote!) : null, getNullOrVal(fieldsToChange.netBorrowLimitWindowSizeTs) ? new BN(fieldsToChange.netBorrowLimitWindowSizeTs!) : null, getNullOrVal(fieldsToChange.borrowWeightScaleStartQuote), getNullOrVal(fieldsToChange.depositWeightScaleStartQuote), false, false, getNullOrVal(fieldsToChange.reduceOnly), null, null, getNullOrVal(fieldsToChange.tokenConditionalSwapTakerFeeRate), getNullOrVal(fieldsToChange.tokenConditionalSwapMakerFeeRate), getNullOrVal(fieldsToChange.flashLoanSwapFeeRate), //do not edit of interest curve scaling null, getNullOrVal(fieldsToChange.interestTargetUtilization), null, null, null, null, false, false, getNullOrVal(fieldsToChange.depositLimit) !== null ? new BN(fieldsToChange.depositLimit!.toString()) : null, getNullOrVal(fieldsToChange.zeroUtilRate), getNullOrVal(fieldsToChange.platformLiquidationFee), fieldsToChange.disableAssetLiquidation === undefined ? null : fieldsToChange.disableAssetLiquidation, getNullOrVal(fieldsToChange.collateralFeePerDay), null, preset.preset_name, ) .accounts({ group: group!.publicKey, fallbackOracle: bank.fallbackOracle && tryGetPubKey(bank.fallbackOracle.toBase58()) ? bank.fallbackOracle : PublicKey.default, oracle: bank.oracle, admin: MANGO_DAO_WALLET, mintInfo: mintInfo.publicKey, }) .remainingAccounts([ { pubkey: bank.publicKey, isWritable: true, isSigner: false, } as AccountMeta, ]) .instruction() proposalTx.push(ix) const walletSigner = wallet as never try { 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, client, 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', description: `${e}`, type: 'error', }) } setProposing(false) }, [ PRESETS, client, connection, fee, group, oracle, proposals, voter.tokenOwnerRecord, vsrClient, wallet, ], ) const mintInfo = group.mintInfosMapByMint.get(bank.mint.toString()) const formattedBankValues = getFormattedBankValues(group, bank) const suggestedValues = getPresetWithAdjustedDepositLimit( getPresetWithAdjustedNetBorrows( PRESETS[proposedTier as LISTING_PRESETS_KEY] as LISTING_PRESET, bank.uiDeposits(), bank.uiPrice, toUiDecimals( PRESETS[proposedTier as LISTING_PRESETS_KEY] .netBorrowLimitPerWindowQuote, 6, ), ), bank.uiPrice, bank.mintDecimals, ) const suggestedFormattedPreset = formatSuggestedValues(suggestedValues) type SuggestedFormattedPreset = typeof suggestedFormattedPreset const invalidKeys: (keyof SuggestedFormattedPreset)[] = Object.keys( suggestedValues, ).length ? compareObjectsAndGetDifferentKeys( formattedBankValues, suggestedFormattedPreset, ).filter( (x: string) => suggestedFormattedPreset[x as keyof SuggestedFormattedPreset] !== undefined && suggestedFormattedPreset[x as keyof SuggestedFormattedPreset] !== null, ) : [] const suggestedFields: Partial = invalidKeys.reduce( (obj, key) => { return { ...obj, [key]: suggestedFormattedPreset[key], } }, {}, ) return (

{bank.name} - Suggested tier: {PRESETS[suggestedTierKey].preset_name}{' '} Current tier: ~ {currentTier?.preset_name}

I want to use pyth oracle (will show presets available for pyth)

setForcePythOracle(checked)} />

{`${formattedBankValues.rate0}% @ ${formattedBankValues.util0}% util, `} {`${formattedBankValues.rate1}% @ ${formattedBankValues.util1}% util, `} {`${formattedBankValues.maxRate}% @ 100% util`} } proposedValue={ (suggestedFields.rate0 || suggestedFields.rate1 || suggestedFields.util0 || suggestedFields.util1 || suggestedFields.maxRate) && ( {`${suggestedFields.rate0 || formattedBankValues.rate0}% @ ${ suggestedFields.util0 || formattedBankValues.util0 }% util, `} {`${suggestedFields.rate1 || formattedBankValues.rate1}% @ ${ suggestedFields.util1 || formattedBankValues.util1 }% util, `} {`${ suggestedFields.maxRate || formattedBankValues.maxRate }% @ 100% util`} ) } />

Price impacts

{midPriceImp .filter((x) => x.symbol === getApiTokenName(bank.name)) .map((x) => (

Amount: ${x.target_amount}

Price impact:{' '} {x.avg_price_impact_percent.toFixed(3)}%

))}
{invalidKeys.length && (

Green values are params that needs to change suggested by current liquidity

)}
) } export default DashboardSuggestedValues const getNullOrVal = (val: number | undefined) => { if (val !== undefined) { return val } return null } const KeyValuePair = ({ label, value, proposedValue, }: { label: string value: number | ReactNode | string proposedValue?: number | ReactNode | string }) => { return (
{label}
{proposedValue && Current: } {value}
{proposedValue && Suggested: } {proposedValue && ( {proposedValue} )}
) }