Appearance
CreateCode
Purpose: carries the raw WASM bytecode for a new hook (hsoCREATE), or — when present but empty — signals deletion of the hook at that chain position (hsoDELETE). It is the only field whose emptiness, not its absence, changes which operation is inferred.
Field declaration: VL (variable-length blob), code 11.
It is soeOPTIONAL on sfHook; soeREQUIRED on ltHOOK_DEFINITION — every definition that exists on the ledger stores the code that produced it, keyed by its own hash.
Operation inference — presence vs. emptiness. Both sfHookHash and sfCreateCode present is invalid; sfHookHash alone infers hsoINSTALL; sfCreateCode alone infers hsoCREATE if the blob is non-empty, otherwise hsoDELETE.
Confirmed by direct unit tests: a non-empty blob ({1}) infers hsoCREATE, an explicitly-set-but-empty blob (ripple::Blob{}) infers hsoDELETE.
Omitting sfCreateCode entirely is not the same as an empty one — with neither sfCreateCode nor sfHookHash present, inference falls through to the NOOP/UPDATE/NSDELETE branch instead (see Operation Matrix).
Size limit — checked in preflight, before operation-specific validation. maxHookWasmSize() returns 0xFFFFU (65,535 bytes). The check runs unconditionally whenever sfCreateCode is present, for every sfHook entry in the transaction: a blob larger than the max is rejected as temMALFORMED ("SetHook operation would create blob larger than max").
This runs before operation inference/entry validation, so an oversized blob is rejected the same way whether it would have been a create or (in the pathological case of a >64 KiB blob that somehow reads as non-empty) any other operation.
Verified by a direct test ("If CreateCode is present, then it must be less than 64kib", long_wasm, temMALFORMED).
hsoCREATE validation runs only after the other create-specific field checks pass (namespace present, API version present and 0, HookOn rules, optional HookName — see HookNamespace, HookApiVersion, HookOn):
- Guard validation. Statically analyzes every loop in the module for the
_g()guard call the Hook API requires (see thehook-dev/hook-apiguides for the guard system itself). This is a generic, non-rippled-specific routine that also returns, on success, an estimated maximum instruction count forhook()and forcbak()— these feed into the fee calculation that sets the new definition'ssfFee/sfHookCallbackFee. - WasmEdge smoke test. Independently of guard analysis, loads the module into a real WasmEdge VM and validates it — catching malformed WASM that passes the guard scan but isn't actually loadable/instantiable.
Both checks must pass; either failure makes the whole SetHook transaction temMALFORMED at preflight, or (if re-run at apply time, see below) tecINTERNAL.
Fee — 500 drops per byte, not owner-reserved. Unlike hook-chain slots and parameters/grants (owner-reserved, 1 unit each), sfCreateCode's cost is billed as a transaction fee surcharge: 500 drops multiplied by the blob's byte count, with an overflow guard that caps the result at the maximum representable value rather than wrapping.
Note on a nearby comment: the code that assembles the final owner-reserve accounting contains a comment claiming sfCreateCode and sfHookParameters are billed "5000 drops per byte" — this does not match the actual arithmetic. computeCreationFee charges 500 drops/byte for sfCreateCode (above), and the top-level submitted sfHookParameters array is charged 1 drop per name+value byte total despite its own comment saying it is "billed at the same rate as code bytes" — it manifestly is not. Treat the executable code (hook::computeCreationFee) as authoritative over either comment.
hsoCREATE deduplicates by hash, falling through to hsoINSTALL. The submitted bytes are hashed and checked against whether a ltHOOK_DEFINITION with that hash already exists — on the ledger or freshly inserted earlier in the same transaction's loop. If so, no new definition is created; the case falls through into hsoINSTALL — see HookHash for the full reference-count lifecycle this implies. Only when the hash is genuinely new is entry validation re-run (this time with ledger access, though the function remains context-free) and the new ltHOOK_DEFINITION SLE constructed with sfCreateCode set verbatim to the submitted bytes.
Empty sfCreateCode — hsoDELETE. Only sfCreateCode (empty) and sfFlags may be present; sfFlags is required and must include hsfOVERRIDE (OVERRIDE_MISSING otherwise); allowed flag bits are limited to hsfOVERRIDE | hsfNSDELETE | hsfCOLLECT (FLAGS_INVALID otherwise). At apply time this places a blank sfHook object at that chain position and decrements (and potentially destroys) the old hash's ltHOOK_DEFINITION — see HookHash for the verified reference-count example.
Verified example — WASM containing memory.fill/memory.copy. These instructions are rejected as malformed (temMALFORMED) only once fix20250131 is enabled; before the fix they pass guard/smoke validation and tesSUCCESS. This shows guard validation rules are themselves amendment-gated and can change what sfCreateCode accepts across network upgrades.
Common mistakes:
- Treating "omitted" and "empty" as equivalent. Omitting
sfCreateCodeentirely (with nosfHookHasheither) infers NOOP or UPDATE/NSDELETE depending on other fields; an explicit empty blob always infershsoDELETE. - Assuming a >64 KiB blob is rejected only for
hsoCREATE. The size check in preflight fires for anysfHookentry carryingsfCreateCode, before the operation is even inferred. - Assuming guard validation and the WasmEdge smoke test are the same check — they are independent and both must pass; guard validation is a static analysis for the
_g()loop-guard convention, the smoke test is a real VM load/validate pass. - Repeating the "5000 drops per byte" figure from the nearby code comment — the actual rate, per
hook::computeCreationFee, is 500 drops/byte for code and roughly 1 drop/byte for parameters (see above).
Related documents
- Hook Fees — the full creation/execution/collect-call/emission fee model this creation-fee figure fits into.
- HookHash — install semantics and the full
ltHOOK_DEFINITIONreference-counting lifecycle thathsoCREATEfalls into on a hash collision. - HookNamespace, HookApiVersion, HookOn — the other fields
hsoCREATErequires alongsidesfCreateCode. - Flags —
hsfOVERRIDE/hsfNSDELETE/hsfCOLLECTand their per-operation legality, including the delete-flag requirement above. - Operation Matrix — full per-operation field matrix.