Skip to content

fix(wallet): refresh mint keysets before signing operations - #560

Closed
KvngMikey wants to merge 3 commits into
cashubtc:mainfrom
KvngMikey:keyset-freshness
Closed

fix(wallet): refresh mint keysets before signing operations#560
KvngMikey wants to merge 3 commits into
cashubtc:mainfrom
KvngMikey:keyset-freshness

Conversation

@KvngMikey

Copy link
Copy Markdown
Member

Summary

When a mint rotates it's keysets, the wallet has no way to automatically update it's records and so can build outputs
on the dead keyset.

This PR ensures keysets are current and resolves the active keyset, before building outputs, also fixes bolt12/onchain never refreshing and a stray double counter-bump

Mint Rotation PR: cashubtc/nutshell#1058

Mints with automatic keyset rotation (cashubtc/nutshell#1058) can
deactivate their active keyset mid-session. Building outputs on a stale
keyset then fails at the mint. Resolve this proactively rather than
reacting to the error.

Centralize the fix in retryOnceOnSignedOutputs, the wrapper every
signature-requesting operation already flows through: add an
ensureKeysetsCurrent helper (keys-only fetchMintKeys refresh, then
active-keyset resolution and wallet construction from the freshly
updated store) and make the wrapper its sole caller. The wrapper now
hands each operation a fresh wallet + keyset id, so every signing path
(send, redeem, bolt11, bolt12, onchain, melt, sendToLock) builds
outputs on the keyset that is active after the refresh.

- fetchMintKeys (keys-only) is used instead of updateMintInfoAndKeys,
  which fetches /v1/info and can trigger a MOTD redirect mid-payment.
- bolt12 and onchain previously never refreshed at all (they omitted
  the update flag); they now refresh via the wrapper.
- Drop the redundant, un-awaited second
  handleOutputsHaveAlreadyBeenSignedError calls in the bolt12/onchain
  catch blocks; the wrapper already handles that path.
- ensureKeysetsCurrent: refreshes keys before resolving the keyset,
  returns the newly-active id and a wallet built after the refresh,
  never calls updateMintInfoAndKeys, and throws on an unknown mint.
- retryOnceOnSignedOutputs: calls ensureKeysetsCurrent once and hands
  its wallet/keyset id to the operation; retries once on "outputs have
  already been signed" with the same keyset (no second refresh);
  rethrows unrelated errors; propagates a second signed-outputs failure.
- Post-rotation routing: send, redeem, bolt11, bolt12, onchain, melt,
  and sendToLock each build on the keyset that becomes active during
  the refresh.
Copilot AI review requested due to automatic review settings July 15, 2026 04:49

Copilot AI 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.

Pull request overview

This PR improves wallet resilience to mint keyset rotation by refreshing mint keysets/keys immediately before any operation that builds/signs outputs, ensuring the active keyset is resolved after the refresh.

Changes:

  • Added a “freshness guarantee” path (ensureKeysetsCurrent) and routed signing operations through a revised retryOnceOnSignedOutputs(mintUrl, unit, op) wrapper.
  • Updated signing flows across Bolt11/Bolt12/onchain minting, melts, swaps, and P2PK lock sends to use the refreshed wallet + resolved active keyset.
  • Expanded/adjusted unit tests to cover the freshness funnel and keyset-rotation behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/vitest/tests/p2pkPaymentRequest.test.ts Stubs mint refresh + wallet rebuild for sendToLock tests now routed through the wrapper.
src/stores/walletOnchain.ts Routes onchain mint signing through the freshness wrapper using mintUrl/unit.
src/stores/walletMelt.ts Routes melt prepare/complete signing through the freshness wrapper using mintUrl/unit.
src/stores/walletBolt12.ts Routes Bolt12 mint signing through the freshness wrapper using mintUrl/unit.
src/stores/walletBolt11.ts Routes Bolt11 mint signing through the freshness wrapper using mintUrl/unit.
src/stores/wallet.ts Introduces ensureKeysetsCurrent, updates retryOnceOnSignedOutputs signature, and applies wrapper to swap/send/receive signing paths.
src/stores/tests/wallet.test.js Adds coverage for freshness guarantee + funnel behavior and rotated-keyset routing across signing paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/stores/walletBolt11.ts
Comment thread src/stores/wallet.ts
Comment thread src/stores/__tests__/wallet.test.js Outdated
Comment thread src/stores/__tests__/wallet.test.js Outdated
Three cases in the funnel describe block shared the "(f)" prefix,
which makes failures hard to tell apart in Vitest output. Relabel
them (f)/(g)/(h) so each case is uniquely identifiable.
@robwoodgate

Copy link
Copy Markdown
Contributor

Cashu-TS handles this automatically in v4.9.0+ and the v5.rc line

@callebtc

Copy link
Copy Markdown
Collaborator

Cashu-TS handles this automatically in v4.9.0+ and the v5.rc line

very nice!

@callebtc callebtc closed this Aug 18, 2026
@KvngMikey
KvngMikey deleted the keyset-freshness branch August 18, 2026 13:26
@KvngMikey

Copy link
Copy Markdown
Member Author

Cashu-TS handles this automatically in v4.9.0+ and the v5.rc line

much better ! :)

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.

4 participants