fema-pf-calc
v1.1.0
Published
Intelligent Solana priority fee estimation engine
Maintainers
Readme
Fema
Fema is a TypeScript SDK for Solana with two tools: estimateFee calculates the right priority fee based on what the network is actually paying right now, and safeSend handles the full transaction lifecycle including simulation, signing, submission, and confirmation.
Supports mainnet-beta and devnet.
The Problem
Every Solana transaction can include a priority fee, a tip paid to validators to land the transaction faster. Set it too low and your transaction gets stuck or dropped. Set it too high and you overpay.
The fee you need also depends on what kind of transaction you are sending. A Jupiter swap competes with other Jupiter swaps. An NFT mint competes with other NFT mints. They are not in the same queue, so they should not use the same fee.
Fema solves this by scanning recent blocks, building a real fee distribution, and giving you a number you can use directly.
How It Works
- Fema connects to Solana via a built-in Helius-backed RPC proxy — no API key or RPC setup required
- For each transaction in the last 30 blocks it extracts the fee paid in microLamports per compute unit
- It sorts these into a distribution and computes percentiles (p25, p50, p75)
- If you specify a protocol or pass your transaction, it filters the data to only transactions that used the same programs as yours
- It returns the fee at the percentile matching your chosen speed and strategy
Results are cached for 7 seconds to avoid hammering the RPC on every call.
Installation
npm install fema-pf-calcestimateFee Quick Start
import { init, estimateFee } from "fema-pf-calc";
import { ComputeBudgetProgram } from "@solana/web3.js";
// Configure once at app startup
init({ cluster: "mainnet-beta" }); // no RPC setup needed
// Get the recommended fee
const { fee } = await estimateFee({ speed: "fast", strategy: "balanced" });
// Apply it to your transaction
const priorityIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: fee,
});That is all a developer needs to do. No RPC key, no math, no guessing.
estimateFee Options
const result = await estimateFee({
speed: "fast", // "slow" | "medium" | "fast"
strategy: "balanced", // "cheap" | "balanced" | "aggressive"
protocol: "jupiter", // see supported protocols below
transaction: myTransaction, // pass your actual tx for automatic protocol detection
maxFee: 500_000, // optional cap in microLamports
includeStats: true, // include full distribution in response
});Speed
| Speed | Percentile | What it means |
| -------- | ---------- | --------------------------------------------------- |
| slow | p25 | Cheaper. May take a few blocks to land. |
| medium | p50 | Balanced. Lands within a couple of blocks. |
| fast | p75 | More expensive. Designed to land in the next block. |
Strategy
| Strategy | Adjustment | What it means |
| ------------ | --------------------- | ------------------------------------------------------------------------- |
| cheap | −10 percentile points | Slightly undercut the target. Risk: might not land if the network spikes. |
| balanced | none | Hit the target exactly. Safe default for most use cases. |
| aggressive | +15 percentile points | Slightly overpay to guarantee landing. |
Protocol-Aware Estimation
The most powerful feature of Fema. When you specify a protocol, Fema filters the fee data to only transactions that used the same programs — giving you a fee based on your actual competition, not the whole network.
// Jupiter swap
const { fee: swapFee } = await estimateFee({
speed: "fast",
protocol: "jupiter",
});
// NFT mint
const { fee: nftFee } = await estimateFee({ speed: "fast", protocol: "nft" });
// Pump.fun trade
const { fee: pumpFee } = await estimateFee({
speed: "fast",
protocol: "pumpfun",
});Alternatively, pass your actual transaction and Fema will detect the protocol automatically:
const { fee } = await estimateFee({
speed: "fast",
transaction: mySwapTransaction,
});Supported Protocols
| Protocol | Programs Included |
| ---------- | --------------------------------------------------- |
| jupiter | Jupiter V4, Jupiter V6 |
| raydium | Raydium AMM V4, AMM V5, CLMM |
| orca | Orca Whirlpool, Orca V1 |
| nft | Token Metadata, Candy Machine V2/V3, Auction House |
| pumpfun | Pump.fun |
| openbook | OpenBook V1, OpenBook V2 |
| system | System Program (SOL transfers) |
| token | Token Program, Token 2022, Associated Token Program |
estimateFee Response
{
fee: 253637, // microLamports per CU — pass directly to setComputeUnitPrice
congestion: "high", // "low" | "medium" | "high"
percentileUsed: 75, // which percentile was used
sampleSize: 1203, // number of transactions analyzed
programFiltered: true, // true = fee is based on your protocol's txs specifically
// only included when includeStats: true
stats: {
avgFee: 180000,
medianFee: 28844,
distribution: {
p10: 1200,
p25: 5011,
p50: 28844,
p75: 253637,
p90: 480000,
p95: 820000,
p99: 2100000,
}
}
}The fee value is in microLamports per compute unit — the exact unit that ComputeBudgetProgram.setComputeUnitPrice expects. No conversion needed.
Configuration
init({
cluster: "mainnet-beta", // "mainnet-beta" | "devnet" (default: "mainnet-beta")
cacheDurationMs: 7000, // how long to cache results in ms (default: 7000)
});Sample Code
import express from "express";
import { init, estimateFee } from "fema-pf-calc";
import { ComputeBudgetProgram } from "@solana/web3.js";
const app = express();
init({ cluster: "mainnet-beta" }); // no RPC setup needed
app.get("/fee", async (req, res) => {
try {
const { fee } = await estimateFee({ speed: "fast", strategy: "balanced" });
const priorityIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: fee,
});
res.json({ fee, priorityIx });
} catch (err) {
console.error(err);
res.status(500).json({ error: err.message });
}
});
app.listen(8000, () => {
console.log("Server is running on port http://localhost:8000");
});
export default app;safeSend
safeSend handles the full lifecycle of sending a Solana transaction. It simulates before sending, signs with your wallet, submits, and confirms on-chain. It catches errors before they cost SOL, retries automatically when safe to do so, and tells you exactly what went wrong when it isn't.
safeSend Quick Start
import { safeSend } from "fema-pf-calc";
const result = await safeSend({
tx: myTransaction,
wallet: walletAdapter, // any wallet with signTransaction (Phantom, Backpack, etc.)
network: "mainnet-beta",
mode: "safe",
});
if (result.success) {
console.log("Confirmed:", result.signature);
} else {
console.log("Failed:", result.error?.type);
}safeSend Options
const result = await safeSend({
tx: myTransaction, // Transaction | VersionedTransaction
wallet: walletAdapter, // must implement signTransaction
network: "mainnet-beta", // "devnet" | "mainnet-beta" (default: "devnet")
mode: "safe", // "safe" | "fast" (default: "safe")
ai: true, // enable AI diagnostics on failure (default: false)
});Mode
| Mode | What it does |
| ------ | -------------------------------------------------------------------------- |
| safe | Simulates first, retries up to 3× on recoverable errors, confirms on-chain |
| fast | Simulates first, sends once, returns immediately — no retries |
safeSend Response
// Success
{ success: true, signature: "5Kj...", network: "mainnet-beta", retriesUsed: 0 }
// Failure
{
success: false,
network: "mainnet-beta",
retriesUsed: 2,
error: {
type: "INSUFFICIENT_FUNDS",
logs: ["..."],
// only included when ai: true
explanation: "The sender account does not have enough lamports...",
suggestedFix: "Fund the account with at least 0.05 SOL before retrying.",
}
}Error Types
| Type | Recoverable | What happened |
| ---------------------- | ----------- | ------------------------------------------------- |
| INSUFFICIENT_FUNDS | No | Sender cannot cover the transfer + fees |
| BLOCKHASH_EXPIRED | Yes | Blockhash went stale, retried with a fresh one |
| COMPUTE_EXCEEDED | Yes | Compute limit too low, auto-bumped and retried |
| INVALID_INSTRUCTION | No | Instruction references a wrong or unknown program |
| NETWORK_FAILURE | No | Could not reach the RPC |
| SEND_FAILED | No | Transaction was rejected at submission |
| SIGNING_REJECTED | No | Wallet refused to sign |
| CONFIRMATION_TIMEOUT | No | Transaction was sent but not confirmed in time |
| MAX_RETRIES_EXCEEDED | No | Hit the 3-retry limit without landing |
AI Diagnostics
Set ai: true and add a GROQ_API_KEY to your environment to get a plain-English explanation and a concrete fix suggestion on every failure.
const result = await safeSend({ tx, wallet, network: "mainnet-beta", ai: true });
if (!result.success) {
console.log(result.error?.explanation); // "The compute budget was exceeded..."
console.log(result.error?.suggestedFix); // "Add ComputeBudgetProgram.setComputeUnitLimit..."
}Get a free Groq API key at console.groq.com. Fema uses Llama 3.3-70b under the hood and falls back silently if the key is missing or the API is unavailable.
License
MIT
