Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
47fcf8d
fix(core): settle a timed-out waitForTransaction only once
chybisov Oct 6, 2026
8b8cd61
fix(core): release waitForTransaction observers once settled
chybisov Oct 6, 2026
14a8e13
fix(core): end waitForTransaction on its timeout option
chybisov Oct 6, 2026
575fe05
test(core): pin the once-only settle and the timer cleanup of waitFor…
chybisov Oct 6, 2026
165cd86
fix(core): give each waitForTransaction wait its own timeout
chybisov Oct 6, 2026
120db5a
fix(core): share a waitForTransaction observer only between waits wit…
chybisov Oct 6, 2026
e1a75fd
test(core): guard the once-only flag of a released waitForTransaction…
chybisov Oct 6, 2026
f278607
fix(core): do not report the awaited transaction as its own replacement
chybisov Oct 6, 2026
7b22865
fix(core): settle withRetry when shouldRetry or delay throws
chybisov Oct 6, 2026
18a0e1f
fix(core): count the waitForTransaction block budget only while unmined
chybisov Oct 6, 2026
5ce6273
fix(core): evict the least recently used LruMap key
chybisov Oct 6, 2026
00402b3
fix(core): compare a replacement with the awaited transaction
chybisov Oct 6, 2026
f259e3e
test(core): cover a mempool answer without confirmations
chybisov Oct 6, 2026
48d17b0
fix(core): keep the block budget while the mined height is unknown
chybisov Oct 6, 2026
9b89783
docs(core): document retryCount and the visible effects in the change…
chybisov Oct 6, 2026
d29e011
fix(core): never report the awaited transaction as a replacement
chybisov Oct 6, 2026
c079164
test(core): pin the budget for a mined replacement
chybisov Oct 6, 2026
3799f4a
fix(core): parse the awaited transaction only for a found replacement
chybisov Oct 6, 2026
6d51caa
fix(core): match the awaited txid in any case
chybisov Oct 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/wait-for-transaction-leaks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@bigmi/core': patch
---

Keep the shared block watcher alive when a `waitForTransaction` times out. When a wait reached its `retryCount` timeout and the same block callback then found the transaction confirmed or replaced, or failed to fetch the block, its internal `done` ran a second time. That second pass ran the watcher's cleanup while another wait on the same client still listened, so that wait and every later wait on the client never settled. It could also settle a newer wait on the same txId with the stale error of the first one. `done` now runs at most once, and an observer's `unwatch` is a no-op once its own listener is gone.

Release the observers of a settled `waitForTransaction`. A wait that joined another wait on the same txId, such as a resumed run, stayed in the module-level `listenersCache` with its callbacks after both settled, and a third wait on that txId joined it and never settled. Every finished wait also left an empty `listenersCache` key behind, one per txId. Each wait now removes its own listener when it settles. When the last listener leaves, `observe` drops the key and its `cleanupCache` entry and runs the cleanup, which removes the observer from the shared block watcher. A wait now joins another wait on the same txId only when both use the same `confirmations`, `pollingInterval`, `retryCount`, numeric `retryDelay` and `senderAddress` (a `retryDelay` function is not compared), so it no longer runs with the options of the wait it joined, such as resolving at 1 confirmation when it asked for 3.

Make the `timeout` option of `waitForTransaction` end the wait. The timer only rejected the promise: it was never cleared and the block watcher kept polling, so a wait whose `getblockcount` never succeeded polled until the page or process ended. The timeout now rejects and removes only its own wait, so another wait on the same txId with a longer timeout or none keeps waiting, and the shared block watcher stops once no wait listens to it. Every way a wait settles clears its timer.
11 changes: 11 additions & 0 deletions .changeset/wait-for-transaction-viem-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@bigmi/core': patch
---

Stop `waitForTransaction` from reporting the awaited transaction as its own replacement. When `getrawtransaction` still reported the transaction unconfirmed but `getblock` already listed it, the replacement search matched the transaction itself, because it spends the same inputs, and called `onReplaced` with it. A replacement that `getrawtransaction` reported with no confirmations also resolved the wait at once, and a replacement that needed more confirmations resolved it in a later block without `onReplaced`. The search now skips the awaited txid, a replacement without confirmations keeps the wait polling, and `onReplaced` fires once, when the replacement has enough confirmations. It reports the awaited transaction as `replacedTransaction` and finds the reason against it, also when the replacement replaced an earlier replacement that left the chain.

Settle `withRetry` when `shouldRetry` or the `delay` function throws. The throw rejected an internal attempt that nothing handled, so the returned promise stayed pending and the error surfaced only as an unhandled rejection. `withRetry` now rejects with that error. A `retryDelay` function of `waitForTransaction` that threw hung the wait in the same way; the wait now rejects with that error.

Wait for every confirmation of a mined transaction in `waitForTransaction`. The `retryCount` block budget also counted the blocks after the transaction was mined, so a wait for more confirmations than the budget had left rejected with `WaitForTransactionReceiptTimeoutError` while the transaction was confirming: with the default `retryCount` of 10, a wait for 6 confirmations rejected if the transaction was mined 6 or more blocks after the wait started. The budget now counts only the blocks in which the transaction, or the replacement it tracks, is not mined or the height of its block is unknown. An unmined transaction still rejects on the same block as before.

Evict the least recently used key from the internal `LruMap` that holds the in-flight requests of request deduplication. A key that was set again kept its old place, a read moved a key to the newest place only when its value was not `undefined`, an empty-string key was never evicted, and the eviction read the keys through the subclass, which viem reports can give a stale iterator on iOS 18 JavaScriptCore. A key that is set again or read now becomes the newest, the oldest key is evicted also when it is an empty string, and the eviction reads the keys of the base `Map`. This matters only when more than 8192 deduplicated requests are in flight at once.
Loading
Loading