Appearance
Overview: The Xahau Hook Model
What Hooks are
A Hook is a WebAssembly program installed on a Xahau account. When a transaction affects that account, the Hook runs on-ledger as part of applying the transaction. During its run a Hook can:
- read the originating transaction that triggered it (the
otxn_*family), - read and write persistent state scoped to the account and a namespace (the
state*family), - read ledger objects (accounts, trustlines, offers, escrows, and more) through slots and keylets,
- emit brand-new transactions to be applied later (the
etxn_*/emitfamily), - do XFL fixed-precision floating-point math (the
float_*family), - and finally
acceptthe transaction (allowing it to apply) orrollbackit (rejecting it).
The Hook API is the set of host functions the WASM module imports from the env module to do all of the above. Every such function is declared in hook/extern.h and registered with the WasmEdge runtime.
Entry points
A Hook WASM module exports at most two functions, both verified in the execution engine, which looks up the exports named "hook" and "cbak":
c
// Main entry point. Called when a transaction touches the hook account.
// reserved: 0 = strong execution, 1 = weak (collect-call/TSH) execution,
// 2 = again-as-weak (post-apply re-run requested via hook_again()).
int64_t hook(uint32_t reserved);
// Callback entry point. Called to report the result of a transaction this
// hook previously emitted. Optional — only needed if the hook emits.
// reserved: 0 = the emitted transaction was accepted, 1 = emit failure.
int64_t cbak(uint32_t reserved);Both must return int64_t. A Hook must export memory and the hook function; cbak is only required if the hook uses callbacks. (The HookSet validator emits diagnostic log codes such as EXPORT_HOOK_FUNC and EXPORT_CBAK_FUNC when the exported signatures are wrong.)
Neither hook nor cbak "returns" a result to the ledger in the usual sense — a Hook terminates by calling accept() or rollback(), which never return.
Execution modes
The execution engine distinguishes three modes:
- Strong — the hook runs before the originating transaction is applied. A strong hook can
rollback()to reject the transaction. (Internally tracked viaisStrong; the emission flag ishefSTRONG = 0x1.) - Weak — a strong pre-apply hook may call
hook_again()to request an additional post-apply re-execution: a strong pre-apply hook can nominate additional weak post-apply execution. A weak execution observes the already-applied result and cannot roll it back. - Callback — when a transaction the hook emitted resolves, the engine calls
cbak(emission flaghefCALLBACK = 0x2). If the emitted transaction failed, the callback is told so.
Which accounts a hook fires for is governed by the TSH (Transactional Stake Holder) mechanism and its flags (tshROLLBACK, tshCOLLECT, tshMIXED). See Glossary for TSH.
Execution environment constraints
These are the enforced execution environment limits:
| Constraint | Value |
|---|---|
| Max WASM size | 65,535 bytes (0xFFFF) |
| Max hooks per account (chain length) | 10 |
| Max namespaces per account | 256 |
| Max entries removed per namespace-delete | 256 |
| Max parameter key size | 32 bytes |
| Max parameter value size | 256 bytes |
| Max hook state scale | 16 |
| Max hook state value size | 256 × scale (256 min, up to 4096) |
| Max slots | 255 |
| Max nonces | 255 |
| Max emitted transactions | 255 |
| Max hook parameters | 16 |
| Max state modifications (per hook) | 256 |
There is a single 256-entry limit, enforced at two points: the state-write API returns TOO_MANY_STATE_MODIFICATIONS (-44) once a hook execution's own modified-entry count reaches 256, and the finalize step returns tecHOOK_REJECTED if the accumulated changes across the combined hook chains exceed the same 256.
Loops must be guarded. Every loop in a hook must call _g(guard_id, maxiter) at its top. The HookSet validator, with log codes GUARD_IMPORT and GUARD_MISSING, rejects hooks whose loops are not properly guarded. At runtime, exceeding a guard's iteration count returns GUARD_VIOLATION (-16). The guard rules are versioned by amendments: GuardRuleFix20250131 and GuardRuleDepth32, gated by fix20250131 and fixGuardDepth32. See Compiling Hooks for the full validator behavior.
Data available inside a hook
- The originating transaction — read fields with
otxn_field, its type withotxn_type, its hash withotxn_id, and any carriedHookParameterwithotxn_param. See Transaction APIs. - Ledger objects — compute a keylet (
util_keylet, or one of theKEYLET_*constants) and load the object into a slot withslot_set, then drill into subfields withslot_subfield/slot_subarray. See Slot APIs. - Hook state — key/value storage under the hook account and a namespace via
state/state_set(and the foreign variants). See State APIs. - Parameters — install-time parameters via
hook_param; parameters attached to the originating transaction viaotxn_param. - Ledger info — sequence (
ledger_seq), last-close time (ledger_last_time), last hash (ledger_last_hash), base fee (fee_base).
Typical processing flow
The common shape of a hook is:
- Read fields off the originating transaction and validate them.
- Read (and possibly write) hook state.
- Optionally reserve and emit transactions.
- Terminate with
accept()(apply) orrollback()(reject).
A minimal annotated hook
This is the smallest meaningful hook: it inspects nothing and simply accepts.
c
#include "hookapi.h"
// Main entry point. `reserved` is 0 here because this hook only runs strong.
int64_t hook(uint32_t reserved)
{
// Read the account this hook is installed on into a 20-byte buffer.
uint8_t acc[20];
hook_account(SBUF(acc)); // SBUF(x) expands to (x, sizeof(x))
// Emit a debug trace of the account id (as hex) to the node's log.
TRACEHEX(acc);
// Terminate: apply the originating transaction. The second argument is a
// return/exit code recorded in the transaction metadata.
accept(SBUF("done"), 0); // never returns
// Unreachable, but the C compiler wants a return.
return 0;
}SBUF, TRACEHEX, and the other helpers are documented in Helper Macros.
Core concepts
accept()vsrollback()— both terminate the hook immediately and do not return.accept()lets the originating transaction proceed;rollback()rejects it and reverts any state changes the hook made. Internally these map to theExitType::ACCEPTandExitType::ROLLBACKvalues, and to the return sentinelsRC_ACCEPT(-20) /RC_ROLLBACK(-19)._gguard — the required loop guard; see the environment constraints above and Control APIs.trace*debugging —trace,trace_num, andtrace_floatwrite to the node's debug log (subject to build/log configuration). See Trace APIs.- Error convention — API functions return
int64_t. A value>= 0is success (often a length or a computed value); a negative value is an error code from the table below.accept/rollbackare the exceptions: they never return.
Error codes
The error codes a Hook API function can return are the following named values (hook_api::hook_return_code), also available as #defines for use in C hooks.
| Name | Value | Meaning |
|---|---|---|
SUCCESS | 0 | Success (values > 0 may carry a payload such as a length). |
OUT_OF_BOUNDS | -1 | Could not read/write a pointer the hook supplied. |
INTERNAL_ERROR | -2 | Internal error (e.g. a corrupt directory). |
TOO_BIG | -3 | Value you tried to store was too big. |
TOO_SMALL | -4 | Value you tried to store/provide was too small. |
DOESNT_EXIST | -5 | Requested thing was not found. |
NO_FREE_SLOTS | -6 | No free slot (max 255). |
INVALID_ARGUMENT | -7 | Invalid argument. |
ALREADY_SET | -8 | A one-time parameter was already set. |
PREREQUISITE_NOT_MET | -9 | A required parameter was not set first. |
FEE_TOO_LARGE | -10 | Operation would produce an absurd fee. |
EMISSION_FAILURE | -11 | Emitted transaction was not accepted. |
TOO_MANY_NONCES | -12 | Exceeded the nonce limit. |
TOO_MANY_EMITTED_TXN | -13 | Emitted more than reserved. |
NOT_IMPLEMENTED | -14 | API reserved for a future version. |
INVALID_ACCOUNT | -15 | Expected an account id, got something else. |
GUARD_VIOLATION | -16 | A guarded loop/function exceeded its max iterations. |
INVALID_FIELD | -17 | Requested field is sfInvalid. |
PARSE_ERROR | -18 | Asked to parse invalid content. |
RC_ROLLBACK | -19 | Terminate due to a rollback() call. |
RC_ACCEPT | -20 | Terminate due to an accept() call. |
NO_SUCH_KEYLET | -21 | Invalid keylet or keylet type. |
NOT_AN_ARRAY | -22 | Count requested on a non-array. |
NOT_AN_OBJECT | -23 | Subfield requested from a non-object. |
INVALID_FLOAT | -10024 | Sentinel that can never be a valid exponent (note: not -24). |
DIVISION_BY_ZERO | -25 | Division by zero. |
MANTISSA_OVERSIZED | -26 | Mantissa too large. |
MANTISSA_UNDERSIZED | -27 | Mantissa too small. |
EXPONENT_OVERSIZED | -28 | Exponent too large. |
EXPONENT_UNDERSIZED | -29 | Exponent too small. |
XFL_OVERFLOW | -30 | A float operation overflowed. |
NOT_IOU_AMOUNT | -31 | Amount was not an IOU amount. |
NOT_AN_AMOUNT | -32 | Value was not an amount. |
CANT_RETURN_NEGATIVE | -33 | Cannot return a negative value. |
NOT_AUTHORIZED | -34 | Not authorized (e.g. foreign state without a grant). |
PREVIOUS_FAILURE_PREVENTS_RETRY | -35 | A previous failure prevents retry. |
TOO_MANY_PARAMS | -36 | Too many parameters. |
INVALID_TXN | -37 | Invalid transaction. |
RESERVE_INSUFFICIENT | -38 | A new state object would exceed the account reserve. |
COMPLEX_NOT_SUPPORTED | -39 | Complex operation not supported. |
DOES_NOT_MATCH | -40 | Two keylets required to match by type did not. |
INVALID_KEY | -41 | User-supplied key was not valid. |
NOT_A_STRING | -42 | Missing NUL terminator on a string argument. |
MEM_OVERLAP | -43 | Two specified buffers overlap in memory. |
TOO_MANY_STATE_MODIFICATIONS | -44 | Exceeded the 256-entry state-modification limit (see Execution environment constraints above). |
TOO_MANY_NAMESPACES | -45 | Exceeded the namespace limit. |
Note the deliberate gap: the sequence goes -23, then INVALID_FLOAT = -10024, then -25; the value -24 is unused.
Execution results in transaction metadata
Each hook execution is recorded in the transaction's metadata. The engine builds an sfHookExecution object and collects them into an sfHookExecutions array. The fields set on each sfHookExecution object are:
sfHookResult— the exit type (ExitType:ACCEPT,ROLLBACK,WASM_ERROR).sfHookAccount— the account the hook that produced this entry is installed on.sfHookReturnCode— the exit/return code the hook passed toaccept/rollback.sfHookReturnString— the message buffer passed toaccept/rollback.sfHookInstructionCount— instructions executed.sfHookEmitCount— number of transactions this execution emitted.sfHookExecutionIndex— the execution's index in the chain.sfHookStateChangeCount— number of state entries this execution changed.sfHookHash— the hash of theHookDefinition(bytecode) that ran.
SetHook lifecycle summary
Hooks are installed, updated, and removed with the SetHook transaction. The per-hook operation is one of the HookSetOperation values:
| Operation | Value | Meaning |
|---|---|---|
hsoINVALID | -1 | Not a valid operation. |
hsoNOOP | 0 | No operation. |
hsoCREATE | 1 | Create a new hook (upload WASM). |
hsoINSTALL | 2 | Install an existing hook definition by hash. |
hsoDELETE | 3 | Delete the hook. |
hsoNSDELETE | 4 | Delete a namespace. |
hsoUPDATE | 5 | Update hook parameters/fields. |
For the fields that control when an installed hook fires and what it may emit() — HookOn, HookOnIncoming, HookOnOutgoing, HookCanEmit, and the hook-targeting HookName field — see SetHook Fields.
The associated HookSetFlags:
| Flag | Value | Meaning |
|---|---|---|
hsfOVERRIDE | 0x01 | Permit overriding/deleting an existing hook. |
hsfNSDELETE | 0x02 | Permit deleting a namespace. |
hsfCOLLECT | 0x04 | Allow collect calls on this hook. |
Compilation pipeline
Hooks are compiled with hooks-compiler, a single command that runs clang, wasm-opt, and hook-cleaner and emits an install-ready module. Under the hood these are the classic wasmcc, hook-cleaner, and (for WAT-text hooks) wat2wasm tools from wasienv, hook-cleaner-c, and wabt, which can still be run by hand. See Compiling Hooks for a full walkthrough of the toolchain and why a stock build needs cleaning before it validates, and Validating Hooks for checking the result with the hook_validate RPC before you install it.
Related documents
- Home
- Compiling Hooks — the wasmcc/hook-cleaner/wat2wasm toolchain in depth.
- Execution Order — strong/weak/AAW ordering across a transaction's TSHs.
- Hook Fees — the full creation/execution/collect-call/emission fee model in one place.
- SetHook Fields
- Glossary
- Helper Macros (
hook/macro.h) - Hook Best Practices
- Control APIs
- Transaction APIs
- State APIs
- Slot APIs
- Ledger APIs
- Emit and Emitted-Transaction APIs
- Float and Amount APIs
- Utility APIs
- STO APIs
- Trace APIs
- Example: Payment Filter