Skip to content

fix: reset nonceManager after failed sends - #4634

Merged
jxom merged 1 commit into
mainfrom
jxom/fix-nonce-manager-reset
May 18, 2026
Merged

jxom merged 1 commit into
mainfrom
jxom/fix-nonce-manager-reset

Conversation

@jxom

@jxom jxom commented May 18, 2026 •

Copy link
Copy Markdown
Member

nonceManager.reset() now clears the consumed nonce cache while preserving the internal cleanup path used by get(). Failed local-account sendTransaction and sendTransactionSync calls reset nonce manager state after submission errors before acceptance, so the next transaction re-reads the pending nonce instead of skipping ahead.

sendTransactionSync keeps the consumed nonce for reverted receipts, since those transactions were accepted and mined. Closes #4364.

Validated with pnpm exec tsc --project ./tsconfig.build.json --noEmit, pnpm exec biome check --write ..., git diff --check, and mocked sendTransaction/sendTransactionSync failure repros. pnpm test src/utils/nonceManager.test.ts is blocked locally by Anvil returning 400 on anvil_setBalance during suite setup.

@vercel

vercel Bot commented May 18, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
viem Ready Ready Preview, Comment May 18, 2026 2:36am

Request Review

@changeset-bot

changeset-bot Bot commented May 18, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f0af207

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

@pkg-pr-new

pkg-pr-new Bot commented May 18, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

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

commit: f0af207

@jxom
jxom force-pushed the jxom/fix-nonce-manager-reset branch from 8cde015 to f0af207 Compare May 18, 2026 02:22
@jxom
jxom merged commit e7b71e5 into main May 18, 2026
32 of 33 checks passed
@jxom
jxom deleted the jxom/fix-nonce-manager-reset branch May 18, 2026 02:25
@github-actions github-actions Bot mentioned this pull request May 18, 2026
@tmm tmm mentioned this pull request May 26, 2026
aelmanaa added a commit to aelmanaa/hardhat-kms that referenced this pull request Oct 3, 2026
…nces (#199)

Closes #186

## What

A `connection.kms.getAccount` account signs transactions that viem fills
and sends itself. Those sends bypassed the plugin's send lock and nonce
high-water mark, so a concurrent send through the plugin from the same
address could take the same nonce. This PR keeps the two paths on
distinct nonces without rewriting any RPC answer:

- **A viem `nonceManager` on the account.** In viem, `toAccount` copies
`account.nonceManager`. `prepareTransactionRequest` calls `consume({
address, chainId, client })` once per send without a nonce.
`sendTransaction` calls `reset({ address, chainId })` when the send
fails. `consume` takes the account's send lock `chainId:address` and
picks the nonce the way a plugin send would. It reads the pending count
through the connection (the read passes through untouched), then goes
past the high-water mark and past reserved nonces.
- **Client through the connection** (transport `custom`): the send keeps
the lock until its `eth_sendRawTransaction` goes out or viem resets it.
A plugin send also keeps the lock from its fill to its broadcast.
- **Client with its own transport** (`http`, `webSocket`, `fallback`):
the lock is released at once, and the nonce is reserved. Plugin sends
skip the reserved nonce without waiting. The reservation ends at
`reset`, when a raw transaction with that nonce reaches the node, when a
plugin send with the caller's nonce at or above it goes out, or after 60
s. Expiry is checked against the clock on each use, so there is no
timer. A warning, once per process, says the broadcast is not ordered.
- **`eth_sendRawTransaction`** and **`eth_sendRawTransactionSync`**
(EIP-7966, sent by viem's `sendTransactionSync` and `writeContractSync`)
from a KMS account of the connection are decoded with micro-eth-signer,
which is already a dependency, and the sender is recovered.
- If a library send holds the lock with that nonce, the transaction goes
out as that holder. Otherwise it takes the lock around its one `next`.
  - The answer comes back unchanged.
- When the node has the transaction (accepted, a receipt, mined with an
error, "already known", or the sync method's timeout code 4, which means
it is in the pool), the nonce raises the mark and ends its reservation.
- If there is no answer, or an "I don't know" from a gateway, the
transaction becomes the sender's uncertain transaction, and the next
plugin send asks the node about it first.
  - Re-entrant sends fail at once.

## Changes from the first version, after review

- The `eth_getTransactionCount` / `eth_fillTransaction` interception and
the read leases are removed. Architecture rule 1 is restored: the only
added inspected method is `eth_sendRawTransaction`, and its answer is
unchanged. Pending and latest reads come back as the node gave them.
- No send waits on a timer in the normal flow. The CLI test below
reproduced exit code 13 with the first version and passes with this one.
- A reverted estimate makes viem call `reset`, which releases the hold
at once. Plain reads do nothing. Explicit-nonce plugin sends clear
reservations. A stream of reads cannot starve anything.

## Changes after the second review

- **`eth_sendRawTransactionSync`** takes the same branch as
`eth_sendRawTransaction`. Before, viem's `sendTransactionSync` and
`writeContractSync` consumed through the nonce manager and broadcast
with the sync method, so the hold never ended (the next sends waited 60
s) and the nonce was not recorded. A receipt, or EIP-7966's timeout
(code 4, in the pool), counts as the node having it; other errors and
uncertain outcomes behave as for the async method.
- **A reset no longer ends a hold that belongs to another consume.**
Per-consume ownership keyed by `client.uid` is not possible: `consume`
gets the client, but viem passes `reset` only `{ address, chainId }`
(checked in 2.55.13, 2.56.0 and 2.57.1), so a reset cannot be matched to
its consume, and routing by transport kind has the same gap.
`resetLibraryNonce` now decides in order: an owed reset is used up; with
no live reservation, the reset ends the hold; with a hold and
reservations both open, reservations below the node's pending count are
dropped first (their sends are past their broadcast), and a reservation
that is left takes the reset while the hold stays. A wrong guess now
costs at most the held send's 60 s, never a shared nonce. The reviewer's
repro (X=0 reserved from an `http` client, Y=1 held from a `custom`
client, X's reset) is a unit test: Y keeps the lock, the plugin send
goes after Y, and the nonces are distinct.
- **Late events after a 60 s expiry.** A held send's raw transaction now
ends only the hold it matched when it arrived (the captured hold object,
not the map entry), so a late raw transaction cannot end the next hold.
A late `reset` still can, since it carries no identity; this is
documented.
- **`consume` settles an uncertain transaction first**, as a plugin send
does, before it chooses the nonce.
- **A raw transaction on a connection that never looked up its KMS
addresses** still ends a library send's hold when one is active: the
sender is recovered when any library hold exists, and the transaction
counts only if a hold exists for that sender on that chain. No KMS call.
A `custom` transport over another provider still leaves a 60 s hold that
keeps the process alive; documented.
- Docs: decision 0014's bullet rewritten, "never share a nonce" now
"within the cases in Sending", the EDR error quote corrected, the sync
actions and mixed transports documented, and the changeset tells npm
users with an exact viem pin below 2.55.13 to bump it.

## Changes after the third review

Two decisions from the third review: a plugin send keeps waiting behind
a library send that holds the lock, and the viem floor is 2.55.13.

- **(a) Floor 2.55.13.** The viem peer range in
`packages/hardhat-kms/package.json` is `^2.55.13`. The changesets,
`transactions.md`, `library-accounts.md`, decision 0014, the
`core.account.viem-too-old` entry in the errors reference and both
READMEs say 2.55.13, and describe the releases below it as "2.49.3 and
earlier" and "2.50.3 to 2.55.11". `test:sdk-floors` installs viem
2.55.13.
- **(b) Runtime version check.** pnpm and Yarn install a viem below the
peer range with only a warning. `getAccount` now reads the version from
viem's `package.json` and refuses one below 2.55.13 with
`core.account.viem-too-old` ("connection.kms.getAccount needs viem
2.55.13 or later, and the project has viem 2.55.11. Upgrade viem to
2.55.13 or later"), before the key lookup and any KMS call. Pre-releases
order as semver (`2.55.13-canary.0` is refused; `3.0.0-next.12` passes).
A version it cannot read passes. The guard stays in `getAccount`, so
sign-only use on old viem is refused too. The version comes from
`viem/package.json`, which viem exports. `new BaseError("").version`
would also work (a bare `X.Y.Z` in every release from 2.47.6 to 2.57.2),
but constructing an error there fails the repo's error-catalogue check.
The check reads the viem that hardhat-kms resolves; in a workspace where
the client's code loads a different, nested viem, it can pass for a
client below the floor. The docs call it best effort.
- **(c) EDR and viem error text.** `transactions.md` and
`library-accounts.md` quote EDR 0.22.0's own refusal ("Nonce too high.
Expected nonce to be 0 but got 1. Note that transactions can't be queued
when automining.") and say that viem's `NonceTooHighError` wraps it: a
viem client prints "Nonce provided for the transaction is higher than
the next one expected." with EDR's text after `Details:`. EDR queues a
gap only with `mining.auto: false`.
- **(d) Own-transport gap.** A client with `http(url)` gets a reserved
nonce, and the plugin skips it. On an automining node, a plugin send
that reaches the node first is refused. `library-accounts.md` documents
this and the two fixes: send through `custom(connection.provider)`, or
wait for the library send's receipt. A new integration test pins it, and
the once-per-process own-transport warning now says an automining node
can refuse the next plugin send.
- **(e) Warnings.** When sends wait 5 s (`LIBRARY_WAIT_WARNING_MS`)
behind a library send that holds the account's lock, a warning prints,
at most once per hold and at most once per waiting send, with the
checksummed account, the chain and the held nonce. When the 60 s limit
ends a hold, a warning says the raw transaction did not reach the plugin
(as with a `custom` transport over another provider), that if it is
broadcast later it and the next send share a nonce and the node refuses
one, and to send through `custom(connection.provider)`. Both link
`library-accounts.md#warnings`. A wait behind a plugin send, a wait
under 5 s, and a hold that ends by broadcast, `reset` or close print
nothing. The test "warns and ends a hold before a send waiting behind it
could stall" pins the order 5 s < 60 s < 120 s (`SEND_LOCK_STALL_MS`).
- **(f) README.** Both READMEs explain why the floor is above
hardhat-viem's `^2.47.6`: with viem 2.49.3 and earlier a failed library
send never resets, so the next plugin send waits up to 60 s; with 2.50.3
to 2.55.11 a stray `reset` can end another send's hold.

## Changes after the fourth review

- Version ranges read "2.49.3 and earlier" and "2.50.3 to 2.55.11"
everywhere; `transactions.md` says 2.50.0 to 2.50.2 and 2.55.12 were
never published to npm and cites wevm/viem#4634.
- `core.account.viem-too-old`'s cause states what each range does to the
user, and that npm refuses only by default (`--legacy-peer-deps`
installs anyway). `core.account.viem-missing` now says "Install viem in
the project", with npm, pnpm and Yarn commands in the fix line.
- The 5 s warning prints at most once per hold (`waitWarned` on the
hold) and at most once per waiting send, so a send that waits behind two
holds in a row prints one. Both warnings print checksummed addresses,
say what to do, and link `library-accounts.md#warnings`. The
own-transport warning links the section on clients with their own
transport.
- `holdForLibrary`'s `end` uses one guard (the hold is still the current
one), so a second call is a no-op and an old hold's `end` cannot remove
a newer one; a test covers it.
- New unit test: with reservations 5 (raw transaction refused) and 6 (in
flight), a `reset` ends 5 first. Emptying `failReservation` now fails
it.
- `releaseReservationsBelow` and `releaseReservationsUpTo` lost a
redundant branch and an optional chain.
- `sdk-floors.yml` also runs on changes to `src/internal/rpc/**`,
`test/floors/**`, `test/helpers/**` and the raw-send and
library-send-cli tests. `testing.md` and `tooling.md` describe the viem
floor run and the 2.55.11 refusal run; `tooling.md` lists `viem/` in
Stryker's scope.
- `library-accounts.md` and `transactions.md` say the version check
reads the viem that hardhat-kms resolves, so a nested older copy in a
workspace can pass; it is best effort.

## Changes after the fifth review

- `sdk-floors.yml` also runs on changes to
`src/internal/hook-handlers/network.ts` and
`src/internal/crypto/address.ts`, which the floor tests run.
- `testing.md` describes everything `send-guard.test.ts` covers: lock
limits, reservations, library holds, both warnings and the order test.
- The docs, TSDoc, changeset and this body say the 5 s warning prints at
most once per hold and at most once per waiting send. The code is
unchanged.
- The Warnings section of `library-accounts.md` also lists the
once-per-process own-transport warning, with its text.

## Where this departs from design (a) as specified

**A plugin send waits for a library send that holds the lock through the
connection.** Design (a) said a plugin send must never wait on a
reservation, and would skip the reserved nonce instead. I built that
first. On `edr-simulated`, all four concurrency tests failed. EDR
refuses a nonce gap while automining ("Nonce too high. Expected nonce to
be 0 but got 1. Note that transactions can't be queued when
automining.", which viem wraps as "Nonce provided for the transaction is
higher than the next one expected"), so a plugin send that skips ahead
of a library send that has not yet broadcast is refused.

The hold makes the library send behave like any other send of the
account: it holds the lock from its nonce to its broadcast. The wait is
bounded by that send's own work (gas estimate, KMS signature,
broadcast), as with two plugin sends:
- A reverted estimate releases the hold at once through `reset`.
- The hold is never ended by a read.
- Its 60 s safety limit (`LIBRARY_HOLD_MS`) uses a timer that keeps the
process alive. It is cleared on every normal path, and it only matters
when a raw transaction never reaches the plugin and viem never resets
the send, for example with a `custom` transport over another provider.

Design (a)'s skip-without-waiting behaviour is kept for clients with
their own transport, where no broadcast ever reaches the plugin.

**`reset` names no nonce and no client.** With holds, only one library
send of an account holds the lock at a time. Two cases are counted per
lock key: a `consume` that failed, and a held send whose broadcast
failed. Each makes viem call `reset` later, and that call uses up one
owed reset instead of ending the next send's hold. When a hold and
own-transport reservations are both open, a reservation takes the reset
(see above). Among reservations, `reset` ends the newest failed one,
else the newest unsigned one, else the newest.

**The viem peer floor goes from `^2.47.6` to `^2.55.13`.** Checked in
the published packages:
- 2.49.3 and earlier never call `reset`.
- 2.50.3 to 2.55.11 call it
([wevm/viem#4634](wevm/viem#4634)), but can call
it without a `consume`, when the send fails before the nonce is chosen.
- From 2.55.13, `nonceManagerParameters` is set inside the wrapper
around `consume`
([wevm/viem#4967](wevm/viem#4967);
`sendTransaction.js:166-188`, identical in 2.55.13 and 2.56.0), and
`reset` runs in the error path (`sendTransaction.js:242-243` in 2.57.1).
2.55.12 was never published.
- `test:sdk-floors` now also runs `nonce-manager.test.ts`,
`raw-send.test.ts` and `library-send-cli.test.ts` at the floor, then
installs viem 2.55.11 and checks that `getAccount` refuses it.

The plugin is not released yet. hardhat-viem's peer range is `^2.47.6`,
so it accepts a viem below hardhat-kms's floor, and the project then
gets a peer warning for hardhat-kms only. npm, by default, upgrades a
range-pinned viem into the range or stops on an exact pin; pnpm and Yarn
only warn, which is why `getAccount` also checks the version, as
described in (b) above.

## Other decisions

- **Cost.** A raw transaction is decoded only when the connection has
already looked up its KMS addresses, or when a library send holds a
lock, so it never causes a KMS call. Decoding and recovering the sender
takes about 4 ms. A raw transaction from another sender takes no lock
and no chain-id read.
- **Raw transactions that are not decoded** (not a string, not hex,
unsigned, or a blob transaction in its network form with sidecars, which
micro-eth-signer does not decode) pass on unchanged. This is documented
and tested. So does a raw transaction whose chain id cannot be read, or
that cannot get the lock.
- **Re-broadcasts.** A raw transaction gets no retry entry, and no
`SendOutcomeUnknownError` wrapping. "already known" passes through
unchanged and raises the mark.
- **Re-entrancy.** A library send started inside a send from the same
account fails at once in `consume`, with `core.account.nonce-reentrant`,
and nothing is signed. A raw transaction in that context fails with
`core.tx.raw-send-reentrant`.
- **Holds are process-global and keyed like the lock**, so a raw
transaction that reaches another connection to the same chain still ends
its hold. Closing the connection ends its holds.
- **Not covered** (documented): clients with their own transport (only
the nonce is reserved), other processes, and transactions that viem
signs with a caller nonce or that are prepared step by step, since those
never call the nonce manager. Their raw transactions are still ordered
and counted when they go through the connection.
- The first-transaction warning is replaced by a warning for clients
with their own transport. The KMS signing path is unchanged. Under
`src/internal/viem/`, `account.ts` gains the nonce manager, and
`signTransaction` notes the nonce it signed, after the signature.

## Tests

- `test/integration/raw-send.test.ts` on `edr-simulated`:
- a plugin send and a library send at the same time, with the mark on
and off, in both orders, 10 rounds each;
  - two parallel library sends, 10 rounds;
- over HTTP against the recording node, whose pending count stays at 0,
10 parallel rounds;
- a library send whose estimate reverts, after which the plugin's next
send takes under 1 s and gets nonce 0;
- pending and latest counts that stay the node's while a nonce is
reserved;
- another sender's raw transaction, a re-broadcast, and the re-entrant
case;
- `library.sendTransactionSync(...)` through a test plugin listed after
hardhat-kms in the chain, which answers `eth_sendRawTransactionSync` by
forwarding it as `eth_sendRawTransaction` and returning the receipt (EDR
does not implement the method), then a plugin send that must take under
1 s, with nonces 0 and 1;
- a library send whose KMS signature fails, after which the plugin's
next send takes under 1 s and gets nonce 0;
- two HTTP connections to one simulated node behind a JSON-RPC server:
the account comes from the first, its client sends through the second,
which never looked up its KMS addresses, and the first connection's next
plugin send takes under 1 s, with nonces 0 and 1.
- `test/integration/library-send-cli.test.ts`: a real `hardhat run`
script with top-level await, in a child process: `getAccount`, a pending
read, a plugin send, a library send, and a plugin send. It must exit 0,
with the read unchanged, nonces 0 to 2, and the first send under 1 s.
Run against the first version, it exits 13 ("Detected unsettled
top-level await").
- `test/unit/rpc/raw-send.test.ts`, with a fake node:
  - every broadcast outcome;
- holds: held until the raw transaction, a second library send waiting,
release at `reset`, a failed broadcast whose late `reset` ends no other
hold, another nonce waiting behind the hold, release when the connection
closes;
  - waiting for a plugin send in progress;
  - re-entrancy, `get`, a foreign chain, a closed connection;
- own-transport reservations: skipped without waiting, `reset`, unsigned
before signed, cleared by an explicit nonce, failed and accepted raw
transactions;
  - a pending read during a held send;
  - the blob wrapper;
- `eth_sendRawTransactionSync`: a receipt raises the mark and ends a
hold; a timeout (code 4) counts as in the pool, ends the hold and owes
its reset, so the next hold survives that reset; a refusal frees the
nonce; another sender passes untouched;
- the mixed-transport repro (X=0 reserved for an `http` client, Y=1
held, X's reset): Y keeps the lock, the plugin send waits for Y, and the
nonces are distinct; a reservation the node already has is dropped
first, so the hold's own reset ends the hold; a failed pending read
keeps the reservations;
- a `consume` after a broadcast with no answer looks the transaction up
first and gives the next nonce;
- a raw transaction on a second connection that never looked up its
addresses ends the hold with no KMS call, and another sender's passes
on.
- `test/unit/rpc/send-guard.test.ts`: reservations (clock expiry with no
timer, reset order, close, `hasReservations`,
`releaseReservationsBelow`) and holds (lock kept, 60 s limit with fake
timers, owed resets, close, choose failure).
- `test/unit/viem/nonce-manager.test.ts`: drives viem's own
`sendTransaction` over a fake EIP-1193 provider. `consume` is called
once, `reset` is called after a failed estimate, and a send with a
caller nonce never asks. It also covers the transport flag and closed
connections. It runs at the viem floor too.
- Third review round:
- `test/integration/raw-send.test.ts`: a library client with `http(url)`
against an automining node behind a JSON-RPC server, its KMS signature
held, and a plugin send that the node refuses with EDR's "Nonce too
high" text; after the library send's receipt, the next plugin send takes
nonce 1. It passed 10 runs in a row. Holding the lock on the
own-transport path instead (manual mutation) makes it fail.
- `test/unit/viem/account.test.ts`: refusals for 2.55.12,
2.55.12-canary.1, 2.55.13-canary.0, 2.55.11, 2.54.99, 2.50.3, 2.47.6 and
1.99.99, with no key lookup or KMS call; passes for 2.55.13,
2.55.13+build.1, 2.55.14, 2.56.0, 2.57.2, 2.100.0, 3.0.0 and
3.0.0-next.12; unreadable versions pass (empty, `unknown`, `2.55`,
`v2.55.12`, `2.55.12x`, a missing or malformed `package.json`); the
version constant matches the peer range.
- `test/floors/viem-below-floor.test.ts`, run only by `test:sdk-floors`
after it installs viem 2.55.11: `getAccount` refuses the real install.
- `test/unit/rpc/send-guard.test.ts`: the wait warning (once per wait,
also across two holds in a row; none behind a plugin send, under 5 s, or
after close), the 60 s warning (none when the hold ends first), and the
order 5 s < 60 s < 120 s stall limit.
- Checked against the previous head (`9e38e1c`, the source reverted, the
new tests kept): the sync and second-connection integration tests fail
with the plugin send taking about 60 s; the unit tests for the sync
receipt and timeout, the mixed-transport repro, the failed pending read,
the uncertain lookup in `consume` and the second-connection raw
transaction fail. The KMS-failure integration test passes there too; it
guards the reset path.
- Manual mutation, against the integration tests: removing the
`eth_sendRawTransaction` branch fails every concurrency test and the CLI
test (holds are never ended by the broadcast). Releasing the lock at
once instead of holding it fails the four `edr-simulated` concurrency
tests and the parallel library test. Choosing the plain pending count
instead of `nonceFor` fails the lagging-node test.
- Stryker, fourth round: `src/internal/viem/account.ts` 100% (304
killed, 0 survivors). `src/internal/rpc/send-guard.ts` is outside the
config's `mutate` list; run by hand it scores 93.17% (312 killed, 70
timeouts, 27 survived, 1 no coverage). The survivors in code this PR
added are all equivalent:
- lines 213 and 255, `holds.getStore() ?? []` becoming a one-string
array: a string has no `key`, so it never matches a hold;
- lines 338 and 342, `control.release?.()` and `control.acquired?.()`
becoming plain calls: both are assigned synchronously by the `Promise`
constructors before use;
- line 345, `holding.then(async () => await acquired)` becoming `() =>
undefined`: `holding` settles only after `acquired`, so the race is
already decided;
- line 403, `owed === 1` becoming `false`: the count is set to 0 instead
of deleted, and `takeOwedLibraryReset` reads a missing entry and 0 the
same way.
- About 10 other survivors are in code from `main` that this PR did not
change, tracked in
[#205](#205); the exact
set varies with machine load, as mutants move between survived and timed
out.
- Previous rounds: Second round: 100% (1381 killed, 7 timeouts, 3
runtime errors, 0 survivors; `account.ts` 251 killed). The first run
left 2 survivors and 1 uncovered mutant. I removed an unreachable branch
and added a debug-line test, and the second run was clean.

## Docs

- `library-accounts.md`: the warning ("within the cases in Sending") and
the Sending section, covering the sync actions, mixed transports, what
is covered, what is not, and the floor.
- `transactions.md`: new section "Library accounts: nonces and raw
transactions", including why the read interception was dropped, the
reset order and why per-consume ownership is not possible, the sync
method, and the EDR error quote.
- `architecture.md`: rule 1 restored, rule 4 extended, the library
account section and its diagram.
- `rpc-methods.md`, `security-model.md` and `debug-output.md`.
- Decision 0014 note, and the regenerated errors reference.
- Both changesets: this one is minor, because it adds a public
`nonceManager`, a new error id and a peer floor change. The `getAccount`
changeset no longer mentions the removed warning, and it states the new
floor.

## Gates

Everything below was run at this head, rebased on `origin/main`
`2745810`, on Node 24 unless noted.
- `pnpm run check` and `pnpm test` pass. Core: 642 unit and 405
integration tests, 0 failures. The provider packages and the live helper
tests pass too.
- `pnpm run coverage`: core 97.88% lines and 97.55% branches;
`account.ts` 100% lines, `send-guard.ts` 99.76% lines.
- `pnpm run docs:check` and `pnpm run pkg:check` pass.
- `pnpm run test:sdk-floors` passes with viem@2.55.13 and the cloud SDK
floors, then installs viem 2.55.11 and `getAccount` refuses it.
- Node 22.13.0 with `NODE_OPTIONS="--import tsx"`, as the CI leg runs
it: the touched viem and raw-send unit tests, `send-guard.test.ts`, and
the `raw-send`, `library-send-cli` and `no-viem` integration tests pass
(169 tests).
- Not rerun this round: `scripts/consumer-typecheck.ts` (no public type
changed; it passed with TypeScript 5.9, 6.0 and 7.0 on `76a9d29`) and
the fork live suite.
- The prose gate is clean on the changed docs, the changesets, the
commit message and this body.
- CI is billing-blocked.

## Fork live (previous head, `165946d`)

`pnpm run test:live` on the Sepolia fork, with the AWS, Google Cloud and
Azure keys: 74 passed, 0 failed, 1 skipped (the real-Sepolia suite). In
`get-account.live.test.ts`, the viem wallet client sends through
`custom(connection.provider)`, so each provider's send went through the
nonce manager's hold and the raw-transaction path with a real KMS key.
The transaction matrix passed for all three, and no transaction reached
the Sepolia RPC.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — f0af207e Deployed May 18, 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.

nonce manager not resetting after network error

1 participant