Skip to content

Validating Hooks ​

hook_validate is an RPC method, added in xahaud release 2026.10.13-release+4119, that runs the SetHook install-time validator against a compiled WASM module you supply, without building, signing, or paying for a transaction. It performs the same two checks that Compiling Hooks describes: the static guard checker (validateGuards) and the WasmEdge load-and-validate smoke test. It also reports the worst-case instruction count the guard checker computed, so you can see how much of the 65,535 budget a hook consumes before you install it.

Use it as the step between "the compiler produced a .wasm" and "submit SetHook": a module that hook_validate reports as valid will not be rejected by the WASM checks in SetHook preflight on a node with the same amendments enabled, and a module it rejects tells you which check failed without burning a transaction.

Where it is available ​

The method is enabled per node by the [hook_validate_rpc] config section:

NodeDefaultEnable with
Standalone (xahaud -a)OnNothing to do; [hook_validate_rpc] 0 turns it off
Networked (mainnet, testnet, your own node)Off[hook_validate_rpc] 1 in xahaud.cfg

When the method is off the node answers with the notSupported error. When it is on it is open to non-admin clients, so it works on a node's public JSON-RPC and WebSocket ports as well as the admin port; the node accounts it as a heavy-burden request for rate limiting.

For local development the simplest setup is a standalone node, where the method is on by default and every amendment can be enabled. See "Results depend on amendments" below for why that matters.

Request ​

The single parameter is code: the compiled and cleaned module as a hex string, exactly the bytes you would put in CreateCode. Upper- and lower-case hex are both accepted. Produce it from a .wasm file with:

sh
xxd -p -c0 hook.wasm

JSON-RPC over HTTP:

sh
curl --json "{\"method\":\"hook_validate\",\"params\":[{\"code\":\"$(xxd -p -c0 hook.wasm)\"}]}" \
  http://localhost:5005

WebSocket, with the usual command form:

json
{ "id": 1, "command": "hook_validate", "code": "0061736D0100..." }

Command line, through the node's own binary (uses the admin port in its config):

sh
xahaud hook_validate $(xxd -p -c0 hook.wasm)

The request is rejected with invalidParams before any validation runs when code is missing, is not a string, is empty, has an odd number of hex digits, is not hex, or decodes to more than 65,535 bytes (the SetHookWASM_TOO_BIG ceiling, applied here as a parameter check).

Response ​

FieldTypeMeaning
validbooleantrue only if the guard checker accepted the module and WasmEdge could load and validate it.
instruction_count_hookstringWorst-case instruction count of hook, as computed by the guard checker. Present only when the guard check passed.
instruction_count_cbakstringThe same for cbak; "0" when the module has no cbak. Present only when the guard check passed.
vm_errorstringWasmEdge's load or validation error. Present only when the guard check passed but the VM rejected the module.
logarray of stringsThe guard checker's log lines, one per entry, plus the message of any exception it threw.

Three details worth knowing when you script against it:

  • A rejected hook is not an RPC error. status is "success" whenever the request was well formed; read valid, not status.
  • The counts are strings, not numbers, so compare them after parsing.
  • The two instruction counts are the figures that the INSTRUCTION_EXCESS check compares against 65,535; instruction_count_hook is the number to watch while you tune guards. See Compiling Hooks: Guard validation at install time.

A valid module ​

Example: Payment Filter from this site, compiled with hooks-compiler and submitted to a standalone node:

json
{
  "result": {
    "instruction_count_cbak": "0",
    "instruction_count_hook": "125",
    "log": [
      "HookSet(50)[]: GuardCheck Total worse-case execution count: 125"
    ],
    "status": "success",
    "valid": true
  }
}

A loop without a guard ​

The same request for a hook whose for loop has no GUARD() call:

json
{
  "result": {
    "log": [
      "HookSet(28)[]: GuardCheck Missing first i32.const after loop instruction codesec: 0 hook byte offset: 112 [0x70]"
    ],
    "status": "success",
    "valid": false
  }
}

No instruction counts are reported, because the guard checker stopped at the first failing check. The byte offset is counted from the start of the module and points at the first instruction inside the loop body, where the guard's i32.const was expected; codesec is the index of the function within the code section.

Over the instruction budget ​

A correctly guarded loop whose declared maxiter is far too large:

json
{
  "result": {
    "log": [
      "HookSet(50)[]: GuardCheck Total worse-case execution count: 2240043",
      "HookSet(51)[]: GuardCheck Maximum possible instructions exceed 65535, please make your hook smaller or check your guards!"
    ],
    "status": "success",
    "valid": false
  }
}

Reading the log ​

Every log line has the shape HookSet(<code>)[]: <message>. The number in parentheses is the validator's log code, the same code that a rejected SetHook transaction records, but in numeric form. The table in Compiling Hooks: Common install-rejection causes lists each code's number next to its name, so a HookSet(28) line can be read as GUARD_MISSING and looked up there. Code 50 (INSTRUCTION_COUNT) is informational and appears in every successful run; it is not a failure.

Results depend on amendments ​

The validator takes two inputs from the node's current open ledger: the Hook API import whitelist, which gains functions as amendments enable them, and the guard-rules version, which decides whether memory.copy/memory.fill are rejected and whether the nesting cap is 16 or 32. A hook that validates on a standalone node with every amendment enabled can still be rejected by a network where the amendment gating one of its imports is not yet active, and the other way round.

So validate against a node whose amendment set matches the network you will deploy to: the target network's own node with [hook_validate_rpc] enabled, or a standalone node configured with the same amendments.

What it does not check ​

hook_validate covers only the WASM module. It does not look at the rest of a SetHook entry (HookOn, HookNamespace, HookApiVersion, parameters, grants), does not check fees or reserves, and does not execute the hook. A module that passes can still produce a rejected SetHook for reasons listed in SetHook Fields, and can still rollback at run time. Test behavior on a standalone node after installing it.