Skip to content

feat(api): unified POST /pay endpoint for any lightning destination#13

Merged
martinsaposnic merged 2 commits intomasterfrom
feat/pay-unified-endpoint
May 6, 2026
Merged

feat(api): unified POST /pay endpoint for any lightning destination#13
martinsaposnic merged 2 commits intomasterfrom
feat/pay-unified-endpoint

Conversation

@martinsaposnic
Copy link
Copy Markdown
Contributor

Summary

  • Adds POST /pay alongside the existing /payinvoice. Accepts BOLT11, BOLT12 offers, LNURL, and lightning addresses (resolved via bitcoin-payment-instructions).
  • Blocks by default (30s, hard cap 50s); waitForPaymentSecs=0 short-circuits to fire-and-return PENDING.
  • Response sourced from PaymentDetails, not events — fields stay 1:1 consistent with GET /payments/outgoing/{paymentId} (preimage, feeSat, status). Pre-dispatch and resolver errors return ApiError; PayResponse{failed} is reserved for payments LDK accepted then later failed.
  • Mirrors lightning-js's malicious-LNURL amount check; rejects payerNote/quantity on BOLT11 destinations.
  • bitcoin-payment-instructions pinned to the same git rev ldk-node already pulls transitively to avoid duplicate compilation.
  • /payinvoice is unchanged.

Test plan

  • nix run .#integration-test (CI) — runs new test_pay_* block alongside existing tests:
    • Validation: invalid destination, amountSat=0, wait>50, BOLT11 amount mismatch, payerNote/quantity on BOLT11
    • LNURL: DNS-failure → 500, amount-mismatch via wiremock → 400
    • Dispatch failure handling
    • End-to-end: blocked-until-succeeded, wait=0 short-circuit
  • Manual smoke: curl -u :PASSWORD -d "destination=lnbcrt..." -d "amountSat=1000" http://localhost:.../pay

Deferred to vNext

  • Idempotency key (no protection against retry-induced double-pay; phoenixd doesn't have one either)
  • Streaming (SSE/WS) for the async case
  • BOLT12 e2e regtest test (regtest BOLT12 routing is finicky)
  • Lightning-address e2e test (HRN resolver hardcodes https://, needs HTTPS test server)

Notes

  • bitcoin-payment-instructions's HTTP resolver reports both connect-time errors and JSON parse errors with the same "Failed to fetch ..." string, so unparseable LNURL responses currently surface as 500 (not 400 as the original spec contemplated). Library patch needed to differentiate.
  • Local dev shell on macOS currently fails to build vss-server, so I couldn't run the integration tests locally. Relying on CI.

🤖 Generated with Claude Code

Adds POST /pay alongside the existing /payinvoice. Accepts BOLT11,
BOLT12 offers, LNURL, and lightning addresses; resolves them via
bitcoin-payment-instructions; dispatches through ldk-node.

Behavior:
- Blocks by default (30s, hard-capped at 50s for ALB/CloudFront idle).
- waitForPaymentSecs=0 short-circuits (fire-and-return PENDING).
- Reads PaymentDetails after wake-up so paymentHash/preimage/feeSat/status
  match GET /payments/outgoing/{paymentId}.
- Subscribes to MdkClient events BEFORE dispatch to avoid losing fast
  failures. Lagged receivers retry; closed sender treated as PENDING.
- Pre-dispatch and resolver errors return ApiError; PayResponse{failed}
  is reserved for payments LDK accepted then later failed.
- Mirrors lightning-js's malicious-LNURL amount check.
- payerNote/quantity rejected on BOLT11 destinations.

bitcoin-payment-instructions pinned to the same git rev that ldk-node
already pulls transitively, so no duplicate compilation.

Tests cover input validation, LNURL transport failure, malicious LNURL
amount mismatch (wiremock), dispatch failure, blocked-until-succeeded,
and wait=0 short-circuit.

/payinvoice is unchanged.
@martinsaposnic martinsaposnic requested a review from amackillop May 6, 2026 17:06
@amackillop
Copy link
Copy Markdown
Contributor

Need to do a followup to start porting the core logic into the library to prepare for lightning-js transition

@martinsaposnic
Copy link
Copy Markdown
Contributor Author

image

@martinsaposnic martinsaposnic merged commit 763ae2f into master May 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants