Repository navigation
fix: detect replacements of dropped transactions in waitForTransactionReceipt - #5284
Conversation
…onReceipt` Added a `replacement` parameter that takes the sender, nonce, and any other known fields of the awaited transaction. When the node no longer returns the transaction by hash, the wait looks it up with `eth_getTransactionBySenderAndNonce`, and resolves with the replacement's receipt through `onReplaced`. Nodes without that method fall back to the block scan. Fixes #5242 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhHtSqh3AgWQbtvBB33RF
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 2822885 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eec9e718ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
`waitForTransactionReceipt` now ignores `replacement` when the chain defines `transactionReplacement.getKey` (e.g. Tempo nonce lanes), since a sender and nonce do not identify a nonce slot there. `replacement` takes `from`, `nonce`, `input`, `to`, and `value` only, so chain hooks never receive a partial chain-specific transaction. Tests that inject RPC errors now run against an ephemeral HTTP proxy in front of Anvil, and the type tests import from the package entrypoints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhHtSqh3AgWQbtvBB33RF
|
Resolved: Earlier,
Generated by Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8ed797625d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… for confirmations The wait now keeps the replacement it found, with its receipt and reason, until the receipt has enough confirmations, and calls `onReplaced` before it resolves. With `confirmations` greater than 1, the block scan previously resolved with the replacement's receipt without calling `onReplaced`, and the sender and nonce lookup had to find the replacement again on each block. The hash of `replacement` is lowercased before it is compared with RPC hashes, so an uppercase `hash` no longer reports the transaction as its own replacement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhHtSqh3AgWQbtvBB33RF
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4d5251b06a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhHtSqh3AgWQbtvBB33RF
|
Thanks for taking this over and landing it so quickly, @jxom. Your review was really useful, and it's good to see the confirmations case for the existing block scan fixed along the way. Happy to pick up anything else in |
Fixes #5242. Supersedes #5256 and addresses its review.
Problem
waitForTransactionReceiptdetects replacements only aftergetTransaction({ hash })succeeds once, because the block scan needs the sender and nonce. If a node dropped the transaction before the wait first read it (for example, a cancel mined before the wait starts), every block returns early,onReplacednever fires, and the wait times out.Change
Adds a
replacementparameter:{ from, nonce, input?, to?, value? }.When the hash is not found, the wait looks up the transaction in its nonce slot with
eth_getTransactionBySenderAndNonce:onReplacedonceconfirmationsis met. A pending replacement, or one whose receipt is not indexed yet, is retried on the next block.MethodNotFoundRpcErrororMethodNotSupportedRpcError, the wait falls back to the block scan for the rest of the wait. Other errors scan the current block and retry the lookup on the next one.The hash lookup skips its retries when
replacementis set, because a dropped transaction does not come back. Hashes are compared in lowercase.checkReplacement: falseignoresreplacement. Chains with their own replacement keys (transactionReplacement.getKey, such as Tempo nonce lanes) also ignore it, since a sender and nonce do not identify a nonce slot there.Also fixed: with
confirmationsabove 1, the existing block scan resolved with the replacement's receipt but never calledonReplaced. Both paths now keep the found replacement until it is confirmed. The changeset covers both changes.Review of #5256
onReplaced: a throw rejects the wait on every path.replacementis ignored, so no other lane can match and Tempo's hooks never receive a partial transaction.replacedTransaction: documented.onReplacedreceives thereplacementfields plushash. Withoutinput,to, andvalue, a speed-up reportsreplaced.Addressimport: now fromabitype.Tests
Each test runs on its own Anvil node. Injected RPC errors come from an ephemeral HTTP proxy (
createHttpServer) in front of Anvil. The tests for lagging receipts route between two real Anvil nodes, as the file's existing lagging-node tests do.replacedTransaction)-32000, then a later lookup succeeds-32601,-32004), so the wait falls back to the block scanhash(hash reads from a lagging node)confirmations: 2with only one successful lookupgetReasongetKeyignoresreplacementonReplacedthrowscheckReplacement: falseonReplacedType tests in
waitForTransactionReceipt.test-d.tsimport fromviemandviem/actions.Validation: CI is green. Locally,
waitForTransactionReceipt.test.tspasses 38/38, and the new tests pass repeatedly with--retry=0. Onmain, 10 of the 12 new tests fail, along with the updated confirmations test. The other 2 new tests expect a timeout, so they pass on both. The only uncovered lines in the module were already uncovered onmain.pnpm check:typesandbiome checkpass.Limitation: without
eth_getTransactionBySenderAndNonce, the fallback scan starts at the block the wait starts on, so it misses a replacement mined earlier. This is documented on the parameter.🤖 Generated with Claude Code
https://claude.ai/code/session_016dhHtSqh3AgWQbtvBB33RF