Proxy wallet: sweep quotes across per-epoch currency units #17

Open
opened 2026-08-12 18:07:56 +01:00 by vnprc-agent · 0 comments
Collaborator

Teach the proxy's wallet to sweep quotes across per-epoch currency units — after the first rotation it would otherwise silently ignore every new-epoch quote forever. Verified: no CDK changes needed. Design: docs/EPOCH_DESIGN.md (proxy wallet section).

Primary goals:

  • Discovery from quotes, not mint info — the mint-quote pubkey lookup is unit-blind end to end (no unit on the wire, no unit predicate in the mint's query, fetched quotes stored with their true unit), so one wallet discovers every epoch; deriving the unit set from fetched quotes is also what lets the mint retire old mint-info entries.
  • Per-unit wallet handles over one store — a CDK wallet only mints the unit it was constructed with (retain(unit == self.unit) in get_unissued_mint_quotes is the single choke point). Lazily create a cheap handle per discovered unit — construction writes nothing — all sharing the one localstore, one seed, and one keyset metadata cache; run the existing sweep body once per unit. Verified safe: wallet schema and NUT-13 keyset counters are unit- or keyset-scoped.
  • Housekeeping — drop handles for units with nothing left to mint; cap batch size per sweep pass (the first pass after a rotation drains a backlog); log the reconcile per unit — "fetched but never minted" divergence is the standing detection signal for this bug class.
  • Must not break, nothing more — faucet and web UI keep working with multiple units present (they under-report old epochs today: faucet balance and spend paths are single-unit); epoch-aware display is out of scope.

Current state and specifics

The single wallet is constructed with hardcoded unit hash in roles/translator/src/lib/payment/wallet.rs and shared by the sweeper, faucet, and an inert notification handler. The sweep loop (roles/translator/src/lib/payment/quote_sweeper.rs) fetches by pubkey (unit-agnostic, persists all units), then filters to the constructed unit — foreign-unit quotes accumulate unminted with no error. batch_mint keeps the P2PK spending conditions; CDK's WalletRepository can serve as the unit→handle map (correct get-or-create locking) but its aggregate sweep is unusable here (drops spending conditions, per-quote HTTP), so use it as a container only, if at all.

Witness

On the dev stack with #16's manual lever: rotate mid-mining; the sweeper mints quotes from both the old and new unit on the next passes; per-unit reconcile log shows both units converging to zero unissued; faucet and web UI stay functional.

Scope

In: translator payment module only. Out: reorg awareness (unpaid quotes never reach the wallet, by design), epoch-aware balance display, cashu.me fork changes.

Part of #13. Gated on #16 (needs a second unit to exist to be testable end to end).

Teach the proxy's wallet to sweep quotes across per-epoch currency units — after the first rotation it would otherwise silently ignore every new-epoch quote forever. Verified: no CDK changes needed. Design: `docs/EPOCH_DESIGN.md` (proxy wallet section). Primary goals: - **Discovery from quotes, not mint info** — the mint-quote pubkey lookup is unit-blind end to end (no unit on the wire, no unit predicate in the mint's query, fetched quotes stored with their true unit), so one wallet discovers every epoch; deriving the unit set from fetched quotes is also what lets the mint retire old mint-info entries. - **Per-unit wallet handles over one store** — a CDK wallet only mints the unit it was constructed with (`retain(unit == self.unit)` in `get_unissued_mint_quotes` is the single choke point). Lazily create a cheap handle per discovered unit — construction writes nothing — all sharing the one localstore, one seed, and one keyset metadata cache; run the existing sweep body once per unit. Verified safe: wallet schema and NUT-13 keyset counters are unit- or keyset-scoped. - **Housekeeping** — drop handles for units with nothing left to mint; cap batch size per sweep pass (the first pass after a rotation drains a backlog); log the reconcile per unit — "fetched but never minted" divergence is the standing detection signal for this bug class. - **Must not break, nothing more** — faucet and web UI keep working with multiple units present (they under-report old epochs today: faucet balance and spend paths are single-unit); epoch-aware display is out of scope. ### Current state and specifics The single wallet is constructed with hardcoded unit `hash` in `roles/translator/src/lib/payment/wallet.rs` and shared by the sweeper, faucet, and an inert notification handler. The sweep loop (`roles/translator/src/lib/payment/quote_sweeper.rs`) fetches by pubkey (unit-agnostic, persists all units), then filters to the constructed unit — foreign-unit quotes accumulate unminted with no error. `batch_mint` keeps the P2PK spending conditions; CDK's `WalletRepository` can serve as the unit→handle map (correct get-or-create locking) but its aggregate sweep is unusable here (drops spending conditions, per-quote HTTP), so use it as a container only, if at all. ### Witness On the dev stack with #16's manual lever: rotate mid-mining; the sweeper mints quotes from both the old and new unit on the next passes; per-unit reconcile log shows both units converging to zero unissued; faucet and web UI stay functional. ### Scope In: translator payment module only. Out: reorg awareness (unpaid quotes never reach the wallet, by design), epoch-aware balance display, cashu.me fork changes. Part of #13. Gated on #16 (needs a second unit to exist to be testable end to end).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
vnprc/hashpool#17
No description provided.