How do abort codes and clever errors work in Sui Move?
November 6, 2026
When Move code aborts, through abort or a failed assert!, the whole transaction fails and nothing it did is applied. The failure reports a u64 abort code plus the module and function where it happened. Plain error constants (const ENotOwner: u64 = 0;) give you a numeric code you have to look up. Clever errors, constants marked #[error], can hold a message like b"Not the owner"; the compiler packs the source line and references to the constant into the u64, and the Sui CLI, GraphQL and SDKs decode it into a readable message.
Plain abort codes
module book::vault;
const ENotOwner: u64 = 0;
const EInsufficient: u64 = 1;
public struct Vault has key {
id: UID,
owner: address,
balance: u64,
}
public fun withdraw(v: &mut Vault, amount: u64, ctx: &TxContext) {
assert!(v.owner == ctx.sender(), ENotOwner);
assert!(v.balance >= amount, EInsufficient);
// ...
}assert!(cond, code) is shorthand for if (!cond) abort code. Conventions and facts:
- Name error constants
E+ CamelCase. - Codes are local to a module: code
1in your module and code1insui::coinmean different things. Always read the code together with the module in the error. - There's no try/catch. An abort can't be caught by the caller; it fails the entire transaction, including every other command in a PTB. You still pay gas for the work done.
Clever errors
module book::vault;
#[error]
const ENotOwner: vector<u8> = b"Only the vault owner can withdraw";
#[error(code = 1)]
const EInsufficient: vector<u8> = b"Not enough balance in the vault";
public struct Vault has key {
id: UID,
owner: address,
balance: u64,
}
public fun withdraw(v: &mut Vault, amount: u64, ctx: &TxContext) {
assert!(v.owner == ctx.sender(), ENotOwner);
assert!(v.balance >= amount, EInsufficient);
}The constant can be any constant type, though vector<u8> messages are the usual choice. At runtime the abort code is still a u64, laid out as:
0x8000_0007_0001_0000
| | | +-- index of the constant's value
| | +------- index of the constant's name
| +------------ source line of the abort
+-------------------- tag bit: this is a clever errorDecoded, it looks like:
Error from '0x42::vault::withdraw' (line 7), abort 'ENotOwner': "Only the vault owner can withdraw"The optional #[error(code = n)] attaches a fixed u8 code, stored in its own field. The Sui framework uses this so each error has a stable number that doesn't depend on line numbers.
Aborts without a code
assert!(cond) and a bare abort are valid. The compiler derives a clever-error code holding the line number, with 0xffff in the name and value fields. That's fine for "should never happen" branches; for errors users can trigger, give a named constant.
Gotchas
- Clever codes change when the file changes. Reformatting or adding a line moves the line number, so the
u64changes. Match errors by constant name (or the explicitcode), never by the raw numeric value. - Macros report the call site. If an
assert!inside a macro fails, the line is where the macro was invoked. - Old abort codes in clients. If your frontend maps numeric codes to messages, switching a constant to
#[error]changes the number it receives.
Reading the error in TypeScript
With @mysten/sui v2, a failed transaction carries a structured error:
const result = await client.signAndExecuteTransaction({ signer, transaction: tx })
if (result.$kind === 'FailedTransaction') {
const err = result.FailedTransaction.status.error
if (err?.$kind === 'MoveAbort') {
console.log(err.MoveAbort.abortCode, err.MoveAbort.location?.functionName)
console.log(err.MoveAbort.cleverError?.constantName, err.MoveAbort.cleverError?.value)
}
}cleverError exposes constantName, value, lineNumber and errorCode when available. Simulating first shows the same error without spending gas; see dry runs and why a transaction failed.
Testing aborts
#[test, expected_failure(abort_code = ENotOwner)]
fun non_owner_cannot_withdraw() { /* ... */ }Clever error constants work in abort_code too. See unit tests.
Sources
- Aborting Execution, The Move Book
- Clever Errors, Move Reference
sui::derived_objectsource (framework use of#[error(code = ...)]), MystenLabs/sui