Skip to content

[BWS, BWC] EVM JIT Nonce#4124

Open
leolambo wants to merge 12 commits intobitpay:masterfrom
leolambo:evmJitNonce
Open

[BWS, BWC] EVM JIT Nonce#4124
leolambo wants to merge 12 commits intobitpay:masterfrom
leolambo:evmJitNonce

Conversation

@leolambo
Copy link
Contributor

@leolambo leolambo commented Mar 13, 2026

Description

EVM nonces assigned at createTx time go stale when proposals sit pending. This adds a deferNonce option that skips the nonce fetch during creation and instead assigns a fresh gap-free nonce via a new assignNonce endpoint, called by the client right before signing.

Companion PRs needed for full end-to-end testing:

Changelog

  • New POST /v1/txproposals/:id/assign-nonce/ endpoint that fetches the confirmed nonce from chain, skips past any pending txp nonces, and stores the result on the txp
  • createTx skips the nonce fetch when deferNonce: true, and signTx no longer flags nonce conflicts on null-nonce txps
  • hasMutableTxData() method on TxProposal consolidates the publish-time guards for both Solana republish and deferred-nonce flows
  • assignNonce() client method added to BWC

Testing Notes

Integration tests are in server.test.ts under #assignNonce (deferred nonce). Run with:

cd packages/bitcore-wallet-service && npm run test:integration

For full E2E, you'll need the companion app and server PRs linked above. Create an ETH txp with deferNonce: true, publish it, call assignNonce, then sign.


Checklist

  • I have read CONTRIBUTING.md and verified that this PR follows the guidelines and requirements outlined in it.
  • I have added the appropriate package tag(s) (e.g. BWC if modifying the bitcore-wallet-client package, CLI if modifying the bitcore-cli package, etc.)
  • I have verified that this is not an existing PR (open or closed)

leolambo added 12 commits March 5, 2026 12:14
EVM nonces assigned at txp creation time go stale when proposals
sit waiting for signing. Add an opt-in deferNonce boolean so
callers can signal that nonce assignment should happen later.

Field added to ITxProposal interface, TxProposal class,
create(), and fromObj() for persistence round-tripping.
When deferNonce is true, skip the getTransactionCount call during
createTx and pass the flag through to TxProposal.create() so it
persists on the proposal object.
Allow deferred-nonce txps to be published and re-published. Use
prePublishRaw to store the original unsigned tx so proposal
signature verification still works after nonce is assigned later.
Deferred-nonce proposals have nonce=null until assignNonce is
called. Without null guards, the nonce comparison would coerce
null to 0 and falsely trigger TX_NONCE_CONFLICT.
New endpoint lets the client request a fresh nonce just before
signing. The handler fetches the confirmed nonce from the
blockchain, skips past any pending proposal nonces in the
database, and stores the gap-free result on the txp.

Runs under _runLocked to prevent concurrent calls from receiving
the same nonce for the same wallet.
Calls POST /v1/txproposals/{id}/assign-nonce/ so the app can
request a fresh nonce from BWS just before signing a
deferred-nonce transaction proposal.
Cover the full lifecycle: createTx with deferNonce, publishTx
with prePublishRaw, assignNonce with gap-free calculation,
signTx without false nonce conflicts, and broadcast.

Includes a bulk-sign scenario that assigns sequential nonces
to three deferred proposals signed one after another.
The repeated isRepublishEnabled() || deferNonce pattern in
publishTx exists because both Solana blockhash refresh and EVM
deferred nonce share the same need: preserve prePublishRaw for
signature verification after the raw tx changes.

Also remove unnecessary spread-copy before sorting pendingNonces
in assignNonce since filter/map already produces a new array.
Consolidate into the existing integration test file rather than
maintaining a separate file for one feature. Tests are placed
under a new #assignNonce describe block between #signTx and
#broadcastTx to match the transaction lifecycle order.
Number(undefined) produces NaN which BigInt cannot convert.
Use 0 as placeholder when nonce is null so checkTx can still
build the transaction during createTx. Also reset the
getTransactionCount stub in test beforeEach to prevent
cross-test contamination.
stubBroadcast must be called after signTx with the actual txid
so the returned txid matches txp.txid. Tests that sign multiple
txps need to broadcast each one before signing the next,
otherwise the accepted txp blocks with TX_NONCE_CONFLICT.
The getTransactionCount stub is static so broadcasting resets the
nonce calculation. The gap-free logic already handles pending txps
so sequential assignNonce calls produce correct nonces without
needing to sign and broadcast each one.
@leolambo leolambo marked this pull request as ready for review March 19, 2026 18:26
@kajoseph kajoseph added BWC This pull request modifies the bitcore-wallet-client package BWS This pull request modifies the bitcore-wallet-service package labels Mar 19, 2026
txp: Txp;
},
/** @deprecated */
cb?: (err?: Error, txp?: Txp) => void
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a brand new function, so no need to support legacy implementations. Let's remove the callback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BWC This pull request modifies the bitcore-wallet-client package BWS This pull request modifies the bitcore-wallet-service package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants