Two requests from building a browser client against @bytesbrains/maktub-sdk@0.1.0-dev.4. The first shapes how everything else gets consumed, so it's worth deciding before the second.
1. Packaging: a browser consumer cannot import the crypto without the Veil/WASM barrel
The package is CommonJS-only — main: dist/index.js, with no module field, no exports map, and no "type": "module". The barrel entry pulls everything unconditionally:
dist/index.js:122 require("./veil/veil.js")
dist/veil/veil.js:62 require("./wasm/warden_wasm.js")
So
import { deriveReadingKeyFromPrfOutput } from "@bytesbrains/maktub-sdk";
evaluates the Veil module and its WASM glue as a side effect of importing one HKDF function. Rollup/esbuild can't reliably tree-shake CommonJS, so it ends up in the bundle.
Why this matters more than bundle size
A browser client that derives reading keys wants the tightest possible CSP on that origin, because the derived key is long-lived and its published half is write-once — one injected script that reads it decrypts everything that user has ever received, unrotatably. script-src 'self' with no 'unsafe-eval' and no 'wasm-unsafe-eval' is achievable for a static bundle.
WebAssembly instantiation requires wasm-unsafe-eval. So if the WASM actually instantiates on import, a consumer either weakens the policy protecting the key-derivation origin, or can't use the SDK's crypto at all.
I've confirmed the glue is required at module-evaluation time; whether the .wasm binary itself instantiates eagerly or lazily depends on warden_wasm.js and I haven't measured that. Either way the structural problem stands: there is currently no way to ask for the crypto alone.
Request
An ESM build alongside CJS, and an exports subpath map, so consumers can import narrowly:
@bytesbrains/maktub-sdk/crypto would then give derivation and ECIES with no Veil, no WASM, and no CSP implications. A "sideEffects": false flag would help bundlers further.
If a full dual build is too much for now, even an ESM build with the Veil re-export moved off the root barrel would solve the immediate problem.
2. Four pieces of smart-wallet client logic that don't exist in the SDK yet
Also missing, and each is a place where two language ports must agree byte-for-byte or a user lands on the wrong wallet / the chain rejects the signature:
| Piece |
Consequence of divergence |
| Counterfactual address derivation — CREATE2 over the ERC-1167 creation code with the implementation spliced in |
A wrong address is an account that cannot be found, and a reading key published against it is stranded |
| P-256 public-key recovery from an ECDSA signature (SEC 1 §4.1.6) |
Used to identify which credential signed an assertion; a mismatch in the candidate set breaks credential-based account discovery |
WebAuthn codecs — DER signature parse, attestation-object CBOR parse, clientDataJSON type/challenge byte offsets |
Feeds all of the above, plus the on-chain signature payload |
PackedUserOperation encoding + the ERC-4337 v0.7 EntryPoint hash |
Byte-exact or the operation is rejected |
These already exist in the Dart client and are pinned there by ground-truth vectors. Porting them independently into each new client is exactly the divergence risk that vectors/reading-key.json exists to prevent for the reading key — and the same argument that made shipping that fixture inside the package worthwhile.
Housing them here, with cross-language vectors alongside the existing ones, means every port is checked against one artifact rather than trusted to reimplement four byte-exact constructions correctly.
Happy to contribute these if the direction is agreeable — in which case the packaging question above decides what the module boundaries should look like, so it's worth settling first.
Two requests from building a browser client against
@bytesbrains/maktub-sdk@0.1.0-dev.4. The first shapes how everything else gets consumed, so it's worth deciding before the second.1. Packaging: a browser consumer cannot import the crypto without the Veil/WASM barrel
The package is CommonJS-only —
main: dist/index.js, with nomodulefield, noexportsmap, and no"type": "module". The barrel entry pulls everything unconditionally:So
evaluates the Veil module and its WASM glue as a side effect of importing one HKDF function. Rollup/esbuild can't reliably tree-shake CommonJS, so it ends up in the bundle.
Why this matters more than bundle size
A browser client that derives reading keys wants the tightest possible CSP on that origin, because the derived key is long-lived and its published half is write-once — one injected script that reads it decrypts everything that user has ever received, unrotatably.
script-src 'self'with no'unsafe-eval'and no'wasm-unsafe-eval'is achievable for a static bundle.WebAssembly instantiation requires
wasm-unsafe-eval. So if the WASM actually instantiates on import, a consumer either weakens the policy protecting the key-derivation origin, or can't use the SDK's crypto at all.I've confirmed the glue is required at module-evaluation time; whether the
.wasmbinary itself instantiates eagerly or lazily depends onwarden_wasm.jsand I haven't measured that. Either way the structural problem stands: there is currently no way to ask for the crypto alone.Request
An ESM build alongside CJS, and an
exportssubpath map, so consumers can import narrowly:{ "type": "module", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" }, "./crypto": { "types": "./dist/crypto/index.d.ts", "import": "./dist/crypto/index.js", "require": "./dist/crypto/index.cjs" }, "./veil": { "types": "./dist/veil/veil.d.ts", "import": "./dist/veil/veil.js", "require": "./dist/veil/veil.cjs" } } }@bytesbrains/maktub-sdk/cryptowould then give derivation and ECIES with no Veil, no WASM, and no CSP implications. A"sideEffects": falseflag would help bundlers further.If a full dual build is too much for now, even an ESM build with the Veil re-export moved off the root barrel would solve the immediate problem.
2. Four pieces of smart-wallet client logic that don't exist in the SDK yet
Also missing, and each is a place where two language ports must agree byte-for-byte or a user lands on the wrong wallet / the chain rejects the signature:
clientDataJSONtype/challenge byte offsetsPackedUserOperationencoding + the ERC-4337 v0.7 EntryPoint hashThese already exist in the Dart client and are pinned there by ground-truth vectors. Porting them independently into each new client is exactly the divergence risk that
vectors/reading-key.jsonexists to prevent for the reading key — and the same argument that made shipping that fixture inside the package worthwhile.Housing them here, with cross-language vectors alongside the existing ones, means every port is checked against one artifact rather than trusted to reimplement four byte-exact constructions correctly.
Happy to contribute these if the direction is agreeable — in which case the packaging question above decides what the module boundaries should look like, so it's worth settling first.