tools/sync-kernel.mjs rewrites the wasm loader so it resolves the binary relative to the module rather than the page:
const loader = (await readFile(new URL('secp-wasm.js', WASM), 'utf8'))
.replace(/loadSecpWasm\(url = [^)]*\)/, "loadSecpWasm(url = new URL('./secp256k1.wasm', import.meta.url).href)");
[^)]* stops at the first ). The replacement text itself contains one, inside new URL(...). So the pattern matches its own output, and re-running the tool against an already-transformed loader corrupts it:
input : export async function loadSecpWasm(url = new URL('./secp256k1.wasm', import.meta.url).href) {
output: export async function loadSecpWasm(url = new URL('./secp256k1.wasm', import.meta.url).href).href) {
CORRUPTED: true
The result is a syntax error — the generated packages/kernel/wasm/secp-wasm.js would fail to parse, taking the whole package with it.
Why it bites in practice
WASM_DIR defaults to ../../bitcoin-kernel.github.io/engine/wasm/, a sibling checkout that is not present in a plain clone of this repo. The natural workaround when regenerating — point WASM_DIR at the existing packages/kernel/wasm/, which is right there and contains exactly the expected filenames — is precisely the case that corrupts the output. The tool gives no error; it writes a broken file and prints its usual success line.
This is currently latent because the current packages/kernel/wasm/secp-wasm.js is correct. It becomes real the moment someone regenerates without the sibling checkout, which is the likely path for anyone bumping the pin.
Found while preparing #7, which is why that PR copies the codec files verbatim instead of re-running the tool.
Fix
Make the rewrite idempotent — skip it when the loader is already in the target form:
const TARGET = "loadSecpWasm(url = new URL('./secp256k1.wasm', import.meta.url).href)";
let loader = await readFile(new URL('secp-wasm.js', WASM), 'utf8');
if (!loader.includes(TARGET)) {
loader = loader.replace(/loadSecpWasm\(url = [^)]*\)/, TARGET);
}
Worth also failing loudly if neither the source nor the target form is found, rather than silently emitting an unrewritten loader that resolves the wasm against the page URL.
tools/sync-kernel.mjsrewrites the wasm loader so it resolves the binary relative to the module rather than the page:[^)]*stops at the first). The replacement text itself contains one, insidenew URL(...). So the pattern matches its own output, and re-running the tool against an already-transformed loader corrupts it:The result is a syntax error — the generated
packages/kernel/wasm/secp-wasm.jswould fail to parse, taking the whole package with it.Why it bites in practice
WASM_DIRdefaults to../../bitcoin-kernel.github.io/engine/wasm/, a sibling checkout that is not present in a plain clone of this repo. The natural workaround when regenerating — pointWASM_DIRat the existingpackages/kernel/wasm/, which is right there and contains exactly the expected filenames — is precisely the case that corrupts the output. The tool gives no error; it writes a broken file and prints its usual success line.This is currently latent because the current
packages/kernel/wasm/secp-wasm.jsis correct. It becomes real the moment someone regenerates without the sibling checkout, which is the likely path for anyone bumping the pin.Found while preparing #7, which is why that PR copies the codec files verbatim instead of re-running the tool.
Fix
Make the rewrite idempotent — skip it when the loader is already in the target form:
Worth also failing loudly if neither the source nor the target form is found, rather than silently emitting an unrewritten loader that resolves the wasm against the page URL.