Overview

This guide demonstrates how to estimate priority fees using serialized transactions, which is the recommended approach for accurate fee estimation with the Helius Priority Fee API.

Why Use Serialized Transactions?

Serialized transactions offer several advantages for fee estimation:

  • Higher accuracy: The API can analyze the exact writable and read-only accounts in your transaction
  • Detailed analysis: Incorporates instruction-specific data that may affect priority fee markets
  • Realistic estimates: Reflects the actual transaction you’ll be sending to the network

Implementation Steps

1. Create Your Transaction

First, build your transaction with all necessary instructions (except the priority fee instruction):

import { 
  Connection, 
  PublicKey, 
  Transaction, 
  SystemProgram, 
  ComputeBudgetProgram 
} from "@solana/web3.js";
import bs58 from "bs58";

// Initialize connection
const connection = new Connection("https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY");

// Create transaction with your instructions
const transaction = new Transaction();

// Add your regular instructions
const transferIx = SystemProgram.transfer({
  fromPubkey: senderKeypair.publicKey,
  toPubkey: recipientPublicKey,
  lamports: 1000000, // 0.001 SOL
});
transaction.add(transferIx);

// Temporarily set required fields for serialization
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.feePayer = senderKeypair.publicKey;

2. Serialize the Transaction

The transaction must be serialized before sending it to the Priority Fee API:

// Serialize the transaction
const serializedTransaction = bs58.encode(transaction.serialize());

3. Call the Priority Fee API

Make a request to the Helius Priority Fee API with the serialized transaction:

async function getPriorityFeeEstimate(connection, serializedTransaction, priorityLevel = "Medium") {
  const response = await fetch(connection.rpcEndpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "1",
      method: "getPriorityFeeEstimate",
      params: [
        {
          transaction: serializedTransaction,
          options: { 
            priorityLevel: priorityLevel,
            recommended: true 
          }
        }
      ]
    })
  });
  
  const result = await response.json();
  
  if (result.error) {
    throw new Error(`Fee estimation failed: ${JSON.stringify(result.error)}`);
  }
  
  return result.result.priorityFeeEstimate;
}

4. Add the Priority Fee to Your Transaction

After receiving the fee estimate, update your transaction with the priority fee instruction:

// Get priority fee estimate
const priorityFee = await getPriorityFeeEstimate(connection, serializedTransaction, "High");
console.log(`Estimated priority fee: ${priorityFee} micro-lamports`);

// Reset transaction instructions
transaction.instructions = [];

// Add priority fee instruction first
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
  microLamports: priorityFee
});
transaction.add(priorityFeeIx);

// Re-add your original instructions
transaction.add(transferIx);

// Update blockhash and sign
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.sign(senderKeypair);

5. Send the Transaction

Finally, send your transaction with the priority fee:

try {
  const signature = await sendAndConfirmTransaction(
    connection,
    transaction,
    [senderKeypair],
    { maxRetries: 0 } // Set to 0 for staked connection usage
  );
  console.log(`Transaction successful with signature: ${signature}`);
  return signature;
} catch (error) {
  console.error("Error sending transaction:", error);
  throw error;
}

Complete Example

Here’s a complete example combining all steps:

const { 
  Connection, 
  PublicKey, 
  Transaction, 
  SystemProgram, 
  ComputeBudgetProgram, 
  sendAndConfirmTransaction,
  Keypair
} = require("@solana/web3.js");
const bs58 = require("bs58");

// Initialize connection and accounts
const connection = new Connection("https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY");
const senderKeypair = Keypair.fromSecretKey(bs58.decode("YOUR_PRIVATE_KEY"));
const receiverPublicKey = new PublicKey("RECIPIENT_PUBLIC_KEY");

async function sendTransactionWithPriorityFee(amount, priorityLevel = "Medium") {
  // Create transaction with transfer instruction
  const transaction = new Transaction();
  const transferIx = SystemProgram.transfer({
    fromPubkey: senderKeypair.publicKey,
    toPubkey: receiverPublicKey,
    lamports: amount,
  });
  transaction.add(transferIx);
  
  // Temporarily set a recent blockhash for serialization
  transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  transaction.feePayer = senderKeypair.publicKey;
  
  // Serialize the transaction
  const serializedTransaction = bs58.encode(transaction.serialize());
  
  // Get priority fee estimate
  const priorityFee = await getPriorityFeeEstimate(connection, serializedTransaction, priorityLevel);
  console.log(`Estimated ${priorityLevel} priority fee: ${priorityFee} micro-lamports`);
  
  // Reset transaction and add priority fee instruction first
  transaction.instructions = [];
  
  // Add priority fee instruction
  const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: priorityFee
  });
  transaction.add(priorityFeeIx);
  
  // Add the original transfer instruction
  transaction.add(transferIx);
  
  // Update blockhash and sign
  transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  transaction.sign(senderKeypair);
  
  // Send transaction
  try {
    const signature = await sendAndConfirmTransaction(
      connection,
      transaction,
      [senderKeypair],
      { maxRetries: 0 } // Set to 0 for staked connection usage
    );
    console.log(`Transaction successful with signature: ${signature}`);
    return signature;
  } catch (error) {
    console.error("Error sending transaction:", error);
    throw error;
  }
}

// Helper function to get priority fee estimate
async function getPriorityFeeEstimate(connection, serializedTransaction, priorityLevel) {
  const response = await fetch(connection.rpcEndpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "1",
      method: "getPriorityFeeEstimate",
      params: [
        {
          transaction: serializedTransaction,
          options: { 
            priorityLevel: priorityLevel,
            recommended: true 
          }
        }
      ]
    })
  });
  
  const result = await response.json();
  
  if (result.error) {
    throw new Error(`Fee estimation failed: ${JSON.stringify(result.error)}`);
  }
  
  return result.result.priorityFeeEstimate;
}

// Send 0.01 SOL with High priority
sendTransactionWithPriorityFee(10000000, "High");

Advanced Options

All Priority Levels

You can request estimates for all priority levels simultaneously:

async function getAllPriorityLevels(connection, serializedTransaction) {
  const response = await fetch(connection.rpcEndpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "1",
      method: "getPriorityFeeEstimate",
      params: [
        {
          transaction: serializedTransaction,
          options: { 
            includeAllPriorityFeeLevels: true
          }
        }
      ]
    })
  });
  
  const result = await response.json();
  return result.result.priorityFeeLevels;
}

// Usage
const allLevels = await getAllPriorityLevels(connection, serializedTransaction);
console.log("Available priority levels:", allLevels);
// Output: { min: 0.0, low: 10.0, medium: 10000.0, high: 25000.0, veryHigh: 1000000.0, unsafeMax: 50000000.0 }

Empty Slot Evaluation

The evaluateEmptySlotAsZero option optimizes fee calculations for accounts with sparse transaction history:

const response = await fetch(connection.rpcEndpoint, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "1",
    method: "getPriorityFeeEstimate",
    params: [
      {
        transaction: serializedTransaction,
        options: { 
          priorityLevel: "Medium",
          recommended: true,
          evaluateEmptySlotAsZero: true  // Default is true
        }
      }
    ]
  })
});

When true (default), this treats slots with no transactions for a particular account as having zero fees, rather than excluding them from the calculation. This can provide more accurate estimates for accounts with infrequent activity.