Skip to content

Conversation

@andrerfneves
Copy link

@andrerfneves andrerfneves commented Dec 20, 2025

Resolves #278

Summary

Implements LUD-21 which allows services to check whether a Lightning invoice has been paid by querying the LNURL server.

What is LUD-21?
When a wallet pays an LNURL invoice, the payer may want to verify that the payment was received (e.g., for proof of payment in e-commerce). LUD-21 defines a verify URL that returns the payment status and preimage.

Changes

Database Schema

Added new table lnurl_pay_invoices

Invoice Creation (/lnurlp/{user}/invoice)

When creating an invoice, we now:

  1. Store the res.id from create_lightning_invoice() as lightning_receive_id
  2. Store the res.invoice as bolt11_invoice
  3. Return a verify URL in the response

Verify Endpoint (/lnurlp/{user}/verify?pr=lnbc...)

  1. Parse the invoice from pr query param to extract payment_hash
  2. Look up the stored lightning_receive_id from the database
  3. Call wallet.fetch_lightning_receive_payment() to get current payment status
  4. Return LUD-21 compliant response with settled and preimage

Assumptions

  1. Payment is settled when preimage exists: We determine settled: true by checking if payment.payment_preimage.is_some(). This assumes Spark only populates the preimage field once the payment is fully received.
  2. Verify requires the full invoice: Per LUD-21, the verify endpoint requires the pr (invoice) as a query parameter. We parse it to extract the payment hash for lookup.
  3. Domain/username validation: We verify the invoice belongs to the user in the URL path to prevent cross-user invoice status leakage.

Test Plan

  • Create an invoice via /lnurlp/{user}/invoice?amount=10000
  • Verify the response includes a verify URL
  • Call verify endpoint before payment → returns {"settled": false, "preimage": null, ...}
  • Pay the invoice via Lightning Network
  • Call verify endpoint after payment → returns {"settled": true, "preimage": "...", ...}
  • Verify that querying with an invoice from a different user returns an error
  • Verify that querying with an unknown invoice returns "invoice not found"

- Add proper LUD-21 support to check if Lightning invoices have been paid.
- The verify endpoint now queries Spark wallet for payment status and returns the settled state with preimage when available.
@andrerfneves
Copy link
Author

Screenshot 2025-12-20 at 11 28 18 AM
2025-12-20T06:50:15.849366Z  INFO breez_sdk_itest::faucet: Initialized faucet client with URL: https://api.lightspark.com/graphql/spark/rc
2025-12-20T06:50:15.849384Z  INFO breez_sdk_itest::helpers: Funding address bcrt1phc2r7xanyvkvax0pgdlpwnqa37s4mw5v6maeuavddfunqpqrku9qwqtp8z with 10000 sats from faucet
2025-12-20T06:50:15.849390Z  INFO breez_sdk_itest::faucet: Requesting funds from faucet: 10000 sats to address bcrt1phc2r7xanyvkvax0pgdlpwnqa37s4mw5v6maeuavddfunqpqrku9qwqtp8z
Error: Faucet request failed with status 401 Unauthorized: <!doctype html>
<html lang=en>
<title>401 Unauthorized</title>
<h1>Unauthorized</h1>
<p>The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn&#39;t understand how to supply the credentials required.</p>



failures:
    test_01_htlc_success
    test_02_htlc_refund

test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.64s

error: test failed, to rerun pass `-p breez-sdk-itest --test spark_htlcs`
     Running tests/tokens.rs (target/debug/deps/tokens-d9436809815809b6)

running 6 tests
test test_01_token_transfer ... ok
test test_02_token_invoice ... ok
test test_03_token_burning ... ok
test test_04_token_freeze_unfreeze ... ok
test test_05_invoice_expiry ... ok
test test_06_supply_limits ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 249.81s

     Running tests/user_settings.rs (target/debug/deps/user_settings-38936128f2dbf2a9)

running 1 test
test test_01_spark_private_mode_user_setting ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.07s

   Doc-tests breez_sdk_itest

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: 6 targets failed:
    `-p breez-sdk-itest --test breez_sdk_tests`
    `-p breez-sdk-itest --test deposit_withdraw`
    `-p breez-sdk-itest --test idempotency_tests`
    `-p breez-sdk-itest --test lnurl`
    `-p breez-sdk-itest --test rtsync`
    `-p breez-sdk-itest --test spark_htlcs`
Error: tests failed
make: *** [Makefile:62: breez-itest] Error 1
Error: Process completed with exit code 2.
0s
0s

Integration tests look to be failing due to something unrelated -- faucet issues it seems.

@JssDWt
Copy link
Collaborator

JssDWt commented Dec 22, 2025

Hey Andre, thanks for the PR!

The lnurl server's wallet is not the user's wallet. This means the payment will not be available inside the wallet field on the state. This means that we need another mechanism to determine whether the invoice was paid or not. We have a similar mechanism for that for nostr zap receipts. We can reuse the same mechanism for lnurl-verify. It works in 2 different ways:

  • if privacy mode is not enabled, the server subscribes to the invoice until it's paid. See subscribe_to_user_for_zaps.
  • if privacy mode is enabled, the client should notify the server that the invoice was paid. Currently that is done by publishing the zap receipt. I think we can simplify that by simply calling an endpoint on the server, telling it the invoice was paid.

So if I would suggest a solution:

  • Create an endpoint on the lnurl server to mark an invoice as paid
  • Rewrite subscribe_to_user_for_zaps (server side) to handle any invoice, not just zaps
  • Rewrite process_pending_zap_receipts (client side) to call the new endpoint to mark invoice as paid, and let the server publish the zap receipt instead of the client.

Writing it up like this, that sounds like a large change. Let me know whether you want to take that or leave it to us.

@roeierez roeierez requested a review from JssDWt December 22, 2025 13:20
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.

LNURL Verify

2 participants