Self-custody payouts + audit hardening (re-land of #26 remainder)#27
Merged
Conversation
The hand-rolled decoder dropped the 6-char checksum without verifying it, so a single mistyped character in an npub decoded to a different (wrong) 32-byte key and was silently accepted — a feedback DM or future zap would then go nowhere. Add the standard BIP-173 polymod checksum verification. This also surfaced a long-standing typo in the nsec test vector (...nlfe9 → ...nlfe5): its body always decoded correctly, so the missing checksum check hid the bad tail.
A single Claude call with max_tokens=1024 truncated its tool output past ~14 "Other" registrants; the partial JSON failed to parse and the whole event silently dropped to the per-slug deterministic fallback — exactly at the scale where AI normalization matters most. Process participants in capped batches with raised max_tokens, so output can't truncate and one failing batch only forces its own members to fallback. Move the static taxonomy + instructions into a cacheable system block, and let the model OMIT a participant it can't confidently place (handled by fallback / manual override) rather than forcing a guess. Prompt-building and response parsing are now pure helpers with direct unit tests.
total_sats was bounded only by gt=0, so one extra zero could fire off a huge payout with a single click. Add a configurable PAYOUT_MAX_SATS ceiling (default 5,000,000) enforced before a wallet is ever touched. Also remove the unused `requests` dependency (only httpx is used).
A payout that failed because an address was wrong had no recovery path: the unique-per-team constraint blocks a new payout, and retry reused the stored (bad) address. Let retry accept an optional address-correction map, applied to failed items before re-resolving. The modal re-enables the address fields when items can be retried and sends the corrections.
Architecture decision record for moving the Lightning send into the organizer's browser: server computes the split and verifies reported preimages, but the NWC spend credential never reaches the backend. Resolves the self-custody and blocking/timeout audit findings together.
…sends)
Adds the server side of client-side NWC: when create_payout is called without
an nwc credential, the server creates pending items and returns them for the
browser to pay. Two new organizer-authenticated endpoints record each result —
/items/{id}/result verifies the reported preimage against the invoice before
marking paid (mismatch -> unverified), /items/{id}/failed records a no-preimage
failure. Both roll the payout status up and are idempotent on already-paid items.
The legacy server-side path still runs when nwc is supplied, so nothing breaks;
the frontend switch and removal of the server-side credential path follow.
New lib/lightning.ts resolves invoices (LUD-16) and performs the NIP-47 pay_invoice over a WebSocket entirely in the browser; the NWC credential never leaves the client. The payout modal now creates a payout without an nwc, pays each item locally, and reports each result for the server to verify. Retry re-drives only the failed items (using any corrected addresses).
…tody) The create endpoint no longer accepts an nwc credential and the server-side execute/retry path is gone, so the backend has no way to spend from a wallet — self-custody is now enforced, not merely the frontend's default. Payment happens in the browser and the server only verifies reported preimages. The NIP-47/LNURL utility modules and their unit tests remain as secret-free, reusable helpers.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR #26 was merged at a stale head and only captured the first two commits (preimage verification + idempotency). This re-lands the remaining 8 commits that didn't make it into
main.All developed under TDD; this is the exact tree that was green before (backend 156, frontend 58, typecheck/lint/build).
Self-custody payouts (architectural)
Design:
docs/superpowers/specs/2026-06-20-self-custody-payouts-design.md.lib/lightning.ts) resolves invoices (LUD-16) and performs the NIP-47pay_invoiceover a WebSocket; the server createspendingitems and verifies each reported preimage before recordingpaid.nwcrequest field are removed, so the backend cannot spend — self-custody is enforced. This also eliminates the old blocking/timeout-prone server payment loop.Also included
PAYOUT_MAX_SATS, enforced pre-flight.max_tokens=1024truncation cliff that silently dropped large events to fallback; now batched with per-batch isolation, a cacheable system block, and abstention.requestsdependency.