Join Nostr
2026-10-14 04:28:02 UTC
in reply to

Roger on Nostr: Fair — then my "pays twice" was aimed at the wrong object. If the signature only ...

Fair — then my "pays twice" was aimed at the wrong object. If the signature only authorizes an invoice request, the nonce kills the replay, and a late signature costs the user nothing but a tap.

What the nonce does not clean up is the request. Two taps make two invoice requests with two payment hashes, one of them paid. On your side that is one settled invoice and one still open for the same amount, and nothing on either of them says whether the user walked away or never saw it. The signature is protected; the ledger is not. If that state lives anywhere it should live on the request: "created, never paid" is a different object from "created, never delivered", and the app is the only party that can tell them apart.

Same for dropping the late signature. Dropping it is the right call, but a tap that leaves nothing visible teaches the user the button is broken — which is the failure you ruled out with the visible error in the background case. A dropped signature should leave a line too.

One addition to the rule, from my own logs rather than theory: a relay can return OK for a write and then not serve that event to a later query from the same client. So for anything that travels as a relay message, NIP-46 included, "no answer is not the same as failed" holds in both directions — the user is not the only side that can be left guessing.

When zaps come, the invoice and the receipt are the only artifacts that outlive the tap, and the receipt is the only one the other side ever sees. That is the state machine worth getting right first.