Skip to content

Hook Fees ​

Hooks introduce several cost mechanisms beyond an ordinary Xahau transaction's base fee: a byte-priced surcharge for deploying WASM bytecode, owner reserves for the ledger objects a hook occupies, a precomputed execution charge folded into the triggering transaction's required fee, and a self-priced fee on every transaction a hook emits. Each is documented in full on its own page; this page draws the complete cost model together — what you pay, when, and who pays it — and links out for the mechanics of each field or API.

Deployment costs (SetHook) ​

Creating new bytecode costs 500 drops per byte of sfCreateCode. A SetHook entry that infers hsoCREATE (a non-empty sfCreateCode blob) is charged byteCount * 500 drops as a surcharge on top of the transaction's ordinary base fee, capped rather than wrapped on overflow.

This is a transaction fee, not an owner reserve — it is burned, not refundable. See CreateCode for the full field reference, including a stale in-source code comment that (incorrectly) claims 5000 drops/byte — the executed arithmetic is 500.

An empty sfCreateCode blob infers hsoDELETE instead of hsoCREATE, and the creation fee for zero bytes is zero, so deletion carries no creation surcharge.

hsoINSTALL (install-by-hash) and hsoUPDATE never carry sfCreateCode at all — the byte-priced fee applies to hsoCREATE only. See Operation Matrix for which of the six operations a SetHook entry can infer to.

The sfHookParameters array nested inside a SetHook entry costs 1 drop per byte of name+value, on hsoCREATE/hsoINSTALL/hsoUPDATE alike, whenever the array is present in the submission — charged once at submission regardless of whether the apply-time three-way merge later discards some entries as matching an existing default.

This fee is unconditional (no amendment gate), unlike a separate, transaction-level sfHookParameters fee described under Runtime execution costs below. See HookParameters for the field's merge semantics and the owner-reserve cost of parameters that end up stored on the account's entry.

A SetHook transaction's total required fee is the sum of these surcharges across every entry in sfHooks, plus the transaction's ordinary base fee (which itself already includes any strong-TSH chain fees, see below) — the SetHook transactor folds the per-entry creation and parameter surcharges on top of the generic fee calculation every transaction gets.

The creation surcharge is computed purely from the submittedsfCreateCode field, before the apply-time hash lookup that can fall an hsoCREATE through to hsoINSTALL behavior when the bytecode already matches an existing ltHOOK_DEFINITION (see Common surprises below).

The 65,535-byte size cap on sfCreateCode (checked before operation inference, for any sfHook entry that carries the field) bounds the maximum possible creation surcharge to 65535 * 500 = 32,767,500 drops per entry.

The hook chain slot itself, and any sfHookGrants/sfHookParameters entries stored on the account, are billed separately — as owner reserve, not a transaction fee — covered next.

Ledger reserves vs fees ​

Everything above is a fee: drops paid to submit the transaction, burned regardless of outcome. Reserves are different — XAH locked against an owner count, refunded when the underlying ledger object is removed.

Installing a hook consumes one owner-reserve unit per chain slot, plus one more per stored sfHookGrants or sfHookParameters entry on that account's sfHook object, refunded if the entry is later removed or the hook is deleted from the slot. The reserve charge per occupied slot is 1 + (stored grants) + (stored parameters), 0 for a blank slot, and the delta between old and new totals across all ten slots is applied to the account's owner count when a SetHook transaction commits.

Hook State entries and namespaces carry their own, separate owner reserve with a 256 * sfHookStateScale byte-size ceiling per entry (default scale 1, max scale 16) and a 256-namespace-per-account cap — see api-reference/state/README for the exact reserve-affordability formula and namespace accounting; this page does not duplicate it.

Neither reserve is a fee: an account can hold hooks, parameters, grants, and state indefinitely at no ongoing cost beyond the XAH locked against its reserve requirement.

Runtime execution costs ​

Every hook definition carries a precomputed, flat execution fee, not a per-run meter. At hsoCREATE time, guard validation returns the guard-declared worst-case instruction count for hook() (and separately cbak(), if present); that count, at 1 drop/instruction, is stored as the new ltHOOK_DEFINITION's sfFee/sfHookCallbackFee and reused on every subsequent firing — instruction usage below the worst case is never refunded, and the fee is never recomputed per execution.

A transaction's required base fee sums the stored sfFee of every hook that will fire in its own chain, plus every strong TSH's chain fee, and this is charged to the originating account up front. The network's base-fee calculation walks the originator's own hook chain and every nominated TSH, adding each strong TSH's chain fee into the same total as the originator's own — the originator pays for a strong TSH's hook execution on an account it doesn't control, not just its own.

This is confirmed again at TSH-execution time: a strong TSH's tshFeeDrops is explicitly forced to 0 before deduction, with the in-source comment noting the originator already paid it via the base fee.

A weak TSH (collect call) instead pays its own hook chain's fee out of its own balance, deducted at the moment its collect call runs — never folded into the originating transaction's required fee. The deduction only happens if the account has opted in with lsfTshCollect and can afford the fee plus its current owner reserve out of its own balance; otherwise the chain is silently skipped, with no fee charged and no hooks run.

See TSH: Conditions for Weak TSH execution for the full asfTshCollect/hsfCOLLECT opt-in mechanics this gates on.

A separate, transaction-level sfHookParameters field — distinct from the SetHook-nested one above — costs 1 drop per byte on any transaction type, once the fixXahauV1 amendment is enabled. Any transaction can carry a top-level HookParameters array read at hook runtime via otxn_param, separate from a hook's own install-time defaults read via hook_param; this array's byte cost is billed the same way, but through the generic per-transaction fee path rather than the SetHook-specific one.

fee_base() returns only the current ledger's plain base fee — it does not include hook-chain surcharges and is not the right function for pricing an emitted transaction; use etxn_fee_base for that (next section).

Emitted-transaction fees ​

An emitted transaction pays its own sfFee, deducted from its own sfAccount's balance at normal apply time — and that sfAccount must be the emitting hook's own account, never the original transaction's sender.

etxn_fee_base() computes the minimum sfFee the emitting hook must write into the transaction before calling emit(): it re-parses the serialized candidate transaction and runs it through the same fee calculation the network itself uses to price a transaction (routed through a dedicated invoke path once fixHookAPI20251128 is enabled).

Because the candidate already carries sfEmitDetails (written by etxn_details in the step before), the fee calculation prices it as an emitted transaction: it adds the emitting hook's sfHookCallbackFee (its cbak's precomputed execution fee, if it has one) and multiplies the whole accumulator by sfEmitBurden before adding that callback fee — the same baseFee * burden + hookExecutionFee order used for every transaction.

An emitted transaction can also nominate its own strong TSHs, exactly as any other transaction can, so an emitting hook can end up paying strong-TSH chain fees on the emitted transaction's counterparties too.

Burden multiplies the base fee at every emission hop, geometrically pricing deep or fanned-out emission chains.etxn_burden() returns otxn_burden() * expected_etxn_count — the reserved emission count set by etxn_reserve() — with an overflow check that fails as FEE_TOO_LARGE rather than wrapping.

See etxn_fee_base and api-reference/emit/README for the full emission lifecycle (etxn_reserve → etxn_details → etxn_fee_base → emit) and the generation counter (capped at 10) that caps emission depth independently of burden.

Summary table ​

CostTriggerPayerDocumented at
Creation surcharge, 500 drops/byteSetHook entry infers hsoCREATE (non-empty sfCreateCode)SetHook senderCreateCode
SetHook-nested HookParameters, 1 drop/bytesfHooks[].sfHookParameters present on create/install/updateSetHook senderHookParameters
Hook-slot/grant/parameter owner reserveOccupied chain slot, stored grant, or stored parameterThe hook's own account (refundable)This page, Ledger reserves vs fees
Hook State/namespace owner reserveStored state entry / new namespaceThe state-owning account (refundable)api-reference/state/README
Own-chain execution fee (sfFee, precomputed)Originator's hooks fireOriginating accountTSH
Strong-TSH execution feeA strong TSH's hooks fireOriginating account (folded into base fee)TSH
Weak/collect-call execution feeA weak TSH's hooks fire (opted in)The TSH account itselfTSH
Transaction-level HookParameters, 1 drop/byteAny tx carries top-level sfHookParameters (fixXahauV1)Transaction senderThis page, Runtime execution costs
Emitted-transaction fee, burden-multipliedemit() queues a transactionThe emitting hook's own accountEmit APIs

Common surprises ​

  • A Payment's required fee can be higher than the sender expects if the destination (or another nominated account) is a strong TSH with an expensive hook chain — the sender pays for hook execution on an account they don't control, folded silently into the transaction's minimum fee. See Runtime execution costs above.
  • SetHook creation cost scales linearly with binary size, not with logical complexity: a larger but simpler WASM module can cost more to deploy than a smaller, more instruction-dense one, since the 500 drops/byte surcharge is independent of the separate, guard-derived execution fee.
  • Resubmitting bytecode that already exists on the ledger via hsoCREATE still costs the full creation surcharge, even though it dedups to hsoINSTALL behavior at apply time — install-by-hash (sfHookHash) avoids this entirely. See Deployment costs above.
  • A weak TSH's hooks can silently not run for lack of funds, with no error surfaced to the originating transaction — if the account can't cover its own chain fee plus reserve at collect-call time, the chain is skipped rather than the transaction failing.