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, getFormattedBankValues, } from 'utils/governance/listingTools' import { Bank, Group } from '@blockworks-foundation/mango-v4' import { AccountMeta } 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 } from 'utils/governance/tools' import { Disclosure } from '@headlessui/react' import { LISTING_PRESETS, LISTING_PRESETS_KEYS, } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools' const DashboardSuggestedValues = ({ isOpen, onClose, bank, group, }: ModalProps & { bank: Bank group: Group }) => { const client = mangoStore((s) => s.client) //do not deconstruct wallet is used for anchor to sign const wallet = useWallet() const connection = mangoStore((s) => s.connection) const voter = GovernanceStore((s) => s.voter) const vsrClient = GovernanceStore((s) => s.vsrClient) const proposals = GovernanceStore((s) => s.proposals) const [suggestedTiers, setSuggestedTiers] = useState< Partial<{ [key: string]: string }> >({}) const getSuggestedTierForListedTokens = useCallback(async () => { type PriceImpactResp = { avg_price_impact_percent: number side: 'ask' | 'bid' target_amount: number symbol: string //there is more fileds they are just not used on ui } type PriceImpactRespWithoutSide = Omit const resp = await fetch( 'https://api.mngo.cloud/data/v4/risk/listed-tokens-one-week-price-impacts', ) const jsonReps = (await resp.json()) as PriceImpactResp[] const filteredResp = jsonReps .reduce((acc: PriceImpactRespWithoutSide[], val: PriceImpactResp) => { if (val.side === 'ask') { const bidSide = jsonReps.find( (x) => x.symbol === val.symbol && x.target_amount === val.target_amount && x.side === 'bid', ) acc.push({ target_amount: val.target_amount, avg_price_impact_percent: bidSide ? (bidSide.avg_price_impact_percent + val.avg_price_impact_percent) / 2 : val.avg_price_impact_percent, symbol: val.symbol, }) } return acc }, []) .filter((x) => x.avg_price_impact_percent < 1) .reduce( ( acc: { [key: string]: PriceImpactRespWithoutSide }, val: PriceImpactRespWithoutSide, ) => { if ( !acc[val.symbol] || val.target_amount > acc[val.symbol].target_amount ) { acc[val.symbol] = val } return acc }, {}, ) const suggestedTiers = Object.keys(filteredResp).reduce( (acc: { [key: string]: string | undefined }, key: string) => { acc[key] = Object.values(LISTING_PRESETS).find( (x) => x.preset_target_amount === filteredResp[key].target_amount, )?.preset_key return acc }, {}, ) setSuggestedTiers(suggestedTiers) }, []) const proposeNewSuggestedValues = useCallback( async ( bank: Bank, invalidFieldsKeys: string[], tokenTier: LISTING_PRESETS_KEYS, ) => { const proposalTx = [] const mintInfo = group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)! const preset = LISTING_PRESETS[tokenTier] const fieldsToChange = invalidFieldsKeys.reduce( (obj, key) => ({ ...obj, [key]: preset[key as keyof typeof preset] }), {}, ) as Partial const isThereNeedOfSendingOracleConfig = fieldsToChange.oracleConfFilter !== undefined || fieldsToChange.maxStalenessSlots !== undefined const isThereNeedOfSendingRateConfigs = fieldsToChange.adjustmentFactor !== undefined || fieldsToChange.util0 !== undefined || fieldsToChange.rate0 !== undefined || fieldsToChange.util1 !== undefined || fieldsToChange.rate1 !== undefined || fieldsToChange.maxRate !== undefined const ix = await client!.program.methods .tokenEdit( null, isThereNeedOfSendingOracleConfig ? { confFilter: fieldsToChange.oracleConfFilter!, maxStalenessSlots: fieldsToChange.maxStalenessSlots!, } : null, null, isThereNeedOfSendingRateConfigs ? { adjustmentFactor: fieldsToChange.adjustmentFactor!, util0: fieldsToChange.util0!, rate0: fieldsToChange.rate0!, util1: fieldsToChange.util1!, rate1: fieldsToChange.rate1!, maxRate: fieldsToChange.maxRate!, } : 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.borrowWeightScale), getNullOrVal(fieldsToChange.depositWeightScale), false, false, bank.reduceOnly ? 0 : null, null, null, ) .accounts({ group: group!.publicKey, 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 { 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!, ) window.open( `https://dao.mango.markets/dao/MNGO/proposal/${proposalAddress.toBase58()}`, '_blank', ) } catch (e) { notify({ title: 'Error during proposal creation', description: `${e}`, type: 'error', }) } }, [ client, connection, group, proposals, voter.tokenOwnerRecord, vsrClient, wallet, ], ) const extractTokenTierForName = ( suggestedTokenObj: Partial<{ [key: string]: string }>, tier: string, ) => { if (tier === 'ETH (Portal)') { return suggestedTokenObj['ETH'] } return suggestedTokenObj[tier] } useEffect(() => { getSuggestedTierForListedTokens() }, [getSuggestedTierForListedTokens]) const mintInfo = group.mintInfosMapByMint.get(bank.mint.toString()) const formattedBankValues = getFormattedBankValues(group, bank) const suggestedTier = extractTokenTierForName(suggestedTiers, bank.name) ? extractTokenTierForName(suggestedTiers, bank.name)! : 'SHIT' const suggestedVaules = LISTING_PRESETS[suggestedTier as LISTING_PRESETS_KEYS] const suggestedFormattedPreset = formatSuggestedValues(suggestedVaules) type SuggestedFormattedPreset = typeof suggestedFormattedPreset const invalidKeys: (keyof SuggestedFormattedPreset)[] = Object.keys( suggestedVaules, ).length ? compareObjectsAndGetDifferentKeys( formattedBankValues, suggestedFormattedPreset, ).filter( (x: string) => suggestedFormattedPreset[x as keyof SuggestedFormattedPreset], ) : [] const suggestedFields: Partial = invalidKeys.reduce( (obj, key) => { return { ...obj, [key]: suggestedFormattedPreset[key], } }, {}, ) return (

{bank.name}

{`${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`} ) } /> {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} )}
) }