Helius Priority Fee API: Estimating Solana Fees That Actually Land
Using the Helius priority fee estimate API for Solana: how percentile modeling, recommended levels, and account-scoped sampling produce fees that actually land.
A hardcoded 10,000 micro-lamports-per-compute-unit priority fee is why your bot pays 40x too much at 3am and still gets dropped during a mint. Static fees lose both ways: overpaying when the chain is quiet, underbidding when a hyped launch pushes the winning bid to 2 million micro-lamports/CU. The getPriorityFeeEstimate method from Helius exists to kill the guesswork, but only if you understand what it's actually sampling and where its numbers come from.
This is not another post rederiving the compute-unit-price × compute-unit-limit math. That math is table stakes. The interesting problem is estimation — turning the noisy, per-account distribution of recently landed fees into a single number you can plug into a ComputeBudgetProgram.setComputeUnitPrice instruction and reasonably expect to land.
What the endpoint actually returns
getPriorityFeeEstimate is a Helius-specific RPC extension, not part of the vanilla Solana JSON-RPC spec. You call it with either a serialized transaction or a bare list of account keys, and it returns a fee level in micro-lamports per compute unit.
The default response gives you a single priorityFeeEstimate. But the useful mode is asking for the full percentile breakdown:
{
"jsonrpc": "2.0",
"id": "1",
"method": "getPriorityFeeEstimate",
"params": [{
"accountKeys": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
"options": {
"includeAllPriorityFeeLevels": true,
"recommended": false
}
}]
}
That returns a priorityFeeLevels object with min, low, medium, high, veryHigh, and unsafeMax keys, mapped to the 0th, 25th, 50th, 75th, 95th, and 100th percentiles of fees paid on transactions that touched those accounts recently. The unsafeMax (100th percentile) is exactly what it sounds like — one whale who overpaid by 100x will sit at the top, so never bid it blindly.
The critical detail most people miss: the estimate is account-scoped. Helius filters the recent fee sample to transactions that wrote to the accounts you passed in. Pass the Pump.fun bonding-curve program and you get the fee distribution for people currently fighting over that program's write locks — which is far higher than the global median. Pass nothing specific and you get a global estimate that will consistently underbid on any hot account.
Why account scoping is the whole game
Solana's fee market is not global. Priority fees buy you position in the queue for the specific accounts your transaction locks. If your swap writes to a Raydium pool that nobody else is touching this second, a low-tier fee lands instantly. If it writes to a pool that's mid-arbitrage-war, even veryHigh might not be enough.
This is why a wallet-tracking or copy system has to compute the estimate against the target's accounts, not a generic value. When we build copy-trading infrastructure, the fee call always includes the exact writable accounts the mirrored transaction will lock, because that's the only sample that reflects real contention. The same logic drives fee selection in a sniper that races into new pools — you scope to the pool init accounts and the AMM program, then bid the high or veryHigh percentile depending on how many other bots you expect.
A concrete example. During a normal window, getPriorityFeeEstimate on a Jupiter route might return:
medium: ~12,000 micro-lamports/CUhigh: ~55,000veryHigh: ~180,000
Fifteen seconds into a trending token, the same call against that token's pool accounts can return high north of 900,000. A bot pinned to a static 50,000 fee lands fine in the first case and gets silently dropped in the second, because the leader packs higher bidders first and the block fills before your transaction is reached.
The recommended flag and its ceiling
Setting recommended: true tells Helius to return a single blended value it considers safe — roughly the 75th percentile, with a floor of 10,000 micro-lamports/CU so you never submit a fee so low it's effectively ignored. It also caps the recommendation to avoid the unsafeMax trap.
The recommended mode is fine for low-stakes transactions where landing within a few slots is acceptable. It is not fine for latency-critical paths. For MEV and arbitrage execution, the difference between the 75th and 95th percentile is the difference between landing in the current block and landing one slot late, by which point the price gap you were capturing is gone. There, you want the raw percentile array and your own bidding curve on top of it.
A sane estimation loop
Here's the pattern that has held up in production for us:
const res = await fetch(HELIUS_RPC, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0", id: 1,
method: "getPriorityFeeEstimate",
params: [{
accountKeys: writableAccounts,
options: { includeAllPriorityFeeLevels: true }
}]
})
}).then(r => r.json());
const levels = res.result.priorityFeeLevels;
// Bid aggressively for hot accounts, back off when quiet.
const base = urgent ? levels.veryHigh : levels.high;
const fee = Math.min(Math.max(base, 10_000), FEE_HARD_CAP);
Three things make this work:
- Always clamp with a hard cap.
FEE_HARD_CAPis your circuit breaker. During a genuine bidding war theveryHighpercentile can spike into the millions, and you need a ceiling that protects you from paying more than the trade is worth. - Refresh per transaction, not per session. The distribution moves in seconds. Caching an estimate for even 10 seconds means bidding into a stale market.
- Set the compute-unit limit tightly too. The fee you pay is price × limit. If you leave the limit at the 1.4M default while only using 200k CUs, you either overpay on the padding or, worse, your effective priority-per-CU is diluted. Simulate the transaction, take the used units, add ~10% headroom, and set the limit explicitly.
Where the estimate stops helping
A fee estimate is necessary but not sufficient. Even a perfectly bid transaction gets dropped if it never reaches the leader — and that failure mode has nothing to do with the fee. If your transactions are dying before inclusion, the priority fee is a red herring and the real culprit is usually QUIC connection throttling silently dropping your packets or the leader deprioritizing your unstaked traffic.
That ordering matters. Priority fees decide position within a block; whether you get into the block at all is decided upstream by stake-weighted quality of service. A bot bidding the 95th percentile from an unstaked connection still loses to a staked sender bidding the median, because SWQoS gates admission before fees are even compared. And if you're trying to observe contention early enough to bid correctly, pulling shreds directly through Jito's low-latency shred feed gives you a head start over polling an RPC that's already a slot behind.
So treat getPriorityFeeEstimate as one input in a landing pipeline, not a silver bullet. The account-scoped percentile model is genuinely good — it's the best public signal for "what is it costing to write to these accounts right now." But pair it with tight compute limits, a hard cap, staked submission, and a fast contention feed, or you'll have the right fee on a transaction that never arrives.
If you'd rather have this whole landing pipeline built and tuned for you instead of assembling it piecemeal, our team ships this as part of dedicated Solana execution and data infrastructure.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article