Skip to content

fix: detect replacements of dropped transactions in waitForTransactionReceipt - #5284

Merged
jxom merged 5 commits into
mainfrom
jxom/zealous-bardeen-tkjx94
Oct 24, 2026
Merged

jxom merged 5 commits into
mainfrom
jxom/zealous-bardeen-tkjx94

Conversation

@jxom

@jxom jxom commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Fixes #5242. Supersedes #5256 and addresses its review.

Problem

waitForTransactionReceipt detects replacements only after getTransaction({ 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, onReplaced never fires, and the wait times out.

Change

Adds a replacement parameter: { from, nonce, input?, to?, value? }.

const receipt = await client.waitForTransactionReceipt({
  hash,
  onReplaced: ({ reason }) => console.log(reason), // 'cancelled'
  replacement: { from: account.address, nonce },
})

When the hash is not found, the wait looks up the transaction in its nonce slot with eth_getTransactionBySenderAndNonce:

  • Original found: the wait uses it and keeps polling for its receipt.
  • Replacement found: the wait keeps the replacement and its receipt, and resolves through onReplaced once confirmations is met. A pending replacement, or one whose receipt is not indexed yet, is retried on the next block.
  • Lookup failure: on MethodNotFoundRpcError or MethodNotSupportedRpcError, 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 replacement is set, because a dropped transaction does not come back. Hashes are compared in lowercase. checkReplacement: false ignores replacement. 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 confirmations above 1, the existing block scan resolved with the replacement's receipt but never called onReplaced. Both paths now keep the found replacement until it is confirmed. The changeset covers both changes.

Review of #5256

  1. Transient lookup errors: only the two "method not supported" errors disable the lookup.
  2. Throwing onReplaced: a throw rejects the wait on every path.
  3. Nonce lanes: the lookup and the scan run only on chains that key replacements by nonce. On Tempo, replacement is ignored, so no other lane can match and Tempo's hooks never receive a partial transaction.
  4. Partial replacedTransaction: documented. onReplaced receives the replacement fields plus hash. Without input, to, and value, a speed-up reports replaced.
  5. Tests: all new branches are covered (see below), with no call-tracking flags.
  6. Address import: now from abitype.

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.

  • replacement mined before the wait (reason and replacedTransaction)
  • first lookup fails with -32000, then a later lookup succeeds
  • lookup unsupported (-32601, -32004), so the wait falls back to the block scan
  • replacement receipt not indexed yet
  • original still holds its nonce slot, with an uppercase hash (hash reads from a lagging node)
  • confirmations: 2 with only one successful lookup
  • chain getReason
  • a chain with its own getKey ignores replacement
  • onReplaced throws
  • the replacement's receipt read fails
  • checkReplacement: false
  • the existing "waits for confirmations (replaced)" test now asserts onReplaced

Type tests in waitForTransactionReceipt.test-d.ts import from viem and viem/actions.

Validation: CI is green. Locally, waitForTransactionReceipt.test.ts passes 38/38, and the new tests pass repeatedly with --retry=0. On main, 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 on main. pnpm check:types and biome check pass.

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

…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
@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
viem Ready Ready Preview Oct 24, 2026 5:08am UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2822885

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
viem Patch

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-24T05:02:27.369085Z 2822885 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/viem@5284

commit: 2822885

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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

jxom commented Oct 24, 2026 •

Copy link
Copy Markdown
Member Author

Resolved: main now includes #5287, which raises these limits by 1 kB, and 4d5251b merges it into this branch. Locally, every size-limit entry passes: import * from 'viem' (esm) is 87.46 kB of 88 kB, and viem/ens is 53.18 kB of 54 kB.

Earlier, Verify / Build (its size-limit step) and Size failed on budgets that main already exceeded:

Entry Old limit main This PR
import * from 'viem' (esm) 87 kB 87.27 kB 87.50 kB
import * from 'viem/ens' 53 kB 53.17 kB 53.15 kB
import { getEnsAvatar } from 'viem/ens' 28 kB 28.53 kB 28.50 kB

Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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
@jxom
jxom merged commit b3e3947 into main Oct 24, 2026
40 checks passed
@jxom
jxom deleted the jxom/zealous-bardeen-tkjx94 branch October 24, 2026 08:59
@github-actions github-actions Bot mentioned this pull request Oct 24, 2026
@PrabalParihar

Copy link
Copy Markdown

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 waitForTransactionReceipt or the wallet actions if you want an extra pair of hands.

This branch was successfully deployed

1 active deployment
Preview — 28228855 Deployed Oct 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

waitForTransactionReceipt never detects a replacement when the original is already gone from the pool on its first getTransaction

3 participants