Summary
Three transaction-lifecycle issues that each cost us real debugging time because the SDK's behavior didn't match what its naming/shape implied.
1. .nonce(N) silently becomes N + 1 on-chain
Problem: .nonce(N) on a transaction builder sets a base nonce; .build() adds 1 internally. To land a transaction with an intended on-chain nonce of N, callers must call .nonce(N - 1n). Nothing about the method name suggests this offset.
Example:
// intent: land with on-chain nonce N
tx.nonce(N); // actually lands as N + 1
tx.nonce(N - 1n); // this is what actually lands as N
We built transactions expecting .nonce(N) to mean on-chain nonce N; they landed at N + 1, only caught via cross-checking against externally-tracked expected nonces.
Current workaround / suggested fix: We call .nonce(N - 1n) everywhere we want on-chain nonce N, with a regression test pinned to this exact offset so an SDK update (or a future "fix") doesn't silently reintroduce the mismatch. Ideally .nonce(N) would mean "the nonce that ends up on-chain" (absorbing the +1 internally), or at minimum the docstring/type would state explicitly that the input is a base value and the actual on-chain nonce is input + 1.
2. gasPaid on the confirmation event is a fee, not a unit count
Problem: gasPaid on the live once.executed() event is already the lux fee paid (gas price × units consumed), not a raw unit count — despite reading, next to fields like gasLimit, as if it should be unit-denominated.
Example:
// assumed gasPaid was a unit count, like gasLimit:
const fee = event.gasPaid * gasPrice; // doubles the actual fee — gasPaid is already the fee
Current workaround / suggested fix: We stopped multiplying and treat gasPaid as the final lux fee directly, with a comment at the call site recording what it actually represents. Renaming the field (e.g. feePaid/luxPaid, reserving gasPaid/gasUsed for a unit count) — or documenting its unit explicitly in the event schema — would prevent this being re-introduced by the next person who reads the name at face value.
3. execute() confirmation is locked to the node it was broadcast to
Problem: Broadcasting (execute()) and confirming (once.executed()) appear coupled to the same RUES session/node. If that node goes unresponsive right after accepting the broadcast, there's no supported way to confirm via a different, healthy connection — undermining redundancy for apps that maintain multiple node connections specifically for that purpose.
Example: We route submission through a pool of connections to route around flaky per-node TCP connect behavior. When the node used for one execute() call degraded right after accepting the broadcast, confirmation had no path through any of our other healthy connections.
Current workaround / suggested fix: We track which node a transaction was broadcast to, and if that connection degrades post-broadcast, fall back to polling transaction status via general chain-state queries on a different connection instead of the once.executed() subscription. If confirmation events are inherently sourced from the originating node's session, exposing either (a) a way to attach a confirmation subscription for a given tx ID from a different Network instance, or (b) a first-class polling-based "confirm by tx ID against any node" API, would remove the need for consumers to build this fallback against internal chain-state APIs themselves.
Summary
Three transaction-lifecycle issues that each cost us real debugging time because the SDK's behavior didn't match what its naming/shape implied.
1.
.nonce(N)silently becomesN + 1on-chainProblem:
.nonce(N)on a transaction builder sets a base nonce;.build()adds 1 internally. To land a transaction with an intended on-chain nonce ofN, callers must call.nonce(N - 1n). Nothing about the method name suggests this offset.Example:
We built transactions expecting
.nonce(N)to mean on-chain nonceN; they landed atN + 1, only caught via cross-checking against externally-tracked expected nonces.Current workaround / suggested fix: We call
.nonce(N - 1n)everywhere we want on-chain nonceN, with a regression test pinned to this exact offset so an SDK update (or a future "fix") doesn't silently reintroduce the mismatch. Ideally.nonce(N)would mean "the nonce that ends up on-chain" (absorbing the +1 internally), or at minimum the docstring/type would state explicitly that the input is a base value and the actual on-chain nonce isinput + 1.2.
gasPaidon the confirmation event is a fee, not a unit countProblem:
gasPaidon the liveonce.executed()event is already the lux fee paid (gas price × units consumed), not a raw unit count — despite reading, next to fields likegasLimit, as if it should be unit-denominated.Example:
Current workaround / suggested fix: We stopped multiplying and treat
gasPaidas the final lux fee directly, with a comment at the call site recording what it actually represents. Renaming the field (e.g.feePaid/luxPaid, reservinggasPaid/gasUsedfor a unit count) — or documenting its unit explicitly in the event schema — would prevent this being re-introduced by the next person who reads the name at face value.3.
execute()confirmation is locked to the node it was broadcast toProblem: Broadcasting (
execute()) and confirming (once.executed()) appear coupled to the same RUES session/node. If that node goes unresponsive right after accepting the broadcast, there's no supported way to confirm via a different, healthy connection — undermining redundancy for apps that maintain multiple node connections specifically for that purpose.Example: We route submission through a pool of connections to route around flaky per-node TCP connect behavior. When the node used for one
execute()call degraded right after accepting the broadcast, confirmation had no path through any of our other healthy connections.Current workaround / suggested fix: We track which node a transaction was broadcast to, and if that connection degrades post-broadcast, fall back to polling transaction status via general chain-state queries on a different connection instead of the
once.executed()subscription. If confirmation events are inherently sourced from the originating node's session, exposing either (a) a way to attach a confirmation subscription for a given tx ID from a differentNetworkinstance, or (b) a first-class polling-based "confirm by tx ID against any node" API, would remove the need for consumers to build this fallback against internal chain-state APIs themselves.