From 32e21b95d015c6b342fdda505bdb46203cab92b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Brzezin=CC=81ski?= Date: Sun, 10 Nov 2024 20:05:37 +0100 Subject: [PATCH] fix priority fee --- components/MangoProvider.tsx | 32 ++++++------ store/mangoStore.ts | 80 ++++-------------------------- utils/priorityFee.ts | 96 ++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 87 deletions(-) create mode 100644 utils/priorityFee.ts diff --git a/components/MangoProvider.tsx b/components/MangoProvider.tsx index 2d31fdc..155ff41 100644 --- a/components/MangoProvider.tsx +++ b/components/MangoProvider.tsx @@ -1,14 +1,14 @@ -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useState } from 'react' import mangoStore from '@store/mangoStore' import { Keypair } from '@solana/web3.js' import useMangoAccount from 'hooks/useMangoAccount' import useInterval from './shared/useInterval' -import { LAST_WALLET_NAME, PRIORITY_FEE_KEY, SECONDS } from 'utils/constants' +import { LAST_WALLET_NAME, SECONDS } from 'utils/constants' import useNetworkSpeed from 'hooks/useNetworkSpeed' import { useWallet } from '@solana/wallet-adapter-react' import useLocalStorageState from 'hooks/useLocalStorageState' -import { DEFAULT_PRIORITY_FEE_LEVEL } from './settings/RpcSettings' import { getStakableTokensDataForTokenName } from 'utils/tokens' +import { handleEstimateFeeWithWs } from 'utils/priorityFee' const set = mangoStore.getState().set const actions = mangoStore.getState().actions @@ -18,11 +18,13 @@ const HydrateStore = () => { const selectedToken = mangoStore((s) => s.selectedToken) const clientContext = getStakableTokensDataForTokenName(selectedToken)?.clientContext + const [liteRpcWs, setLiteRpcWs] = useState(null) + const updateFee = mangoStore((s) => s.actions.updateFee) const connection = mangoStore((s) => s.connection) const slowNetwork = useNetworkSpeed() - const { wallet } = useWallet() + const { wallet, publicKey } = useWallet() const [, setLastWalletName] = useLocalStorageState(LAST_WALLET_NAME, '') @@ -87,19 +89,17 @@ const HydrateStore = () => { // (slowNetwork ? 60 : 20) * SECONDS, // ) - // estimate the priority fee every 30 seconds - useInterval( - async () => { - if (wallet?.adapter.publicKey) { - const priorityFeeMultiplier = Number( - localStorage.getItem(PRIORITY_FEE_KEY) ?? - DEFAULT_PRIORITY_FEE_LEVEL.value, - ) - actions.estimatePriorityFee(priorityFeeMultiplier) + //fee estimates + // ------------------------------------------------------------------------------------------------------- + useEffect(() => { + if (liteRpcWs === null && publicKey !== null) { + try { + handleEstimateFeeWithWs(setLiteRpcWs, updateFee) + } catch (e) { + console.log(e) } - }, - (slowNetwork ? 60 : 20) * SECONDS, - ) + } + }, [liteRpcWs, publicKey, updateFee]) // The websocket library solana/web3.js uses closes its websocket connection when the subscription list // is empty after opening its first time, preventing subsequent subscriptions from receiving responses. diff --git a/store/mangoStore.ts b/store/mangoStore.ts index c2a21df..6480bfc 100644 --- a/store/mangoStore.ts +++ b/store/mangoStore.ts @@ -32,7 +32,6 @@ import { FALLBACK_ORACLES, ClientContextKeys, MANGO_DATA_API_URL, - MAX_PRIORITY_FEE_KEYS, PAGINATION_PAGE_LENGTH, RPC_PROVIDER_KEY, SWAP_MARGIN_KEY, @@ -63,20 +62,9 @@ import { TRITON_DEDICATED_URL, } from '@components/settings/RpcSettings' import { themeData } from 'utils/theme' -import maxBy from 'lodash/maxBy' -import mapValues from 'lodash/mapValues' -import groupBy from 'lodash/groupBy' -import sampleSize from 'lodash/sampleSize' import { Token } from 'types/jupiter' import { sleep } from 'utils' -import { - ConfirmOptions, - Connection, - Keypair, - LAMPORTS_PER_SOL, - PublicKey, - RecentPrioritizationFees, -} from '@solana/web3.js' +import { ConfirmOptions, Connection, Keypair, PublicKey } from '@solana/web3.js' const MANGO_BOOST_ID = new PublicKey( 'zF2vSz6V9g1YHGmfrzsY497NJzbRr84QUrPry4bLQ25', @@ -310,7 +298,7 @@ export type MangoStore = { fetchWalletTokens: (walletPk: PublicKey) => Promise connectMangoClientWithWallet: (wallet: WalletAdapter) => Promise updateConnection: (url: string) => void - estimatePriorityFee: (feeMultiplier: number) => Promise + updateFee: (fee: number) => Promise } } @@ -843,65 +831,15 @@ const mangoStore = create()( state.client = newClient }) }, - estimatePriorityFee: async (feeMultiplier) => { + updateFee: async (feeEstimate) => { const set = get().set - const group = mangoStore.getState().group - const client = mangoStore.getState().client - - if (!group || !client) return - - const altResponse = await connection.getAddressLookupTable( - new PublicKey('AgCBUZ6UMWqPLftTxeAqpQxtrfiCyL2HgRfmmM6QTfCj'), - ) - - const altKeys = altResponse.value?.state.addresses - if (!altKeys) return - - const addresses = sampleSize(altKeys, MAX_PRIORITY_FEE_KEYS) - const fees = await connection.getRecentPrioritizationFees({ - lockedWritableAccounts: addresses, - }) - - if (fees.length < 1) return - - // get max priority fee per slot (and sort by slot from old to new) - const maxFeeBySlot = mapValues(groupBy(fees, 'slot'), (items) => - maxBy(items, 'prioritizationFee'), - ) - const maximumFees = Object.values(maxFeeBySlot).sort( - (a, b) => a!.slot - b!.slot, - ) as RecentPrioritizationFees[] - - // get median of last 20 fees - const recentFees = maximumFees.slice( - Math.max(maximumFees.length - 20, 0), - ) - const mid = Math.floor(recentFees.length / 2) - const medianFee = - recentFees.length % 2 !== 0 - ? recentFees[mid].prioritizationFee - : (recentFees[mid - 1].prioritizationFee + - recentFees[mid].prioritizationFee) / - 2 - const feeEstimate = Math.min( - Math.ceil(medianFee * feeMultiplier), - LAMPORTS_PER_SOL * 0.01, - ) - - //can use any provider doesn't matter both should be same - const provider = client.jlp.program.provider as AnchorProvider - provider.opts.skipPreflight = true + const currentFee = get().priorityFee - const priorityFee = get()?.priorityFee ?? DEFAULT_PRIORITY_FEE - const newClient = initMangoClient(provider, { - prioritizationFee: priorityFee, - fallbackOracleConfig: FALLBACK_ORACLES, - multipleConnections: backupConnections, - }) - set((state) => { - state.priorityFee = feeEstimate - state.client = newClient - }) + if (currentFee !== feeEstimate) { + set((state) => { + state.priorityFee = feeEstimate + }) + } }, }, } diff --git a/utils/priorityFee.ts b/utils/priorityFee.ts new file mode 100644 index 0000000..b55adf5 --- /dev/null +++ b/utils/priorityFee.ts @@ -0,0 +1,96 @@ +import { Dispatch, SetStateAction } from 'react' +import { PRIORITY_FEE_KEY } from './constants' +import { DEFAULT_PRIORITY_FEE_LEVEL } from '@components/settings/RpcSettings' +import { LAMPORTS_PER_SOL } from '@solana/web3.js' +import { LITE_RPC_URL } from '@store/mangoStore' + +export const handleEstimateFeeWithWs = ( + setWs: Dispatch>, + updateFee: (fee: number) => void, +) => { + try { + let ws: null | WebSocket = null + let lastProcessedTime: null | number = null + let lastFee: null | number = null + let reportedUndefinedFeeCount = 0 + + const wsUrl = new URL(LITE_RPC_URL.replace('https', 'wss')) + ws = new WebSocket(wsUrl) + + ws.addEventListener('open', () => { + try { + console.log('Fee WebSocket opened') + const message = JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'blockPrioritizationFeesSubscribe', + interval: 30, + }) + ws?.send(message) + + setWs(ws) + } catch (e) { + ws?.close(1000) + throw e + } + }) + ws.addEventListener('close', () => { + console.log('Fee WebSocket closed') + setWs(null) + }) + ws.addEventListener('error', () => { + try { + console.log('Fee WebSocket error') + setWs(null) + } catch (e) { + console.log(e) + throw e + } + }) + ws.addEventListener('message', function incoming(data: { data: string }) { + try { + const currentTime = Date.now() + const priorityFeeMultiplier = Number( + localStorage.getItem(PRIORITY_FEE_KEY) ?? + DEFAULT_PRIORITY_FEE_LEVEL.value, + ) + + if (reportedUndefinedFeeCount >= 5) { + ws?.close(1000) + } + if ( + !lastFee || + !lastProcessedTime || + currentTime - lastProcessedTime >= 30000 + ) { + const seventyFivePerc = JSON.parse(data.data)?.params?.result?.value + ?.by_tx[18] + + if (seventyFivePerc === undefined) { + reportedUndefinedFeeCount += 1 + } else { + const feeEstimate = Math.max( + Math.min( + Math.ceil(seventyFivePerc * priorityFeeMultiplier), + LAMPORTS_PER_SOL * 0.005, + ), + 100000, + ) + console.log(feeEstimate) + updateFee(feeEstimate) + lastFee = feeEstimate + lastProcessedTime = currentTime + } + } + } catch (e) { + console.log(e) + throw e + } + }) + return ws + } catch (e) { + console.log(e) + setWs(null) + throw e + } +}