Build / Deployment and Production

Sustained Use Gas Discounts

Somnia automatically reduces the base gas price paid by accounts that consistently consume a lot of gas. The more gas you use over a sustained period, the less you pay for each unit of gas.

There is nothing to opt into, claim, stake or sign. Discounts are computed by the network from observed gas usage and applied automatically when your transactions execute.

How the discount is applied

A discount is a percentage off the base gas price only. Any priority fee you pay is added on top, undiscounted:

price charged per gas = base_gas_price * (100 - discount_percent) / 100  +  priority_fee

Discounts never reach 100% - gas is never free.

The discount changes the price you pay per unit of gas. It never changes how many units of gas an operation costs, and it does not raise any gas limit.

How usage is measured

Usage is tracked per sender address - the from of the transaction, which is the account that pays. Gas consumed by contracts you call counts toward your usage, not theirs.

Somnia divides time into epochs. On mainnet and testnet an epoch is 3000 ledger blocks, which is roughly 5 minutes of wall-clock time.

  • Every epoch, the gas used by each account is tracked. An account is only recorded for that epoch if it consumed more than ten million gas units in that epoch, and only the 1000 heaviest gas consumers of the epoch are recorded.
  • Your discount is derived from your total tracked gas across the most recent 16 epochs - a rolling window of roughly 80 minutes. As each epoch passes, the oldest epoch's usage drops out of the window.
  • That rolling total is compared against a table of tiers. Each tier names a total amount of gas and the discount earned by reaching it; you get the discount of the highest tier your rolling total reaches. Below the lowest tier, your discount is zero.

A newly earned discount applies to every transaction you send from the start of the next epoch, so it takes effect at the next 5-minute boundary and reflects roughly your last 80 minutes of activity.

Current tiers

The tier table on mainnet:

Gas units used over the rolling window Discount
900,000,000 70%
4,000,000,000 90%

At a base gas price of 6 gwei, sustaining those rates costs roughly 100 SOMI and 430 SOMI per day on gas at the undiscounted price.

Tiers are network configuration and may be retuned over time; the authoritative table is always the live one returned by getTiers() (see Reading discounts on chain).

Which transactions accrue usage

Transaction type Accrues usage Receives a discount
Ordinary transactions you sign and send Yes Yes
Transactions that revert or run out of gas Yes - you paid for the gas Yes
Reactivity handler executions Yes, to the subscription owner Yes
Reactivity subscription deactivations No - they consume no chargeable gas n/a
Protocol-level and zero-gas transactions No No

Because a Reactivity subscription owner may be a contract, contract accounts can accrue usage and earn discounts for the handler executions they pay for.

Keeping and losing a discount

Your discount is derived from a rolling window, not from a single epoch, so it is stable across quiet periods. If you stop transacting entirely, your usage ages out gradually and your discount only reaches zero after the full window - 16 epochs, roughly 80 minutes - has passed with no activity.

There is no penalty for bursty traffic. Usage accumulates into the window whenever you are busy, and a spike is credited in full.

What this means for sending transactions

Warning

You must still submit a gasPrice at or above the full, undiscounted base fee. The discount is applied when your transaction executes, not when it is accepted.

Concretely:

  • Transaction admission is undiscounted. A transaction whose gasPrice is below the current base fee is rejected from the mempool, whatever your discount. Keep quoting eth_gasPrice (or your wallet's estimate) as normal.
  • Balance requirements are undiscounted while pending. The node reserves gasPrice * gas at the full price for in-flight transactions, so you need to be able to cover the undiscounted cost even though you will be charged less.
  • You are charged the discounted price. The saving shows up as a smaller balance deduction, and in effectiveGasPrice on the receipt.

The practical upshot for a dApp is that no change to your submission logic is needed. Fee estimation, gasPrice selection and balance checks all continue to work against the undiscounted base fee; your users simply pay less.

RPC behaviour

No new RPC methods or response fields are involved. Existing fields reflect the discount where - and only where - they describe an actual charge.

Method / field Reflects the sender's discount?
effectiveGasPrice on a transaction receipt Yes - the price actually charged. This is the authoritative record.
gasPrice on eth_getTransactionByHash / ...ByBlock... Yes - note this reports the price charged, which for a discounted sender is lower than the gasPrice you submitted.
debug_traceTransaction, debug_traceBlockByHash, debug_traceBlockByNumber Yes - replayed with exactly the discount that was charged at the time.
eth_call, eth_estimateGas, eth_createAccessList against latest / pending Yes - simulated with the from account's current discount.
eth_call or debug_traceCall pinned to a past block No - historical simulations deliberately run undiscounted.
eth_gasPrice No - the request has no sender, so it returns the network base price.
eth_feeHistory, block.baseFeePerGas No - these describe blocks, not senders.
eth_maxPriorityFeePerGas Unchanged (priority fees are never discounted).

Note

Because receipts record the discount that was applied, a transaction's effectiveGasPrice is reproducible forever, even after the sender's discount has changed or expired.

Reading discounts on chain

Discounts live in the enshrined GasDiscounts contract, at a per-network address. The contract is a UUPS proxy, so the address is stable across upgrades.

Look the address up for the network you are on, rather than hardcoding it, using the somnia_getProtocolParameters method:

cast rpc somnia_getProtocolParameters '["latest"]' \
  --rpc-url https://api.infra.testnet.somnia.network \
  | jq -r '.protocol_parameters.gas_discounts_contract_address'

The useful read-only views are:

View Returns
getTiers() → (uint256 gasThreshold, uint256 discountBps)[] The live tier table. gasThreshold is gas used over the whole rolling window; thresholds are inclusive, and the highest one you reach applies.
getAccountsWithDiscountsForEpoch() → (address[] accounts, uint256[] discountBps, uint256 hotAccountGasThreshold, uint256 maxReportedAccounts) The discount set active for the current epoch - only accounts earning a non-zero discount, in ascending address order - plus the per-epoch minimum gas and the number of accounts recorded per epoch.
getTrackedAccounts() → (address[] accounts, uint256[] discountBps, uint256[] rollingGasUsed) Every account with a live usage window, including those not yet earning a discount, with each window's total.
lastReportedEpoch() → uint256 The epoch of the most recent update.

Discount values in these responses are in basis points, where 10000 is 100% - so a 70% discount reads as 7000.

For example, to fetch the tier table and the current discount set:

PROXY=$(cast rpc somnia_getProtocolParameters '["latest"]' \
  --rpc-url $RPC | jq -r '.protocol_parameters.gas_discounts_contract_address')

cast call $PROXY "getTiers()((uint256,uint256)[])" --rpc-url $RPC
cast call $PROXY "getAccountsWithDiscountsForEpoch()(address[],uint256[],uint256,uint256)" --rpc-url $RPC

Note

There is deliberately no per-account lookup function. To find your own discount, read the full set and filter client-side; both arrays are sorted ascending by address, so a binary search is possible. rollingGasUsed from getTrackedAccounts() is each window's total as of the last update, not aged forward to the current epoch.

The simplest way to confirm a discount is being applied to real transactions is to compare effectiveGasPrice on one of your receipts against the block's base gas price.

Events

Event Emitted
DiscountSetComputed(uint256 indexed epochReceivingDiscounts, uint256 indexed setCounter, uint256 numDiscountedAccounts) Once per epoch, naming the epoch the new discount set applies to.
HotAccountsReported(uint256 indexed epoch, address[] accounts, uint256[] gasUsedInEpoch) Once per epoch, with that epoch's tracked usage. Addresses are in the payload, not indexed, so you cannot filter by address.
TiersUpdated(Tier[] newTiers) When the tier table changes.
ReportingParametersUpdated(uint256 hotAccountGasThreshold, uint256 maxReportedAccounts) When the per-epoch minimum gas or the number of accounts recorded per epoch changes.

Indexing DiscountSetComputed and TiersUpdated is the cheapest way to keep a dashboard in sync without polling the full set every epoch.

See also

Copied from docs.somnia.network (somnia-main-docs) by scripts/import-gitbook.mts, until it finds a home in a product's docs.Edit this page