Summary
Four gaps in the generic contract-call/event layer: no help constructing signature-bearing call arguments, write calls that return nothing about their own effect, events with no correlating metadata, and no bulk-subscribe/resume for event streams.
1. No helper for building signed, structured contract-call arguments
Problem: Some contract calls require structured arguments containing a domain signature - e.g. a call needing a signature over a specific constructed message. w3sper's call-building/encoding layer offers no support for this: constructing the message and producing the signature has to be done entirely by hand, reimplementing key-derivation and signing logic in JS, even though the SDK already ships key-derivation/signing primitives elsewhere for its own transaction signing.
Example:
// what we'd want: the SDK helping construct + sign a schema-typed argument
// const signedArg = await profile.signStructuredArg(schema, fields);
// what we do: hand-roll the entire message construction + signing path
// ourselves, independently of anything the SDK's own tx-signing code does
const message = buildSignatureMessage(/* ...201 bytes, by hand... */);
const signature = signWithBls(profile, message); // our own implementation
Current workaround / suggested fix: We reimplemented the relevant signing logic from scratch, duplicating cryptographic code the SDK already has internally for its own purposes. Exposing that same signing capability as a general-purpose helper - "sign this byte message with this profile's key" - would let consumers building signature-bearing arguments reuse the SDK's implementation instead of independently re-deriving it (and independently maintaining its correctness).
2. Contract write calls return no effect data about what they actually did
Problem: Calls that mutate contract state don't return the resulting on-chain effect (e.g. an amount released) from the submission call itself. To learn what a write actually did, callers must separately pre-subscribe to a correlated contract event before submitting, race it against a timeout, and handle the "nothing arrived in time" case - essentially building an event-correlation layer just to observe your own transaction's outcome. This is distinct from the return-type inconsistency covered in #4072 - here nothing meaningful comes back at all, consistently.
Example:
// submission returns only confirmation that it landed, not its effect:
await executeContractTx(...); // succeeded - but released how much?
// so we pre-arm a correlated event listener before submitting, and race it:
const resultPromise = awaitContractEvent("liquidate", provisionerId, { timeoutBlocks: 10 });
await executeContractTx(...);
const result = await resultPromise; // now we know the amount
Current workaround / suggested fix: We built a dedicated await-and-correlate module for this, including a deliberate no-op .catch() to avoid an unhandled-rejection crash if the event never arrives. Having write calls resolve with a structured result describing their actual on-chain effect - the same data the corresponding event carries - would remove the need to build a parallel event-correlation path just to observe a transaction's own outcome.
3. Contract events carry no correlating block/tx metadata
Problem: Event payloads delivered via contract.events.<name>.on(...) include only the decoded event data - no block height, no transaction hash. Any consumer that needs to know when or in which transaction an event happened has to build its own correlation layer.
Example:
contractInstance.events.liquidate.on((event) => {
// event has the decoded payload only - no blockHeight, no txHash
// we maintain a separate block-tx cache + fallback GraphQL lookup
// purely to attach that metadata ourselves
});
Current workaround / suggested fix: We built a two-tier fallback (first check our own executor-row correlation, then a block-tx cache backed by a GraphQL lookup) just to attach block height and tx hash to each event. Including a basic correlating envelope - block height and tx hash - alongside every emitted event would remove an entire correlation subsystem that every event-driven consumer likely needs to build independently otherwise.
4. No bulk-subscribe and no resume-from-height for contract events
Problem: Subscribing to a contract's events means registering each event type individually (events.deposit.on(...), events.donate.on(...), events.reward.on(...), etc. - one call per event name). On every disconnect, all of these have to be torn down and individually re-registered one at a time. There's no "subscribe to everything on this contract" call, and no cursor/height-based resubscribe - anything emitted during a disconnected window is permanently lost from the live stream unless the consumer has its own out-of-band reconciliation.
Example:
// one call per event type, repeated on every reconnect:
contractInstance.events.deposit.on(handleDeposit);
contractInstance.events.donate.on(handleDonate);
contractInstance.events.reward.on(handleReward);
// ...N more, each separately awaited, each needing its own re-registration logic
Current workaround / suggested fix: We rely on a periodic backfill query against chain history to catch anything missed during a disconnected window, as a reconciliation bandaid rather than a real fix - it papers over gaps after the fact rather than preventing them. A bulk-subscribe API (contract.events.on("*", handler), or a way to subscribe to a named group) and a resume-from-block-height option on reconnect would remove both the per-event-type registration boilerplate and the permanent-loss window.
Summary
Four gaps in the generic contract-call/event layer: no help constructing signature-bearing call arguments, write calls that return nothing about their own effect, events with no correlating metadata, and no bulk-subscribe/resume for event streams.
1. No helper for building signed, structured contract-call arguments
Problem: Some contract calls require structured arguments containing a domain signature - e.g. a call needing a signature over a specific constructed message. w3sper's call-building/encoding layer offers no support for this: constructing the message and producing the signature has to be done entirely by hand, reimplementing key-derivation and signing logic in JS, even though the SDK already ships key-derivation/signing primitives elsewhere for its own transaction signing.
Example:
Current workaround / suggested fix: We reimplemented the relevant signing logic from scratch, duplicating cryptographic code the SDK already has internally for its own purposes. Exposing that same signing capability as a general-purpose helper - "sign this byte message with this profile's key" - would let consumers building signature-bearing arguments reuse the SDK's implementation instead of independently re-deriving it (and independently maintaining its correctness).
2. Contract write calls return no effect data about what they actually did
Problem: Calls that mutate contract state don't return the resulting on-chain effect (e.g. an amount released) from the submission call itself. To learn what a write actually did, callers must separately pre-subscribe to a correlated contract event before submitting, race it against a timeout, and handle the "nothing arrived in time" case - essentially building an event-correlation layer just to observe your own transaction's outcome. This is distinct from the return-type inconsistency covered in #4072 - here nothing meaningful comes back at all, consistently.
Example:
Current workaround / suggested fix: We built a dedicated await-and-correlate module for this, including a deliberate no-op
.catch()to avoid an unhandled-rejection crash if the event never arrives. Having write calls resolve with a structured result describing their actual on-chain effect - the same data the corresponding event carries - would remove the need to build a parallel event-correlation path just to observe a transaction's own outcome.3. Contract events carry no correlating block/tx metadata
Problem: Event payloads delivered via
contract.events.<name>.on(...)include only the decoded event data - no block height, no transaction hash. Any consumer that needs to know when or in which transaction an event happened has to build its own correlation layer.Example:
Current workaround / suggested fix: We built a two-tier fallback (first check our own executor-row correlation, then a block-tx cache backed by a GraphQL lookup) just to attach block height and tx hash to each event. Including a basic correlating envelope - block height and tx hash - alongside every emitted event would remove an entire correlation subsystem that every event-driven consumer likely needs to build independently otherwise.
4. No bulk-subscribe and no resume-from-height for contract events
Problem: Subscribing to a contract's events means registering each event type individually (
events.deposit.on(...),events.donate.on(...),events.reward.on(...), etc. - one call per event name). On every disconnect, all of these have to be torn down and individually re-registered one at a time. There's no "subscribe to everything on this contract" call, and no cursor/height-based resubscribe - anything emitted during a disconnected window is permanently lost from the live stream unless the consumer has its own out-of-band reconciliation.Example:
Current workaround / suggested fix: We rely on a periodic backfill query against chain history to catch anything missed during a disconnected window, as a reconciliation bandaid rather than a real fix - it papers over gaps after the fact rather than preventing them. A bulk-subscribe API (
contract.events.on("*", handler), or a way to subscribe to a named group) and a resume-from-block-height option on reconnect would remove both the per-event-type registration boilerplate and the permanent-loss window.