Showing gas fees people understand
How to show network fees in local currency and native token, present estimates and ranges, offer speed options, handle spikes and explain fees plainly.
5 min read
A network fee is a cost the person pays to someone other than you, in a unit most people can't price, with an amount that changes from minute to minute. It's no surprise that fees cause hesitation. The interface can't make fees smaller, but it can make them predictable: shown early, in money people understand, with an honest range and a short reason for why they exist.
This guide covers what to show, how to calculate it, when speed options are worth offering, and how to handle the moments when fees jump.
Answer three questions
Whenever a fee appears, the person is trying to answer three things:
- How much is this in real money?
- Is that normal, or high right now?
- What happens if I don't want to pay it?
Many fee displays answer none of them. "Max fee 0.00094 ETH, 31.2 gwei" is accurate and nearly useless to someone who doesn't follow gas prices. Compare:
Network fee ≈ $1.84 (0.00052 ETH)
Fees are higher than usual right now.
Lead with local currency, keep the native amount beside it, and add context only when it's relevant.
Fiat first, native token second
The native amount matters because that's what the wallet will show and what the person needs to hold. The fiat amount matters because that's what they can judge. Show both, in that order, and use "≈" since the conversion is an estimate.
Be precise about which token pays the fee. On Ethereum and on rollups like Arbitrum One and Base it's ETH, on Solana it's SOL, and other chains have their own. People who hold USDC on a network but none of its gas token can't do anything there, and the error they get is rarely clear. Check the native balance before the person starts, and say it plainly:
You need a small amount of ETH on Base to pay the network fee. Your ETH balance on Base is 0.
Estimates, maximums and what's actually charged
On Ethereum and most EVM chains, a transaction sets a gas limit and a maximum fee per unit of gas. The fee actually charged is the gas used multiplied by the price per unit at inclusion time, which is usually less than the maximum. The difference is never taken.
That gives you two honest numbers: the expected fee and the most it could cost.
// Values in wei, from your RPC or client library
function feeRange(
gasEstimate: bigint,
baseFeePerGas: bigint,
priorityFeePerGas: bigint,
gasLimit: bigint,
maxFeePerGas: bigint,
) {
const expected = gasEstimate * (baseFeePerGas + priorityFeePerGas)
const maximum = gasLimit * maxFeePerGas
return { expected, maximum }
}
For units: a simple ETH transfer uses 21,000 gas. At 20 gwei per gas, that's 420,000 gwei, or 0.00042 ETH. Contract interactions such as swaps use several times more gas, and the exact amount depends on the contract and the route.
Show the expected value prominently and the maximum as secondary text: "≈ $1.20, up to $2.10". If you only show one number, show the expected fee and make sure the maximum is visible in the details. Showing only the maximum makes every action look more expensive than it is.
Some networks add components to this. Rollups also charge for publishing transaction data to their parent chain, and that part can vary independently. Solana charges a small base fee per signature plus optional priority fees. The display rule stays the same across all of them: one total, in fiat and native token, with a breakdown on request.
Speed options: only when they matter
Wallets have long offered slow, standard and fast tiers. In your own interface, ask whether the choice is meaningful for this transaction on this network before offering it.
| Situation | Recommendation |
|---|---|
| Low-fee network, fees stable | No choice, use the default |
| Mainnet, normal conditions | Default to standard, options in a details panel |
| Mainnet, fees rising quickly | Show options with expected time for each |
| Time-sensitive action, such as a liquidation | Default to fast, explain why |
When you do show options, label them by time and cost, not by gwei: "Standard, about 30 seconds, ≈ $1.20". If you can't estimate times reliably on a network, don't invent them; label the tiers by cost alone.
When fees spike
Fees rise sharply during busy periods: popular mints, market swings, large airdrops. Two patterns help.
Compare with recent levels. If the current fee is well above the recent typical level, say "Fees are higher than usual right now" and offer to wait. Define "higher than usual" from your own data, for example the median of recent blocks, rather than from a fixed threshold that will be wrong next year.
Compare with the amount. A $3 fee on a $5,000 swap is fine. A $3 fee on a $10 swap is 30% of the value, and the person should know before they confirm:
The network fee is about 30% of the amount you're sending.
Don't auto-retry with a higher fee, and don't silently raise the maximum. The fee is the person's money; changing it is their decision.
Explaining why fees exist
People accept costs better when they know what they pay for. One or two sentences in a tooltip or help link are enough. Keep them accurate:
Network fees pay for processing your transaction on the blockchain. They go to the network, not to us.
If your app also charges a fee, show it as a separate line with its own label. Never fold your fee into "network fee". A typical swap review has up to four lines about cost, and each deserves its own row:
| Line | What it is |
|---|---|
| Network fee | Paid to process the transaction |
| App or protocol fee | Charged by you or the protocol, if any |
| Price impact | How much the trade moves the price |
| Minimum received | The worst outcome allowed by the slippage setting |
Amounts in each row should follow the same formatting rules, covered in formatting token amounts and decimals.
When the fee is wrong
Estimates fail. A transaction can run out of gas and fail, or the base fee can rise above the maximum and leave it waiting. Plan copy for both: what happened, whether any fee was charged, and what to do. A reverted transaction still uses gas, so say so honestly instead of implying the attempt was free. Writing error messages for dApps has before-and-after examples, and transaction state design covers what to show while the transaction is pending.