QuipNetwork/hashsigs-rs · error · Error

{err.message}

Error message

{err.message}

What it means

This is a wasm-binding boundary error: any WasmErr produced inside the Rust library is converted into a JavaScript js_sys::Error whose message text is the underlying err.message. The library throws it so Rust-side failures (parsing, validation, internal errors) surface as native JS Error objects when the wasm API is called from JavaScript. A machine-readable `code` property is also attached to the Error via Reflect::set.

Solutions

  1. Read error.code from the caught Error object to get the machine-readable error code, not just message.
  2. If code is absent, parse the leading `[CODE] ` prefix from error.message (the fallback format used when Reflect::set fails).
  3. Validate inputs in JS before calling the wasm API so WasmErr is never produced.
  4. Check instanceof Error and the library's documented code values to branch on failure kinds.

Example fix

// before
catch (e) {
  console.log(e.message); // loses machine-readable code
}
// after
catch (e) {
  const code = e.code ?? (/^\[(.+?)\] /.exec(e.message)?.[1]);
  console.log(code, e.message);
}
Defensive patterns

Strategy: try-catch

Validate before calling

function assertValidPublicKey(s) {
  if (typeof s !== 'string' || !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(s)) {
    throw new TypeError('invalid publicKey argument');
  }
}

Type guard

function isWasmError(e) {
  return e instanceof Error && (typeof e.code === 'string' || /^\[.+?\] /.test(e.message));
}

Try / catch

try {
  wasmApi(input);
} catch (e) {
  if (isWasmError(e)) {
    const code = e.code ?? /^\[(.+?)\] /.exec(e.message)?.[1];
    handleWasmFailure(code, e.message);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling any wasm-exported API (compiled with the `wasm-bindings` feature) that internally returns a WasmErr; js_error() is invoked and creates the JS Error with the original err.message as its message and err.code as a `code` property.

Common situations: JS/TypeScript callers passing malformed input (bad publicKey, invalid serialized data) into wasm functions; callers reading only error.message and losing the `code` field because they don't know it's a custom property; environments where Reflect::set silently fails and the code is folded into the message as `[CODE] message` instead.

Related errors


AI-assisted analysis of QuipNetwork/hashsigs-rs@128c4ccb5c (2026-09-08). Data as JSON: /api/errors/2aef699df06d774c. Report an issue: GitHub.

Appendix: source

Thrown at src/wasm/mod.rs:1680

        stateful_policy: stateful_policy_name(account.statefulPolicy()),
        next_stateful_leaf_index: account.nextStatefulLeafIndex(),
        recovery_mode: account.recoveryMode(),
    }
}

#[cfg(any(test, feature = "wasm-bindings"))]
fn hex_string(bytes: &[u8]) -> String {
    let mut out = String::from("0x");
    for byte in bytes {
        use core::fmt::Write;
        let _ = write!(out, "{byte:02x}");
    }
    out
}

#[cfg(feature = "wasm-bindings")]
fn js_error(err: WasmErr) -> JsValue {
    let e = js_sys::Error::new(&err.message);
    if js_sys::Reflect::set(&e, &JsValue::from_str("code"), &JsValue::from_str(err.code)).is_err() {
        // Reflect::set failed, so the machine-readable code would be lost off the
        // Error object. Fold it into the message so callers never lose it.
        return js_sys::Error::new(&format!("[{}] {}", err.code, err.message)).into();
    }
    e.into()
}

/// Build a serde-boundary error handler tagged with a STATIC argument label
/// (e.g. `"publicKey"`). The label identifies which argument failed to
/// deserialize without ever echoing the caller-supplied value (inputs include
/// secret seeds). Returns a closure so call sites read
/// `.map_err(js_error_from_serde("publicKey"))`.
#[cfg(feature = "wasm-bindings")]
fn js_error_from_serde(label: &'static str) -> impl Fn(serde_wasm_bindgen::Error) -> JsValue {
    move |_error| {
        js_error(WasmErr {
            code: ERR_INVALID_INPUT,

View on GitHub (pinned to 128c4ccb5c)