Analysis of cdk-wasm release build (cargo build --target wasm32-unknown-unknown --release).
Binary size: ~6.6 MB raw (before gzip/brotli compression).
Tooling used: twiggy for symbol-level profiling.
| Component | Size (KB) | % | Notes |
|---|---|---|---|
| [data/rodata] | 1554.8 | 23.1% | String literals, lookup tables, constants |
| cdk (wallet core) | 944.1 | 14.0% | Wallet sagas, swap, receive, restore, OIDC |
| core/alloc/std | 841.9 | 12.5% | Rust stdlib (formatting, collections, etc.) |
| [function names] | 793.3 | 11.8% | Debug symbol names — stripped by wasm-pack |
| [other] | 597.9 | 8.9% | Mostly secp256k1 C symbols + serde monomorphs |
| serde (all) | 425.9 | 6.3% | JSON/CBOR deserialization monomorphization |
| tracing | 251.7 | 3.7% | Logging framework |
| cashu | 223.9 | 3.3% | Protocol types + their serde impls |
| secp256k1 | 221.8 | 3.3% | Elliptic curve crypto (C code compiled to WASM) |
| wasm-bindgen | 189.6 | 2.8% | JS glue layer |
| cdk_wasm + cdk_common | 191.7 | 2.9% | FFI wrappers + shared types |
| url/idna | 66.3 | 1.0% | URL parsing + international domain names |
| lightning | 50.5 | 0.8% | Invoice parsing |
| bitcoin_hashes | 31.2 | 0.5% | SHA256, SHA512, RIPEMD160 |
| Everything else | ~110 | 1.6% | bip39, cbor, encoding, tokio, etc. |
Biggest wins would come from: reducing cdk code size (14%), trimming serde monomorphization (6.3%), and evaluating if tracing (3.7%) can be compiled out for WASM.
.rodata at 23% is large — this is string literals (error messages, format strings, tracing span names) and lookup tables (unicode normalization, secp256k1 precomputed tables). Hard to reduce without removing features.
[function names] (11.8%) disappears when wasm-pack uses --release with name stripping, so the real production binary is closer to ~5.9 MB raw (before gzip).
secp256k1 + bitcoin_hashes (3.8% combined) is the cost of doing cryptography in WASM — this is the C secp256k1 library compiled to WASM. Can't avoid it.
serde monomorphization (6.3%) is the classic Rust WASM bloat problem — every Deserialize impl for each type generates a separate copy of the JSON parser. miniserde or hand-written parsing would shrink this but at massive effort.
jsonwebtoken/ring is completely gone from the WASM build — zero cost, replaced by the browser's built-in Web Crypto API via SubtleCrypto.
tracing instrumentation behind cfg(not(target_arch = "wasm32")) or use console.log directly. Saves ~250 KB.wasm-opt -Oz on the output. Typically 10-20% reduction..wasm file compresses well (~60-70% reduction).#[serde(deserialize_with)] to share deserializer instances across similar types, or use serde_json::Value as an intermediate.# Install twiggy
cargo install twiggy
# Build release WASM
cargo build --target wasm32-unknown-unknown -p cdk-wasm --release
# Analyze
twiggy top target/wasm32-unknown-unknown/release/cdk_wasm.wasm -n 50
# Dominator tree (what retains what)
twiggy dominators target/wasm32-unknown-unknown/release/cdk_wasm.wasm -d 2 | head -80
# CSV export for scripting
twiggy top target/wasm32-unknown-unknown/release/cdk_wasm.wasm -n 30000 --format csv > twiggy.csv