Files
amethyst/amethyst
Claude 7a5193bcec feat: page the Cashu transaction list backward instead of showing a relay's suffix
The kind:7375 fix made the balance whole; the transaction list was still
whatever one uncapped REQ returned. CashuWalletEoseManager asks six kinds
of one relay in a single REQ with no limit, and kind:7376 history is the
most numerous of them, so the list every device shows is a recent-N
suffix chosen by that relay's cap — and no later REQ asks for the rest,
because the EOSE moves `since` forward.

Proofs and history want opposite fixes. A balance summed over a partial
proof set is wrong rather than incomplete, so those are walked to
exhaustion in one shot. History is display-only and unbounded, and the
user reads it newest-first, so pulling all of it at launch would be a
large download for something they may never scroll. That is exactly the
shape until+limit paging is for.

Built on the existing machinery rather than a new one: BackwardRelayPager
with cursors on the Account (Account.cashuHistory, beside
notificationHistory), modelled on AccountNotificationsHistoryEoseManager
for the loader and on the NIP-29 thread list for the two UI drivers —
a bootstrap that fills the first screen and a look-ahead that pulls
another page only while the user is scrolling toward the bottom. Nothing
is fetched while the wallet is off screen, and a relay that finished a
page parks at its cursor so another relay advancing doesn't re-REQ it.

One deliberate divergence from the DM and notification pagers: they floor
at `now - liveTail` because a separate live loader provably covers
everything newer. The wallet has no such guarantee — its live REQ carries
neither `since` nor `limit`, so how far back it reaches is whatever the
relay decided, which is the bug being fixed. Flooring at a fixed tail
would leave a band between the relay's cap and the tail boundary that
neither loader ever asks for. This pager floors at `now` and overlaps the
live subscription completely; duplicates are free (both LocalCache and
CashuWalletState.historyEvents are keyed by event id) and gaplessness is
worth more than the overlap.

The footer splits on stalledCount rather than reporting `exhausted` as
"all loaded": exhausted means nothing more is reachable right now, and a
relay that answered an auth CLOSE or went silent is stalled, not done.
Telling someone their transaction history is complete when part of it was
never served is a lie about their own money.

Page limit is 100, not the notification pager's 500 — every kind:7376 row
costs a NIP-44 decrypt to render, which on an external signer is an
out-of-process round-trip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HaZ8RprmKC3sidsq6W8dKY
2026-08-17 00:43:56 +00:00
..