Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Keyset final_expiry silently kills held balances — wallets must rotate off inactive keysets (NUT-02)

Open
#417 3 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
28/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
go
Domain
backend, payments

Research direction

Start with tests/cloud-lab/run-keyset-rotation.sh and the related issues #409, #413, and #416 to understand the existing rotation and error-classification work. The issue proposes several separable directions, so confirm which one is in scope before starting; for the lifecycle monitor, inspect the keyset refresh and held-proof handling entry points. Done means the selected behavior is covered by tests, including the expired-keyset case.

Written by the indexing model from the issue text.

Description

Wallet hygiene: keyset final_expiry silently kills held balances — wallets must rotate off inactive keysets (NUT-02 SHOULD). Proven in the cloud lab.

What happened (lab evidence, #416): paying with proofs whose keyset was expired fails with "could not swap proofs: Keyset has expired" — the proofs are permanently unspendable at that mint. This is spec-correct mint behavior, not a bug: NUT-02 Keyset Final Expiry says that after final_expiry the mint "is no longer obliged to fulfill promises signed with the keys from that keyset" and may irrevocably delete the keyset's nullifiers. The spec even standardized the refusal: error code 12003 "Keyset has expired" (nuts PR #367). Implementations differ — cdk enforces expiry, nutshell never expires (nutshell #7, tracked in the cashu-audit divergence database) — and the spec's active flag conflates mintable vs redeemable, but none of that is ours to fix upstream.

Why it matters to TollGate: any wallet balance held across a keyset rotation+expiry window can be wiped:

  • Reseller mode: the reseller holds balances at upstream mints (min_balance in accepted_mints) between purchases — an upstream that rotates with expiry (cdk mints do) silently destroys that float.
  • Operator payouts: the merchant wallet holds received funds between payout cycles (min_payout_amount, payout_interval_seconds); a fee-charging cdk-style mint rotating keysets mid-cycle destroys receivables.
  • Customers: a customer holding a token from an expired keyset gets a refusal — currently misclassified as payment-error-mint-unreachable (see the note on #409), telling them to retry a permanently dead token.

Suggested work items (split-able):

  1. Keyset lifecycle monitor: on keyset refresh, detect active=false for keysets with held proofs and proactively swap them to the active keyset (NUT-02: wallets SHOULD prioritize this; NUT-03 swap). React to final_expiry in the keyset response with an operator warning when held value is affected.
  2. Classifier fix (already noted on #409): match "Keyset has expired" / code 12003 to a dedicated payment-error-keyset-expired notice instead of mint-unreachable, keeping the cause in the message. The pin in test_keyset_rotation.py updates consciously with it.
  3. Advertisement/trust surface (product question): consider surfacing keyset-expiry policy (e.g. keysets with final_expiry set) in mint metadata evaluation, so routers can prefer mints without expiry time bombs for float.

Repro: tests/cloud-lab/run-keyset-rotation.sh (phase A mints on a 100-ppk keyset; phase B expires it; the stashed token dies with 12003-class refusal).

Refs: #416 (rotation lane + pins), #409 (classifier), #413 (fee mint), cashu-audit issues/keyset-active-flag-semantics.md + divergences/CDK-VS-NUTSHELL-ALIGNMENT-2026-07-29.md ("#7 old keysets never expire"), nuts #367 (error 12003), NUT-02 §Keyset Final Expiry.

Dominant language
Go
Stars
12
Forks
14
Avg merge
1d 5h
Merged PRs (30d)
232

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from OpenTollGate/tollgate-module-basic-go

All issues in OpenTollGate/tollgate-module-basic-go

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.