SIGN IN SIGN UP

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