You call getRecentPrioritizationFees, take the 75th percentile of what comes back, add a margin, sign — and the transaction does not land. So next time you take the 90th percentile. It still does not land, and now you are paying for the ones that do.
The method is doing exactly what its code says. The problem is that almost every estimator built on top of it reads it as "what people are paying" when it reports "the least anyone paid and still got in". Those are different numbers, and in a busy market they are very different numbers.
This is how the cache behind that call is built, what each value actually is, and how to use it for what it is good at.
What the Cache Stores: One Minimum Per Account, Per Slot
Every validator keeps a small table for the last 150 finalized slots — about a minute at today's slot time — plus a buffer of up to 128 unfinalized ones. For each slot it records two things.
- The block minimum. The lowest compute-unit price among all transactions that landed in that block.
- A per-account minimum. For each writable account that appeared in the block, the lowest compute-unit price among the transactions that wrote to it.
Then it prunes. Any account whose minimum is not strictly higher than the block minimum is discarded; the cache keeps only accounts that were more expensive to write than the block floor. That pruning is the whole point of the table — it is a list of contended accounts and the cheapest price that got through on each.
Three kinds of transaction never enter the table: simple votes, transactions with a compute-unit limit of zero, and anything in a bank for that slot other than the one optimistically confirmed.
What the Call Returns: A Max of Minimums
When you ask for fees with a list of account keys — up to 128 of them — the node computes, for each of the 150 slots:
Read that carefully. It is the highest of several minimums. If you pass the pool account your swap writes to, you get, per slot, the cheapest price that successfully wrote to that pool. If you pass nothing, or accounts that were not contended, you get the block floor.
It is not the median price paid. It is not the price of the marginal transaction that just made it. It is the floor beneath which nothing landed on that account — and it says nothing about how far above the floor the block actually filled.
Worth knowing: the price the cache records is derived, not read. It is priority fee in lamports × 1,000,000 ÷ compute-unit limit, so a legacy transaction whose fee was rounded up at signing reports a slightly higher price than the one its author set.
Why Percentiles of This Number Mislead
Take the 150 values for a hot pool and sort them. The 75th percentile is the 75th percentile of a series of floors. In a calm market the floor is near zero and the percentile says "pay almost nothing", which is right. In a hot market the floor rises because even the cheapest landed transaction had to bid — and the percentile still describes the cheapest ones, not the ones you are competing with.
The gap shows up in one direction: you underbid in the moments that matter. Congestion is precisely when the distribution above the floor is wide, and this call cannot see above the floor.
The opposite failure exists too. If you pass an account that was written by one absurdly overpaid transaction in an otherwise empty slot, that slot's "minimum" for the account is that one transaction's price. A percentile over that series overbids for a market that does not exist. The cache stores the minimum of the transactions that touched the account, and if there was one, that is the minimum.
What It Is Genuinely Good For
Used for what it measures, the call is valuable, and cheap.
- Detecting contention. An account appears in the table at all only when writing to it cost more than the block floor. If your pool shows up with a rising minimum across the last 150 slots, it is contended, and the trend is real.
- Setting a floor, not a bid. A bid below the account's recent minimum has landed nowhere in the last minute. Start there and add the margin that your own landing rate justifies.
- Choosing between markets. Two pools, two floors. The cheaper floor is the cheaper market to enter, even though neither number tells you the clearing price.
- Sanity-checking a wallet's default. If a wallet's suggested fee is below the floor of the accounts the transaction writes, the transaction is likely to sit until the blockhash expires — and you know that before signing.
What to Combine It With
The number that decides whether you land is not your compute-unit price at all. The leader ranks transactions by reward divided by cost, where cost includes the compute you requested — and a transaction that reserves 1.4 million units it does not use ranks far below one that reserves 80,000 at the same price. That formula, and the 16,384 units almost every transaction reserves without knowing, are in the companion post on Solana's ranking formula; the short version is that right-sizing the compute-unit limit moves your rank more than any fee bump does.
The other half is timing: which leader's window you are in, how full their block is, and whether their node ever saw your transaction. Those are covered in the post on transactions that vanish without an error.
One more input moves underneath all of this: transaction v1 replaces "price × limit" with a flat priority fee, and it is behind a feature gate. The upgrades tracker reads the gate accounts from the chain every minute, so you can see which cluster has it before your estimator meets it.
Priority Fee Estimation: Questions People Actually Ask
Does getRecentPrioritizationFees return the average priority fee?
No. Per slot it returns the highest of several minimums: the block’s lowest landed compute-unit price, or the lowest price that wrote to the most expensive account you passed. It never sees prices above the floor.
How many slots does getRecentPrioritizationFees cover?
150 finalized slots, roughly one minute at 400 ms per slot. The node keeps up to 128 unfinalized slots in a buffer, but the result is limited to the finalized window.
Why does my pool show a fee when the block minimum is zero?
Because writing to that pool cost more than the block floor, and the cache keeps only accounts like that. The value is the cheapest price that still wrote to the pool, which is real evidence of contention.
Should I use a percentile of the returned priority fee values?
Only as a floor. A percentile of minimums is still a minimum. Combine it with a right-sized compute-unit limit, which changes your rank in the leader’s queue more than a higher price does.
Read It as a Floor
The honest one-line summary of getRecentPrioritizationFees: per slot, the cheapest price that wrote to the most expensive account you asked about. Build on that and the call is a good contention detector and a good floor. Build a percentile on top of it and you have a confident-looking number that is wrong in exactly the moments you need it.
This post does not tell you what to bid. It tells you what the number you are bidding from actually is, so that whatever margin you add is a decision rather than a superstition.
Constants read from the Agave validator source at commit beee69b958: runtime/src/prioritization_fee_cache.rs, runtime/src/prioritization_fee.rs and rpc/src/rpc.rs. They move with releases — re-read them before you depend on a number here.