fix: three criticals from round 5, and tests that actually catch them
The round-5 audit's most useful finding was not a bug: it was that deleting entire guards left all 117 tests green. `paidNothingLastTime`, `markPending`, `pollOnce`, `totalReceived`, the cancel route and the Host guard appeared in no test file at all. That is why each round's fixes broke the next thing. CRITICAL - a double spend decided by a substring. `didNotReachTheChain` looked for "invalid" / "expired" / "is not connected" anywhere in a wallet's free-text error, and that verdict cleared the pending marker. A wallet that broadcast and then said "Invalid response from the node" had its marker erased and the payer paid twice; the wallet vendor chose the wording. Submission status is now a structural fact: WalletActionError carries `submitted`, defaulting to true, set false only on paths that provably precede the submit call. CRITICAL - an invoice confirmed with nobody paying. The baseline was read at `latest` and the block height in a separate round trip, with nothing tying them together. On a load-balanced endpoint the height came back lower, the event scan started inside the range the baseline already covered, and `max(events, delta)` let the double-counted sum win. The height is read first and the baseline pinned to that exact block; credit is also capped at balance growth plus provable outflow, so two measurements that disagree can never credit more than the chain can account for. CRITICAL - the "I checked my wallet: nothing was sent. Pay now" button did not pay. clearPending emptied the store but left the local `prior` truthy, so control fell into the already-sent branch: no wallet ever opened, and the payer was told "your payment was sent once" when nothing had been sent. On pay-live each confirm is a ten-minute poll, so they waited twenty minutes for a false statement. Also: the createdBlock backfill was missing the guard that stops a poll writing back over a row cancelled or deleted underneath it; the React binding always synthesised a confirm, so a React integrator with no backend got a receipt asserting the money had arrived; a transient RPC failure cached "fee unknown" for the whole session; pay-live re-read its baseline on every load, so paying and reloading produced a permanent false "not confirmed"; the dashboard emitted a `watcher` parameter the payer page ignores by design. Two new test files drive the real widget and the real watcher, and every guard in them was verified by mutation: breaking it makes a test fail. The DOM double now honours disabled and hidden, so button gating is testable at all. Three existing tests that asserted nothing were replaced or strengthened. 135 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
B
bongbongcrypto committed
abcebf2a870c8a1d9b607ca998828e454f947402
Parent: 57076aa