Gas and Fees
Supra prices EVM execution from the same gas schedule that prices Move execution. This page states the figures a transaction has to satisfy, how to read them from the chain, and how the EIP-1559 fields behave.
For connecting a client see Getting Started with SupraEVM; for the JSON-RPC surface and the latest-only state model see Differences from Ethereum; for request rate limits and history retention see Devnet.
The gas price floor
The chain has one gas schedule. A Move transaction’s gas price is quoted in quants per gas unit; an EVM transaction’s is quoted in wei per gas. The two are converted by a fixed factor, so that a transaction of either kind pays a comparable amount for comparable work and so that the mempool can order both against one another on a single scale.
The chain’s minimum Move gas price, carried through that conversion, is the EVM base fee, and the
base fee is the minimum an EVM transaction has to offer. On Devnet it is 333.33 gwei
(333,333,300,000 wei, 0x4d9c368320).
A transaction whose maxFeePerGas (or, for a legacy transaction, gasPrice) is below the base fee
is refused at submission, with the message Ethereum clients classify as a fee-cap rejection:
max fee per gas less than block base fee: maxFeePerGas <offered>, baseFee <required>Read the floor from the chain rather than compiling it into a client. The conversion factor is
settled for Devnet, but it derives from the Move gas schedule and both the schedule and the
factor are on-chain parameters that governance can change. eth_gasPrice reports the median gas
price of recently executed Move transactions, converted to wei, which is at or above the floor and
moves with what senders pay. The floor itself is the baseFeePerGas that eth_feeHistory reports,
and the minGasPrice() of the gas config precompile.
The latest baseFeePerGas is the last element of the array that this request returns:
curl -s -X POST https://rpc-devnet.supra.com/rpc/v1/eth -H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"eth_feeHistory","params":["0x1","latest",[]]}'The per-transaction gas limit
An EVM transaction may declare a gas_limit of at most 16,777,216 gas, the per-transaction cap
Ethereum applies from Osaka (EIP-7825). The bound is checked when the transaction is admitted to the
mempool and again before it is executed, so it holds for a transaction that reaches a block by any
route. A transaction above it is refused:
transaction gas limit too high (cap: 16777216, tx: <declared>)A deployment that needs more than 16,777,216 gas cannot be submitted, whatever the sender is willing to pay. Split such a contract into several deployments, or reduce its initcode. The size limits are Ethereum’s: deployed code at most 24,576 bytes (EIP-170) and initcode at most 49,152 bytes (EIP-3860).
eth_estimateGas is bounded by the same figure, because its answer is what a caller puts in a
transaction’s gas_limit. An estimate that would exceed the cap is not returned as a larger number.
Reading the figures from a contract
A contract that has to agree with what the chain will accept can read both figures above, the
per-transaction gas cap and the minimum gas price (the base fee, in wei), from the EVM gas config
precompile at 0x0000000000000000000000000000000053555005. Its interface, ISupraEvmGasConfig, is
described in EVM gas config. The values are
those of the epoch the executing block belongs to, and eth_call and eth_estimateGas serve the
simulated block’s.
Automation tasks
The EVM automation registry holds every task to the same two figures when it is registered,
because the transaction the chain builds for a task carries the task’s maxGasAmount as its gas
limit and is priced no higher than its gasPriceCap:
- A task, user or system, whose
maxGasAmountis above the per-transaction gas cap is refused withMaxGasAmountExceedsChainCap(maxGasAmount, txGasLimitCap). - A user task whose
gasPriceCapis below the minimum gas price is refused withGasPriceCapBelowMinimum(gasPriceCap, minGasPrice). System tasks run at a gas price of 0 and are exempt.
Simulating the registration with eth_call or eth_estimateGas reports the same error, so a
client can check a task before sending it.
Clearing the floor makes a task eligible to run, not certain to. A task is priced against the lowest gas price included in the block it would run in, so a task registered at exactly the minimum can be passed over in a block whose transactions all paid more. Register with some margin above the minimum if the task has to run at every opportunity.
When the figures change
Both figures are governed and can change at an epoch boundary, so a task registered within them can fall outside them later. Such a task is removed rather than left registered and charged:
- An active task is no longer scheduled from the first block of the epoch that changes the
figures. The chain then submits a removal record for it, and the registry removes it and emits
TaskRemovedByGasConfigUpdate. A user task’s owner is refunded the cycle fee for the rest of the current cycle and the whole deposit. A system task pays no fees and is refunded nothing. - A pending task, one registered during the current cycle, is checked when the cycle ends. If
it is outside the figures in force then, the registry drops it instead of charging it for the
next cycle, refunds the whole deposit and emits
TaskRemovedByGasConfigUpdate. - A task the registry processed for the next cycle before the figures changed is checked again when that cycle starts and is removed as an active task is.
The registry checks a task against the figures in force when it applies the removal, and leaves a task that still fits them in place.
A task removed for its own failure, such as a predicate that cannot be executed, keeps a different
refund. Its owner is refunded half of the cycle fee for the rest of the cycle and the whole
deposit, or half of the deposit for a pending task, and the registry emits TaskRemovedBySystem.
The flat registration fee and the registration transaction’s fee are not refunded in either case. A task registered during a cycle that is removed before it first runs therefore still costs its owner those two fees. Governance changes to either figure should be announced ahead of the epoch that applies them, so that owners of affected tasks can cancel or re-register them first.
A task whose gasPriceCap is at or above the minimum gas price stays registered, even if it is
below the price of a particular block. It is skipped in that block only, as described above.
Simulation limits
eth_call and debug_traceCall are answered rather than submitted, so they are not held to what a
transaction may declare. Each simulates a request at a gas ceiling: the limit used when the request
names none, and the most a request may name. When a request names no gas but names a from
account that exists and a nonzero gas price, the limit used is lowered to what that account’s
balance, less the value sent, pays for at that price. A request that names a larger gas is refused rather
than clamped, with the method’s name first and the ceiling in force:
eth_call error: the requested gas limit of <gas> is above the <ceiling> this request may be simulated witheth_call’s ceiling is the larger of a gas figure configured on the node and the per-transaction
gas cap of the epoch the simulated block belongs to. Where governance raises the cap above the
figure, the ceiling rises with the cap, so a call that a transaction could make is always one
eth_call simulates. A figure above the cap raises the ceiling, and one below it lowers nothing.
debug_traceCall’s ceiling follows the same rule, with its own configured figure. A call that a
transaction could make can therefore always be traced as well as called.
The ceiling is not the simulated block’s gas limit. GASLIMIT returns the epoch’s
block gas budget plus its per-transaction gas cap whatever the ceiling.
A request whose gas limit exceeds that figure is simulated rather than refused; the gas limit it
is simulated at is still bounded by the method’s ceiling.
The figures configured on Devnet’s node are listed in the description of the
SupraEVM JSON-RPC 2.0 API operation on the Devnet API page, https://rpc-devnet.supra.com/docs .
A call that succeeds at the simulation ceiling is therefore not proof that the same work can be submitted as a transaction: a call consuming more gas than the per-transaction gas cap (16,777,216 at genesis) has no transaction that can carry it.
Estimating gas
eth_estimateGas reports a gas limit at which the call was observed to succeed. It searches for it
as geth’s eth_estimateGas does:
- It runs the call once at a ceiling. The ceiling is the
gasthe request names, or, when it names none, the per-transaction gas cap, lowered to the gas the sender’s balance covers when the request names afromand a gas price. A call that reverts or halts at the ceiling is answered with that failure. - It runs the call again at the gas the first run used. If the call succeeds there, that figure is the answer.
- If the call fails there, it bisects between that figure and the ceiling for the smallest limit at which the call succeeds, to within 1.5%, and answers that limit.
No margin is added to the answer, as geth adds none; any padding is the client’s to add.
The second run is there for a contract whose behaviour depends on the gas it is given: one that
branches on gasleft(), or forwards gas to a subcall, of which EIP-150 withholds a 64th at each
call. For such a contract the gas used at a high limit can be less than the limit the call needs,
and the search finds the limit instead. The answer never exceeds the ceiling.
A request that names its own gas is searched the same way, with that gas as the ceiling, so the
answer is a limit the call was observed to succeed at and is never more than the named gas.
The per-transaction gas cap here is the smaller of the cap of the simulated block’s epoch and the cap in force now, which admission applies to the transaction sent with the estimate. The two differ for a block in an earlier epoch: a past height the wallet endpoint accepts, and the last block of an epoch before any block of the next has executed.
A named gas above the per-transaction gas cap is refused, because no transaction may carry it.
At the genesis cap of 16,777,216:
eth_estimateGas error: the requested gas limit of <gas> is above the 16777216 this request may be simulated withA call that needs more than the per-transaction gas cap fails at the ceiling and is answered with
the reason it halted, typically eth_estimateGas halted: out of gas.
The estimate runs the same execution semantics as a block does, against the latest state, so a call whose cost depends on state that changes before the transaction executes can need more than the estimate.
EIP-1559 fields
baseFeePerGasreports the fee floor described above. It is derived from the gas schedule and does not respond to block fullness, so it is the same in every block of an epoch and changes only when governance changes the parameters it derives from.maxFeePerGasmust be at least the base fee. It is the ceiling the sender offers.maxPriorityFeePerGasis honoured as EIP-1559 defines it: the price charged ismin(maxFeePerGas, baseFeePerGas + maxPriorityFeePerGas). The mempool orders by the price charged rather than by the ceiling offered, converted to the Move gas price scale by dividing by the conversion factor (3,333,333 wei on Devnet) and rounding down. A priority fee therefore raises what the sender pays, and raises the transaction’s position when it raises the converted price by at least one: on Devnet, a priority fee below 3,333,333 wei per gas changes nothing. RaisingmaxFeePerGasalone, with no priority fee, buys no position.eth_maxPriorityFeePerGasreturns0x0. A transaction that pays the base fee and no more is admitted and executed.eth_feeHistoryreturns the current base fee for every block of the requested window plus the next one, each block’sgasUsedRatioas its header’sgasUsed / gasLimit(see The block gas budget), and arewardrow of zeroes per requested percentile. Because the base fee is fixed within an epoch, the history is flat by construction and carries no signal about congestion.- A legacy or EIP-2930 transaction’s
gasPriceis treated as both the ceiling and the price.
eth_feeHistory is bounded by what the node still holds. A newestBlock above the chain’s tip is
refused with the tip named, so a client can retry once the chain has advanced. A newestBlock the
node no longer holds is refused as pruned, with the oldest block it does hold named in the error
data, rather than answered from current parameters as though the block were still there. A window
whose newestBlock is retained but which reaches back below the retained floor is shortened to
start at that floor, and oldestBlock in the response says where it starts. Retention is covered in
Devnet.
The block gas budget
Each block has an EVM gas budget, derived from on-chain parameters of the epoch. It bounds the gas
the block’s submitted EVM transactions use: the block executes a submitted transaction while the
gas its earlier submitted transactions used is below the budget, whatever gas_limit the
transaction declares. The transaction that takes the block to or past the budget is executed and
committed, and uses at most the epoch’s per-transaction gas cap, the most gas one transaction may
declare, beyond it. The block’s
system pseudo-transactions
(block-metadata, automated transactions and automation records) do not draw on the budget.
Settlement credits use no gas.
A block’s header reports:
gasUsed: the gas used by every transaction the block lists, submitted and system alike. It equals the sum of the block’s receipts’gasUsed, and the last receipt’scumulativeGasUsed.gasLimit: the budget plus the per-transaction gas cap, plus the gas the block’s block-metadata, automated transactions and automation records used.
GASLIMIT (block.gaslimit) returns the budget plus the per-transaction gas cap, without the gas
of the block’s system pseudo-transactions, in the block and under eth_call, eth_estimateGas and
debug_traceCall; see GASLIMIT.
gasUsed never exceeds gasLimit. gasLimit − gasUsed is the part of the budget plus the
per-transaction gas cap that the block’s submitted transactions did not use.
eth_getBlockByHash, eth_getBlockByNumber and the WebSocket newHeads subscription report the
same two figures, and eth_feeHistory’s gasUsedRatio for a block is its gasUsed / gasLimit.
Once the gas used reaches the budget, every later transaction in the block is deferred, not
rejected: it is returned to the mempool and proposed again in a later block. The block does not
skip a transaction and execute later ones, so a sender’s transactions execute in nonce order.
Automated tasks are not counted against the budget and are not deferred. A deferred transaction is
not charged, produces no receipt for the block that deferred it, and keeps its hash
and nonce. eth_getTransactionReceipt returns null for it until it executes, as it does for a
transaction that is still queued.
Transaction types
| Type | Name | Notes |
|---|---|---|
0x00 | Legacy | Accepted. Must carry a chain id (EIP-155). |
0x01 | EIP-2930 access list | Accepted. |
0x02 | EIP-1559 | Accepted. |
0x03 | EIP-4844 blob | Refused; see below. |
0x04 | EIP-7702 set code | Accepted. |
A pre-EIP-155 legacy transaction, which declares no chain id, is refused. Its signature has no network component, so the identical bytes would be valid on every chain the sender is funded on. The refusal names the chain id to use:
Evm transaction does not specify a chain id, so it is replayable on other networks. It must specify chain id <chain id>The keyless deterministic deployment (“Nick’s method”) used elsewhere to bootstrap the CREATE2 deployer proxy is a transaction of this kind, so it does not work here. The chain provides that proxy at genesis instead; see Predeployed Contracts.
A type 3 transaction is refused at submission, in either encoding: the consensus encoding, and the pooled network encoding that carries the blob sidecar. The network provides no blob data availability: nothing transmits, stores or serves a blob. The refusal is geth’s message for an unsupported transaction type, followed by the reason:
transaction type not supported: this network provides no blob data availability, so an EIP-4844 blob transaction cannot be honouredUse a type 2 transaction with calldata instead.
EVM version
The chain executes at the Osaka EVM specification. A contract must be compiled for osaka or an
earlier version.
The specification is set by on-chain governance, not fixed in the node: it changes at an epoch
boundary, on every validator at once, after the validators have upgraded to a node that supports
the new one. Every block executes at the specification in force for its epoch. A simulation runs at
the head’s, except an eth_estimateGas at a past height on the wallet-integration endpoint, which
runs at that block’s. The value in force is
supra_framework::evm_config::current_spec(), a code rather than a name: 1 is Prague and 2 is
Osaka.
In the examples, <RPC_HOST> is the RPC host with its scheme, such as
https://rpc-devnet.supra.com.
curl --request POST --url '<RPC_HOST>/rpc/v3/view' \
--header 'Content-Type: application/json' \
--data '{"function":"0x1::evm_config::current_spec",
"type_arguments":[],
"arguments":[]}'A later specification is announced before the governance proposal that sets it. Its codes only
increase, so bytecode that runs today keeps running, except where the new specification itself
changes an existing opcode’s or precompile’s behaviour. Bytecode compiled for a later version, such as solc’s amsterdam target, can
contain an opcode Osaka does not define, which halts when it is reached.
Compiler defaults differ by version, so set the target explicitly rather than relying on them:
- solc 0.8.31 and later default to
osaka. - solc 0.8.30 defaults to
prague. - solc 0.8.25 to 0.8.29 default to
cancun. - Hardhat and Foundry each carry their own default EVM version, which may differ from the compiler’s and from each other’s.
In Foundry, set evm_version = "osaka" in foundry.toml. In Hardhat, set evmVersion: "osaka"
under the Solidity compiler settings. Compiling for an earlier version is safe: Prague, Cancun and
older bytecode all execute at Osaka.
The Osaka changes a contract can meet:
- The
CLZopcode (0x1e, EIP-7939), which counts the leading zero bits of a word. solc exposes it as theclzbuiltin in inline assembly. - The
modexpprecompile at0x05refuses a base, exponent or modulus longer than 1,024 bytes (EIP-7823), and costs at least 500 gas, more than before for large inputs (EIP-7883). - The per-transaction gas cap of 16,777,216 (EIP-7825), described above.
- The
P256VERIFYprecompile at0x100(EIP-7951) costs 6,900 gas.
Precompiles
The standard Ethereum precompiles in force at Osaka are listed in Standard Ethereum precompiles. The same page covers the precompiles Supra adds in its own range: native transfers to SupraMVM, on-chain randomness, the microsecond block timestamp and the chain’s gas parameters.
Where the fee goes
The fee is burnt. The sender is debited gasUsed × effectiveGasPrice, and no account is credited
any part of it: the block proposer receives nothing, and there is no fee recipient to direct a tip
to. The priority component is destroyed along with the base component. COINBASE returns the zero
address, and no fee is credited to it.
A transaction that reverts or halts is still charged for the gas it consumed, refunds already applied, and that fee is burnt too. The exception is a transaction in which a value read from the randomness precompile was undone, because the call that read it or a call between that call and the transaction reverted or halted. It is charged its whole gas limit, whether or not the transaction itself failed; see SupraEVM Precompiles. A transaction rejected before execution (bad signature, wrong chain id, fee below the base fee, gas limit above the cap) is charged nothing.