Skip to Content
Supra Layer 1SupraEVMSupraEVM Precompiles

SupraEVM Precompiles

SupraEVM serves two sets of precompiles: the standard Ethereum set in force at Osaka, at the usual addresses, and a set of its own in a reserved range. This page is the reference for both: what is at each address, and, for the Supra range, its interface, what it costs and how it refuses. For the gas schedule the rest of the address space follows, see Gas and Fees; for the JSON-RPC surface, see Differences from Ethereum.

Standard Ethereum precompiles

SupraEVM runs at the Osaka hardfork, and every precompile Ethereum defines at Osaka is present at its usual address, with Ethereum’s inputs, outputs and gas costs:

AddressFunctionSpecification
0x01ecRecoverYellow Paper
0x02SHA2-256Yellow Paper
0x03RIPEMD-160Yellow Paper
0x04identityYellow Paper
0x05modexpEIP-198, repriced by EIP-2565 and EIP-7883, input bounded by EIP-7823
0x06ecAdd (BN254)EIP-196, repriced by EIP-1108
0x07ecMul (BN254)EIP-196, repriced by EIP-1108
0x08ecPairing (BN254)EIP-197, repriced by EIP-1108
0x09blake2fEIP-152
0x0apoint evaluation (KZG)EIP-4844
0x0bBLS12-381 G1ADDEIP-2537
0x0cBLS12-381 G1MSMEIP-2537
0x0dBLS12-381 G2ADDEIP-2537
0x0eBLS12-381 G2MSMEIP-2537
0x0fBLS12-381 PAIRING_CHECKEIP-2537
0x10BLS12-381 MAP_FP_TO_G1EIP-2537
0x11BLS12-381 MAP_FP2_TO_G2EIP-2537
0x100P256VERIFY (secp256r1)EIP-7951

Two of them follow Osaka’s rules rather than Prague’s. modexp has a minimum cost of 500 gas and refuses a base, exponent or modulus longer than 1,024 bytes. P256VERIFY costs 6,900 gas per call; it returns a 32-byte word holding 1 for a valid signature and empty output for any other input.

The KZG point-evaluation precompile is present even though the chain refuses blob-carrying (type 3) transactions, so a contract can verify a KZG proof it is given as call data.

The reserved range

Supra reserves every address from

0x0000000000000000000000000000000053555000

through

0x00000000000000000000000000000000535550ff

inclusive — 256 addresses. The range belongs to the protocol. Addresses in it that are not listed below are unallocated and may be given a meaning in a later release, so do not build on their current behaviour and do not attempt to place a contract at one.

No transaction may be sent from an address in the range. A user transaction whose sender falls in it is rejected before execution, so a key that happened to produce such an address is unusable.

AddressWhat is thereCostInterface
0x…53555000VM signer — the sender of the chain’s own internal transactions. Holds no code and is not a precompile.—none
0x…53555001Transaction hash18 gasnone; returns 32 bytes
0x…53555002Native crossing to SupraMVM100,000 gasISupraNativeCrossing
0x…53555003Randomness1,000 / 2,500 gasISupraRandomness
0x…53555004Block timestamp in microseconds20 gasISupraBlockTimestamp
0x…53555005EVM gas config: the per-transaction gas cap and the minimum gas price20 gasISupraEvmGasConfig
0x…53555006 – 0x…535550ffReserved, unallocated——

The five implemented addresses are warm from the start of every transaction, as the standard precompiles are, so calling one does not pay the cold-account access surcharge. The rest of the range is cold like any other address.

Every gas figure below is what the precompile itself charges, on top of the CALL overhead the calling frame pays.

The four interfaces are published as Solidity files in the Supra revm repository; the links above and in each section point there.

0x53555000 — VM signer

0x0000000000000000000000000000000053555000

The address the chain signs its own transactions with: the per-block metadata transaction, the transactions that record automation-registry bookkeeping, and the transactions that credit crossings from SupraMVM. It is not a precompile and holds no code, so a call to it behaves as a call to any empty account — it succeeds and returns nothing. An automated task’s own action is sent from the task’s registered sender, not from this address.

An ordinary transaction may not be sent from it, and neither may any other address in the reserved range. Transactions from it are accepted only when the chain itself produces them.

0x53555001 — Transaction hash

0x0000000000000000000000000000000053555001

Returns the 32-byte hash of the transaction currently executing. The value is the same for every frame of the transaction.

There is no declared Solidity interface. The precompile takes no arguments and ignores whatever call data it is given, so call it with empty call data and decode the 32 bytes it returns:

address constant TX_HASH = 0x0000000000000000000000000000000053555001; function currentTransactionHash() view returns (bytes32 hash) { (bool ok, bytes memory ret) = TX_HASH.staticcall(""); require(ok, "tx hash unavailable"); hash = abi.decode(ret, (bytes32)); }

STATICCALL is served, because the read touches no state.

Cost. 18 gas: the standard identity-precompile price of 15 plus 3 for the one word returned.

Failure modes. The only one is an out-of-gas frame, reported as a precompile out-of-gas rather than a revert. There is no selector to get wrong and no call data that is refused.

0x53555002 — Native crossing to SupraMVM

0x0000000000000000000000000000000053555002

Moves native SUPRA out of SupraEVM and records a crossing for SupraMVM to credit. See Performing Cross-VM Transfers for the whole procedure, including the Move-to-EVM direction and how to follow a crossing after you submit it.

interface ISupraNativeCrossing { event NativeCrossingToMove(bytes32 indexed settlementId, address indexed sender, bytes32 indexed recipient, uint256 amountQuants); event NativeCrossingFromMove(bytes32 indexed settlementId, address indexed recipient, uint256 amountQuants); error UnknownSelector(); error MalformedInput(); error NotACall(); error StaticCall(); error NotWholeQuants(); error AmountTooLarge(); error BadRecipient(); error SettlementDisabled(); error BacklogFull(); function crossToMove(bytes32 recipient) external payable returns (uint64 amountQuants); }

crossToMove has the selector 0xe9892167. The value sent with the call is the amount that crosses:

FieldValue
to0x0000000000000000000000000000000053555002
valueThe amount to cross, in wei. Non-zero, and a whole multiple of 10^10.
dataThe selector 0xe9892167 followed by the 32 bytes of the Move recipient address: 36 bytes, and nothing else.

It must be a plain CALL, from an externally owned account or from a contract. A contract calling it through the interface needs solc 0.8.10 or newer, because earlier compilers check that the callee has code before a high-level call, and a precompile has none.

The precompile debits the value, takes it out of SupraEVM’s circulation, and emits NativeCrossingToMove at its own address. settlementId is the crossing’s settlement id: the id SupraMVM’s InboundSettlementApplied or InboundSettlementSenderRejected event names the crossing by, and the id the settlements endpoints look it up by. sender is the address debited, and the address a refund returns the value to; recipient is the Move address the value is owed to. amountQuants in the event and the return value are the amount in quants, SupraMVM’s unit: the value sent divided by 10^10. The event’s topic is 0xf54db30b56b0d1ff3aac68b7368ab780b36f8af2e1980cb0c94a355d89fc84aa. See Tracing Cross-VM Transfers.

The settlement id depends on the position of the crossing’s transaction in its block. eth_call, eth_estimateGas and debug_traceCall run the call as transaction index 0, so a crossing log they return carries a settlementId that is not the id the crossing gets when its transaction is included in a block. Take the id from the log in the transaction’s receipt.

A second event is logged at this address for the other direction. The chain credits a crossing from SupraMVM directly to the recipient’s balance, without calling the precompile, and places one log at this address in the credit’s receipt: NativeCrossingFromMove, declared in ISupraNativeCrossing above. settlementId is the crossing’s settlement id, the address of its SupraMVM record; recipient is the SupraEVM address credited; amountQuants is the amount credited in quants. The event’s topic is 0xeb92f00ae1449f5ec894595973acc45046a692dc4111e9ae3282ea58fe23624d. The precompile never emits this event, so a call to it cannot produce one. See Tracing Cross-VM Transfers.

Cost. A flat 100,000 gas, charged only for a crossing that is recorded. The figure prices the SupraMVM work the crossing causes — a durable queue row, a settlement transaction entry, an escrow withdrawal and a deposit — none of which SupraMVM charges for. Every check below runs before the charge, so a refused call pays none of it.

Failure modes. A refusal reverts the frame with its own four-byte custom error, which returns the value sent with it. A permanent refusal means the call has to change; a transient one means the same call succeeds on a later retry.

ErrorKindCause
UnknownSelector()PermanentThe call data is four bytes or longer and does not begin with 0xe9892167.
MalformedInput()PermanentThe call data is shorter than four bytes, or is not the selector followed by exactly one 32-byte word. A mis-sized recipient is refused rather than padded or truncated.
NotACall()PermanentNot entered as a plain CALL: DELEGATECALL and CALLCODE report a value that was never transferred to this address.
StaticCall()PermanentCalled in a static frame. A crossing changes state.
NotWholeQuants()PermanentThe value is zero, or carries a sub-quant remainder. SupraMVM settles whole quants, so a sub-quant amount is refused rather than rounded; see Performing Cross-VM Transfers.
AmountTooLarge()PermanentMore quants than a Move fungible-asset amount can express.
BadRecipient()PermanentThe recipient is the Move zero address, which cannot hold a balance.
SettlementDisabled()TransientThe chain has the SUPRA_EVM_SETTLEMENT feature off.
BacklogFull()TransientThe outstanding-crossing bound has been reached. Retry later.

A CALL whose value exceeds the caller’s balance never reaches the precompile: the EVM refuses it as it builds the frame, so the CALL returns 0 with empty return data and no error is carried. Out of gas is reported as a precompile out-of-gas rather than a revert, carries no error, and leaves nothing behind.

0x53555003 — Randomness

0x0000000000000000000000000000000053555003

Synchronous unbiasable randomness, and the seeds of recently committed blocks. Read Using Native Randomness from an EVM Contract before using it: a synchronous read is safe only if your contract also closes the two ways a caller can undo one, and that article works through them.

The interface is published as ISupraRandomness.sol, with the rules and hazards of each entry point in its NatSpec. Its declarations:

interface ISupraRandomness { error UnknownSelector(); error MalformedInput(); error SeedUnavailable(); error SeedBiasable(); error CallerNotTransactionRoot(); error OriginHasCode(); error ExecutionModeNotPermitted(); error HeightOutOfWindow(); event UndoneReadCharged(uint256 gasCharged, uint256 gasUsedBeforeCharge); function next() external returns (bytes32); function seedAt(uint64 height) external view returns (bytes32); }

next() returns 32 fresh bytes for the current transaction. Every read on the chain is distinct: two reads in one transaction differ, and the count does not rewind on a revert. It is served only to the contract the transaction itself invoked, when every call between the transaction and the read runs as that contract, in a transaction whose sender has no code. A sender with an EIP-7702 delegation has code, so every delegated sender is refused, a self-sponsored one included.

seedAt(height) returns keccak256 of the seed of a committed block in the window [block.number - 256, block.number - 1], the same window as BLOCKHASH. The current block is not served. A committed block’s seed is public, so anything derived from it can be computed by anyone.

A next() value cannot be known by anyone before its transaction is ordered. Once its block commits, anyone can compute it: the seed is published with the block’s commit certificate and the transaction hash is public. Do not use it as a secret, such as hidden cards, a sealed draw, a salt or a key.

Both entry points are called with CALL or STATICCALL. A DELEGATECALL to the precompile is decided on the calling frame’s msg.sender instead of its context. ETH sent with a call to 0x53555003 stays at that address and is not recoverable.

Cost. next() costs 1,000 gas and seedAt() 2,500. A transaction in which a value served by next() is undone — the call that read it, or any call between that call and the transaction, reverts or halts, running out of gas included — is charged its entire gas limit rather than the gas it used, even if the transaction succeeds. That makes aborting on an unfavourable outcome cost the whole limit wherever the abort is caught, and the charge dominates both figures. A reader that catches the failure of a callee it calls after the read is not charged, unless that callee was itself served a value. Simulation (eth_call, eth_estimateGas, debug_traceCall) does not apply the charge. When the charge raises what a successful transaction costs, the transaction has UndoneReadCharged(gasCharged, gasUsedBeforeCharge) from 0x53555003 as its last log: the gas limit charged, and the gas the transaction used before the charge. A failed transaction carries no logs: a reverted or halted transaction that undid a read shows status 0 and a gasUsed equal to its gas limit, and its receipt does not otherwise mark the charge. See An undone read costs the whole gas limit and How a receipt shows the charge.

A function that reads must not be reachable from a batch whose other entries the sender chooses, a strict self-delegating multicall included: the sender can make the batch revert on an outcome they dislike and pay only the gas limit. Behind a batch, a self-call or a DELEGATECALL the reading function’s gasleft() is a share of the transaction’s gas. A require(gasleft() >= N) check before the read uses the N at which the most expensive outcome succeeds when started at the read, which covers the 1/64 of available gas each call on its path keeps back (EIP-150) and is not reduced by refunds. See Checking the gas left before the read.

Failure modes. Each reverts with its own four-byte custom error.

ErrorWhen
CallerNotTransactionRoot()the contract was reached from another contract, or another contract’s call is between the transaction and the read
OriginHasCode()the transaction’s sender account has code, which includes every EIP-7702 delegated account
SeedUnavailable()the chain has no seed yet, or seedAt names a height this node has no block for
SeedBiasable()that block’s seed is the fallback the proposer knew in advance
HeightOutOfWindow()seedAt was given a height outside the 256-block window
ExecutionModeNotPermitted()one of the chain’s internal transactions tried to read; not reachable from user code
UnknownSelector()the call data is four bytes or longer and names neither entry point
MalformedInput()the call data is shorter than four bytes, or is not the length the named entry point takes, or the uint64 argument has bits set above its 64th

An out-of-gas frame is reported as a precompile out-of-gas and carries no error selector.

0x53555004 — Block timestamp in microseconds

0x0000000000000000000000000000000053555004

The timestamp of the block being executed, in microseconds. block.timestamp reports the same instant in whole seconds. See Reading the Block Timestamp in Microseconds.

The interface is published as ISupraBlockTimestamp.sol. Its declarations:

interface ISupraBlockTimestamp { error UnknownSelector(); error MalformedInput(); error TimestampUnavailable(); function microseconds() external view returns (uint64); }

The value is the same number SupraMVM’s timestamp::now_microseconds() returns for that block, and is identical for every transaction in the block and every frame of every transaction. There are no rules about who may read: STATICCALL, a nested CALL, a DELEGATECALL from a proxy, a constructor and an automated task are all served.

Cost. 20 gas, the same as BLOCKHASH.

Failure modes. Each reverts with its own four-byte custom error, so a zero can never be mistaken for a timestamp.

ErrorWhen
UnknownSelector()the call data is four bytes or longer and names something other than microseconds()
MalformedInput()the call data is not exactly the four selector bytes
TimestampUnavailable()there is no certified block behind the execution, which from a transaction means genesis contract deployment

An out-of-gas frame is reported as a precompile out-of-gas and carries no error selector.

0x53555005 — EVM gas config

0x0000000000000000000000000000000053555005

The chain’s governed EVM gas parameters, for a contract that has to agree with what the chain will accept rather than hard-code a copy that governance can make stale. The automation registry is one such contract: it refuses to register a task whose gas the chain could never execute.

The interface is published as ISupraEvmGasConfig.sol. Its declarations:

interface ISupraEvmGasConfig { error UnknownSelector(); error MalformedInput(); function txGasLimitCap() external view returns (uint64); function minGasPrice() external view returns (uint256); }
  • txGasLimitCap() is the largest gas limit an EVM transaction may declare. A transaction above it is refused by the mempool and discarded by block execution.
  • minGasPrice() is the lowest price per gas, in wei, an EVM transaction may offer: the base fee every block of the epoch executes at. A transaction whose fee cap is below it is refused.

Both are the figures of the epoch the executing block belongs to. They change only at an epoch boundary, when governance changes the gas schedule, and are identical for every transaction in an epoch. eth_call and eth_estimateGas serve the figures of the epoch of the block they simulate, not the simulator’s own gas ceiling, so a call answers as that block would have. As for the block timestamp, there are no rules about who may read: STATICCALL, a nested CALL, a DELEGATECALL from a proxy, a constructor and an automated task are all served.

Cost. 20 gas for either entry point, the same as BLOCKHASH.

Failure modes. Each reverts with its own four-byte custom error, so a zero can never be mistaken for a cap. There is no “unavailable” refusal: every execution belongs to an epoch, and so has both figures.

ErrorWhen
UnknownSelector()the call data is four bytes or longer and names neither entry point
MalformedInput()the call data is not exactly the four selector bytes

An out-of-gas frame is reported as a precompile out-of-gas and carries no error selector.

0x53555006 – 0x535550ff — Reserved, unallocated

Nothing is served at these addresses today. A call to one behaves as a call to an empty account: it succeeds and returns nothing.

They remain Supra’s to allocate. Treat them as unavailable: do not rely on the empty-account behaviour, do not attempt to place a contract at one, and do not send value to one.

Importing the Solidity libraries

LibRandomness and LibBlockTimestamp wrap the randomness and block-timestamp precompiles. They and the interfaces they import are in solidity/supra_contracts/src of the revm fork at tag revm-29.0.0-supra-v1.0.0.rc17, and the examples in Using Native Randomness from an EVM Contract and Reading the Block Timestamp in Microseconds import them as supra_contracts/libraries/....

In a Foundry project, check out the four files into lib/supra-revm:

git clone --depth 1 --filter=blob:none --no-checkout \ --branch revm-29.0.0-supra-v1.0.0.rc17 https://github.com/Entropy-Foundation/revm.git lib/supra-revm cd lib/supra-revm git sparse-checkout set --no-cone \ /solidity/supra_contracts/src/libraries/LibRandomness.sol \ /solidity/supra_contracts/src/libraries/LibBlockTimestamp.sol \ /solidity/supra_contracts/src/interfaces/ISupraRandomness.sol \ /solidity/supra_contracts/src/interfaces/ISupraBlockTimestamp.sol git checkout cd ../..

Then map the import prefix in the [profile.default] table of foundry.toml:

remappings = ["supra_contracts/=lib/supra-revm/solidity/supra_contracts/src/"]

The four files declare pragma solidity 0.8.34, so they compile with solc 0.8.34 only. A contract that imports them must accept that version, for example with pragma solidity ^0.8.34;.

Last updated on