feat: add LNURLw withdraw support (Boltcard / LUD-17)#456
Conversation
142f045 to
b8b25d6
Compare
b8b25d6 to
308c780
Compare
|
Can we not rely on the emulator for the test? We're not going to be able to add that to CI which means we'd need to run it manually, which means it will likely break in the future. We shouldn't be relying on dependencies outside of nix too. Is there a more self-contained way (and ideally automated) that we can test LNURLw without a boltcard? |
|
Hi @m1sterc001guy. The constraint is that full end-to-end testing requires a Fedimint federation, which we don't have wired up for CI. The HTTP protocol parsing is already covered by Rust unit tests. What's missing is test coverage for the deep link URL conversion (lnurlw:// → https://), which we can add to the existing Flutter test suite with no new infrastructure. The invoice creation and payment completion path can't be tested without devimint, which would be a separate piece of work. i'll update that and remove the script for manual testing. |
fefb489 to
59d29e7
Compare
Implements the full LNURLw withdraw flow (LUD-03 / LUD-17) so users can
receive funds by tapping a Boltcard NFC card.
1. NFC tap fires lnurlw://host/path?k1=TOKEN deep link
2. App converts lnurlw:// → https:// and GETs the endpoint
3. Server responds with callback URL, k1, min/max withdrawable, description
4. App creates a Lightning invoice for the chosen amount
5. App GETs callback?k1=TOKEN&pr=<invoice>
6. Server pays the invoice — funds land in the wallet
Register the lnurlw scheme in the intent-filter so the OS routes Boltcard
deep links to this app.
- Add DeepLinkType.lnurlWithdraw enum value
- Parse lnurlw:// URIs: convert to https:// for clearnet hosts, http://
for localhost / 127.0.0.1 / 10.0.2.2 / .onion (enables the local test
harness without TLS)
Route DeepLinkType.lnurlWithdraw directly to LnurlWithdrawScreen, skipping
the Rust parse pipeline (type is already known from the scheme).
- LnurlWithdrawParams struct (#[frb]) with callback, k1, min/max msats,
defaultDescription
- fetch_lnurl_withdraw: HTTP GET + JSON parse; pure read, no side effects
- parse_lnurl_withdraw_response: private helper (testable without network);
checks status=ERROR, validates tag=withdrawRequest, extracts all fields
- execute_lnurl_withdraw: creates Lightning invoice via existing
multimint.receive(), GETs the callback URL, returns OperationId
- build_lnurlw_callback_url: appends k1 and pr with correct ? / & separator
- 7 unit tests covering happy path, optional fields, error responses,
wrong tag, missing fields, and callback URL building
4-state machine: loading → showingDetails → executing → waitingForPayment
- loading: fetches LNURLw params and gateway list in parallel
- showingDetails: fixed amounts show a static label; variable amounts show
a TextField (digits only, sats unit) validated against [min, max] with
inline error and disabled Withdraw button while out of range; gateway
picker row shown when multiple gateways available
- executing: spinner while invoice is created and callback is called
- waitingForPayment: spinner + Cancel button; awaitReceive with 5-minute
timeout; navigates to Success screen on payment, shows error toast on
timeout or failure, always pops to dashboard in finally block
New keys: lnurlWithdrawTitle, lnurlWithdrawConfirm, lnurlWithdrawRequesting,
lnurlWithdrawWaiting, lnurlWithdrawFailed, lnurlWithdrawCallbackError,
lnurlWithdrawFixedAmount, lnurlWithdrawAmountRange
Standalone Rust binary (no Python dependency):
- Binds to a random port, generates a random k1 from /dev/urandom
- Fires lnurlw:// deep link via open (macOS) or adb (--android flag)
- Serves LNURLw JSON on GET /lnurlw
- Validates callback: k1 match + bolt11 prefix check + percent-decode
- Returns {status:OK} or {status:ERROR} accordingly
- Exits 0 on success, 1 on failure or 60s timeout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
59d29e7 to
9f2312a
Compare
Implements issue #331. Boltcards use the lnurlw:// URI scheme (LUD-17) to pull Lightning payments into the user's wallet via the LNURL Withdraw protocol (LUD-03).
Changes:
Manual Testing Instructions
scripts/test-lnurlw/ is a self-contained Rust binary that acts as a mock Boltcard server. It replaces the earlier Python script and does the same job: spins up a local HTTP server, fires a lnurlw:// deep link to the running app, and validates that the app sends back the correct k1 token and a valid bolt11 invoice.
Prerequisites
Rust toolchain installed (cargo)
App built and running on device/emulator
Run on macOS (simulator or physical device via USB)
cd scripts/test-lnurlw
cargo run
Run on Android emulator
cd scripts/test-lnurlw
cargo run -- --android
What to expect
The terminal prints the mock server details and fires the deep link
The app opens to the Boltcard Withdraw screen showing "Boltcard test withdraw", amount range 1–100 sats
Select a gateway, enter or accept the amount, tap Withdraw
The terminal prints ✓ k1 matched then ✓ bolt11 invoice received
The app transitions to "Waiting for payment…" — at this point the mock server has accepted the invoice (it does not actually pay it, so the app will time out after 5 minutes and show a "Withdraw failed" toast, which is expected behaviour in the test)
The terminal exits with ✓ Test passed — LNURLw withdraw flow completed successfully.
Testing with a real Boltcard
Tap the card while the app is open and select a federation when prompted. The full flow completes including actual payment.
This covers both paths (mock + real card) and sets the right expectation that the mock server validates the protocol but does not actually pay the invoice