Optional REGISTRATION_REQUIRE_PAYMENT gate: a new wallet must settle an
invoice on an external, non-custodial payment processor before it can
register. Pull model — the grant reads the processor's authenticated API as
the source of truth, so there is no inbound webhook to secure and a settled
invoice cannot be forged over HTTP. Returning wallets and self-hosting bypass
it, exactly like the invite and PoW gates.
- infra/payment: a pluggable PaymentProvider seam (neutral Invoice /
InvoiceStatus / InvoiceRequest) with a BTCPay-compatible first adapter that
also serves this project's xmrcheckout/wowcheckout apps + the BTCPay Monero
plugin, so "operator picks any coin" costs one adapter. A future ETH/native
adapter (BTCPay serves neither) implements the same trait. Only `Settled`
grants (unknown status fails safe); the API key rides in a redacting Secret
and hostile responses are size-capped.
- payment_invoices: invoice<->pubkey_hash binding + atomic single-use consume
(UPDATE ... WHERE consumed_at IS NULL AND pubkey_hash=$ RETURNING), so one
invoice grants at most one registration and never a different identity.
- POST /auth/payment-invoice mints + binds; enforce_payment (bind -> processor
`Settled` -> atomic consume) runs beside enforce_invite; payment_invoice_id
on the register request.
- Fail-closed config: https-in-prod, positive-decimal price, >= 1 confirmation
(bars a 0-conf settled-then-double-spent free registration), bounded expiry.
/capabilities advertises payment_required + price; OpenAPI + .env.example.
Tests: adapter status/speedPolicy mapping + config validation (unit), and L1
binding + atomic single-use (concurrent-consume-one-wins, mis-bind rejected).
Reviewed adversarially across payment-integrity, leak/oracle, and config/DoS
lenses; the confirmed findings (0-conf, expiry bound, Settled-means-paid-in-full
adapter contract, rotation-branch bypass guard) are addressed here.