Skip to content

State APIs ​

This page documents the state Hook APIs: the functions that read and write a hook's persistent key/value storage, both on the hook's own account and — with an explicit grant — on other accounts.

All function signatures on this page match the Hook API.

Return codes reference the shared error table in Glossary, and the values quoted below match the Hook API's error definitions.

The implementation uses WASM-facing wrappers and a state cache.


What Hook State is ​

Hook State is a persistent key/value store attached to an account. Each stored entry is addressed by a triple:

  • Account — whose ledger the entry lives on. For state/state_set this is always the account the hook is installed on (hook_account). For the _foreign variants you name the account explicitly.
  • Namespace — a 32-byte (uint256) tag that partitions an account's state. Each hook runs with a current namespace (sfHookNamespace). state/state_set and the _foreign variants with a zero-length namespace use that current namespace; otherwise you pass a 32-byte namespace.
  • Key — a 32-byte key. Keys shorter than 32 bytes are accepted and zero-padded on the left to 32 bytes: the key "key" is stored as 29 zero bytes followed by the three ASCII bytes. A key must be 1..32 bytes; 0 bytes returns TOO_SMALL and more than 32 returns TOO_BIG.

Each entry is a HookState ledger object (ltHOOK_STATE) identified by its account, key, and namespace.

Value size ​

A state value may be up to the scaled state-data limit.

  • The base limit is 256 bytes.
  • An account may raise it with sfHookStateScale (1..16); the limit is 256 * scale, i.e. up to 4096 bytes at scale 16. Accounts without sfHookStateScale use scale 1 (256 bytes).

Writing more than the limit returns TOO_BIG. The scale is read from the account object at write time.

State entries cost reserve ​

Creating a new state entry consumes owner reserve. The first time an account is touched in a hook execution, the implementation computes how many reserve increments the account can still afford:

availableForReserves = (balance - accountReserve(ownerCount)) / feeIncrement

Each new entry consumes scale reserve positions. If the account cannot afford the next entry the write fails with RESERVE_INSUFFICIENT (-38). Overwriting an existing entry does not consume additional reserve. Deleting an entry (see state_set below) frees it.

Namespaces ​

An account may hold at most 256 namespaces.

Creating a state entry in a brand-new namespace counts against this limit and returns TOO_MANY_NAMESPACES (-45) if exceeded. The new-namespace accounting is gated behind the fixXahauV1 amendment.

Two distinct "too many modifications" limits ​

There are two separate limits on how much state a single transaction may change, and they are enforced at different points:

  1. Per-execution cache limit — 256. A write is rejected with TOO_MANY_STATE_MODIFICATIONS (-44) once 256 entries have been modified within the current hook's state cache.
  2. Combined-chain write limit. When state is finally flushed to the ledger, modified entries are counted across the combined hook chains, and the transaction returns tecHOOK_REJECTED if the count exceeds the 256-entry limit. The TOO_MANY_STATE_MODIFICATIONS (-44) error is raised by this same 256-entry limit. Treat 256 as the operative figure for both mechanisms above.

Index ​

FunctionPurpose
stateRead state under the hook account's current namespace.
state_setWrite (or delete) state under the hook account's current namespace.
state_foreignRead state from a specified account and namespace.
state_foreign_setWrite state on another account's namespace (requires a grant).