Transaction admission
These messages come frometh_sendRawTransaction before the transaction reaches
the EVM.
| Error | Cause | Corrective action |
|---|---|---|
unsupported Ethereum envelope type | Envelope is not replay-protected legacy (0x00), EIP-2930 (0x01), EIP-1559 (0x02), standard blob (0x03), or Metal (0x7f) | Use an accepted envelope. Carry EIP-7702 authorizations inside 0x7f |
transaction has no chain id | Legacy transaction lacks EIP-155 replay protection | Sign the legacy transaction with the Metal chain ID or use EIP-1559 |
value must be zero | value != 0 in the envelope | Set value = 0 |
gas_token ... not in MIP20 allowlist | The selected gas token is absent from MIP20Factory.isMIP20 | Use a MIP20 token. Standard Ethereum envelopes use the default gas token |
insufficient gas-token balance | The signing account’s balance in the gas token cannot cover the maximum gas cost, any blob fee, and a recognized same-token transfer debit | Fund the signing account in the selected or default gas token (the faucet dispenses *USDZ) |
max_fee_per_gas ... exceeds cap | Fee cap is greater than 1_000_000_000_000 base units | Lower max_fee_per_gas to the chain cap or below |
effective gas price ... below minimum of 1 | min(max_fee_per_gas, max_priority_fee_per_gas) is zero | Set both fee fields to at least 1 |
nonce too low | A previous transaction with this nonce was already included | Reconcile the original transaction, then read getTransactionCount(sender, "pending") before preparing a new attempt |
Blob admission
These failures apply to standard0x03 transactions. Transaction pool
validation messages can include additional details about the sidecar or failed
KZG check.
| Error or condition | Cause | Corrective action |
|---|---|---|
blob transaction must be submitted in EIP-7594 network form with its sidecar | A consensus-form transaction was submitted without its sidecar | Submit the complete pooled network encoding |
max_fee_per_blob_gas must be at least 1 | Blob-fee bid is zero | Set a positive maxFeePerBlobGas |
max_fee_per_blob_gas ... exceeds cap | Blob-fee bid exceeds 1_000_000_000_000 base units | Lower the bid to the cap or below |
blob transactions are not accepted via observer ingress; submit to a validator | An observer cannot forward the blob sidecar | Use a validator RPC endpoint |
| Missing, empty, or legacy sidecar | The sidecar is absent, carries no blobs, or uses the EIP-4844 shape | Supply the EIP-7594 blobs, commitments, and cell proofs |
| Too many blobs | Transaction exceeds the active blob limit | Include no more than six blobs in the transaction |
| Invalid KZG commitment or cell proof | Sidecar does not verify against the transaction’s versioned hashes | Rebuild the sidecar and verify it before submission |
MIP20
MIP20 uses custom errors from ERC-20, ERC-3009 and ReceivePolicy. Decode revert data using the ABI for the token and the called policy contract.| Error | Meaning | Corrective action |
|---|---|---|
ERC20InvalidSender(address) | The source address is zero | Supply a nonzero source |
ERC20InvalidReceiver(address) | The recipient is zero, is ReceivePolicy directly, or differs from the submitter of receiveWithAuthorization | Check the recipient address. For receiveWithAuthorization, the caller must be the recipient |
ERC20InvalidSpender(address) | The approved spender is zero | Supply a nonzero spender |
ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed) | Spendable balance is below the amount | Read balanceOf(from) and reduce or fund the transfer |
ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed) | The caller lacks allowance | Approve the required finite amount |
InsufficientHold(address owner, address spender, uint256 held, uint256 needed) | The caller’s hold is below the requested capture or release | Read heldBalanceOf(owner, caller) |
ERC3009InvalidAuthorizationTime(uint256 validAfter, uint256 validBefore) | The authorization is outside its strict validity bounds | Check the signed window against chain time |
ERC3009UsedAuthorization(address authorizer, bytes32 nonce) | The nonce was used or canceled | Reconcile AuthorizationUsed, AuthorizationCanceled and the payment outcome before deciding on another authorization |
ERC3009InvalidSignature() | The signature does not authorize the exact payment or cancellation | Check domain, fields and signer, and ERC-1271 for the bytes form |
CreditRefused(uint8 reason) | ReceivePolicy refused an ordinary credit in REVERT mode | Read reasonFor and correct the input or recipient policy |
OwnableUnauthorizedAccount(address) | Caller lacks token-owner authority | Use the current owner for mint, burn or ownership changes |
OwnableInvalidOwner(address) | An ownership transfer names zero | Use a nonzero owner, or explicitly renounce ownership |
(v,r,s) overloads can also return ECDSAInvalidSignature() or
ECDSAInvalidSignatureS(bytes32). The bytes-signature form reports an
unsuccessful owner-key and ERC-1271 check as ERC3009InvalidSignature().
Quarantine records a successful transfer into escrow under a quarantine receipt.
The Receive Policy reference defines refusal
reasons and the UnknownReceipt, NotRecoveryAuthority, StillBlocked and
ClaimTransferFailed recovery errors.
MIP410
The pool at0x0000000000000000000000000000000000000410 defines the custom
errors below. Calls can also return revert data from the MIP20 token,
precompiles, or ABI decoding. A valid rejection submitted with its resolution
proof and policy-wrapper proof succeeds and records rejection as the payment
outcome.
Proofs and field rules
| Error | Meaning | Corrective action |
|---|---|---|
NonCanonicalField() | A calldata word checked by the pool is not a canonical BN254 field encoding | Re-encode with the circuit package. Do not reduce arbitrary values modulo the field |
InvalidProof(circuitId) | The named circuit’s proof did not verify — an invalid proof, or one built under a key the circuit-ID table no longer names | Rebuild the proof for this chain and pool under the currently registered key. On a two-proof call the argument says which proof failed |
Caller failures
| Error | Meaning | Corrective action |
|---|---|---|
ReentrantCall() | The pool was re-entered during a value transfer | Avoid reentering a pool value operation. The outer call is rolled back |
NotAuthority() | The caller is not authority() | Submit from the authority account |
NotPendingAuthority() | acceptAuthority was called by an account other than the one named by transferAuthority | Have the named account accept, or name it first |
Paused() | A value path was called while the pool is paused | Wait for PoolUnpaused. Reclaims and registry writes still succeed |
Shield boundaries
| Error | Meaning | Corrective action |
|---|---|---|
AssetNotAdmitted(asset) | The authority has not admitted the asset, or has revoked it | Shield an admitted asset. Read assetAdmitted(asset) first |
ZeroAmount() | The shield or unshield amount is zero | Supply a positive amount |
AmountOutOfRange() | The shield or unshield amount is not below 2^128 | Reduce the amount or split it across valid operations |
FutureShieldTimestamp() | shieldTimestamp is after the block timestamp | Use the current chain time when minting the note |
StaleShieldTimestamp() | shieldTimestamp is more than 3600 seconds before the block timestamp | Rebuild the note with a fresh timestamp and resubmit promptly |
DepositDeltaMismatch(expected, received) | The pool received a different MIP20 amount than the request states | Use an asset whose transfer delivers the exact amount. The pull is rolled back |
Commitment tree invariants
| Error | Meaning | Corrective action |
|---|---|---|
ZeroLeaf() | A zero note commitment was presented for appending | Generate a real note |
EmptyLeafSentinel() | The tree’s empty-leaf sentinel was presented as a commitment | Generate a real note |
DuplicateCommitment() | The note commitment is already in the tree | Generate a fresh note secret and rebuild the proof |
TreeFull() | The commitment tree holds 2^32 leaves | No further notes can be appended to this pool |
Hold and lock rules
| Error | Meaning | Corrective action |
|---|---|---|
WrongInputArity() | inputLockIds or nullifiers does not carry exactly MAX_INPUTS = 2 entries | Pad with zero dummy slots to the fixed arity |
AllDummyInputs() | Every input slot is a dummy | Spend at least one real note |
DuplicateInput() | Both input lock IDs of an initiation name the same note | Spend two distinct notes, or one note and a dummy slot |
UnknownCommitmentRoot() | The commitment tree never had the named root | Rebuild the tree witness from the pool’s appends and prove against commitmentRoot() |
UnknownRegistrationRoot() | The named registration root is neither the current one nor, after an admission, the one before it | Re-read registrationRoot() and rebuild the wrapper’s leaf opening. See Root windows |
UnknownSanctionsRoot() | The named sanctions root is not the current one. A sanctions write retires the previous root at once | Re-read sanctionsRoot() and rebuild the non-membership opening. See Root windows |
InputAlreadyLocked() | An input is reserved by a live hold, or tombstoned by an accepted one | After Rejected or Reclaimed, rebuild from the released inputs. After Accepted, refresh the wallet’s unspent notes |
HoldNotAbsent() | A hold with this payment effect hash already exists | Reuse the existing hold. Holds are never deleted |
DeadlinePast() | The initiation deadline is not in the future | Choose a later deadline and rebuild the proof |
ZeroPayloadCommitment() | The initiation names no payload commitment | Commit to the payment’s payload and rebuild the proof |
DeadlineExceedsCap() | The deadline is more than maxHoldDuration() ahead | Choose a deadline within the ceiling and rebuild the proof |
HoldNotHeld() | The hold is absent or already terminal | Read holdOf(holdId) and stop retrying a terminal transition |
DeadlinePassed() | A resolution arrived after the deadline | Reclaim the hold instead of resolving it |
EffectMismatch(recomputed) | The supplied resolution effect does not hash to the hold ID | Supply the four words the initiation sealed. The argument is what the supplied words hash to |
NullifierAlreadyUsed() | A nullifier is already recorded | Refresh the wallet’s unspent notes and rebuild the proof |
HoldNotExpired() | Reclaim was submitted at or before the deadline | Wait until the finalized chain time is beyond the deadline |
Exit rules
| Error | Meaning | Corrective action |
|---|---|---|
InsufficientBacking(backing, amount) | The asset’s recorded backing cannot cover the proven amount | Treat as a pool-accounting failure and stop submission |
TransferFailed() | The outbound MIP20 transfer returned false | Inspect the token’s transfer behavior. The operation is rolled back |
SolvencyBroken(balance, backing) | After the transfer, the pool’s balance fell below the asset’s remaining backing | Treat as a pool-accounting failure and stop submission |
Bridge rules
| Error | Meaning | Corrective action |
|---|---|---|
NotBridgeGateway() | The caller is not bridgeGateway(), or no gateway is set | Submit through the configured gateway |
BridgeDepositAlreadyMinted() | The deposit id has already minted its note | Stop retrying. Read bridgeDepositMinted(depositId) |
InsufficientBridgeLiability(liability, amount) | The asset’s bridge liability cannot cover the proven exit amount | Treat as a pool-accounting failure and stop submission |
BridgeAssetShieldable(asset) | bridgeMint named an asset admitted for shield or still backing shielded notes | Bridge under an asset id the shield path does not use |
AssetBridged(asset) | admitAsset named an asset that bridged notes still denominate | Admit a different asset id for shield |
The VASP tree
The tree records virtual asset service providers (VASPs).| Error | Meaning | Corrective action |
|---|---|---|
FiIndexOutOfRange(index) | The slot is beyond the tree’s 1,024 leaves | Use a slot below 1,024 |
FiSlotOccupied(index) | admitFi named a slot that already holds an institution | Admit at a vacant slot |
FiSlotVacant(index) | revokeFi or updateFi named an empty slot | Read fiIndexOf(fiId) for the institution’s slot |
FiIdInUse(fiId) | The identifier is already admitted at another slot | Revoke the existing admission first, or use the existing slot |
InvalidFiRecord() | The identifier or the policy verification key hash is zero | Supply the derived identifier and policy verification key hash |
InvalidMessagingKey() | The messaging key is the identity, off the curve, or non-canonical | Supply the key’s affine coordinates below the field modulus |
MessagingKeyInUse(ownerFiId) | admitFi or updateFi named a messaging key another institution holds or has held | Generate a fresh messaging key. A key never moves between institutions |
InvalidFiSignature() | The updateFi signature does not verify under the slot’s current key over the current registration root and the slot’s update nonce | Sign with the key being replaced, over the root and fiUpdateNonce(index) read in the same block. See The VASP tree |
The sanctioned set
| Error | Meaning | Corrective action |
|---|---|---|
InvalidAffilId() | A zero identifier. Zero is the start-of-chain key | Sanction a real affilID |
SanctionsIndexOutOfRange(index) | The position is at or past sanctionsCount() | Read the set and name a live position |
SanctionsBracketMismatch() | The entry at lowIndex does not bracket the identifier, so it is already present or lowIndex is the wrong leaf | Read sanctionsLeaves and name the entry whose key is the greatest below the identifier |
SanctionsListUnsorted() | A reset list is not strictly ascending | Sort and deduplicate before calling resetSanctions |
Circuit Registry
| Error | Meaning | Corrective action |
|---|---|---|
UnknownBackend(uint8 backend) | Registration uses a backend other than UltraHonk 0x02 | Use the supported backend tag |
InvalidVerificationKeyLength(uint256 length) | Verification-key length or stored blob shape is invalid | Use a key between 256 and 24,574 bytes |
UnknownCircuit(bytes32 circuitId) | Key lookup names an unregistered circuit | Register the key or use its correct content-addressed ID |
CircuitRegistry.verify(bytes) returns false for a malformed request, unknown
circuit, or failed proof. See the
Circuit Registry for
its wire and failure rules.
Interoperability
| Error | Contracts | Meaning |
|---|---|---|
TreeFull() | Metal outbox | The message tree has no remaining leaf capacity |
AlreadyConsumed() | MetalInbox, ForeignInbox | This inbox has already executed the nonce successfully |
NotInTree() | MetalInbox | Message index, leaf count, or Merkle path does not verify against the checkpoint message root |
MessageMismatch() | ForeignInbox | The proved outbox slot does not contain the submitted message commitment |
AccountProofInvalid() | ForeignInbox | The account proof does not authenticate the source outbox under the checkpoint state root |
CodeHashMismatch() | ForeignInbox | The proved source outbox account does not run the outbox code hash the inbox was configured with |
InvalidOutboxCodeHash() | ForeignInbox | Constructor outbox code hash is zero or the hash of empty code |
TrieProofTraversalError(uint8) | ForeignInbox | An account or storage Merkle Patricia proof does not verify. The argument is OpenZeppelin’s TrieProof.ProofError code |
RLPInvalidEncoding() | ForeignInbox | A proved account or storage value is not valid RLP |
TargetReverted() | MetalInbox, ForeignInbox | The destination contract call reverted. The nonce remains unconsumed |
UnknownCheckpoint() | Metal, Ethereum, Tempo, and Arc light clients | No checkpoint is stored for the requested block hash |
InvalidCheckpointHeight() | Metal, Tempo, and Arc light clients | Header height is zero, reserved for the unknown-checkpoint sentinel |
Malformed() | Metal, Tempo, and Arc light clients | Header RLP cannot be parsed in the required source format |
NotMonotonic() | Metal, Tempo, and Arc light clients | The checkpoint does not advance the required height and consensus-round ordering |
BadValidatorSet() | Arc light client | Constructor validator keys or voting powers are invalid |
BadKeySet() | Metal light client | Constructor key lists are empty or differ in length, a key’s compressed and uncompressed forms are the wrong size or disagree, or the keys are not in strictly ascending order |
BadProofOfPossession() | Metal light client | A constructor validator key’s proof of possession fails verification |
BadIdentity() | Tempo light client | Constructor group key or its encoding is invalid |
UnsupportedEpoch() | Metal and Tempo light clients | Metal requires epoch 0. Tempo requires an epoch at or after its configured key’s starting epoch |
BadBitmap() | Metal light client | Signer bitmap has an invalid length or trailing bits |
BadVoteOrder() | Arc light client | Vote indices are out of range, duplicated, or unordered |
QuorumNotMet() | Metal and Arc light clients | Valid signers do not meet the required quorum |
BadSignature() | Metal, Tempo, and Arc light clients | The supplied consensus signature fails verification |
Decode a custom error
import { decodeErrorResult } from "viem";
const decoded = decodeErrorResult({
abi: contractAbi,
data: revertData,
});
console.log(decoded.errorName, decoded.args);
eth_sendRawTransaction.