From 07e676ced5c48581ed65ffb683a9b2e935014a0a Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Tue, 29 Apr 2025 13:38:56 +0200 Subject: [PATCH 1/7] Add BOLT12 receive docs --- examples/python/cli/cli.py | 6 +- snippets/csharp/ParsingInputs.cs | 4 + snippets/csharp/ReceivePayment.cs | 20 ++++ .../dart_snippets/lib/parsing_inputs.dart | 2 + .../dart_snippets/lib/receive_payment.dart | 14 +++ snippets/go/parsing_inputs.go | 4 + snippets/go/receive_payment.go | 14 +++ .../com/example/kotlinmpplib/ParsingInputs.kt | 3 + .../example/kotlinmpplib/ReceivePayment.kt | 16 +++ snippets/python/src/parsing_inputs.py | 2 + snippets/python/src/receive_payment.py | 18 ++++ snippets/react-native/parsing_inputs.ts | 8 +- snippets/react-native/receive_payment.ts | 13 +++ snippets/rust/src/parsing_inputs.rs | 7 ++ snippets/rust/src/receive_payment.rs | 17 ++++ .../Sources/ParsingInputs.swift | 3 + .../Sources/ReceivePayment.swift | 16 +++ snippets/wasm/parsing_inputs.ts | 6 ++ snippets/wasm/receive_payment.ts | 13 +++ src/guide/lnurl_pay_service.md | 31 ++++-- src/guide/payments.md | 4 +- src/guide/receive_payment.md | 98 ++++++++++++++++++- src/guide/send_payment.md | 4 + src/notifications/custom_messages.md | 2 + src/notifications/getting_started.md | 35 ++++++- 25 files changed, 340 insertions(+), 20 deletions(-) diff --git a/examples/python/cli/cli.py b/examples/python/cli/cli.py index d48ad0b3..a3cc5ec4 100644 --- a/examples/python/cli/cli.py +++ b/examples/python/cli/cli.py @@ -176,8 +176,8 @@ def receive_payment(params): Args: params (argparse.Namespace): Command-line arguments containing: - - method (str): The payment method (e.g., 'LIGHTNING', 'BITCOIN_ADDRESS', 'LIQUID_ADDRESS') - - amount_sat (int): The amount to receive in satoshis + - method (str): The payment method (e.g., 'LIGHTNING', 'BOLT12_OFFER', 'BITCOIN_ADDRESS', 'LIQUID_ADDRESS') + - amount_sat (int): The optional amount to receive in satoshis - asset_id (str): The optional id of the asset to receive - amount (float): The optional amount to receive of the asset @@ -311,7 +311,7 @@ def main(): # receive receive_parser = subparser.add_parser('receive', help='Receive a payment') receive_parser.add_argument('-m', '--method', - choices=['LIGHTNING', 'BITCOIN_ADDRESS', 'LIQUID_ADDRESS'], + choices=['LIGHTNING', 'BOLT12_OFFER', 'BITCOIN_ADDRESS', 'LIQUID_ADDRESS'], help='The payment method', required=True) receive_parser.add_argument('-a', '--amount_sat', diff --git a/snippets/csharp/ParsingInputs.cs b/snippets/csharp/ParsingInputs.cs index 7c83708e..82a2f789 100644 --- a/snippets/csharp/ParsingInputs.cs +++ b/snippets/csharp/ParsingInputs.cs @@ -24,6 +24,10 @@ public void ParseInput(BindingLiquidSdk sdk) Console.WriteLine($"Input is BOLT11 invoice for {amount} msats"); break; + case InputType.Bolt12Offer bolt12: + Console.WriteLine($"Input is BOLT12 offer for min {bolt12.offer.minAmount} msats - BIP353 was used: {bolt12.bip353Address != null}"); + break; + case InputType.LnUrlPay lnUrlPay: Console.WriteLine( $"Input is LNURL-Pay/Lightning address accepting min/max {lnUrlPay.data.minSendable}/{lnUrlPay.data.maxSendable} msats - BIP353 was used: {lnUrlPay.bip353Address != null}" diff --git a/snippets/csharp/ReceivePayment.cs b/snippets/csharp/ReceivePayment.cs index b21c449e..9335caee 100644 --- a/snippets/csharp/ReceivePayment.cs +++ b/snippets/csharp/ReceivePayment.cs @@ -28,6 +28,26 @@ public void PrepareReceiveLightning(BindingLiquidSdk sdk) // ANCHOR_END: prepare-receive-payment-lightning } + public void PrepareReceiveLightningBolt12(BindingLiquidSdk sdk) + { + // ANCHOR: prepare-receive-payment-lightning-bolt12 + try + { + var prepareRequest = new PrepareReceiveRequest(PaymentMethod.Bolt12Offer); + var prepareResponse = sdk.PrepareReceivePayment(prepareRequest); + + // If the fees are acceptable, continue to create the Receive Payment + var minReceiveFeesSat = prepareResponse.feesSat; + var swapperFeerate = prepareResponse.swapperFeerate; + Console.WriteLine($"Fees: {minReceiveFeesSat} sats + {swapperFeerate}% of the sent amount"); + } + catch (Exception) + { + // Handle error + } + // ANCHOR_END: prepare-receive-payment-lightning-bolt12 + } + public void PrepareReceiveOnchain(BindingLiquidSdk sdk) { // ANCHOR: prepare-receive-payment-onchain diff --git a/snippets/dart_snippets/lib/parsing_inputs.dart b/snippets/dart_snippets/lib/parsing_inputs.dart index b3ec18aa..b1c70ec1 100644 --- a/snippets/dart_snippets/lib/parsing_inputs.dart +++ b/snippets/dart_snippets/lib/parsing_inputs.dart @@ -12,6 +12,8 @@ Future parseInput() async { String amountStr = inputType.invoice.amountMsat != null ? inputType.invoice.amountMsat.toString() : "unknown"; print("Input is BOLT11 invoice for $amountStr msats"); + } else if (inputType is InputType_Bolt12Offer) { + print("Input is BOLT12 offer for min ${inputType.offer.minAmount} msats - BIP353 was used: ${inputType.bip353Address != null}"); } else if (inputType is InputType_LnUrlPay) { print( "Input is LNURL-Pay/Lightning address accepting min/max ${inputType.data.minSendable}/${inputType.data.maxSendable} msats - BIP353 was used: ${inputType.bip353Address != null}"); diff --git a/snippets/dart_snippets/lib/receive_payment.dart b/snippets/dart_snippets/lib/receive_payment.dart index 6801bb87..f72eb010 100644 --- a/snippets/dart_snippets/lib/receive_payment.dart +++ b/snippets/dart_snippets/lib/receive_payment.dart @@ -25,6 +25,20 @@ Future prepareReceivePaymentLightning() async { return prepareResponse; } +Future prepareReceivePaymentLightningBolt12() async { + // ANCHOR: prepare-receive-payment-lightning-bolt12 + PrepareReceiveResponse prepareResponse = await breezSDKLiquid.instance!.prepareReceivePayment( + req: PrepareReceiveRequest(paymentMethod: PaymentMethod.bolt12Offer), + ); + + // If the fees are acceptable, continue to create the Receive Payment + BigInt minReceiveFeesSat = prepareResponse.feesSat; + double? swapperFeerate = prepareResponse.swapperFeerate; + print("Fees: $minReceiveFeesSat sats + $swapperFeerate% of the sent amount"); + // ANCHOR_END: prepare-receive-payment-lightning-bolt12 + return prepareResponse; +} + Future prepareReceivePaymentOnchain() async { // ANCHOR: prepare-receive-payment-onchain // Fetch the Receive onchain limits diff --git a/snippets/go/parsing_inputs.go b/snippets/go/parsing_inputs.go index 6e38326f..0701f28f 100644 --- a/snippets/go/parsing_inputs.go +++ b/snippets/go/parsing_inputs.go @@ -23,6 +23,10 @@ func ParseInput(sdk *breez_sdk_liquid.BindingLiquidSdk) { } log.Printf("Input is BOLT11 invoice for %s msats", amount) + case breez_sdk_liquid.InputTypeBolt12Offer: + log.Printf("Input is BOLT12 offer for min %v msats - BIP353 was used: %t", + inputType.Offer.MinAmount, inputType.Bip353Address != nil) + case breez_sdk_liquid.InputTypeLnUrlPay: log.Printf("Input is LNURL-Pay/Lightning address accepting min/max %d/%d msats - BIP353 was used: %t", inputType.Data.MinSendable, inputType.Data.MaxSendable, inputType.Bip353Address != nil) diff --git a/snippets/go/receive_payment.go b/snippets/go/receive_payment.go index 6bc08fbc..449f47d3 100644 --- a/snippets/go/receive_payment.go +++ b/snippets/go/receive_payment.go @@ -30,6 +30,20 @@ func PrepareReceiveLightning(sdk *breez_sdk_liquid.BindingLiquidSdk) { // ANCHOR_END: prepare-receive-payment-lightning } +func PrepareReceiveLightningBolt12(sdk *breez_sdk_liquid.BindingLiquidSdk) { + // ANCHOR: prepare-receive-payment-lightning-bolt12 + prepareRequest := breez_sdk_liquid.PrepareReceiveRequest{ + PaymentMethod: breez_sdk_liquid.PaymentMethodBolt12Offer, + } + if prepareResponse, err := sdk.PrepareReceivePayment(prepareRequest); err == nil { + // If the fees are acceptable, continue to create the Receive Payment + minReceiveFeesSat := prepareResponse.FeesSat + swapperFeerate := prepareResponse.SwapperFeerate + log.Printf("Fees: %v sats + %v%% of the sent amount", minReceiveFeesSat, swapperFeerate) + } + // ANCHOR_END: prepare-receive-payment-lightning-bolt12 +} + func PrepareReceiveOnchain(sdk *breez_sdk_liquid.BindingLiquidSdk) { // ANCHOR: prepare-receive-payment-onchain // Fetch the onchain Receive limits diff --git a/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ParsingInputs.kt b/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ParsingInputs.kt index 4491de6a..c647ff0e 100644 --- a/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ParsingInputs.kt +++ b/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ParsingInputs.kt @@ -16,6 +16,9 @@ class ParsingInputs { val amountStr = inputType.invoice.amountMsat?.toString() ?: "unknown" println("Input is BOLT11 invoice for $amountStr msats") } + is InputType.Bolt12Offer -> { + println("Input is BOLT12 offer for min ${inputType.offer.minAmount} msats - BIP353 was used: ${inputType.bip353Address != null}") + } is InputType.LnUrlPay -> { println("Input is LNURL-Pay/Lightning address accepting min/max " + "${inputType.data.minSendable}/${inputType.data.maxSendable} msats - BIP353 was used: ${inputType.bip353Address != null}") diff --git a/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt b/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt index 53100b9e..7d3a5a48 100644 --- a/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt +++ b/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt @@ -23,6 +23,22 @@ class ReceivePayment { } // ANCHOR_END: prepare-receive-payment-lightning } + + fun prepareReceiveLightningBolt12(sdk: BindingLiquidSdk) { + // ANCHOR: prepare-receive-payment-lightning-bolt12 + try { + val prepareRequest = PrepareReceiveRequest(PaymentMethod.BOLT12_OFFER) + val prepareResponse = sdk.prepareReceivePayment(prepareRequest) + + // If the fees are acceptable, continue to create the Receive Payment + val minReceiveFeesSat = prepareResponse.feesSat; + val swapperFeerate = prepareResponse.swapperFeerate; + // Log.v("Breez", "Fees: ${minReceiveFeesSat} sats + ${swapperFeerate}% of the sent amount") + } catch (e: Exception) { + // handle error + } + // ANCHOR_END: prepare-receive-payment-lightning-bolt12 + } fun prepareReceiveOnchain(sdk: BindingLiquidSdk) { // ANCHOR: prepare-receive-payment-onchain diff --git a/snippets/python/src/parsing_inputs.py b/snippets/python/src/parsing_inputs.py index f8533f56..233fcc97 100644 --- a/snippets/python/src/parsing_inputs.py +++ b/snippets/python/src/parsing_inputs.py @@ -14,6 +14,8 @@ def parse_input(sdk: BindingLiquidSdk): if parsed_input.invoice.amount_msat: amount = str(parsed_input.invoice.amount_msat) logging.debug(f"Input is BOLT11 invoice for {amount} msats") + elif isinstance(parsed_input, InputType.BOLT12_OFFER): + logging.debug(f"Input is BOLT12 offer for min {parsed_input.offer.min_amount} msats - BIP353 was used: {parsed_input.bip353_address is not None}") elif isinstance(parsed_input, InputType.LN_URL_PAY): logging.debug(f"Input is LNURL-Pay/Lightning address accepting min/max {parsed_input.data.min_sendable}/{parsed_input.data.max_sendable} msats - BIP353 was used: {parsed_input.bip353_address is not None}") elif isinstance(parsed_input, InputType.LN_URL_WITHDRAW): diff --git a/snippets/python/src/receive_payment.py b/snippets/python/src/receive_payment.py index ce8c2858..011e4c31 100644 --- a/snippets/python/src/receive_payment.py +++ b/snippets/python/src/receive_payment.py @@ -27,6 +27,24 @@ def prepare_receive_lightning(sdk: BindingLiquidSdk): raise # ANCHOR_END: prepare-receive-payment-lightning +def prepare_receive_lightning_bolt12(sdk: BindingLiquidSdk): + # ANCHOR: prepare-receive-payment-lightning-bolt12 + try: + prepare_request = PrepareReceiveRequest( + payment_method=PaymentMethod.BOLT12_OFFER + ) + prepare_response = sdk.prepare_receive_payment(prepare_request) + + # If the fees are acceptable, continue to create the Receive Payment + min_receive_fees_sat = prepare_response.fees_sat + swapper_feerate = prepare_response.swapper_feerate + logging.debug(f"Fees: {min_receive_fees_sat} sats + {swapper_feerate}% of the sent amount") + return prepare_response + except Exception as error: + logging.error(error) + raise + # ANCHOR_END: prepare-receive-payment-lightning-bolt12 + def prepare_receive_onchain(sdk: BindingLiquidSdk): # ANCHOR: prepare-receive-payment-onchain try: diff --git a/snippets/react-native/parsing_inputs.ts b/snippets/react-native/parsing_inputs.ts index be496d56..1744661b 100644 --- a/snippets/react-native/parsing_inputs.ts +++ b/snippets/react-native/parsing_inputs.ts @@ -21,12 +21,18 @@ const parseInputs = async () => { console.log( `Input is BOLT11 invoice for ${ parsed.invoice.amountMsat != null - ? parsed.invoice.amountMsat.toString() + ? JSON.stringify(parsed.invoice.amountMsat) : 'unknown' } msats` ) break + case InputTypeVariant.BOLT12_OFFER: + console.log( + `Input is BOLT12 offer for min ${JSON.stringify(parsed.offer.minAmount)} msats - BIP353 was used: ${parsed.bip353Address != null}` + ) + break + case InputTypeVariant.LN_URL_PAY: console.log( `Input is LNURL-Pay/Lightning address accepting min/max ${parsed.data.minSendable}/${parsed.data.maxSendable} msats - BIP353 was used: ${parsed.bip353Address != null}` diff --git a/snippets/react-native/receive_payment.ts b/snippets/react-native/receive_payment.ts index 97b7efdb..21a89658 100644 --- a/snippets/react-native/receive_payment.ts +++ b/snippets/react-native/receive_payment.ts @@ -35,6 +35,19 @@ const examplePrepareLightningPayment = async () => { // ANCHOR_END: prepare-receive-payment-lightning } +const examplePrepareLightningBolt12Payment = async () => { + // ANCHOR: prepare-receive-payment-lightning-bolt12 + const prepareResponse = await prepareReceivePayment({ + paymentMethod: PaymentMethod.BOLT12_OFFER + }) + + // If the fees are acceptable, continue to create the Receive Payment + const minReceiveFeesSat = prepareResponse.feesSat + const swapperFeerate = prepareResponse.swapperFeerate + console.log(`Fees: ${minReceiveFeesSat} sats + ${swapperFeerate}% of the sent amount`) + // ANCHOR_END: prepare-receive-payment-lightning-bolt12 +} + const examplePrepareOnchainPayment = async () => { // ANCHOR: prepare-receive-payment-onchain // Fetch the Onchain lightning limits diff --git a/snippets/rust/src/parsing_inputs.rs b/snippets/rust/src/parsing_inputs.rs index b656c285..bad7faaf 100644 --- a/snippets/rust/src/parsing_inputs.rs +++ b/snippets/rust/src/parsing_inputs.rs @@ -21,6 +21,13 @@ async fn parse_input(sdk: Arc) -> Result<()> { .map_or("unknown".to_string(), |a| a.to_string()) ); } + InputType::Bolt12Offer { offer, bip353_address } => { + println!( + "Input is BOLT12 offer for min {:?} msats - BIP353 was used: {}", + offer.min_amount, + bip353_address.is_some() + ); + } InputType::LnUrlPay { data, bip353_address } => { println!( "Input is LNURL-Pay/Lightning address accepting min/max {}/{} msats - BIP353 was used: {}", diff --git a/snippets/rust/src/receive_payment.rs b/snippets/rust/src/receive_payment.rs index c12cadaf..5d52cfa9 100644 --- a/snippets/rust/src/receive_payment.rs +++ b/snippets/rust/src/receive_payment.rs @@ -29,6 +29,23 @@ async fn prepare_receive_lightning(sdk: Arc) -> Result<()> { Ok(()) } +async fn prepare_receive_lightning_bolt12(sdk: Arc) -> Result<()> { + // ANCHOR: prepare-receive-payment-lightning-bolt12 + let prepare_response = sdk + .prepare_receive_payment(&PrepareReceiveRequest { + payment_method: PaymentMethod::Bolt12Offer, + amount: None, + }) + .await?; + + // If the fees are acceptable, continue to create the Receive Payment + let min_receive_fees_sat = prepare_response.fees_sat; + let swapper_feerate = prepare_response.swapper_feerate; + info!("Fees: {} sats + {:?}% of the sent amount", min_receive_fees_sat, swapper_feerate); + // ANCHOR_END: prepare-receive-payment-lightning-bolt12 + Ok(()) +} + async fn prepare_receive_onchain(sdk: Arc) -> Result<()> { // ANCHOR: prepare-receive-payment-onchain // Fetch the Receive onchain limits diff --git a/snippets/swift/BreezSDKExamples/Sources/ParsingInputs.swift b/snippets/swift/BreezSDKExamples/Sources/ParsingInputs.swift index 1a4452be..6bf347ed 100644 --- a/snippets/swift/BreezSDKExamples/Sources/ParsingInputs.swift +++ b/snippets/swift/BreezSDKExamples/Sources/ParsingInputs.swift @@ -15,6 +15,9 @@ func parseInput(sdk: BindingLiquidSdk) { let amount = invoice.amountMsat.map { String($0) } ?? "unknown" print("Input is BOLT11 invoice for \(amount) msats") + case .bolt12Offer(let offer, let bip353Address): + print("Input is BOLT12 offer for min \(offer.minAmount) msats - BIP353 was used: \(bip353Address != nil)") + case .lnUrlPay(let data, let bip353Address): print( "Input is LNURL-Pay/Lightning address accepting min/max \(data.minSendable)/\(data.maxSendable) msats - BIP353 was used: \(bip353Address != nil)" diff --git a/snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift b/snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift index 77bc5f7c..8244b5b3 100644 --- a/snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift +++ b/snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift @@ -23,6 +23,22 @@ func prepareReceiveLightning(sdk: BindingLiquidSdk) -> PrepareReceiveResponse? { return prepareResponse } +func prepareReceiveLightningBolt12(sdk: BindingLiquidSdk) -> PrepareReceiveResponse? { + // ANCHOR: prepare-receive-payment-lightning-bolt12 + let prepareResponse = try? sdk + .prepareReceivePayment(req: PrepareReceiveRequest( + paymentMethod: PaymentMethod.bolt12Offer + )); + + // If the fees are acceptable, continue to create the Receive Payment + let minReceiveFeesSat = prepareResponse!.feesSat; + let swapperFeerate = prepareResponse!.swapperFeerate; + print("Fees: {} sats + {}% of the sent amount", minReceiveFeesSat, swapperFeerate); + // ANCHOR_END: prepare-receive-payment-lightning-bolt12 + + return prepareResponse +} + func prepareReceiveOnchain(sdk: BindingLiquidSdk) -> PrepareReceiveResponse? { // ANCHOR: prepare-receive-payment-onchain // Fetch the Receive onchain limits diff --git a/snippets/wasm/parsing_inputs.ts b/snippets/wasm/parsing_inputs.ts index 040abcfb..9220f32d 100644 --- a/snippets/wasm/parsing_inputs.ts +++ b/snippets/wasm/parsing_inputs.ts @@ -19,6 +19,12 @@ const parseInputs = async (sdk: BindingLiquidSdk) => { ) break + case 'bolt12Offer': + console.log( + `Input is BOLT12 offer for min ${JSON.stringify(parsed.offer.minAmount)} msats - BIP353 was used: ${parsed.bip353Address != null}` + ) + break + case 'lnUrlPay': console.log( `Input is LNURL-Pay/Lightning address accepting min/max ${parsed.data.minSendable}/${ diff --git a/snippets/wasm/receive_payment.ts b/snippets/wasm/receive_payment.ts index 4a7c6408..fd181344 100644 --- a/snippets/wasm/receive_payment.ts +++ b/snippets/wasm/receive_payment.ts @@ -28,6 +28,19 @@ const examplePrepareLightningPayment = async (sdk: BindingLiquidSdk) => { // ANCHOR_END: prepare-receive-payment-lightning } +const examplePrepareLightningBolt12Payment = async (sdk: BindingLiquidSdk) => { + // ANCHOR: prepare-receive-payment-lightning-bolt12 + const prepareResponse = await sdk.prepareReceivePayment({ + paymentMethod: 'bolt12Offer' + }) + + // If the fees are acceptable, continue to create the Receive Payment + const minReceiveFeesSat = prepareResponse.feesSat + const swapperFeerate = prepareResponse.swapperFeerate + console.log(`Fees: ${minReceiveFeesSat} sats + ${swapperFeerate}% of the sent amount`) + // ANCHOR_END: prepare-receive-payment-lightning-bolt12 +} + const examplePrepareOnchainPayment = async (sdk: BindingLiquidSdk) => { // ANCHOR: prepare-receive-payment-onchain // Fetch the Onchain lightning limits diff --git a/src/guide/lnurl_pay_service.md b/src/guide/lnurl_pay_service.md index 6a7faf03..1a7b2e78 100644 --- a/src/guide/lnurl_pay_service.md +++ b/src/guide/lnurl_pay_service.md @@ -1,6 +1,8 @@ # Receiving payments using LNURL-Pay -Breez SDK - Nodeless *(Liquid Implementation)* users have the ability to receive Lightning payments using [LNURL-Pay](https://github.com/lnurl/luds/blob/luds/06.md). +Breez SDK - Nodeless *(Liquid Implementation)* users have the ability to receive Lightning payments using [LNURL-Pay](https://github.com/lnurl/luds/blob/luds/06.md) and [BOLT12 offers](receive_payment.html#bolt12-offer). + +#### LNURL-Pay LNURL-Pay requires a web service that serves LNURL-Pay requests. This service needs to communicate with the SDK in order to fetch the necessary metadata data and the associated payment request. To interact with the SDK, the service uses a simple protocol over push notifications: @@ -8,19 +10,24 @@ To interact with the SDK, the service uses a simple protocol over push notificat * The app responds to reply URL with the required data. * The service forwards the data to the payer. +#### BOLT12 offer + +Unlike LNURL-Pay, BOLT12 offers do not require a web service that serves requests. Instead when someone wants to pay a BOLT12 offer they use the Lightning network to request a BOLT12 invoice. That BOLT12 offer can also be made available via a DNS TXT lookup using [BIP353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki). You can do this in the workflow below by registering both a `username` and `offer`. + ## General workflow The following workflow is application specific and the steps detailed below refer to the misty-breez wallet implementation which requires running [breez-lnurl](https://github.com/breez/breez-lnurl) service. ![pay](https://github.com/breez/breez-sdk-docs/assets/5394889/ef0a3111-3604-4789-89c6-23adbd7e5d52) -### Step 1: Registering for an LNURL-Pay service -Use a POST request to the service endpoint ```https://app.domain/lnurlpay/[pubkey]``` with the following payload to register the app for an LNURL-Pay service: +### Step 1: Registering with the service +Use a POST request to the service endpoint ```https://app.domain/lnurlpay/[pubkey]``` with the following payload to register: ```json { "time": 1231006505, // Current UNIX timestamp "webhook_url": "[notification service webhook URL]", "username": "[optional username]", + "offer": "[optional offer]", "signature": "[signed message]" } ``` @@ -35,6 +42,10 @@ or, when the optional `username` field is set: [time]-[webhook_url]-[username] ``` where `time`, `webhook_url` and `username` are the payload fields. +``` +[time]-[webhook_url]-[username]-[offer] +``` +where `time`, `webhook_url`, `username` and `offer` are the payload fields. The service responds with following payload: ```json @@ -44,6 +55,8 @@ The service responds with following payload: } ``` +When registering with both `username` and `offer` the resulting `lightning_address` is both BIP353 and LNURL-Pay compatible, meaning it can be paid to via both BOLT12 offer/invoice or LNURL-Pay depending how the payer's client chooses to parse it. +

Developer note

@@ -51,7 +64,7 @@ When a user changes their already registered username, this previous username is
-### Step 2: Processing an LNURL-Pay request +### Step 2: Processing an LNURL-Pay request (LNURL-Pay only) When an LNURL-Pay GET request is received at ```https://app.domain/lnurlp/[identifier]``` (or ```https://app.domain/.well-known/lnurlp/[identifier]``` for lightning addresses) the service then sends a push notification to the app with the LNURL-Pay request and a callback URL. The payload may look like the following: ```json @@ -67,7 +80,7 @@ When an LNURL-Pay GET request is received at ```https://app.domain/lnurlp/[ident The ```reply_url``` is used by the app to respond to the LNURL-Pay request. The ```callback_url``` is the LNURL-Pay callback URL, used by the payer to fetch the invoice. -### Step 3: Responding to the callback url +### Step 3: Responding to the callback URL (LNURL-Pay only) When the app receives the push notification, it parses the payload and then uses the ```reply_url``` to respond with the required data, for example: ```json @@ -82,16 +95,16 @@ When the app receives the push notification, it parses the payload and then uses The service receives the response from the app and forwards it to the sender. -### Step 4: Fetching a bolt11 invoice +### Step 4: Fetching a BOLT11 invoice (LNURL-Pay only) -The sender fetches a bolt11 invoice by invoking a GET request to the ```callback``` URL when a specific amount is added as a query parameter. For example: +The sender fetches a BOLT11 invoice by invoking a GET request to the ```callback``` URL when a specific amount is added as a query parameter. For example: ``` https://app.domain/lnurlpay/[identifier]/invoice?amount=1000 ``` -An additional push notification is triggered to send the invoice request to the app. Then the app responds with the bolt11 invoice data. +An additional push notification is triggered to send the invoice request to the app. Then the app responds with the BOLT11 invoice data. ### Step 5: Paying the invoice -In the last step, the payer pays the received bolt11 invoice. Follow the steps [here](/notifications/getting_started.md) to receive payments via push notifications. +In the last step, the payer pays the received invoice. Follow the steps [here](/notifications/getting_started.md) to receive payments via push notifications. ## Reference implementation For a complete reference implementation, see: diff --git a/src/guide/payments.md b/src/guide/payments.md index 4ce65753..2bb38b88 100644 --- a/src/guide/payments.md +++ b/src/guide/payments.md @@ -1,8 +1,8 @@ # Payment fundamentals This section details sending and receiving payments: -- **[Receiving payments]** via a bolt11 invoice, Liquid/Bitcoin BIP21 or plain Liquid address -- **[Sending payments]** via a bolt11 invoice or Liquid/Bitcoin BIP21 +- **[Receiving payments]** via a Lightning invoice, Liquid/Bitcoin BIP21 or plain Liquid address +- **[Sending payments]** via a Lightning invoice or Liquid/Bitcoin BIP21 - **[Listing payments]** that have been sent or received in the past [Receiving payments]: receive_payment.md diff --git a/src/guide/receive_payment.md b/src/guide/receive_payment.md index 31cc10ee..a65f8653 100644 --- a/src/guide/receive_payment.md +++ b/src/guide/receive_payment.md @@ -20,11 +20,15 @@ During the prepare step, the SDK ensures that the inputs are valid with respect and also returns the relative fees related to the payment so they can be confirmed. -The SDK currently supports three methods of receiving: Lightning, Bitcoin and Liquid: +The SDK currently supports three methods of receiving: Lightning, Bitcoin and Liquid. ### Lightning -When receiving via Lightning, we generate an invoice to be paid. Note that the payment may fallback to a direct Liquid payment (if the payer's client supports this). +Two types of Lightning destinations are possible: BOLT11 invoices and BOLT12 offers. + +#### BOLT11 invoice + +When receiving via Lightning, we can generate a BOLT11 invoice to be paid. Note that the payment may fallback to a direct Liquid payment (if the payer's client supports this). **Note:** The amount field is currently mandatory when paying via Lightning. @@ -102,6 +106,96 @@ When receiving via Lightning, we generate an invoice to be paid. Note that the +#### BOLT12 offer + +A BOLT12 offer is a static payment code that can be paid to multiple times. When a payer wishes to pay the BOLT12 offer, the SDK is communicated with via a Web Socket stream when active, or when offline via a [registered webhook](using_webhooks.md). + +**Note:** The amount field should **not** be set. + + +
Rust
+
+ +```rust,ignore +{{#include ../../snippets/rust/src/receive_payment.rs:prepare-receive-payment-lightning-bolt12}} +``` +
+ +
Swift
+
+ +```swift,ignore +{{#include ../../snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift:prepare-receive-payment-lightning-bolt12}} +``` +
+ +
Kotlin
+
+ +```kotlin,ignore +{{#include ../../snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt:prepare-receive-payment-lightning-bolt12}} +``` +
+ +
Javascript
+
+ +```typescript +{{#include ../../snippets/wasm/receive_payment.ts:prepare-receive-payment-lightning-bolt12}} +``` +
+ +
React Native
+
+ +```typescript +{{#include ../../snippets/react-native/receive_payment.ts:prepare-receive-payment-lightning-bolt12}} +``` +
+ +
Dart
+
+ +```dart,ignore +{{#include ../../snippets/dart_snippets/lib/receive_payment.dart:prepare-receive-payment-lightning-bolt12}} +``` +
+ +
Python
+
+ +```python,ignore +{{#include ../../snippets/python/src/receive_payment.py:prepare-receive-payment-lightning-bolt12}} +``` +
+ +
Go
+
+ +```go,ignore +{{#include ../../snippets/go/receive_payment.go:prepare-receive-payment-lightning-bolt12}} +``` +
+ +
C#
+
+ +```cs,ignore +{{#include ../../snippets/csharp/ReceivePayment.cs:prepare-receive-payment-lightning-bolt12}} +``` +
+
+ +
+

Developer note

+A webhook URL must be registered to receive BOLT12 invoice requests when the SDK is offline. +
+ +
+

Developer note

+Consider implementing the Notification Plugin when using the Breez SDK in a mobile application. By registering a webhook the application can receive notifications to process the BOLT12 invoice request in the background. +
+ ### Bitcoin When receiving via Bitcoin, we generate a Bitcoin BIP21 URI to be paid. diff --git a/src/guide/send_payment.md b/src/guide/send_payment.md index 6fe22061..62949b66 100644 --- a/src/guide/send_payment.md +++ b/src/guide/send_payment.md @@ -22,6 +22,8 @@ The `destination` field of the payment request supports Liquid BIP21, Liquid add ### Lightning Two types of Lightning destinations are possible: BOLT11 invoices and BOLT12 offers. +#### BOLT11 invoice + For BOLT11 invoices, the amount **must** be set. If the optional prepare request amount is also set, the SDK will make sure the two values match, else an error will be thrown. The SDK will also validate that the amount is within the send lightning limits of the swap service. @@ -100,6 +102,8 @@ The SDK will also validate that the amount is within the send lightning limits o +#### BOLT12 offer + Payments to a BOLT12 offer can be done in a similar way. However, when paying to a BOLT12 offer, the SDK's prepare request **must** include an amount. diff --git a/src/notifications/custom_messages.md b/src/notifications/custom_messages.md index a9b11dba..8fce951b 100644 --- a/src/notifications/custom_messages.md +++ b/src/notifications/custom_messages.md @@ -32,6 +32,8 @@ class NotificationService: SDKNotificationService { self.logger.log(tag: TAG, line: "\(notificationType) data string: \(payload)", level: "INFO") switch(notificationType) { + case Constants.MESSAGE_TYPE_INVOICE_REQUEST: + return InvoiceRequestTask(payload: payload, logger: self.logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent) case Constants.MESSAGE_TYPE_SWAP_UPDATED: return SwapUpdatedTask(payload: payload, logger: self.logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent) case Constants.MESSAGE_TYPE_LNURL_PAY_INFO: diff --git a/src/notifications/getting_started.md b/src/notifications/getting_started.md index 796998e4..2b043abf 100644 --- a/src/notifications/getting_started.md +++ b/src/notifications/getting_started.md @@ -27,7 +27,7 @@ The structure and fields of this data can be changed by [customising the push me The Notification Plugin handles several use cases by default to automatically process push notifications sent via the NDS when an SDK service calls the registered webhook. If your use case isn't covered by the Notification Plugin, you can extend the plugin to [handle custom notifications](custom_notifications.md). -#### Updating a swap +### Updating a swap When making a payment via lightning or a Bitcoin onchain address, the SDK uses a swap service to convert between the Liquid sidechain and Bitcoin onchain or Lightning. A swap service is used to monitor changes to the swap status and send push notifications to wake the device to progress the swap. When the Notification Plugin receives this notification from the NDS, it will start the SDK and let the SDK complete the swap or progress it to the next state. @@ -49,9 +49,38 @@ The `swap_updated` notification type will be received by the webhook in the foll _* Depending if a zero-conf swap is accepted_ -#### Handling LNURL pay requests +### Handling pay requests -Having the ability to process push notifications when the application is in the background or closed also opens up the ability to handle payment requests from a static LNURL or Lightning address. To do this, the application also needs to register a webhook with an [LNURL-pay service](/guide/lnurl_pay_service.md), then when the LNURL service receives a request on the static LNURL address, it will forward it via the NDS to the application. The Notification Plugin handles the two-step flow for fulfilling these requests. +Having the ability to process push notifications when the application is in the background or closed also opens up the ability to handle payment requests from a static BOLT12 offer, LNURL or Lightning address. + +#### BOLT12 invoice requests + +When [creating a BOLT12 offer](/guide/receive_payment.html#bolt12-offer) the SDK uses a swap service to register the BOLT12 offer with. When the swap service receives an invoice request for this BOLT12 offer, it will send a webhook request to the [registered webhook](using_webhooks.md) to fetch an invoice from the SDK. The NDS then forwards this request via push notification to the Notification Plugin. Once the Notification Plugin receives this notification from the NDS, it will start the SDK and create a new BOLT12 invoice for this request. + +If you also want to have the BOLT12 offer accessable with a Lightning address, it needs to be registered alongside the webhook URL and username with the [LNURL-pay service](/guide/lnurl_pay_service.md). + +The `invoice_request` notification type will be received by the webhook in the following format: +```json +{ + "event": "invoice.request", + "data": { + "offer": "", // The BOLT12 offer + "invoiceRequest": "" // The invoice request + } +} +``` +The NDS then adds a reply URL for the response to be returned to. The final data received by the Notification Plugin is in the following format: +```json +{ + "offer": "", // The BOLT12 offer + "invoice_request": "", // The invoice request + "reply_url": "" // The URL to reply to this request +} +``` + +#### LNURL-pay requests + +To do this, the application also needs to register a webhook with an [LNURL-pay service](/guide/lnurl_pay_service.md), then when the LNURL service receives a request on the static LNURL address, it will forward it via the NDS to the application. The Notification Plugin handles the two-step flow for fulfilling these requests. Firstly the LNURL service receives a request for LNURL-pay information to get the min/max amount that can be received. The LNURL service calls the registered webhook, and upon receiving this notification, the Notification Plugin will connect to the Breez SDK and send a response back to the LNURL service based on the swap service limits. From e0f02193460a69ebfad5b7e9a4f86ed3f4384ea0 Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Tue, 29 Apr 2025 13:39:43 +0200 Subject: [PATCH 2/7] Replace Lightning payment method with Bolt11Invoice --- snippets/csharp/ReceivePayment.cs | 2 +- snippets/dart_snippets/lib/receive_payment.dart | 2 +- snippets/go/receive_payment.go | 2 +- .../kotlin/com/example/kotlinmpplib/ReceivePayment.kt | 2 +- snippets/python/src/receive_payment.py | 2 +- snippets/react-native/receive_payment.ts | 2 +- snippets/rust/src/receive_payment.rs | 2 +- snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift | 2 +- snippets/wasm/receive_payment.ts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/snippets/csharp/ReceivePayment.cs b/snippets/csharp/ReceivePayment.cs index 9335caee..2eeb25b9 100644 --- a/snippets/csharp/ReceivePayment.cs +++ b/snippets/csharp/ReceivePayment.cs @@ -14,7 +14,7 @@ public void PrepareReceiveLightning(BindingLiquidSdk sdk) // Set the invoice amount you wish the payer to send, which should be within the above limits var optionalAmount = new ReceiveAmount.Bitcoin(5000); - var prepareRequest = new PrepareReceiveRequest(PaymentMethod.Lightning, optionalAmount); + var prepareRequest = new PrepareReceiveRequest(PaymentMethod.Bolt11Invoice, optionalAmount); var prepareResponse = sdk.PrepareReceivePayment(prepareRequest); // If the fees are acceptable, continue to create the Receive Payment diff --git a/snippets/dart_snippets/lib/receive_payment.dart b/snippets/dart_snippets/lib/receive_payment.dart index f72eb010..9012201f 100644 --- a/snippets/dart_snippets/lib/receive_payment.dart +++ b/snippets/dart_snippets/lib/receive_payment.dart @@ -13,7 +13,7 @@ Future prepareReceivePaymentLightning() async { ReceiveAmount_Bitcoin optionalAmount = ReceiveAmount_Bitcoin(payerAmountSat: 5000 as BigInt); PrepareReceiveResponse prepareResponse = await breezSDKLiquid.instance!.prepareReceivePayment( req: PrepareReceiveRequest( - paymentMethod: PaymentMethod.lightning, + paymentMethod: PaymentMethod.bolt11Invoice, amount: optionalAmount, ), ); diff --git a/snippets/go/receive_payment.go b/snippets/go/receive_payment.go index 449f47d3..1b4aaadb 100644 --- a/snippets/go/receive_payment.go +++ b/snippets/go/receive_payment.go @@ -19,7 +19,7 @@ func PrepareReceiveLightning(sdk *breez_sdk_liquid.BindingLiquidSdk) { PayerAmountSat: uint64(5_000), } prepareRequest := breez_sdk_liquid.PrepareReceiveRequest{ - PaymentMethod: breez_sdk_liquid.PaymentMethodLightning, + PaymentMethod: breez_sdk_liquid.PaymentMethodBolt11Invoice, Amount: &optionalAmount, } if prepareResponse, err := sdk.PrepareReceivePayment(prepareRequest); err == nil { diff --git a/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt b/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt index 7d3a5a48..668b9c82 100644 --- a/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt +++ b/snippets/kotlin_mpp_lib/shared/src/commonMain/kotlin/com/example/kotlinmpplib/ReceivePayment.kt @@ -12,7 +12,7 @@ class ReceivePayment { // Set the invoice amount you wish the payer to send, which should be within the above limits val optionalAmount = ReceiveAmount.Bitcoin(5_000.toULong()) - val prepareRequest = PrepareReceiveRequest(PaymentMethod.LIGHTNING, optionalAmount) + val prepareRequest = PrepareReceiveRequest(PaymentMethod.BOLT11_INVOICE, optionalAmount) val prepareResponse = sdk.prepareReceivePayment(prepareRequest) // If the fees are acceptable, continue to create the Receive Payment diff --git a/snippets/python/src/receive_payment.py b/snippets/python/src/receive_payment.py index 011e4c31..e594d58a 100644 --- a/snippets/python/src/receive_payment.py +++ b/snippets/python/src/receive_payment.py @@ -13,7 +13,7 @@ def prepare_receive_lightning(sdk: BindingLiquidSdk): # Set the invoice amount you wish the payer to send, which should be within the above limits optional_amount = ReceiveAmount.BITCOIN(5_000) prepare_request = PrepareReceiveRequest( - payment_method=PaymentMethod.LIGHTNING, + payment_method=PaymentMethod.BOLT11_INVOICE, amount=optional_amount ) prepare_response = sdk.prepare_receive_payment(prepare_request) diff --git a/snippets/react-native/receive_payment.ts b/snippets/react-native/receive_payment.ts index 21a89658..9400928a 100644 --- a/snippets/react-native/receive_payment.ts +++ b/snippets/react-native/receive_payment.ts @@ -25,7 +25,7 @@ const examplePrepareLightningPayment = async () => { } const prepareResponse = await prepareReceivePayment({ - paymentMethod: PaymentMethod.LIGHTNING, + paymentMethod: PaymentMethod.BOLT11_INVOICE, amount: optionalAmount }) diff --git a/snippets/rust/src/receive_payment.rs b/snippets/rust/src/receive_payment.rs index 5d52cfa9..b39dc5fe 100644 --- a/snippets/rust/src/receive_payment.rs +++ b/snippets/rust/src/receive_payment.rs @@ -17,7 +17,7 @@ async fn prepare_receive_lightning(sdk: Arc) -> Result<()> { }); let prepare_response = sdk .prepare_receive_payment(&PrepareReceiveRequest { - payment_method: PaymentMethod::Lightning, + payment_method: PaymentMethod::Bolt11Invoice, amount: optional_amount, }) .await?; diff --git a/snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift b/snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift index 8244b5b3..30d61797 100644 --- a/snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift +++ b/snippets/swift/BreezSDKExamples/Sources/ReceivePayment.swift @@ -11,7 +11,7 @@ func prepareReceiveLightning(sdk: BindingLiquidSdk) -> PrepareReceiveResponse? { let optionalAmount = ReceiveAmount.bitcoin(payerAmountSat: 5_000) let prepareResponse = try? sdk .prepareReceivePayment(req: PrepareReceiveRequest( - paymentMethod: PaymentMethod.lightning, + paymentMethod: PaymentMethod.bolt11Invoice, amount: optionalAmount )); diff --git a/snippets/wasm/receive_payment.ts b/snippets/wasm/receive_payment.ts index fd181344..2cbe738a 100644 --- a/snippets/wasm/receive_payment.ts +++ b/snippets/wasm/receive_payment.ts @@ -18,7 +18,7 @@ const examplePrepareLightningPayment = async (sdk: BindingLiquidSdk) => { } const prepareResponse = await sdk.prepareReceivePayment({ - paymentMethod: 'lightning', + paymentMethod: 'bolt11Invoice', amount: optionalAmount }) From cc2f69c4d705bcc8f176172a9b1a4a4466992b1c Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Tue, 29 Apr 2025 17:23:40 +0200 Subject: [PATCH 3/7] Add BIP353 registration docs --- src/SUMMARY.md | 6 ++- src/guide/bip353_pay_service.md | 36 +++++++++++++++++ src/guide/lnurl_pay_service.md | 59 +++++++++++++--------------- src/guide/pay_service.md | 13 ++++++ src/notifications/getting_started.md | 4 +- 5 files changed, 82 insertions(+), 36 deletions(-) create mode 100644 src/guide/bip353_pay_service.md create mode 100644 src/guide/pay_service.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index b420a63f..2b7def22 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -17,9 +17,11 @@ - [Refunding payments](guide/refund_payment.md) - [Rescanning swaps](guide/rescanning_swaps.md) - [Sending an on-chain transaction](guide/pay_onchain.md) -- [Using LNURL & Lightning addresses](guide/lnurl.md) +- [Using LNURL, Lightning and BIP353 addresses](guide/lnurl.md) - [Sending payments using LNURL-Pay/Lightning address](guide/lnurl_pay.md) - - [Receiving payments using LNURL-Pay/Lightning address](guide/lnurl_pay_service.md) + - [Receiving payments using LNURL-Pay, Lightning and BIP353 addresses](guide/pay_service.md) + - [LNURL-Pay and BIP353 registration](guide/lnurl_pay_service.md) + - [BIP353 registration](guide/bip353_pay_service.md) - [Receiving payments using LNURL-Withdraw](guide/lnurl_withdraw.md) - [Authenticating using LNURL-Auth](guide/lnurl_auth.md) - [Signing and verifying messages](guide/messages.md) diff --git a/src/guide/bip353_pay_service.md b/src/guide/bip353_pay_service.md new file mode 100644 index 00000000..d71be414 --- /dev/null +++ b/src/guide/bip353_pay_service.md @@ -0,0 +1,36 @@ +# BIP353 registration + +Unlike LNURL-Pay, BOLT12 offers do not require a web service that serves requests. Instead when someone wants to pay a BOLT12 offer they use the Lightning network to request a BOLT12 invoice. That BOLT12 offer can also be made available via a DNS TXT lookup using [BIP353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki). You can do this in the workflow below by registering both a `username` and `offer`. + +### Registering with the service +Use a POST request to the service endpoint ```https://app.domain/bolt12offer/[pubkey]``` with the following payload to register: + +```json +{ + "time": 1231006505, // Current UNIX timestamp + "username": "[username]", + "offer": "[BOLT12 offer]", + "signature": "[signed message]" +} +``` + +The `signature` refers to the result of a message signed by the private key of the `pubkey`, where the message is comprised of the following text: + +``` +[time]-[username]-[offer] +``` +where `time`, `username` and `offer` are the payload fields. + +The service responds with following payload: +```json +{ + "bip353_address": "username@app.domain" +} +``` + +
+

Developer note

+ +When a user changes their already registered username, this previous username is freely available to be registered by another user. + +
diff --git a/src/guide/lnurl_pay_service.md b/src/guide/lnurl_pay_service.md index 1a7b2e78..74249cca 100644 --- a/src/guide/lnurl_pay_service.md +++ b/src/guide/lnurl_pay_service.md @@ -1,6 +1,4 @@ -# Receiving payments using LNURL-Pay - -Breez SDK - Nodeless *(Liquid Implementation)* users have the ability to receive Lightning payments using [LNURL-Pay](https://github.com/lnurl/luds/blob/luds/06.md) and [BOLT12 offers](receive_payment.html#bolt12-offer). +# LNURL-Pay and BIP353 registration #### LNURL-Pay @@ -19,16 +17,16 @@ The following workflow is application specific and the steps detailed below refe ![pay](https://github.com/breez/breez-sdk-docs/assets/5394889/ef0a3111-3604-4789-89c6-23adbd7e5d52) -### Step 1: Registering with the service +### Registering with the service Use a POST request to the service endpoint ```https://app.domain/lnurlpay/[pubkey]``` with the following payload to register: ```json { - "time": 1231006505, // Current UNIX timestamp - "webhook_url": "[notification service webhook URL]", - "username": "[optional username]", - "offer": "[optional offer]", - "signature": "[signed message]" + "time": 1231006505, // Current UNIX timestamp + "webhook_url": "[notification service webhook URL]", + "username": "[optional username]", + "offer": "[optional BOLT12 offer]", + "signature": "[signed message]" } ``` @@ -50,12 +48,13 @@ where `time`, `webhook_url`, `username` and `offer` are the payload fields. The service responds with following payload: ```json { - "lnurl": "[LNURL-pay encoded endpoint]", - "lightning_address": "username@app.domain" // Only set when username is included + "lnurl": "[LNURL-pay encoded endpoint]", + "lightning_address": "username@app.domain", // Only set when username is included + "bip353_address": "username@app.domain" // Only set when username and offer is included } ``` -When registering with both `username` and `offer` the resulting `lightning_address` is both BIP353 and LNURL-Pay compatible, meaning it can be paid to via both BOLT12 offer/invoice or LNURL-Pay depending how the payer's client chooses to parse it. +After registering with both `username` and `offer` the resulting `lightning_address` and `bip353_address` are the same internet identifier, meaning it is both BIP353 and LNURL-Pay compatible and can be paid to via both BOLT12 offer/invoice or LNURL-Pay depending how the payer's client chooses to parse it.

Developer note

@@ -64,49 +63,45 @@ When a user changes their already registered username, this previous username is
-### Step 2: Processing an LNURL-Pay request (LNURL-Pay only) +### Lifecycle of an LNURL-Pay payment + +#### Processing an LNURL-Pay request When an LNURL-Pay GET request is received at ```https://app.domain/lnurlp/[identifier]``` (or ```https://app.domain/.well-known/lnurlp/[identifier]``` for lightning addresses) the service then sends a push notification to the app with the LNURL-Pay request and a callback URL. The payload may look like the following: ```json { - "template": "lnurlpay_info", - "data": { - "reply_url": "https://app.domain/respond/[request_id]" - "callback_url": "https://app.domain/lnurlpay/[identifier]/invoice" - } + "template": "lnurlpay_info", + "data": { + "reply_url": "https://app.domain/respond/[request_id]", + "callback_url": "https://app.domain/lnurlpay/[identifier]/invoice" + } } ``` The ```reply_url``` is used by the app to respond to the LNURL-Pay request. The ```callback_url``` is the LNURL-Pay callback URL, used by the payer to fetch the invoice. -### Step 3: Responding to the callback URL (LNURL-Pay only) +#### Responding to the callback URL When the app receives the push notification, it parses the payload and then uses the ```reply_url``` to respond with the required data, for example: ```json { - "callback": "https://app.domain/lnurlpay/[identifier]/invoice", - "maxSendable": 10000, - "minSendable": 1000, - "metadata": "[[\"text/plain\",\"Pay to Breez\"]]", - "tag": "payRequest" + "callback": "https://app.domain/lnurlpay/[identifier]/invoice", + "maxSendable": 10000, + "minSendable": 1000, + "metadata": "[[\"text/plain\",\"Pay to Breez\"]]", + "tag": "payRequest" } ``` The service receives the response from the app and forwards it to the sender. -### Step 4: Fetching a BOLT11 invoice (LNURL-Pay only) - +#### Fetching a BOLT11 invoice The sender fetches a BOLT11 invoice by invoking a GET request to the ```callback``` URL when a specific amount is added as a query parameter. For example: ``` https://app.domain/lnurlpay/[identifier]/invoice?amount=1000 ``` An additional push notification is triggered to send the invoice request to the app. Then the app responds with the BOLT11 invoice data. -### Step 5: Paying the invoice +#### Paying the invoice In the last step, the payer pays the received invoice. Follow the steps [here](/notifications/getting_started.md) to receive payments via push notifications. - -## Reference implementation -For a complete reference implementation, see: -* [Breez's NotificationService](https://github.com/breez/misty-breez/blob/main/ios/NotificationService/NotificationService.swift) -* [Breez's LNURL-Pay service](https://github.com/breez/breez-lnurl) diff --git a/src/guide/pay_service.md b/src/guide/pay_service.md new file mode 100644 index 00000000..650bdd59 --- /dev/null +++ b/src/guide/pay_service.md @@ -0,0 +1,13 @@ +# Receiving payments using LNURL-Pay, Lightning and BIP353 addresses + +Breez SDK - Nodeless *(Liquid Implementation)* users have the ability to receive Lightning payments using [LNURL-Pay](https://github.com/lnurl/luds/blob/luds/06.md) and [BOLT12 offers](receive_payment.html#bolt12-offer). + +Whether or not you are able to use a webhook URL to receive payment events will determine if you want to register for LNURL-Pay. If you have no way to receive webhook events, you can still register a BOLT12 Offer as a BIP353 DNS record. In this case the SDK needs to remain online to receive the BOLT12 invoice requests. + +- **[LNURL-Pay and BIP353 registration](lnurl_pay_service.md)** using a webhook URL and BOLT12 Offer +- **[BIP353 registration](bip353_pay_service.md)** using a BOLT12 Offer + +## Reference implementation +For a complete reference implementation, see: +* [Breez's NotificationService](https://github.com/breez/misty-breez/blob/main/ios/NotificationService/NotificationService.swift) +* [Breez's LNURL-Pay service](https://github.com/breez/breez-lnurl) diff --git a/src/notifications/getting_started.md b/src/notifications/getting_started.md index 2b043abf..113b12ad 100644 --- a/src/notifications/getting_started.md +++ b/src/notifications/getting_started.md @@ -57,7 +57,7 @@ Having the ability to process push notifications when the application is in the When [creating a BOLT12 offer](/guide/receive_payment.html#bolt12-offer) the SDK uses a swap service to register the BOLT12 offer with. When the swap service receives an invoice request for this BOLT12 offer, it will send a webhook request to the [registered webhook](using_webhooks.md) to fetch an invoice from the SDK. The NDS then forwards this request via push notification to the Notification Plugin. Once the Notification Plugin receives this notification from the NDS, it will start the SDK and create a new BOLT12 invoice for this request. -If you also want to have the BOLT12 offer accessable with a Lightning address, it needs to be registered alongside the webhook URL and username with the [LNURL-pay service](/guide/lnurl_pay_service.md). +If you also want to have the BOLT12 offer accessable with a Lightning address, it needs to be registered alongside the webhook URL and username with the [LNURL-pay service](/guide/pay_service.md). The `invoice_request` notification type will be received by the webhook in the following format: ```json @@ -80,7 +80,7 @@ The NDS then adds a reply URL for the response to be returned to. The final data #### LNURL-pay requests -To do this, the application also needs to register a webhook with an [LNURL-pay service](/guide/lnurl_pay_service.md), then when the LNURL service receives a request on the static LNURL address, it will forward it via the NDS to the application. The Notification Plugin handles the two-step flow for fulfilling these requests. +To do this, the application also needs to register a webhook with an [LNURL-pay service](/guide/pay_service.md), then when the LNURL service receives a request on the static LNURL address, it will forward it via the NDS to the application. The Notification Plugin handles the two-step flow for fulfilling these requests. Firstly the LNURL service receives a request for LNURL-pay information to get the min/max amount that can be received. The LNURL service calls the registered webhook, and upon receiving this notification, the Notification Plugin will connect to the Breez SDK and send a response back to the LNURL service based on the swap service limits. From 543bc67060674828771c888f49c5df931584c4e8 Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Thu, 15 May 2025 06:45:18 +0200 Subject: [PATCH 4/7] Update description info --- src/guide/receive_payment.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/guide/receive_payment.md b/src/guide/receive_payment.md index a65f8653..7dd326a2 100644 --- a/src/guide/receive_payment.md +++ b/src/guide/receive_payment.md @@ -110,7 +110,7 @@ When receiving via Lightning, we can generate a BOLT11 invoice to be paid. Note A BOLT12 offer is a static payment code that can be paid to multiple times. When a payer wishes to pay the BOLT12 offer, the SDK is communicated with via a Web Socket stream when active, or when offline via a [registered webhook](using_webhooks.md). -**Note:** The amount field should **not** be set. +**Note:** The BOLT12 offer minimum amount will be set to the minimum receivable amount.
Rust
@@ -381,7 +381,8 @@ Once the payment has been prepared, all you have to do is pass the prepare respo receive method, optionally specifying a description. **Note:** The description field will be used differently, depending on the payment method: -- For Lightning payments, it will be encoded in the invoice +- For BOLT11 invoices, it will be encoded in the invoice. +- For BOLT12 offers, it will be encodes in the offer. - For Bitcoin/Liquid BIP21 payments, it will be encoded in the URI as the `message` field. - For plain Liquid payments, the description has no effect. From 4f8e2309f2ae860f742a1dcdb5c0836f23c16b96 Mon Sep 17 00:00:00 2001 From: Ross Savage <551697+dangeross@users.noreply.github.com> Date: Sat, 17 May 2025 13:32:20 +0000 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: Erdem Yerebasmaz --- src/guide/bip353_pay_service.md | 4 ++-- src/guide/lnurl_pay_service.md | 4 ++-- src/guide/pay_service.md | 2 +- src/guide/receive_payment.md | 2 +- src/notifications/getting_started.md | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/guide/bip353_pay_service.md b/src/guide/bip353_pay_service.md index d71be414..a34fee0b 100644 --- a/src/guide/bip353_pay_service.md +++ b/src/guide/bip353_pay_service.md @@ -1,6 +1,6 @@ # BIP353 registration -Unlike LNURL-Pay, BOLT12 offers do not require a web service that serves requests. Instead when someone wants to pay a BOLT12 offer they use the Lightning network to request a BOLT12 invoice. That BOLT12 offer can also be made available via a DNS TXT lookup using [BIP353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki). You can do this in the workflow below by registering both a `username` and `offer`. +Unlike LNURL-Pay, BOLT12 offers do not require a web service that serves requests. Instead when someone wants to pay a BOLT12 offer, they use the Lightning network to request a BOLT12 invoice. That BOLT12 offer can also be made available via a DNS TXT lookup using [BIP353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki). You can do this in the workflow below by registering both a `username` and an `offer`. ### Registering with the service Use a POST request to the service endpoint ```https://app.domain/bolt12offer/[pubkey]``` with the following payload to register: @@ -31,6 +31,6 @@ The service responds with following payload:

Developer note

-When a user changes their already registered username, this previous username is freely available to be registered by another user. +When a user changes their already registered username, this previous username becomes freely available to be registered by another user.
diff --git a/src/guide/lnurl_pay_service.md b/src/guide/lnurl_pay_service.md index 74249cca..cad06e61 100644 --- a/src/guide/lnurl_pay_service.md +++ b/src/guide/lnurl_pay_service.md @@ -10,7 +10,7 @@ To interact with the SDK, the service uses a simple protocol over push notificat #### BOLT12 offer -Unlike LNURL-Pay, BOLT12 offers do not require a web service that serves requests. Instead when someone wants to pay a BOLT12 offer they use the Lightning network to request a BOLT12 invoice. That BOLT12 offer can also be made available via a DNS TXT lookup using [BIP353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki). You can do this in the workflow below by registering both a `username` and `offer`. +Unlike LNURL-Pay, BOLT12 offers do not require a web service that serves requests. Instead when someone wants to pay a BOLT12 offer, they use the Lightning network to request a BOLT12 invoice. That BOLT12 offer can also be made available via a DNS TXT lookup using [BIP353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki). You can do this in the workflow below by registering both a `username` and an `offer`. ## General workflow The following workflow is application specific and the steps detailed below refer to the misty-breez wallet implementation which requires running [breez-lnurl](https://github.com/breez/breez-lnurl) service. @@ -54,7 +54,7 @@ The service responds with following payload: } ``` -After registering with both `username` and `offer` the resulting `lightning_address` and `bip353_address` are the same internet identifier, meaning it is both BIP353 and LNURL-Pay compatible and can be paid to via both BOLT12 offer/invoice or LNURL-Pay depending how the payer's client chooses to parse it. +After registering with both a `username` and an `offer`, the resulting `lightning_address` and `bip353_address` are the same internet identifier, meaning it is both BIP353 and LNURL-Pay compatible and can be paid to via both BOLT12 offer/invoice or LNURL-Pay, depending how the payer's client chooses to parse it.

Developer note

diff --git a/src/guide/pay_service.md b/src/guide/pay_service.md index 650bdd59..3d87c3f6 100644 --- a/src/guide/pay_service.md +++ b/src/guide/pay_service.md @@ -2,7 +2,7 @@ Breez SDK - Nodeless *(Liquid Implementation)* users have the ability to receive Lightning payments using [LNURL-Pay](https://github.com/lnurl/luds/blob/luds/06.md) and [BOLT12 offers](receive_payment.html#bolt12-offer). -Whether or not you are able to use a webhook URL to receive payment events will determine if you want to register for LNURL-Pay. If you have no way to receive webhook events, you can still register a BOLT12 Offer as a BIP353 DNS record. In this case the SDK needs to remain online to receive the BOLT12 invoice requests. +Whether or not you are able to use a webhook URL to receive payment events will determine if you want to register for LNURL-Pay. If you have no way to receive webhook events, you can still register a BOLT12 Offer as a BIP353 DNS record. In this case the SDK must remain online to receive the BOLT12 invoice requests. - **[LNURL-Pay and BIP353 registration](lnurl_pay_service.md)** using a webhook URL and BOLT12 Offer - **[BIP353 registration](bip353_pay_service.md)** using a BOLT12 Offer diff --git a/src/guide/receive_payment.md b/src/guide/receive_payment.md index 7dd326a2..a1638fac 100644 --- a/src/guide/receive_payment.md +++ b/src/guide/receive_payment.md @@ -382,7 +382,7 @@ receive method, optionally specifying a description. **Note:** The description field will be used differently, depending on the payment method: - For BOLT11 invoices, it will be encoded in the invoice. -- For BOLT12 offers, it will be encodes in the offer. +- For BOLT12 offers, it will be encoded in the offer. - For Bitcoin/Liquid BIP21 payments, it will be encoded in the URI as the `message` field. - For plain Liquid payments, the description has no effect. diff --git a/src/notifications/getting_started.md b/src/notifications/getting_started.md index 113b12ad..e43b88a5 100644 --- a/src/notifications/getting_started.md +++ b/src/notifications/getting_started.md @@ -57,7 +57,7 @@ Having the ability to process push notifications when the application is in the When [creating a BOLT12 offer](/guide/receive_payment.html#bolt12-offer) the SDK uses a swap service to register the BOLT12 offer with. When the swap service receives an invoice request for this BOLT12 offer, it will send a webhook request to the [registered webhook](using_webhooks.md) to fetch an invoice from the SDK. The NDS then forwards this request via push notification to the Notification Plugin. Once the Notification Plugin receives this notification from the NDS, it will start the SDK and create a new BOLT12 invoice for this request. -If you also want to have the BOLT12 offer accessable with a Lightning address, it needs to be registered alongside the webhook URL and username with the [LNURL-pay service](/guide/pay_service.md). +If you also want to have the BOLT12 offer accessible with a Lightning address, it needs to be registered alongside the webhook URL and username with the [LNURL-pay service](/guide/pay_service.md). The `invoice_request` notification type will be received by the webhook in the following format: ```json @@ -80,7 +80,7 @@ The NDS then adds a reply URL for the response to be returned to. The final data #### LNURL-pay requests -To do this, the application also needs to register a webhook with an [LNURL-pay service](/guide/pay_service.md), then when the LNURL service receives a request on the static LNURL address, it will forward it via the NDS to the application. The Notification Plugin handles the two-step flow for fulfilling these requests. +To do this, the application also needs to register a webhook with an [LNURL-pay service](/guide/pay_service.md). When the LNURL service receives a request on the static LNURL address, it will forward it via the NDS to the application. The Notification Plugin handles the two-step flow for fulfilling these requests. Firstly the LNURL service receives a request for LNURL-pay information to get the min/max amount that can be received. The LNURL service calls the registered webhook, and upon receiving this notification, the Notification Plugin will connect to the Breez SDK and send a response back to the LNURL service based on the swap service limits. From 6f99363682f22bf11949aae972d4fbb5da961d6d Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Mon, 19 May 2025 09:37:23 +0200 Subject: [PATCH 6/7] Re-add reference implementation sections --- src/guide/bip353_pay_service.md | 7 +++++++ src/guide/lnurl_pay_service.md | 7 ++++++- src/guide/pay_service.md | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/guide/bip353_pay_service.md b/src/guide/bip353_pay_service.md index a34fee0b..b5867cfb 100644 --- a/src/guide/bip353_pay_service.md +++ b/src/guide/bip353_pay_service.md @@ -2,6 +2,8 @@ Unlike LNURL-Pay, BOLT12 offers do not require a web service that serves requests. Instead when someone wants to pay a BOLT12 offer, they use the Lightning network to request a BOLT12 invoice. That BOLT12 offer can also be made available via a DNS TXT lookup using [BIP353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki). You can do this in the workflow below by registering both a `username` and an `offer`. +The following workflow is application specific and the steps detailed below refer to the [Misty Breez](https://github.com/breez/misty-breez) implementation which requires running an [LNURL service](https://github.com/breez/breez-lnurl) to register the BOLT12 offer as a BIP353 address. + ### Registering with the service Use a POST request to the service endpoint ```https://app.domain/bolt12offer/[pubkey]``` with the following payload to register: @@ -34,3 +36,8 @@ The service responds with following payload: When a user changes their already registered username, this previous username becomes freely available to be registered by another user.
+ +## Reference implementation +For a complete reference implementation, see: +* [Misty Breez's NotificationService](https://github.com/breez/misty-breez/blob/main/ios/NotificationService/NotificationService.swift) +* [Breez's LNURL service](https://github.com/breez/breez-lnurl) diff --git a/src/guide/lnurl_pay_service.md b/src/guide/lnurl_pay_service.md index cad06e61..00a482e1 100644 --- a/src/guide/lnurl_pay_service.md +++ b/src/guide/lnurl_pay_service.md @@ -13,7 +13,7 @@ To interact with the SDK, the service uses a simple protocol over push notificat Unlike LNURL-Pay, BOLT12 offers do not require a web service that serves requests. Instead when someone wants to pay a BOLT12 offer, they use the Lightning network to request a BOLT12 invoice. That BOLT12 offer can also be made available via a DNS TXT lookup using [BIP353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki). You can do this in the workflow below by registering both a `username` and an `offer`. ## General workflow -The following workflow is application specific and the steps detailed below refer to the misty-breez wallet implementation which requires running [breez-lnurl](https://github.com/breez/breez-lnurl) service. +The following workflow is application specific and the steps detailed below refer to the [Misty Breez](https://github.com/breez/misty-breez) implementation which requires running an [LNURL service](https://github.com/breez/breez-lnurl) to register and receive LNURL-Pay requests. ![pay](https://github.com/breez/breez-sdk-docs/assets/5394889/ef0a3111-3604-4789-89c6-23adbd7e5d52) @@ -105,3 +105,8 @@ An additional push notification is triggered to send the invoice request to the #### Paying the invoice In the last step, the payer pays the received invoice. Follow the steps [here](/notifications/getting_started.md) to receive payments via push notifications. + +## Reference implementation +For a complete reference implementation, see: +* [Misty Breez's NotificationService](https://github.com/breez/misty-breez/blob/main/ios/NotificationService/NotificationService.swift) +* [Breez's LNURL service](https://github.com/breez/breez-lnurl) diff --git a/src/guide/pay_service.md b/src/guide/pay_service.md index 3d87c3f6..1e2f5140 100644 --- a/src/guide/pay_service.md +++ b/src/guide/pay_service.md @@ -9,5 +9,5 @@ Whether or not you are able to use a webhook URL to receive payment events will ## Reference implementation For a complete reference implementation, see: -* [Breez's NotificationService](https://github.com/breez/misty-breez/blob/main/ios/NotificationService/NotificationService.swift) -* [Breez's LNURL-Pay service](https://github.com/breez/breez-lnurl) +* [Misty Breez's NotificationService](https://github.com/breez/misty-breez/blob/main/ios/NotificationService/NotificationService.swift) +* [Breez's LNURL service](https://github.com/breez/breez-lnurl) From 592153bfc70a6f5b9f3e8f19396c2e459b115911 Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Wed, 14 May 2025 19:45:05 +0200 Subject: [PATCH 7/7] Add LNURL-verify use case --- src/notifications/getting_started.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/notifications/getting_started.md b/src/notifications/getting_started.md index e43b88a5..aff129db 100644 --- a/src/notifications/getting_started.md +++ b/src/notifications/getting_started.md @@ -96,13 +96,31 @@ The `lnurlpay_info` notification type will be received by the webhook in the fol ``` Secondly the LNURL service receives a request for an invoice based on the selected payment amount. The LNURL service calls the registered webhook, and upon receiving this notification, the Notification Plugin will start the SDK and call receive payment for the requested amount. The resulting invoice is then returned to the LNURL service. +The LNURL service also implements [LUD-21 verify base spec](https://github.com/lnurl/luds/blob/luds/21.md) which allows the initiator to verify the payment has been made. The Notification Plugin then replaces the `{payment_hash}` with the payment hash of the invoice and returns the verify URL in the response. + The `lnurlpay_invoice` notification type will be received by the webhook in the following format: ```json { "template": "lnurlpay_invoice", "data": { "amount": 0, // The amount in millisatoshis within the min/max sendable range - "reply_url": "" // The URL to reply to this request + "reply_url": "https://app.domain/response/[response_id]", // The URL to reply to this request + "verify_url": "https://app.domain/lnurlpay/[identifier]/{payment_hash}" // The optional verify URL + } +} +``` + +#### LNURL-verify requests + +When the verify URL is used to query if the LNURL-pay payment has been made, the LNURL service forwards it via the NDS to the application, where the Notification Plugin handles the response. + +The `lnurlpay_verify` notification type will be received by the webhook in the following format: +```json +{ + "template": "lnurlpay_verify", + "data": { + "payment_hash": "", // The payment hash from the verify URL + "reply_url": "" // The URL to reply to this request } } ```