Most Accurate Method: Get the highest precision priority fee estimates by analyzing your exact transaction. Recommended for production applications where accuracy matters most.

Overview

Serialized transactions provide the most accurate fee estimates because the API can analyze the exact accounts and operations that will be performed in your transaction.

Why Use Serialized Transactions

  • Highest accuracy - Analyzes exact operations
  • Detailed analysis - Instruction-specific patterns
  • Realistic estimates - Reflects actual transaction
  • Production-ready - Built for critical applications

Best For

  • Production applications
  • Complex transactions
  • Critical operations
  • Maximum accuracy needed

Advantages Over Account Keys

Instruction-Specific Analysis

The API can analyze specific operations and their historical fee patterns, not just account activity.

Transaction Size Awareness

Considers the actual size and complexity of your transaction for more accurate estimates.

Read-Only Account Handling

Better analysis of both writable and read-only accounts in their transaction context.

Implementation Guide

1

Build Your Transaction

Create your transaction with all instructions (except priority fee)
2

Serialize the Transaction

Convert your transaction to a serialized format
3

Get Fee Estimate

Call the Priority Fee API with your serialized transaction
4

Apply Priority Fee

Add the priority fee instruction and send your transaction

Quick Start Example

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

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

// 1. Build your transaction (without priority fee)
const transaction = new Transaction();
const transferIx = SystemProgram.transfer({
  fromPubkey: senderKeypair.publicKey,
  toPubkey: recipientPublicKey,
  lamports: 1000000, // 0.001 SOL
});
transaction.add(transferIx);

// 2. Set required fields and serialize
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.feePayer = senderKeypair.publicKey;
const serializedTx = bs58.encode(transaction.serialize());

// 3. Get priority fee estimate
const priorityFee = await getPriorityFeeEstimate(connection, serializedTx, "Medium");

// 4. Add priority fee and send
transaction.instructions = []; // Reset
transaction.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }));
transaction.add(transferIx);
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.sign(senderKeypair);

Core Implementation Function

Here’s a reusable function for getting priority fee estimates from serialized transactions:
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;
}

Complete Implementation Examples

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") {
  // 1. Create transaction with transfer instruction
  const transaction = new Transaction();
  const transferIx = SystemProgram.transfer({
    fromPubkey: senderKeypair.publicKey,
    toPubkey: receiverPublicKey,
    lamports: amount,
  });
  transaction.add(transferIx);
  
  // 2. Set required fields for serialization
  transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  transaction.feePayer = senderKeypair.publicKey;
  
  // 3. Serialize the transaction
  const serializedTransaction = bs58.encode(transaction.serialize());
  
  // 4. Get priority fee estimate
  const priorityFee = await getPriorityFeeEstimate(connection, serializedTransaction, priorityLevel);
  console.log(`Estimated ${priorityLevel} priority fee: ${priorityFee} micro-lamports`);
  
  // 5. Reset transaction and add priority fee instruction first
  transaction.instructions = [];
  
  // Add priority fee instruction
  const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: priorityFee
  });
  transaction.add(priorityFeeIx);
  
  // Re-add the original transfer instruction
  transaction.add(transferIx);
  
  // 6. Update blockhash and sign
  transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  transaction.sign(senderKeypair);
  
  // 7. Send the 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;
  }
}

// Usage
sendTransactionWithPriorityFee(1000000, "High"); // Send 0.001 SOL with high priority

Advanced Configuration Options

Get estimates for all priority levels at once:
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("All priority levels:", allLevels);
/*
Output:
{
  "min": 0,
  "low": 1000, 
  "medium": 5000,
  "high": 15000,
  "veryHigh": 50000,
  "unsafeMax": 100000
}
*/
Request detailed information about the fee calculation:
async function getDetailedFeeAnalysis(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: { 
          includeDetails: true,
          priorityLevel: "Medium"
        }
      }]
    })
  });
  
  const result = await response.json();
  console.log("Detailed analysis:", result.result);
  return result.result;
}
This provides insights into how the fee was calculated, including per-account analysis.
Adjust the number of slots analyzed:
async function getCustomLookbackEstimate(connection, serializedTransaction, lookbackSlots = 50) {
  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",
          lookbackSlots: lookbackSlots  // 1-150, default is 150
        }
      }]
    })
  });
  
  const result = await response.json();
  return result.result.priorityFeeEstimate;
}

// Compare different lookback periods
const recentEstimate = await getCustomLookbackEstimate(connection, serializedTx, 30);
const longerEstimate = await getCustomLookbackEstimate(connection, serializedTx, 100);

console.log(`Recent (30 slots): ${recentEstimate} micro-lamports`);
console.log(`Longer (100 slots): ${longerEstimate} micro-lamports`);

Best Practices

Transaction Serialization

Always serialize your actual transaction, not a simplified version
// ✅ Good - serialize actual transaction
const transaction = new Transaction();
transaction.add(actualInstruction1);
transaction.add(actualInstruction2);
const serialized = bs58.encode(transaction.serialize());

Instruction Order

Include all instructions except the priority fee in your estimation transaction
// ✅ Good - all business logic included
transaction.add(createAccountIx);
transaction.add(initializeIx);
transaction.add(transferIx);

// ❌ Don't include priority fee in estimation

Error Handling Strategies

class SerializedTransactionFeeEstimator {
  constructor(connection) {
    this.connection = connection;
    this.fallbackFee = 10000; // 10k micro-lamports
  }

  async getEstimate(serializedTransaction, priorityLevel = "Medium") {
    try {
      // Primary attempt with serialized transaction
      return await this.getPrimaryEstimate(serializedTransaction, priorityLevel);
    } catch (error) {
      console.warn("Serialized transaction estimate failed:", error.message);
      
      // Fallback to account-based estimation
      try {
        return await this.getFallbackEstimate(serializedTransaction, priorityLevel);
      } catch (fallbackError) {
        console.warn("Fallback estimate failed:", fallbackError.message);
        return this.getFallbackFee(priorityLevel);
      }
    }
  }

  async getPrimaryEstimate(serializedTransaction, priorityLevel) {
    const response = await fetch(this.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(result.error.message);
    }
    
    return result.result.priorityFeeEstimate;
  }

  async getFallbackEstimate(serializedTransaction, priorityLevel) {
    // Extract account keys from transaction and use account-based estimation
    const transaction = Transaction.from(bs58.decode(serializedTransaction));
    const accountKeys = transaction.instructions
      .flatMap(ix => [ix.programId, ...ix.keys.map(k => k.pubkey)])
      .map(key => key.toString());

    const uniqueAccountKeys = [...new Set(accountKeys)];
    
    // Use account-based estimation as fallback
    const response = await fetch(this.connection.rpcEndpoint, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        jsonrpc: "2.0",
        id: "1",
        method: "getPriorityFeeEstimate",
        params: [{
          accountKeys: uniqueAccountKeys,
          options: { 
            priorityLevel: priorityLevel,
            recommended: true 
          }
        }]
      })
    });

    const result = await response.json();
    if (result.error) {
      throw new Error(result.error.message);
    }
    
    return result.result.priorityFeeEstimate;
  }

  getFallbackFee(priorityLevel) {
    const fallbacks = {
      "Low": 1000,
      "Medium": 5000,
      "High": 15000,
      "VeryHigh": 50000
    };
    
    return fallbacks[priorityLevel] || 5000;
  }
}

// Usage
const estimator = new SerializedTransactionFeeEstimator(connection);
const fee = await estimator.getEstimate(serializedTransaction, "High");

Common Issues & Solutions

Problem: Error serializing incomplete transactionsSolution: Always set required fields before serialization:
// ✅ Always set these fields
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.feePayer = keypair.publicKey;

// Then serialize
const serialized = bs58.encode(transaction.serialize());
Problem: Using stale blockhash causes transaction failuresSolution: Always get fresh blockhash before final send:
// Get estimate with temporary blockhash
const tempBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.recentBlockhash = tempBlockhash;
const serialized = bs58.encode(transaction.serialize());

const priorityFee = await getPriorityFeeEstimate(connection, serialized, "Medium");

// Reset and rebuild with fresh blockhash
transaction.instructions = [];
transaction.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }));
transaction.add(originalInstruction);

// Get FRESH blockhash before sending
const freshBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.recentBlockhash = freshBlockhash;
Problem: Transaction too large for serializationSolution: Use versioned transactions or break into multiple transactions:
import { VersionedTransaction, TransactionMessage } from "@solana/web3.js";

// For large transactions, use versioned transactions
const messageV0 = new TransactionMessage({
  payerKey: keypair.publicKey,
  recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
  instructions: [instruction1, instruction2, instruction3] // Many instructions
}).compileToV0Message();

const versionedTransaction = new VersionedTransaction(messageV0);
const serialized = bs58.encode(versionedTransaction.serialize());

When to Use vs Account Keys

Use Serialized Transactions

Production applications
  • Maximum accuracy required
  • Complex multi-instruction transactions
  • Critical operations
  • Performance-sensitive applications
Development scenarios
  • Final integration testing
  • Performance optimization
  • Production deployment

Use Account Keys

Development & prototyping
  • Quick estimates during development
  • Simple transactions
  • Pre-transaction planning
  • Architecture constraints preventing serialization
Analysis scenarios
  • Account-level fee pattern analysis
  • Batch account analysis
  • Quick market research