Appearance
hooks-compiler
hooks-compiler compiles a C hook to an install-ready WASM module with a single command, and produces the same bytes every time for the same source. Clang, the linker, wasm-opt, and hook-cleaner all run as WebAssembly inside Node, so there is nothing to install beyond Node itself: no wasienv, no system clang, no hook-cleaner build. The same package also runs in a browser Web Worker, which is how it is used by web IDEs; this page covers the local command-line use.
Requirements
- Node 20.12 or newer. The toolchain (about 28 MB) ships inside the package's dependencies, so the first
npxrun downloads it once. - The Hook API headers. The compiler does not bundle
hookapi.h; point it at a directory that holds the headers your hook includes. The canonical set is xahaud'shook/genesis/headersdirectory (hookapi.h,macro.h,sfcodes.h,extern.h,error.h,types.h), which is what the compiler's own determinism tests compile against.
Build a hook
sh
npx hooks-compiler build hook.c -I include -o hook.wasm -Ozhook.cis the entry file. More.c/.hfiles may follow it;-I <dir>(repeatable) adds every file underdir, keyed by its path relative todir, so-I includemakesinclude/hookapi.hreachable as#include "hookapi.h".-O0to-O3,-Os,-Ozselect the clang optimization level. The default is-O2;-Ozgives the smallest module, which matters because creation fees are charged per byte (see Hook Fees).-onames the output (default<entry>.wasm).--watalso writes<out>.wat, the final module as WebAssembly Text, for the inspection steps in Developing Hooks in Languages Other Than C: Inspecting your module before install.--jsonprints a machine-readable summary to stdout instead of the one-line report:output,sha256,rawSize,optimizedSize,finalSize,imports,exports(andwatwith--wat).
A successful build of the payment-filter example from this site reports:
json
{
"output": "pf.wasm",
"rawSize": 1187,
"optimizedSize": 899,
"finalSize": 772,
"sha256": "dd07912276c519dcdd18e6fb7fdd06b99fa9c856ac1ef1979b7799dc06ae33c4",
"imports": ["env._g", "env.otxn_type", "env.accept", "env.otxn_field", "env.rollback", "env.otxn_slot", "env.slot_subfield", "env.slot_float", "env.float_set", "env.float_compare"],
"exports": ["hook"]
}imports and exports are worth a glance on every build: every import must be env.<hook api name> and the exports must be hook and, if present, cbak, exactly as Compiling Hooks requires. finalSize is the byte count that SetHook sizes against the 65,535-byte ceiling and that the creation fee is charged on.
check runs clang in syntax-only mode and prints its diagnostics without producing a module; it exits non-zero if any line contains error::
sh
npx hooks-compiler check hook.c -I includeWhat the pipeline does
hook.c → clang -c → wasm-ld → wasm-opt -Oz (MVP only) → hook-cleaner → hook.wasmClang 22 compiles for wasm32 with -mcpu=mvp -nostdlib -nostartfiles; wasm-ld links with --export=hook --export-if-defined=cbak and --allow-undefined, which is what turns your Hook API calls into env imports; binaryen's wasm-opt shrinks the module while restricted to the WebAssembly MVP feature set, so no post-MVP opcode such as memory.copy can be introduced; and hook-cleaner strips the custom sections and extra exports that would otherwise fail install. The output is already the "clean" module described under Compiling Hooks: The underlying tools; there is no separate cleaning step to run.
Two compiler behaviors to know about:
- Calling a function that has no prototype is an error, not a warning (
-Werror=implicit-function-declaration). A typo in a Hook API name, or a missing header, fails the build instead of producing an import the validator would reject later. Other warnings are suppressed. - A hook that never calls
_gfails at thehook-cleanerstep withGuard function _g was not imported / missing.That is the same requirement the on-ledger validator enforces asGUARD_IMPORT, caught before you reach a node.
Deterministic output
Compiling the same source files with the same options produces byte-identical output, so the sha256 the compiler reports identifies the build. The project pins the exact versions of clang, binaryen, and hook-cleaner it runs, and its own test suite builds a set of real hooks on several platforms and compares the hashes against committed values.
This is what makes a hook's source auditable: anyone with the source, the header set, the compiler version, and the optimization level can rebuild the module and compare it with the one on the ledger. Note that the ledger's HookHash is the SHA-512Half of the module bytes, not the SHA-256 the compiler prints, so compare the bytes themselves or compute the ledger hash from the output:
sh
openssl dgst -sha512 hook.wasm | awk '{ print toupper(substr($2, 1, 64)) }'Record the three inputs together with the hash when you publish a hook: source, header set, and the hooks-compiler version with its -O flag.
Next steps
- Run the module through
hook_validateon a node whose amendments match your target network. The compiler checks C and module shape; the node's guard checker is what decides whetherSetHookwill accept the module and how much of the instruction budget it uses. - Hex-encode
hook.wasminto CreateCode and install it withSetHook.