v0.1.5
What's changed
Two wallet fixes reported from downstream use, plus a round of signing and transport performance work.
Fixed
- A viem
WalletClientover a local account lost the raw-digest fast path.createWalletClient({ account: privateKeyToAccount(key), … })— what wagmi and viem hand around when the key is in process — always satisfies the JSON-RPC guard, so it was adapted as a remote wallet and every L1 action went through generic typed-data encoding. Measured downstream at 2.17× on every order. The JSON-RPC adapter now sources its raw-digest signer from the embedded account; typed data, address and chain ID still go through the client, sosignatureChainIdis unchanged. createFastLocalWallethard-failed where dynamic import is unavailable. Its docstring promised thetiny-secp256k1fallback was "never a hard failure", but the fallback doesawait import("viem/accounts"), which throws under Jest without--experimental-vm-modulesand some React Native bundlers. viem is not a dependency of this package, so the import must stay dynamic — callers in those hosts now passoptions.privateKeyToAccount, used on both viem-dependent paths. When viem is genuinely unreachable the error names the cause and the remedy instead of surfacing an opaque host message.createL1ActionHashvalidatesvaultAddressagain. It is publicly exported and was taking the address with no runtime check — a0x${string}type constrains neither charset nor length at runtime. A malformed address silently hashed to different bytes than the caller described, and a 32-byte address hashed identically to its 20-byte truncation.
Faster
User-signed actions no longer pay viem's generic hashTypedData. Typehash plans compile once per types identity and domain separators once per chainId. Digests are byte-identical to viem's across all 17 types × 5 chain IDs. 2.5 µs vs 56 µs per digest; multisig_user_signed_3_signers −22%.
The order path releases its lock before signing. The per-wallet nonce lock now covers only nonce issuance and a dispatch-chain claim; signing happens outside it, so concurrent callers on one wallet sign simultaneously. Wire order is preserved by a per-(wallet × network) dispatch chain instead of by the lock, so the server still sees strictly increasing nonces.
With a remote wallet, concurrent approveAgent:
| latency | eth_accounts |
eth_chainId |
signTypedData |
|
|---|---|---|---|---|
| N=1 | 116 ms | 1 | 1 | 1 |
| N=8 | 116 ms | 1 | 1 | 8 |
| N=20 | 116 ms | 1 | 1 | 20 |
Latency is now constant in N rather than linear, bounded by the data-dependency floor. Because signing moved out of the lock, the redundant per-action eth_chainId and multi-sig leader-address round trips also collapse — their dedupe caches can finally fire.
Websocket and HTTP. Dispatcher timeouts moved to the shared TimeoutWheel (ws_request_round_trip −10%). Routing stops allocating per frame: toLowerCase() is gated behind an uppercase check and routed event-type strings are interned per channel+key — webData3_frame_dispatch_e2e −38%, l2book_dispatch_50_coins −28%.
Notes
Ordering under the new dispatch chain is covered by _dispatchOrder tests: signatures completing in reverse order, jittered latency, and signing failures at the first, middle and last position — a burned nonce must leave a gap the server tolerates without stalling later requests or letting them overtake an earlier nonce still being signed.
perf:gate passes at 56 scenarios compared, 0 regressed.