mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 08:27:04 +00:00
Addresses the critical findings from the post-implementation audit:
A1. State holder lives on Account, not the ViewModel
New CashuWalletState owns the wallet event, decrypted token contents,
history, mint-quote, and inbound-nutzap indexes. It's constructed on
Account and runs for the lifetime of the login session — so nutzaps
arriving while the user is on Home/DMs/etc. get auto-redeemed without
requiring the wallet screen to be open. ViewModel becomes a thin
presenter that forwards flows + holds per-flow UI state (mint quote
in progress, melt confirmation pending).
A2. Reactive observation via LocalCache.live.newEventBundles
The state object backfills once from cache.notes at construction time,
then receives incremental updates from the live new/deleted event
bundles for any NIP-60/61 event authored by us (or addressed to us
via #p for nutzaps). NIP-44 decryption results for kind:7375 events
are cached by event-id, so the per-refresh re-decrypt is gone (D2).
A3. Mutex-guarded auto-redeem (no more duplicate /v1/swap races)
redeemPendingNutzapsSerialized uses tryLock so a sweep already in
flight short-circuits any new triggers; subsequent cache updates
catch up via the next bundle.
A4. Mint-quote recovery on launch
pendingQuotes flow surfaces unfulfilled kind:7374 events whose
expiration hasn't passed and whose id isn't yet referenced with a
"destroyed" marker in any kind:7376. ViewModel.resumeMintQuote()
re-polls the mint for the original quote and rebuilds the flow.
B1. NutzapInfoEvent now carries the wallet's outbox relays so senders
publish nutzaps where our assembler is actually listening.
B2. Subscription tracks the outboxRelaysFlow — when the relay list
changes, the assembler subscription is rebuilt with the new set.
B5. New MintProtocolException distinguishes "HTTP fine, protocol said
no" (e.g. melt state != PAID) from "HTTP error". Both surface
through describeMintError() (now top-level — C4).
B7. redeemNutzap now pre-checks the P2PK secret's pubkey matches our
wallet pubkey before signing — saves a wasted mint round-trip when
the lock targets someone else.
B8. Melt is a two-phase flow: startMelt() returns a Quoted state with
amount + fee_reserve so the UI confirms before paying; confirmMelt()
actually spends. No more silent fee acceptance.
C1. MintHttpClient + CashuMintOperations cached per mint URL via a
ConcurrentHashMap.
C3. AddCashuWalletScreen has a "Verify" button that pings /v1/info
before adding, with inline success / failure feedback.
C7. Inline JsonObject FQN in P2PK.kt replaced with proper import.
C8. Dead .also { _ -> secretJson } removed from redeemNutzap.
D1. runCatching {}.getOrNull() callsites in the state holder now log
via Log.w("CashuWallet") so silent failures surface in logcat.
D5. CashuWalletQueryState made @Immutable + data class for Compose
stability hygiene.
Touched files: Account.kt (state field + constructor params),
AccountCacheState.kt + AppModules.kt (wire the assembler factory +
okHttpClientForMoney through), CashuWalletOps.kt (decouples from
Account, takes signer + publish callback), CashuWalletState.kt (new),
CashuWalletViewModel.kt (presenter rewrite), CashuWalletScreen.kt
(two-phase melt UI), AddCashuWalletScreen.kt (Verify button),
strings.xml (new keys).
All 20 NIP-60 jvm tests still pass; playDebug + fdroidDebug compile
clean.
https://claude.ai/code/session_01MdWddiar819f8XYt5N8BjP