feat: secure cash-in via server-stamped create_withdraw RPC (#52) #66

Merged
padreug merged 1 commit from cash-in-create-withdraw into dev 2026-06-22 13:56:00 +00:00
Owner

The bitspire consumer half of the secure cash-in design (spirekeeper#31, schema pinned; spirekeeper#32 handler live on the dev stack). Replaces the dev-stack-proof cash-in path (ATM creates the link + sets amount/extra itself) with the verified-attribution flow.

What changes

  • LnbitsClient.createWithdraw(walletId, body) — new signed kind-21000 RPC. Request: { principal_sats, fiat_amount?, fiat_code?, title?, wait_time?, client_ref? }. Response: { link_id, lnurl, net_sats, principal_sats, fee_sats }. Non-idempotent (mints a link) → not retry-wrapped.
  • lightning.ts generateLnurlWithdraw — createWithdrawLink → createWithdraw. The ATM sends only the hardware-attested principal_sats; the operator side derives fee + NET and stamps source/nostr_sender_pubkey from the verified signer. The ATM no longer computes the amount or extra.
  • LNURL-session map re-keyed on link_id — the secure response carries no unique_hash. Settlement-watch half unchanged (subscribe_payments { tag:'withdraw', link_id }).

Security

Closes both issues from the 2026-06-22 spec supersession: no client-controlled amount (can't understate the fee) and no self-asserted attribution (server stamps from the verified sender). principal stays ATM-attested — same trust boundary as the cash-out wire amount.

Status

Server RPC is live (spirekeeper#32), so this is ready for the joint cash-in test. typecheck 12/12; full suite (29 lnbits / 29 machine) + electron prod build green. I'll coordinate the deploy with the bunker/lnbits side watching the full chain (RPC → server-stamped net link → claim → cash_in settlement + super payout).

Do NOT use the MCP merge endpoint — merge via the Forgejo UI after review.

🤖 Generated with Claude Code

The bitspire consumer half of the secure cash-in design (spirekeeper#31, schema pinned; spirekeeper#32 handler **live on the dev stack**). Replaces the dev-stack-proof cash-in path (ATM creates the link + sets amount/extra itself) with the verified-attribution flow. ### What changes - **`LnbitsClient.createWithdraw(walletId, body)`** — new signed kind-21000 RPC. Request: `{ principal_sats, fiat_amount?, fiat_code?, title?, wait_time?, client_ref? }`. Response: `{ link_id, lnurl, net_sats, principal_sats, fee_sats }`. Non-idempotent (mints a link) → **not** retry-wrapped. - **`lightning.ts` `generateLnurlWithdraw`** — `createWithdrawLink` → `createWithdraw`. The ATM sends **only the hardware-attested `principal_sats`**; the operator side derives fee + NET and stamps `source`/`nostr_sender_pubkey` from the *verified* signer. The ATM no longer computes the amount or extra. - **LNURL-session map re-keyed on `link_id`** — the secure response carries no `unique_hash`. Settlement-watch half unchanged (`subscribe_payments { tag:'withdraw', link_id }`). ### Security Closes both issues from the 2026-06-22 spec supersession: no client-controlled amount (can't understate the fee) and no self-asserted attribution (server stamps from the verified sender). `principal` stays ATM-attested — same trust boundary as the cash-out wire amount. ### Status Server RPC is live (spirekeeper#32), so this is **ready for the joint cash-in test**. typecheck 12/12; full suite (29 lnbits / 29 machine) + electron prod build green. I'll coordinate the deploy with the bunker/lnbits side watching the full chain (RPC → server-stamped net link → claim → cash_in settlement + super payout). Do NOT use the MCP merge endpoint — merge via the Forgejo UI after review. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Replaces the cash-in LNURL-withdraw creation with the secure create_withdraw
RPC (aiolabs/spirekeeper#31/#32). The ATM now sends only the hardware-attested
gross principal_sats; the operator side verifies the signer, derives fee + NET,
and stamps the link's attribution (source/nostr_sender_pubkey) from the VERIFIED
sender. Closes the dev-stack weakness where the ATM set the withdraw amount +
extra itself (could understate the fee / forge attribution).

- LnbitsClient.createWithdraw(walletId, {principal_sats, fiat_amount?, fiat_code?,
  title?, wait_time?, client_ref?}) -> {link_id, lnurl, net_sats, principal_sats,
  fee_sats}. Non-idempotent (mints a link) -> not retry-wrapped.
- lightning.ts generateLnurlWithdraw: createWithdrawLink -> createWithdraw; the
  ATM no longer computes amount/fee/extra. LNURL-session map re-keyed on link_id
  (the secure response carries no unique_hash); settlement-watch half unchanged
  (subscribe_payments tag:'withdraw', link_id).

Server RPC is live on the dev stack (spirekeeper#32 registered create_withdraw),
so this is ready for the joint cash-in test. typecheck 12/12, full suite + prod
build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
padreug deleted branch cash-in-create-withdraw 2026-06-22 13:56:00 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/bitspire!66
No description provided.