Summary
A set of capabilities that aren't bugs so much as gaps — things every non-trivial consumer of a chain SDK eventually needs, that we ended up building ourselves rather than getting from the SDK. Grouped here because they share the same shape: each one, if it existed, would let us delete a chunk of hand-rolled infrastructure.
1. No fee/gas-price estimation tied to the transaction builder
Problem: There's no Gas.estimate() or builder-integrated fee suggestion. The only path to a live gas price is a generic, untyped RPC passthrough (the node's /on/blocks/gas-price endpoint, exposed as network.blocks.call["gas-price"]()), disconnected from the transaction being built.
Example:
// what exists: a raw RPC call, unrelated to the tx you're about to build
const price = await network.blocks.call["gas-price"]();
// what we built around it: caching + timeout + fallback, by hand
let gasPriceCache = { data: null, ts: 0 };
try {
const fresh = await withTimeout(network.blocks.call["gas-price"](), 3000);
gasPriceCache = { data: fresh, ts: Date.now() };
} catch {
if (gasPriceCache.data) return gasPriceCache.data;
return { average: "1", max: "1", median: "1", min: "1" }; // safe fallback
}
Current workaround / suggested fix: Both consumer codebases independently built a caching layer (TTL, timeout, hardcoded fallback) around the raw RPC call. A first-class Gas.estimate(tx) — returning a suggested price/limit for the specific transaction being built, with the SDK owning caching/staleness — would remove the need for every consumer to reinvent this.
2. No dry-run/simulate-before-submit
Problem: There's no way to validate a transaction (correct nonce, sufficient gas, valid call arguments) without actually broadcasting it. The only feedback loop is: build, submit, wait for once.executed() or block inclusion, and find out then whether it was valid.
Current workaround / suggested fix: We don't have a workaround beyond over-validating on our end before submission and treating failed submissions as an expected, handled case in our automation. A simulate(tx)/dryRun(tx) API — running the same validation the node would apply, without committing anything — would let a caller (especially an automated signer submitting many transactions) catch a bad transaction before it costs a real submission and a wait for failure.
3. No built-in reconnect/backoff
Problem: Network.connect() establishes one connection with no retry/backoff of its own — a dropped connection is entirely the consumer's problem to notice and recover from. (Reliably detecting that a connection has gone bad is a separate, deeper problem — see #4070. This item is narrower: even with good detection, there's no SDK-provided retry/backoff primitive to reconnect with.
Current workaround / suggested fix: We built our own exponential-backoff-with-jitter reconnect loop. A built-in connect({ retry: true })-style option (or a documented reconnect helper) would cover the common case without every consumer re-deriving backoff-with-jitter from scratch.
4. No multi-node/multi-endpoint connection pooling
Problem: A Network instance maps to exactly one node/URL — there's no pool abstraction for spreading load or reads/writes across multiple RUES endpoints. #4070 point 2 — a hazard specific to holding multiple instances open at once, which pooling support would also need to account for.)
Current workaround / suggested fix: We built a dedicated connection-pool module that manages one Network per candidate URL, with its own health tracking and selection logic layered on top of individually-managed instances. A NetworkPool-style construct — accepting a list of endpoints and handling selection/health/failover internally — is a natural SDK-level feature rather than something every multi-node-aware consumer should re-derive.
5. No official testing/mocking utilities
Problem: The SDK ships no mock network client or test harness of its own. Consumers who want to unit-test code that calls into w3sper have to hand-author test doubles that approximate the real interface, with no upstream guarantee they stay accurate as the SDK evolves.
Current workaround / suggested fix: We maintain hand-rolled mocks for the network client and wallet (including a hardcoded fake mnemonic for test fixtures) across our test suite. This is a real drift risk — our own test setup even has a code comment acknowledging that "real network" test mode falls back to the mock as a safety measure, meaning some of our tests never actually exercise real SDK behavior at all. An officially maintained MockNetwork/test-double package (even a thin one, versioned alongside the real client) would let consumer test suites track the actual interface instead of a best-guess reimplementation of it.
6. No batch/multi-transaction submission, and no observability hooks
Problem (batching): There's no atomic/batched submission primitive — submitting several transactions means serializing them one at a time and managing nonces by hand (compounded by the nonce +1 auto-increment behavior covered in #4072, which means every manual submission also has to pre-decrement).
Problem (observability): The SDK exposes no metrics, tracing, or structured-logging hooks — its only internal instrumentation is a handful of plain console.log/console.warn calls inside SDK internals (e.g. in the address/account syncers), which a consumer can neither suppress, redirect, nor correlate with their own structured logs.
Current workaround / suggested fix: For batching, we serialize submissions ourselves with manual nonce management, with no atomicity guarantee across the set. For observability, we built an entire logging taxonomy at the consumer layer (dozens of hand-defined log domains) purely to get structured, correlatable logs — while the SDK's own stray console.log calls still bypass that system entirely and show up unstructured in our output. A batched-submission API (even just "submit N transactions with correctly-sequenced nonces, roll back/report which succeeded") would remove the manual nonce bookkeeping; and either removing the internal console.* calls in favor of a pluggable logger/event-emitter, or at minimum making them silenceable, would let consumers own their own log output completely.
Summary
A set of capabilities that aren't bugs so much as gaps — things every non-trivial consumer of a chain SDK eventually needs, that we ended up building ourselves rather than getting from the SDK. Grouped here because they share the same shape: each one, if it existed, would let us delete a chunk of hand-rolled infrastructure.
1. No fee/gas-price estimation tied to the transaction builder
Problem: There's no
Gas.estimate()or builder-integrated fee suggestion. The only path to a live gas price is a generic, untyped RPC passthrough (the node's/on/blocks/gas-priceendpoint, exposed asnetwork.blocks.call["gas-price"]()), disconnected from the transaction being built.Example:
Current workaround / suggested fix: Both consumer codebases independently built a caching layer (TTL, timeout, hardcoded fallback) around the raw RPC call. A first-class
Gas.estimate(tx)— returning a suggested price/limit for the specific transaction being built, with the SDK owning caching/staleness — would remove the need for every consumer to reinvent this.2. No dry-run/simulate-before-submit
Problem: There's no way to validate a transaction (correct nonce, sufficient gas, valid call arguments) without actually broadcasting it. The only feedback loop is: build, submit, wait for
once.executed()or block inclusion, and find out then whether it was valid.Current workaround / suggested fix: We don't have a workaround beyond over-validating on our end before submission and treating failed submissions as an expected, handled case in our automation. A
simulate(tx)/dryRun(tx)API — running the same validation the node would apply, without committing anything — would let a caller (especially an automated signer submitting many transactions) catch a bad transaction before it costs a real submission and a wait for failure.3. No built-in reconnect/backoff
Problem:
Network.connect()establishes one connection with no retry/backoff of its own — a dropped connection is entirely the consumer's problem to notice and recover from. (Reliably detecting that a connection has gone bad is a separate, deeper problem — see #4070. This item is narrower: even with good detection, there's no SDK-provided retry/backoff primitive to reconnect with.Current workaround / suggested fix: We built our own exponential-backoff-with-jitter reconnect loop. A built-in
connect({ retry: true })-style option (or a documented reconnect helper) would cover the common case without every consumer re-deriving backoff-with-jitter from scratch.4. No multi-node/multi-endpoint connection pooling
Problem: A
Networkinstance maps to exactly one node/URL — there's no pool abstraction for spreading load or reads/writes across multiple RUES endpoints. #4070 point 2 — a hazard specific to holding multiple instances open at once, which pooling support would also need to account for.)Current workaround / suggested fix: We built a dedicated connection-pool module that manages one
Networkper candidate URL, with its own health tracking and selection logic layered on top of individually-managed instances. ANetworkPool-style construct — accepting a list of endpoints and handling selection/health/failover internally — is a natural SDK-level feature rather than something every multi-node-aware consumer should re-derive.5. No official testing/mocking utilities
Problem: The SDK ships no mock network client or test harness of its own. Consumers who want to unit-test code that calls into w3sper have to hand-author test doubles that approximate the real interface, with no upstream guarantee they stay accurate as the SDK evolves.
Current workaround / suggested fix: We maintain hand-rolled mocks for the network client and wallet (including a hardcoded fake mnemonic for test fixtures) across our test suite. This is a real drift risk — our own test setup even has a code comment acknowledging that "real network" test mode falls back to the mock as a safety measure, meaning some of our tests never actually exercise real SDK behavior at all. An officially maintained
MockNetwork/test-double package (even a thin one, versioned alongside the real client) would let consumer test suites track the actual interface instead of a best-guess reimplementation of it.6. No batch/multi-transaction submission, and no observability hooks
Problem (batching): There's no atomic/batched submission primitive — submitting several transactions means serializing them one at a time and managing nonces by hand (compounded by the nonce
+1auto-increment behavior covered in #4072, which means every manual submission also has to pre-decrement).Problem (observability): The SDK exposes no metrics, tracing, or structured-logging hooks — its only internal instrumentation is a handful of plain
console.log/console.warncalls inside SDK internals (e.g. in the address/account syncers), which a consumer can neither suppress, redirect, nor correlate with their own structured logs.Current workaround / suggested fix: For batching, we serialize submissions ourselves with manual nonce management, with no atomicity guarantee across the set. For observability, we built an entire logging taxonomy at the consumer layer (dozens of hand-defined log domains) purely to get structured, correlatable logs — while the SDK's own stray
console.logcalls still bypass that system entirely and show up unstructured in our output. A batched-submission API (even just "submit N transactions with correctly-sequenced nonces, roll back/report which succeeded") would remove the manual nonce bookkeeping; and either removing the internalconsole.*calls in favor of a pluggable logger/event-emitter, or at minimum making them silenceable, would let consumers own their own log output completely.