Priority fees are an optional fee you can add to your transactions to incentivize block producers (leaders) to include your transaction in the next block. Priority fees are priced in micro-lamports per compute unit. It is recommended to include a priority fee in your transactions, but how much should you pay?
Helius Priority Fee API
The getPriorityFeeEstimate is a new RPC method that provides fee recommendations based on historical data. Most importantly, it considers both global and local fee markets.
To use this API, you need to provide either a serialized transaction or a list of account keys.
To obtain the serialized transaction, you can use the transaction.serialize() method from the @solana/web3.js library. For more details on transaction serialization, refer to the Solana Web3.js documentation.
Here's an example using a serialized transaction::
The estimate is priced in micro-lamports per compute unit.
Alternatively, you can provide a list of account keys.
Here's an example that demonstrates how to extract account keys from a transaction and use them in the API request:
const { Transaction, Connection, ComputeBudgetProgram, PublicKey, TransactionInstruction } = require('@solana/web3.js');
// Initialize Connection object with Helius endpointconstconnection=newConnection("https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY");(asyncfunctiongetPriorityFeeEst() {consttransaction=newTransaction();// A sample instruction to the transactiontransaction.add(newTransactionInstruction({ programId:newPublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), data:Buffer.from("Experimenting","utf8"), keys: [], }) )transaction.recentBlockhash = (awaitconnection.getLatestBlockhash()).blockhash;transaction.feePayer =newPublicKey("A3r2FvL1BkAGmcv74vQ5fVpFdm7GttDKhn9RhYV3zifL");// Extract all account keys from the transactionconstaccountKeys=transaction.compileMessage().accountKeys;// Convert PublicKeys to base58 stringsconstpublicKeys=accountKeys.map(key =>key.toBase58());try {constresponse=awaitfetch(connection.rpcEndpoint, { method:'POST', headers: { 'Content-Type':'application/json' }, body:JSON.stringify({ jsonrpc:'2.0', id:'helius-example', method:'getPriorityFeeEstimate', params: [ { accountKeys: publicKeys, options: { recommended:true, }, } ], }), });constdata=awaitresponse.json();constpriorityFeeEstimate=data.result?.priorityFeeEstimate;// Add the priority fee to the transactiontransaction.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFeeEstimate }), )console.log("Estimated priority fee:", priorityFeeEstimate);return priorityFeeEstimate; } catch (err) {console.error(`Error: ${err}`);return10_000; }})();
Here we are extracting all the account keys from the transaction using compileMessage, parsing the output and then passing all the public keys to the API.
It is crucial to extract and provide all relevant account keys because the local fee market is influenced by the number of transactions trying to interact with specific accounts. Providing all account keys allows the API to assess this local market condition accurately.
By following this method, you ensure that the priority fee estimate is as accurate and relevant as possible for your specific transaction.
Empty Slot Evaluation
The evaluateEmptySlotAsZero flag helps optimize priority fee calculations for accounts with sparse transaction history. When true, this mechanism smooths out fee estimation by treating slots with no transactions for a particular account as having zero fees, rather than excluding them from the calculation.
To implement, add the evaluateEmptySlotAsZero flag to your options parameter:
This approach typically results in more reasonable fee estimates for accounts with infrequent activity. Monitor your transaction success rates when implementing this feature to ensure optimal performance for your specific use case.
How it Works
The method uses a set of predefined priority levels (percentiles) to dictate the returned estimate. Users can optionally specify to receive all the priority levels and adjust the window with which these are calculated via lookbackSlots
fnget_recent_priority_fee_estimate(request:GetPriorityFeeEstimateRequest) ->GetPriorityFeeEstimateResponsestructGetPriorityFeeEstimateRequest { transaction:Option<String>, // estimate fee for a serialized txn accountKeys:Option<Vec<String>>, // estimate fee for a list of accounts options:Option<GetPriorityFeeEstimateOptions>}structGetPriorityFeeEstimateOptions { priorityLevel:Option<PriorityLevel>, // Default to MEDIUM includeAllPriorityFeeLevels:Option<bool>, // Include all priority level estimates in the response transactionEncoding:Option<UiTransactionEncoding>, // Default Base58 lookbackSlots: Option<u8>, // The number of slots to look back to calculate the estimate. Valid numbers are 1-150, default is 150
recommended:Option<bool>, // The Helius recommended fee for landing transactions includeVote:Option<bool>, // Include vote transactions in the priority fee estimate calculation. Default to true }enumPriorityLevel {Min, // 0th percentileLow, // 25th percentileMedium, // 50th percentileHigh, // 75th percentileVeryHigh, // 95th percentile// labelled unsafe to prevent people from using and draining their funds by accidentUnsafeMax, // 100th percentile Default, // 50th percentile}structGetPriorityFeeEstimateResponse { priority_fee_estimate:Option<MicroLamportPriorityFee> priority_fee_levels:Option<MicroLamportPriorityFeeLevels>}typeMicroLamportPriorityFee=f64structMicroLamportPriorityFeeLevels { min:f64, low:f64, medium:f64, high:f64, veryHigh:f64, unsafeMax:f64,}
Sending a transaction with the Priority Fee API (Javascript)
This code snippet showcases how one can transfer SOL from one account to another. In this code, the transaction is passed to the priority fee API which then determines the specified priority fee from all the accounts involved in the transaction.
const {Connection,SystemProgram,Transaction,sendAndConfirmTransaction,Keypair,ComputeBudgetProgram,} =require("@solana/web3.js");constbs58=require("bs58");constHeliusURL="https://mainnet.helius-rpc.com/?api-key=<YOUR_API_KEY>";constconnection=newConnection(HeliusURL);constfromKeypair=Keypair.fromSecretKey(Uint8Array.from("[Your secret key]")); // Replace with your own private keyconst toPubkey = "CckxW6C1CjsxYcXSiDbk7NYfPLhfqAm3kSB5LEZunnSE"; // Replace with the public key that you want to send SOL to
asyncfunctiongetPriorityFeeEstimate(priorityLevel, transaction) {constresponse=awaitfetch(HeliusURL, { method:"POST", headers: { "Content-Type":"application/json" }, body:JSON.stringify({ jsonrpc:"2.0", id:"1", method:"getPriorityFeeEstimate", params: [ { transaction:bs58.encode(transaction.serialize()),// Pass the serialized transaction in Base58 options: { priorityLevel: priorityLevel }, }, ], }), });constdata=awaitresponse.json();console.log("Fee in function for", priorityLevel," :",data.result.priorityFeeEstimate );returndata.result;}asyncfunctionsendTransactionWithPriorityFee(priorityLevel) {consttransaction=newTransaction();consttransferIx=SystemProgram.transfer({ fromPubkey:fromKeypair.publicKey, toPubkey, lamports:100, });transaction.add(transferIx);let feeEstimate = { priorityFeeEstimate:0 };if (priorityLevel !=="NONE") { feeEstimate =awaitgetPriorityFeeEstimate(priorityLevel, transaction);constcomputePriceIx=ComputeBudgetProgram.setComputeUnitPrice({ microLamports:feeEstimate.priorityFeeEstimate, });transaction.add(computePriceIx); }transaction.recentBlockhash= (awaitconnection.getLatestBlockhash() ).blockhash;transaction.sign(fromKeypair);try {consttxid=awaitsendAndConfirmTransaction(connection, transaction, [ fromKeypair, ]);console.log(`Transaction sent successfully with signature ${txid}`); } catch (e) {console.error(`Failed to send transaction: ${e}`); }}sendTransactionWithPriorityFee("High"); // Choose between "Min", "Low", "Medium", "High", "VeryHigh", "UnsafeMax"
Calculating the Percentiles (v1)
To calculate the percentiles, we need to consider the global and local fee market over transactions in the last N slots
where txn_fees are the txn_fees from the last 150 blocks, and account_fees(accounts) are the fees for transactions containing these accounts from the last 150 blocks. Here, we are considering the total set of fees seen for accounts and transactions as opposed to the minimum.
Global Fee Market Estimate
The global fee market estimate is a percentile of priority fees paid for transactions in the last N slots.
Local Fee Market Estimate
The local fee market is influenced by the number of people trying to obtain a lock on an account. We can estimate this similarly to the global fee market but instead use the percentile of fees paid for transactions involving a given account(s). If a user requests an estimate for multiple accounts in the same transaction, we will take the max of the percentiles across those accounts.
Priority Fee Estimate
The priority_fee_estimate will be the max of the global and local fee market estimates.
Extensions
This method could also be integrated into simulateTransaction and returned with the response context. This way, developers using simulateTransaction can eliminate an extra RPC call.
Version 2 (V2)
The v2 logic more closely mimics the way the Agave scheduler prioritizes transactions. The algorithm is as follows
Updated Algorithm
The new algorithm for priority_estimate is as follows:
The V2 algorithm will become the canonical version on September 30th. To access it before then you can by adding priority-fee-version=2.0 to the query params of your request.
NOTE: You can double check that you are correctly hitting the new endpoint if you see x-priority-fee-version=2.0 in your response headers.
Key Differences from V1
Individual Account Consideration: Instead of calculating a single percentile for all accounts involved (account_fees(accounts)), v2 calculates the percentile for each account separately.
Exclude readonly accounts: Local fee markets kick in when there is high demand to write to an account, so the calculation only considers historical fees for writable accounts. If you supply accounts in the request payload it will assume that all accounts are writable in the calculation. If you supply a transaction in the request payload it will only consider the writable accounts in the calculation.
Potential for Higher Estimates: In scenarios where one account has significantly higher fees than others, the v2 algorithm is more likely to capture this, potentially resulting in higher priority fee estimates compared to v1.