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:
| Address | Function | Specification |
|---|---|---|
0x01 | ecRecover | Yellow Paper |
0x02 | SHA2-256 | Yellow Paper |
0x03 | RIPEMD-160 | Yellow Paper |
0x04 | identity | Yellow Paper |
0x05 | modexp | EIP-198, repriced by EIP-2565 and EIP-7883, input bounded by EIP-7823 |
0x06 | ecAdd (BN254) | EIP-196, repriced by EIP-1108 |
0x07 | ecMul (BN254) | EIP-196, repriced by EIP-1108 |
0x08 | ecPairing (BN254) | EIP-197, repriced by EIP-1108 |
0x09 | blake2f | EIP-152 |
0x0a | point evaluation (KZG) | EIP-4844 |
0x0b | BLS12-381 G1ADD | EIP-2537 |
0x0c | BLS12-381 G1MSM | EIP-2537 |
0x0d | BLS12-381 G2ADD | EIP-2537 |
0x0e | BLS12-381 G2MSM | EIP-2537 |
0x0f | BLS12-381 PAIRING_CHECK | EIP-2537 |
0x10 | BLS12-381 MAP_FP_TO_G1 | EIP-2537 |
0x11 | BLS12-381 MAP_FP2_TO_G2 | EIP-2537 |
0x100 | P256VERIFY (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
0x0000000000000000000000000000000053555000through
0x00000000000000000000000000000000535550ffinclusive — 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.
| Address | What is there | Cost | Interface |
|---|---|---|---|
0x…53555000 | VM signer — the sender of the chain’s own internal transactions. Holds no code and is not a precompile. | — | none |
0x…53555001 | Transaction hash | 18 gas | none; returns 32 bytes |
0x…53555002 | Native crossing to SupraMVM | 100,000 gas | ISupraNativeCrossing |
0x…53555003 | Randomness | 1,000 / 2,500 gas | ISupraRandomness |
0x…53555004 | Block timestamp in microseconds | 20 gas | ISupraBlockTimestamp |
0x…53555005 | EVM gas config: the per-transaction gas cap and the minimum gas price | 20 gas | ISupraEvmGasConfig |
0x…53555006 – 0x…535550ff | Reserved, 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
0x0000000000000000000000000000000053555000The 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
0x0000000000000000000000000000000053555001Returns 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
0x0000000000000000000000000000000053555002Moves 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:
| Field | Value |
|---|---|
to | 0x0000000000000000000000000000000053555002 |
value | The amount to cross, in wei. Non-zero, and a whole multiple of 10^10. |
data | The 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.
| Error | Kind | Cause |
|---|---|---|
UnknownSelector() | Permanent | The call data is four bytes or longer and does not begin with 0xe9892167. |
MalformedInput() | Permanent | The 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() | Permanent | Not entered as a plain CALL: DELEGATECALL and CALLCODE report a value that was never transferred to this address. |
StaticCall() | Permanent | Called in a static frame. A crossing changes state. |
NotWholeQuants() | Permanent | The 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() | Permanent | More quants than a Move fungible-asset amount can express. |
BadRecipient() | Permanent | The recipient is the Move zero address, which cannot hold a balance. |
SettlementDisabled() | Transient | The chain has the SUPRA_EVM_SETTLEMENT feature off. |
BacklogFull() | Transient | The 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
0x0000000000000000000000000000000053555003Synchronous 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.
| Error | When |
|---|---|
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
0x0000000000000000000000000000000053555004The 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.
| Error | When |
|---|---|
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
0x0000000000000000000000000000000053555005The 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.
| Error | When |
|---|---|
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;.