eth_feeHistory: Base Fees and Priority Fee Percentiles

eth_feeHistoryRead methodEthereum JSON-RPC

eth_feeHistory is the EIP-1559 fee oracle you build yourself. One request returns the base fee of each recent block, how full each block was, and the priority fees paid at the percentiles you ask for. Where eth_gasPrice hands you one number and hides its reasoning, eth_feeHistory hands you the distribution and lets you decide.

1 credit
per call on BLAZED.sh
Read
call type
3
parameters
All clients
client support

Try eth_feeHistory

Request as curl
curl -s https://ethereum-rpc.publicnode.com \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_feeHistory",
  "params": [
    "0x4",
    "latest",
    [
      10,
      50,
      90
    ]
  ]
}'

Runs the example straight from your browser against the endpoint above. The prefilled URL is a third-party public endpoint, not run by BLAZED.sh.

What eth_feeHistory does

You ask for a window: how many blocks, ending at which block, and which reward percentiles you care about. The node walks those blocks and answers with three parallel arrays plus, when you asked for percentiles, a fourth. Everything is hex, and everything is per block in ascending order starting at oldestBlock.

The reward percentiles are gas-weighted, which is the detail that makes them useful. For each block the node sorts that block's transactions by the priority fee they actually paid and walks the cumulative gas, so the 50th percentile is the tip paid at the block's halfway point by gas, not the median transaction. A tip at the 10th percentile is what the cheapest quarter of the block's work paid; the 90th is what impatient flow paid.

Base fees need no percentile because they are determined by the protocol: each block's base fee follows mechanically from the previous block's gas usage and can move at most 12.5% per block. That is why the returned baseFeePerGas array holds one more entry than you asked for: the extra, final entry is the base fee of the block after the newest one, already computable and the number you should actually price against.

Parameters

#NameTypeDescription
1blockCountstringHow many blocks to report, as a hex quantity, from 0x1 up to the client's cap (1024 on Geth). Counted backwards from newestBlock.
2newestBlockstringThe highest block in the window: a hex block number or one of latest, pending, safe, finalized, earliest.
3rewardPercentilesoptionalarrayAscending list of percentiles between 0 and 100, e.g. [10, 50, 90]. Omit it and the response carries no reward array at all.

Percentiles must be monotonically increasing and within 0 to 100; an unsorted list is rejected outright. Each extra percentile makes the node read more of every block in the window, so a 1,024-block window with five percentiles is a genuinely heavy request compared to a ten-block window with three.

What it returns

oldestBlock, the first block in the window; baseFeePerGas, an array of blockCount + 1 entries where the last is the projected base fee for the block after the window; gasUsedRatio, how full each block was as a float between 0 and 1; and reward, one array per block holding the tip at each requested percentile. Post-Cancun responses add baseFeePerBlobGas and blobGasUsedRatio, which follow the same layout for the separate blob fee market.

Blocks with no transactions still appear: their gasUsedRatio is 0 and their reward entries are zeros. The base fee is defined for every block regardless, so the baseFeePerGas array never has gaps.

Example response

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "oldestBlock": "0x18789a3",
    "baseFeePerGas": ["0x335642e", "0x31e8878", "0x2efe39d", "0x325ddb7", "0x306ece9"],
    "gasUsedRatio": [0.3886851833, 0.2663517333, 0.7871539, 0.3464228833],
    "reward": [
      ["0xf4240", "0xf9311d", "0x3b9aca00"],
      ["0xfec8", "0x2d75888", "0x547906e6"],
      ["0xf4240", "0xc7b3bf", "0x38aae663"],
      ["0xf4240", "0x2faf080", "0x3b9aca00"]
    ]
  }
}

Four blocks, three percentiles. Note the five base fees for four blocks: the last one is the base fee of the next block, which is the one to price against. In the second row the 10th percentile paid 0xfec8 wei (0.000065 gwei) while the 90th paid 0x547906e6 (about 1.42 gwei), which is the spread a single averaged gas price would have hidden.

eth_feeHistory with ethers.js

import { JsonRpcProvider, formatUnits } from "ethers";

const provider = new JsonRpcProvider("https://ethereum-rpc.publicnode.com");

// ethers has no wrapper for eth_feeHistory; use provider.send
const history = await provider.send("eth_feeHistory", ["0x14", "latest", [50]]);

// the last baseFeePerGas entry is the NEXT block's base fee
const nextBaseFee = BigInt(history.baseFeePerGas.at(-1));

// median tip across the window
const tips = history.reward.map((r) => BigInt(r[0])).sort((a, b) => (a < b ? -1 : 1));
const medianTip = tips[Math.floor(tips.length / 2)];

const maxFeePerGas = nextBaseFee * 2n + medianTip; // room for one 12.5% climb and then some
console.log(formatUnits(maxFeePerGas, "gwei"), "gwei cap,", formatUnits(medianTip, "gwei"), "gwei tip");

Gotchas and common errors

baseFeePerGas has one entry too many, on purpose

Ask for 4 blocks and you get 5 base fees. The extra entry at the end is the base fee of the block after newestBlock, derived from how full the newest block was. Zipping the arrays naively pairs every base fee with the wrong block and quietly shifts your entire fee model by one slot; pair gasUsedRatio[i] with baseFeePerGas[i], and price your transaction against the final entry.

Percentiles are weighted by gas, not by transaction count

The node sorts a block's transactions by effective priority fee and walks cumulative gas, so a single gas-hungry transaction paying a high tip moves the upper percentiles far more than a dozen cheap transfers do. That is the correct weighting for "what would it cost me to get in", and it is not the same as the median tip per transaction, which is what most dashboards show.

Percentiles must be sorted

[90, 10] is an error, not a reordering. So is a value above 100 or below 0. Duplicates are accepted but pointless. Since the parameter is optional, sending a malformed list is worse than sending none, because omitting it returns a valid response with no reward array and code that assumed the field is present crashes on undefined.

The window has a ceiling and a cost

Geth caps blockCount at 1024 and silently clamps larger requests rather than erroring, so a request for 5,000 blocks quietly returns 1,024 and your averages cover a shorter period than you think. Check oldestBlock against what you asked for. Wide windows with several percentiles are also the version of this call that gets slow, since the node must read the transactions of every block in the range.

Blob fees are a separate market

baseFeePerBlobGas and blobGasUsedRatio describe EIP-4844 blob pricing, which moves independently of execution gas and by a different formula. A rollup batcher prices against those fields; an ordinary transaction ignores them. Mixing the two is how blob-posting jobs end up wildly over- or under-priced.

History is not the future

Everything here is backward-looking. Base fee can climb 12.5% per block, so a window of quiet blocks says nothing about the next mint or liquidation cascade. Fee strategy built on this data still needs a cap you are willing to pay and a plan for replacing a stuck transaction with a bumped one.

What eth_feeHistory costs on BLAZED.sh

eth_feeHistory costs 1 credit per call on BLAZED.sh regardless of how wide the window is, which makes one 20-block request with three percentiles a far better deal than twenty separate block fetches. Very wide windows can produce large responses, and anything over 100KB adds 50 credits per MB, but a normal fee-strategy request of a few dozen blocks is nowhere near that. Bots that re-price every block get the extra benefit of the local path: the call resolves on the node host in under 10ms instead of racing a gateway round-trip against the next slot.

See the full credit price list

eth_feeHistory: frequently asked questions

Why does baseFeePerGas have one more element than I requested?

The final entry is the base fee of the block after the newest block in your window. It is already determined by the newest block's gas usage, and it is the number you should price a new transaction against.

What percentiles should I request?

[10, 50, 90] covers most needs: the low end shows what patient flow pays, the middle is a reasonable default tip, and the high end shows what competitive inclusion currently costs. Time-sensitive bots usually track the upper percentile and add a margin.

What is the difference between eth_feeHistory and eth_gasPrice?

eth_gasPrice returns one number from the node's own oracle with no explanation. eth_feeHistory returns the underlying distribution, base fee per block and tips at the percentiles you choose, so you can implement whatever strategy you want rather than inheriting the node's.

How many blocks can I request?

Up to the client's cap, which is 1024 on Geth. Larger values are clamped rather than rejected, so compare oldestBlock with what you expected before averaging over the result.

Does eth_feeHistory include blob fees?

Since Cancun, yes: baseFeePerBlobGas and blobGasUsedRatio arrive alongside the execution gas fields and describe the separate blob fee market used by rollup batch posters.

Call eth_feeHistory from the node itself

Deploy your container or script next to a synced Ethereum node. No rate limits, no compute units, sub-10ms local RPC.