Custom Errors
Named error declarations describe typed failure ABIs. On EVM they lower to
Solidity custom errors and are the preferred way to fail a route, a where
guard, or a from check.
Declaration
Errors may appear at program scope or entity scope:
error InsufficientBalance(have: U256, need: U256);
entity Vault {
error Unauthorized();
routes {
#[factory_only]
init setup(owner: address) => []
withdraw(amount: U256)
from m_owner : throw Unauthorized()
where m_balance >= amount : throw InsufficientBalance(m_balance, amount)
=> [
~> msg::sender with { value: amount }
]
disabled()
from m_owner : throw Unauthorized()
=> [
throw Unauthorized()
]
accept receive() => []
}
m_owner: address {
in setup(owner) => owner
}
m_balance: U256 {
in receive() => m_balance + msg::value
in withdraw(amount) => m_balance - amount
}
}
Raising errors
| Form | Where |
|---|---|
: throw ErrorName(args) | After a where condition or a from clause |
throw ErrorName(args) | As a terminator action in a route body |
throw / throw Name(...) is a terminator: later actions in the same
route (or phase) do not run.
Validation
| Rule | Code |
|---|---|
| Error must be declared at program or entity scope | V38 |
| Argument arity / types must match | V39 |
EVM lowering
| Cambrian | Solidity |
|---|---|
error Name(...); | error Name(...); |
throw Name(args) / : throw Name(args) | revert Name(args); |
Named vs numeric throw
Older sources may still use a numeric form (: throw 100). Prefer named
errors for new code on EVM: they produce typed custom errors that tools and
callers can decode. Numeric throw N remains accepted for compatibility and
lowers to a string-style revert, but it is not the preferred EVM story.