Finishes the escrow-mint work: advertiseEscrowKey now also publishes
cashu_mint (the seller's preferred mint from Wallet > Escrow) on kind 0,
documented in NIP.md. The buyer's EscrowRequestDialog reads the seller's
cashu_mint, includes it in the mint picker even if it is not in the buyer's
trust list, and prefers it (falling back to the buyer's preference, then
first trusted mint). Funds locked at a mint the seller actually uses are
the ones they can redeem. Still open: seller-side receive from a mint they
do not already have. New marketUi.escrowRequest.sellerMintHint (en/es).
- usePayPaymentPointer now checks the seller's committed payment_hash (from
the accepted offer) against the invoice actually paid; a swapped pointer
is rejected before any payment.
- PayWithSats no longer offers an in-app sats Pay button for a non-SATS
offer: a fiat amount is never paid as if it were sats. New
marketUi.pay.nonSats (en/es).
- ListingOffersPanel passes paymentHash + currency through.
Docs updated (still open: NWC->WebLN/Cashu fallback, empty preimage).
The escrow mint was silently the buyer's preferred/first trusted mint
(useCashuEscrow: prefs.preferredMintUrl || mints[0].url) with no UI, even
though the seller redeems there. Now:
- the escrow dialog shows the mint and lets the buyer pick from their
trusted mints (defaults to their preference);
- a mint whose NUT-06 info advertises no NUT-11 (P2PK) support is blocked
with a clear message before minting (cashuEscrow.mintSupportsP2PK);
- the create call uses the selected mint.
Still open (scoped separately): consult a seller-advertised mint and
seller-side receive from an unfamiliar mint. Recorded in the audit.
Next batch from MARKETPLACE_FLOW_AUDIT.md:
- bolt11.ts: getBolt11Expiry / isBolt11Expired; usePayPaymentPointer refuses
an expired invoice before paying (a lapsed offer invoice used to fail deep
in the wallet with a confusing error).
- PayWithSats shows unsupported pointer kinds (creq/clink) as unavailable up
front instead of enabling a button that fails after the click.
- useTradeAttestations: verify events (onlyVerified) and keep the newest per
(author, kind, d), so a newer s=revoked supersedes a stale valid copy;
useTradeReputation no longer trusts unverified relay events.
- EscrowRequestDialog persists the minted token locally before publishing
(lib/pendingEscrow.ts), so a failed send cannot strand the locked funds.
Deep audit (MARKETPLACE_FLOW_AUDIT.md): full trade/escrow/offers lifecycle,
severity-ranked findings, and a state-coverage matrix.
Fixes:
- Grid Mark as sold now opens MarkSoldDialog, so the kind-31871 attestation
(buyer, method) is recorded from the grid too, not just the detail page.
- Offers accept attaches a payment pointer only when the accepting user is the
listing owner (seller); previously the buyer could publish their own lud16 as
the payee and misdirect payment.
- advertiseEscrowKey refuses to publish kind 0 before the profile loads, so it
cannot overwrite name/picture/lud16 with a cashu_escrow-only profile.
- useListingOffers passes requireAny, so the offers panel error state actually
fires on an outage (previously an outage looked like no offers).
- Offer messages invalidate the offer queries, so accept/counter/decline update
immediately instead of after the stale window.