All Blog Posts
Ethereum Gas Fees Explained: How They Work, What EIP-1559 Changed, and How to Query Them

Ethereum Gas Fees Explained: How They Work, What EIP-1559 Changed, and How to Query Them

By Ben Chatwin 21st April 2026 15min read

You submit a transaction from your dApp. The user clicks "Confirm" in MetaMask, and the wallet needs to display an estimated fee. That estimate comes from somewhere - your application queried gas price data from a node via RPC, multiplied it by an estimated gas consumption, and presented a number in USD.

If the estimate is wrong, the transaction either overpays or gets stuck. Every production dApp faces this problem.

Ethereum gas fees have dropped dramatically (often >90% YoY depending on the metric). Base fees are currently around ~0.05 gwei, down significantly from 2025 levels. A simple ETH transfer costs ~$0.01 depending on ETH price and priority fee. But low fees do not mean you can ignore gas mechanics. The formula Gas Units x (Base Fee + Priority Fee) still determines what every user pays, and your application still needs to estimate both components accurately. Getting this wrong means failed transactions, stuck queues, and frustrated users.

This guide covers how gas fees work, what EIP-1559 changed about the fee model, how fees compare across L1 and L2, and - the part most guides skip - how to query and estimate gas fees programmatically using RPC methods like eth_feeHistory and eth_estimateGas.

What Are Gas Fees and Why They Exist

Gas is the unit of computational effort on the Ethereum Virtual Machine (EVM). Every operation your smart contract executes has a fixed gas cost defined by the protocol. An ADD instruction costs 3 gas. A cold SLOAD (reading storage for the first time in a transaction) costs 2,100 gas. Writing a new value to storage with SSTORE costs 20,000+ gas. These costs reflect the actual computational and storage burden each operation places on the network.

Gas serves two purposes. First, it prevents infinite loops - every transaction has a gas limit, and execution halts when that limit is reached. Second, it compensates validators for the computational resources they spend processing transactions. Without gas, anyone could submit arbitrarily complex computations and congest the network for free.

The gas cost of a transaction depends on its complexity. A simple ETH transfer always costs exactly 21,000 gas - this is a fixed intrinsic cost. A Uniswap token swap typically consumes 150,000 to 300,000 gas depending on the route and number of hops. A complex DeFi operation involving multiple contract calls can exceed 500,000 gas.

Gas fees are denominated in gwei, a sub-unit of ETH. The unit hierarchy:

  • 1 ETH = 1,000,000,000 gwei (10^9)
  • 1 gwei = 1,000,000,000 wei (10^9)
  • 1 ETH = 1,000,000,000,000,000,000 wei (10^18)

When you see "gas price: 12 gwei," that means each unit of gas costs 0.000000012 ETH.

How EIP-1559 Changed the Fee Model

Before August 2021, Ethereum used a first-price auction for gas. Users submitted a gas price they were willing to pay, and miners prioritized the highest bids. The problem: users had no reliable way to gauge the "right" price. They frequently overpaid during congestion or underpaid and waited blocks for inclusion. Gas price estimation was guesswork.

EIP-1559 (activated in the London hard fork, August 2021) replaced the auction with a structured fee model containing two components:

Base fee - Set algorithmically by the protocol, not by users. The base fee targets 50% block utilization. When a block is more than 50% full, the base fee increases by up to 12.5% for the next block. When utilization drops below 50%, it decreases by up to 12.5%. This creates a predictable, self-adjusting price signal.

Article Image

The base fee is burned - removed from circulation entirely - rather than paid to validators.

Priority fee (tip) - Set by the user. This goes directly to the validator as an incentive to include the transaction. During low congestion, a small tip (0.01-0.1 gwei) suffices. During high demand, higher tips buy faster inclusion.

Users also set a max fee (maxFeePerGas) - the absolute ceiling they will pay per gas unit. Users only pay base fee + priority fee - any unused portion of maxFeePerGas is refunded. This protects users from unexpected fee spikes between the time they submit and the time their transaction is included.

The complete formula:

TEXT
Total Fee = Gas Units x (Base Fee + Priority Fee)
Article Image

A worked example: you send a simple ETH transfer (21,000 gas) when the base fee is 12 gwei and you set a 2 gwei priority fee.

TEXT
21,000 gas x (12 + 2) gwei = 294,000 gwei = 0.000294 ETH
At $2,500/ETH: $0.74

Since EIP-1559 launched, over 4.52 million ETH has been burned through the base fee mechanism, permanently removing it from supply.

Why Gas Prices Fluctuate

Block space is a finite resource. When demand for transaction inclusion exceeds supply, the base fee rises. When demand drops, it falls. The 12.5% adjustment cap per block means the base fee cannot spike instantly - it takes several consecutive full blocks to drive a significant increase.

Historical gas spikes correlate directly with demand surges. During DeFi Summer in 2020, average gas prices hit 480 gwei as users competed for AMM trades and yield farming positions. The NFT boom in May 2021 pushed average transaction fees to $53. Today, average base fees sit below 0.1 gwei - a reflection of transaction activity migrating to Layer 2 networks.

Time-of-day patterns are consistent. Gas prices tend to be lowest between 02:00 and 05:00 UTC on weekends, when US and European users are least active. The highest prices typically occur Tuesday through Thursday during the US/EU overlap window (14:00-18:00 UTC).

The current low-fee environment has structural causes. Ethereum's gas limit increased from 30 million (2021) to 60 million (current), doubling available block space. Combined with transaction volume migrating to L2s, mainnet block utilization sits around 25-35% - well below the 50% target - pushing the base fee to historic lows. At ~0.05 gwei, a simple ETH transfer costs roughly $0.01 (varying with ETH price), and a token swap runs $0.25-$0.39.

Gas Limit vs Gas Price

These two terms get confused regularly, and the confusion causes real problems.

Gas limit is the maximum number of gas units the sender authorizes for a transaction. Set it too low and the transaction reverts - but the gas consumed up to the revert point is still charged. Set it too high and you are fine; unused gas is refunded. The standard gas limit for a simple ETH transfer is 21,000 (the exact amount consumed).

Gas price is the cost per gas unit, denominated in gwei. Under EIP-1559, this is split into base fee and priority fee.

A failed transaction from setting the gas limit below the required amount still costs money. The EVM executes operations until the gas runs out, then reverts - but the validator already did the computation, so the consumed gas is not refunded. This is why eth_estimateGas matters: it simulates execution and tells you how much gas a transaction will actually consume.

Mainnet vs Layer 2 Gas Costs

Layer 2 networks execute transactions off-chain and post compressed data back to Ethereum. The cost structure differs from mainnet because L2 gas fees have two components: L2 execution cost (cheap) and L1 data posting cost (the dominant factor).

ChainSimple TransferToken SwapData Model
Ethereum Mainnet~$0.01-$0.10~$0.25-$0.39Full execution + storage
Arbitrum~$0.005-$0.008~$0.01-$0.05L2 execution + L1 blob data
Base~$0.001-$0.01~$0.003-$0.03L2 execution + L1 blob data
Optimism~$0.01-$0.03~$0.02-$0.05L2 execution + L1 blob data

Typical ranges under low congestion (April 2026 conditions). Actual costs vary with network load, ETH price, and L1 blob fee market dynamics.

EIP-4844 (the Dencun upgrade, March 2024) introduced blob transactions - a new data type optimized for L2 data posting. Before EIP-4844, L2s posted data as calldata, which competes with regular transactions for block space. Blob data uses a separate fee market and is pruned after approximately 18 days (only the KZG commitment persists on-chain permanently). This reduced L2 data costs by 10-100x.

L2 networks now handle the vast majority of Ethereum's transaction throughput (often >90%). For most user-facing applications, L2 deployment cuts fees by 90%+ while inheriting Ethereum's security guarantees.

Querying Gas Fees via RPC

Your dApp needs gas data to function. Wallet UIs display fee estimates. Transaction builders set maxFeePerGas and maxPriorityFeePerGas. Automated systems need to decide whether to submit now or wait for lower fees. All of this data comes from RPC calls to a node.

Article Image

Four RPC methods handle gas fee queries and estimation. Choosing the right one for each situation directly affects the accuracy of your fee estimates.

eth_gasPrice

The simplest gas price query. Returns the node's suggested gas price in wei as a single value.

BASH
curl -X POST https://ethereum-rpc.publicnode.com \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_gasPrice",
    "params": [],
    "id": 1
  }'
JSON
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x3b9aca00"
}

The hex value 0x3b9aca00 equals 1,000,000,000 wei = 1 gwei. In JavaScript:

JAVASCRIPT
import { JsonRpcProvider, formatUnits } from 'ethers';

const provider = new JsonRpcProvider('YOUR_RPC_ENDPOINT');
const gasPrice = await provider.getFeeData();
console.log(`Gas price: ${formatUnits(gasPrice.gasPrice, 'gwei')} gwei`);

eth_gasPrice is a legacy method that returns a single value predating the EIP-1559 base fee/priority fee split. It works for quick estimates, but EIP-1559 transactions require the granularity of eth_feeHistory.

eth_feeHistory

This is the method purpose-built for EIP-1559 fee estimation. It returns historical base fees, block utilization ratios, and priority fee percentiles across a range of recent blocks.

BASH
curl -X POST https://ethereum-rpc.publicnode.com \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_feeHistory",
    "params": ["0x5", "latest", [25, 50, 75]],
    "id": 1
  }'

Parameters:

  • blockCount (hex): number of blocks to return (1-1,024)
  • newestBlock: the most recent block to include ("latest" or a block number)
  • rewardPercentiles: array of percentile values for priority fee sampling

The response includes three arrays:

  • baseFeePerGas[] - The base fee at each block (plus the next block's projected base fee)
  • gasUsedRatio[] - Block utilization as a decimal (0.5 = 50% full, 1.0 = 100% full)
  • reward[][] - Priority fees at each requested percentile for each block

Build a 3-tier fee estimator using eth_feeHistory:

JAVASCRIPT
import { JsonRpcProvider, formatUnits } from 'ethers';

const provider = new JsonRpcProvider('YOUR_RPC_ENDPOINT');

// Fetch fee data for the last 20 blocks with 10th, 50th, and 90th percentile tips
const feeHistory = await provider.send('eth_feeHistory', [
  '0x14',    // 20 blocks
  'latest',
  [10, 50, 90]
]);

// The last element of baseFeePerGas is the projected next-block base fee
const nextBaseFee = BigInt(feeHistory.baseFeePerGas[feeHistory.baseFeePerGas.length - 1]);

// Calculate median priority fees at each percentile across the 20-block window
const percentileMedians = [0, 1, 2].map(pIdx => {
  const values = feeHistory.reward
    .map(block => BigInt(block[pIdx]))
    .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
  return values[Math.floor(values.length / 2)];
});

console.log(`Next block base fee: ${formatUnits(nextBaseFee, 'gwei')} gwei`);
console.log(`Slow (P10 tip):     ${formatUnits(percentileMedians[0], 'gwei')} gwei`);
console.log(`Standard (P50 tip): ${formatUnits(percentileMedians[1], 'gwei')} gwei`);
console.log(`Fast (P90 tip):     ${formatUnits(percentileMedians[2], 'gwei')} gwei`);

// Build fee parameters for a "standard" EIP-1559 transaction
const standardTip = percentileMedians[1];
const maxFee = nextBaseFee * 2n + standardTip; // 2x base fee as buffer for fee spikes

console.log(`\nRecommended maxFeePerGas:         ${formatUnits(maxFee, 'gwei')} gwei`);
console.log(`Recommended maxPriorityFeePerGas: ${formatUnits(standardTip, 'gwei')} gwei`);

The pattern: use P10 priority fees for "slow" (willing to wait several blocks), P50 for "standard" (next few blocks), and P90 for "fast" (next block priority). Setting maxFeePerGas to baseFee * 2 + tip provides a buffer against short-term base fee increases. Why 2x? The base fee can increase by at most 12.5% per block when blocks are full. A 2x multiplier covers approximately 6 consecutive full blocks of 12.5% increases (1.125^6 ~ 2.03), meaning your transaction stays viable even through a sustained demand spike. Any unused portion is refunded, so this generous buffer costs nothing if the transaction succeeds.

eth_estimateGas

While fee history tells you the price per gas unit, eth_estimateGas tells you how many gas units a transaction will consume. It simulates execution against the current state and returns the estimated gas usage.

BASH
curl -X POST https://ethereum-rpc.publicnode.com \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_estimateGas",
    "params": [{
      "from": "0xYourAddress",
      "to": "0xContractAddress",
      "data": "0xabcdef..."
    }],
    "id": 1
  }'
JAVASCRIPT
import { JsonRpcProvider, parseEther } from 'ethers';

const provider = new JsonRpcProvider('YOUR_RPC_ENDPOINT');

const estimatedGas = await provider.estimateGas({
  from: '0xYourAddress',
  to: '0xRecipientAddress',
  value: parseEther('0.1')
});

console.log(`Estimated gas: ${estimatedGas.toString()} units`);

// Add a 20% buffer for production
const gasLimit = estimatedGas * 120n / 100n;
console.log(`Gas limit with buffer: ${gasLimit.toString()} units`);

eth_estimateGas has a critical limitation: it simulates against the state at the current block. If state changes between your estimate and your transaction's inclusion (another transaction modifies the same contract storage, a price oracle updates, a liquidity pool rebalances), the actual gas consumption can differ. Nested calls add complexity too - the 63/64 gas forwarding rule (where each nested CALL retains 1/64 of available gas) makes deeply nested gas usage harder to reason about, and a top-level simulation may underestimate what the full call stack requires.

A concrete example: suppose your contract calls an external contract that conditionally writes to storage based on an oracle price. You estimate gas when the price is below a threshold (no write, ~50,000 gas). By the time your transaction executes, the price has updated, triggering the storage write branch (~70,000 gas). If your gas limit was set to the original estimate without a buffer, the transaction reverts - and you still pay for all the gas consumed up to that point.

Production recommendation: add a 20-25% buffer to the estimate for standard transactions, and 30%+ for complex multi-call operations like flash loan arbitrage or multi-hop swaps. The unused gas is refunded, so a generous buffer costs nothing if the transaction succeeds.

Production recommendation: add a 20-25% buffer to the estimate for standard transactions, and 30%+ for complex multi-call operations like flash loan arbitrage or multi-hop swaps. The unused gas is refunded, so a generous buffer costs nothing if the transaction succeeds.

eth_maxPriorityFeePerGas

Returns the node's suggested priority fee (tip) based on recent block data.

BASH
curl -X POST https://ethereum-rpc.publicnode.com \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_maxPriorityFeePerGas",
    "params": [],
    "id": 1
  }'

This provides a quick suggested tip without building your own percentile analysis. Combine it with eth_feeHistory for a complete picture: eth_feeHistory gives you the base fee trend and historical tip distribution, while eth_maxPriorityFeePerGas gives you the node's current single-value recommendation.

Reliable Gas Estimation Is an Infrastructure Problem

Building accurate fee estimates requires calling these methods on every transaction, often multiple times as users adjust parameters. Your RPC endpoint needs to respond consistently and quickly - a slow or unreliable gas estimate delays the entire transaction flow. If eth_feeHistory takes 500ms instead of 50ms, your wallet UI feels sluggish. If eth_estimateGas returns an error because the node is overloaded, the user cannot submit at all.

Dwellir provides Ethereum RPC endpoints with archive access, flat-rate pricing (no compute unit surprises when calling gas estimation methods on every transaction), and sub-100ms response times. For applications that query eth_feeHistory and eth_estimateGas on every user interaction, endpoint reliability directly affects user experience.

Gas Optimization for Smart Contract Developers

Lower gas consumption means lower fees for your users regardless of network conditions. The biggest wins come from reducing storage operations, which dominate gas costs in most contracts.

OptimizationBeforeAfterGas Saved
Storage packing3 slots, 3x SSTORE1 slot, 1x SSTORE~40,000 per write
calldata vs memoryCopy to memoryRead directly~2,000-10,000 per call
Events vs storageSSTORE (20,000+)LOG (~375 base)~19,000+ per write
constant/immutableCold SLOAD (2,100)PUSH (3)2,097 per read
Batch transactions21,000 intrinsic each21,000 shared once21,000 per batched tx

Storage packing is the single highest-impact optimization in most contracts. The EVM reads and writes in 32-byte slots, so two uint128 values packed into one slot cost a single SSTORE instead of two. Three values (uint128 + uint64 + uint64) also fit in one slot:

SOLIDITY
// Bad: 3 storage slots, 3x SSTORE cost
uint256 price;
uint256 timestamp;
uint256 amount;

// Good: 1 storage slot
uint128 price;
uint64 timestamp;
uint64 amount;

For data that only needs to be read off-chain (dashboards, analytics, indexers), emit events instead of writing to storage. A LOG operation costs 375 gas base plus 375 per topic and 8 per byte - roughly 50x cheaper than an SSTORE. Similarly, values that never change after deployment should use constant (inlined at compile time, 3 gas to read) or immutable (embedded in bytecode) instead of regular storage variables that cost 2,100 gas per cold read.

External function parameters declared as calldata instead of memory avoid an unnecessary copy, saving thousands of gas per call. And batching multiple operations into a single transaction amortizes the 21,000 intrinsic gas cost - ten transfers batched into one call save 189,000 gas in intrinsic costs alone.

Conclusion

Gas fees on Ethereum are at historic lows - a simple transfer for $0.01 was unimaginable during the 480 gwei spikes of 2020. But the mechanics have not changed. The EIP-1559 base fee still adjusts by up to 12.5% per block based on utilization. eth_estimateGas still simulates against state that can change before your transaction lands. And your application still needs to query eth_feeHistory to build accurate fee tiers for users.

The difference between a good gas estimation implementation and a bad one comes down to the RPC calls: using eth_feeHistory with appropriate percentiles instead of relying on a single eth_gasPrice value, adding buffers to eth_estimateGas results, and making these calls against an endpoint that responds consistently under load. For a complete reference on Ethereum gas-related RPC methods, see Dwellir's Ethereum RPC documentation.

For teams building production dApps that need reliable gas estimation: Get started with Dwellir.

read another blog post