Confirmed live 2026-09-08 against three real Lightning payments to a registered @minibits.cash address: POST /claim (the only claim source claim_and_redeem checked) always returned an empty array, no matter how long or how often it was polled. Independently queried wss://relay.minibits.cash and found all three payments sitting there as NIP-04-encrypted kind-4 DMs, #p-tagged to the wallet's own Nostr pubkey and authored by the Minibits service key — that is the actual delivery channel for a payment made to the address, and this module never looked at it. fetch_relay_dms queries CLAIM_RELAY_URLS (the service's own relay plus two public fallbacks) for kind-4 events tagged to our pubkey, feeding matching content into the existing pending_claims retry pipeline unchanged. A new last_dm_seen_at watermark stops the same (immutable, never-expiring) relay event from being re-fetched and re-attempted on every poll. The REST /claim call stays in place alongside it in case it serves some other payment path — this only adds the missing one. fix(ecash): trim stray whitespace before parsing a cashuA/cashuB token Once the relay fix above surfaced the three real payments, all three failed to redeem with "Invalid base64 in cashuB token" — the decrypted NIP-04 content had a trailing space after the base64 payload (Minibits' own encoding), which every base64 alphabet in decode_token_base64 rejects outright. CashuToken::deserialize now trims the whole token string before touching the "cashuA"/"cashuB" prefix or payload. This is a general robustness fix, not just a Minibits workaround — the same stray-whitespace failure could hit a hand-pasted token from a clipboard copy just as easily. Both fixes verified end-to-end against production: all three stuck payments (20 + 5 + 20 = 45 sats) redeemed cleanly on the first poll after deploying this build to archy-x250-pa3. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EawZPP9iidXj6Tvg3EpG3a