Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sending payments using LNURL-Pay and Lightning address

Preparing LNURL Payments API docs

During the prepare step, the SDK ensures that the inputs are valid with respect to the LNURL-pay request, and also returns the fees related to the payment so they can be confirmed.

Rust
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
let lnurl_pay_url = "lightning@address.com";

if let Ok(InputType::LightningAddress(details)) = sdk.parse(lnurl_pay_url).await {
    let amount_sats = 5_000;
    let optional_comment = Some("<comment>".to_string());
    let optional_validate_success_action_url = Some(true);

    let prepare_response = sdk
        .prepare_lnurl_pay(PrepareLnurlPayRequest {
            amount_sats,
            pay_request: details.pay_request,
            comment: optional_comment,
            validate_success_action_url: optional_validate_success_action_url,
        })
        .await?;

    // If the fees are acceptable, continue to create the LNURL Pay
    let fee_sats = prepare_response.fee_sats;
    info!("Fees: {fee_sats} sats");
}
Swift
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
let lnurlPayUrl = "lightning@address.com"

let inputType = try await sdk.parse(input: lnurlPayUrl)
if case .lightningAddress(v1: let details) = inputType {
    let amountSats: UInt64 = 5_000
    let optionalComment = "<comment>"
    let payRequest = details.payRequest
    let optionalValidateSuccessActionUrl = true

    let request = PrepareLnurlPayRequest(
        amountSats: amountSats,
        payRequest: payRequest,
        comment: optionalComment,
        validateSuccessActionUrl: optionalValidateSuccessActionUrl
    )
    let response = try await sdk.prepareLnurlPay(request: request)

    // If the fees are acceptable, continue to create the LNURL Pay
    let feesSat = response.feeSats
    print("Fees: \(feesSat) sats")
}
Kotlin
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
val lnurlPayUrl = "lightning@address.com"
try {
    val inputType = sdk.parse(lnurlPayUrl)
    if (inputType is InputType.LightningAddress) {
        val amountSats = 5_000.toULong()
        val optionalComment = "<comment>"
        val payRequest = inputType.v1.payRequest
        val optionalValidateSuccessActionUrl = true

        val req = PrepareLnurlPayRequest(
            amountSats,
            payRequest,
            optionalComment,
            optionalValidateSuccessActionUrl
        )
        val prepareResponse = sdk.prepareLnurlPay(req)

        // If the fees are acceptable, continue to create the LNURL Pay
        val feeSats = prepareResponse.feeSats;
        // Log.v("Breez", "Fees: ${feeSats} sats")
    }
} catch (e: Exception) {
    // handle error
}
C#
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43r
//     vv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3k
//     vdnxx5crxwpjvyunsephsz36jf
var lnurlPayUrl = "lightning@address.com";
var parsedInput = await sdk.Parse(lnurlPayUrl);
if (parsedInput is InputType.LightningAddress lightningAddress)
{
    var details = lightningAddress.v1;
    var amountSats = 5_000UL;
    var optionalComment = "<comment>";
    var payRequest = details.payRequest;
    var optionalValidateSuccessActionUrl = true;

    var request = new PrepareLnurlPayRequest(
        amountSats: amountSats,
        payRequest: payRequest,
        comment: optionalComment,
        validateSuccessActionUrl: optionalValidateSuccessActionUrl
    );
    var prepareResponse = await sdk.PrepareLnurlPay(request: request);

    // If the fees are acceptable, continue to create the LNURL Pay
    var feeSats = prepareResponse.feeSats;
    Console.WriteLine($"Fees: {feeSats} sats");
}
Javascript
// Endpoint can also be of the
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
const lnurlPayUrl = 'lightning@address.com'

const input = await sdk.parse(lnurlPayUrl)
if (input.type === 'lightningAddress') {
  const amountSats = 5_000
  const optionalComment = '<comment>'
  const payRequest = input.payRequest
  const optionalValidateSuccessActionUrl = true

  const prepareResponse = await sdk.prepareLnurlPay({
    amountSats,
    payRequest,
    comment: optionalComment,
    validateSuccessActionUrl: optionalValidateSuccessActionUrl
  })

  // If the fees are acceptable, continue to create the LNURL Pay
  const feeSats = prepareResponse.feeSats
  console.log(`Fees: ${feeSats} sats`)
}
React Native
// Endpoint can also be of the
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
const lnurlPayUrl = 'lightning@address.com'

const input = await sdk.parse(lnurlPayUrl)
if (input.tag === InputType_Tags.LightningAddress) {
  const amountSats = BigInt(5_000)
  const optionalComment = '<comment>'
  const payRequest = input.inner[0].payRequest
  const optionalValidateSuccessActionUrl = true

  const prepareResponse = await sdk.prepareLnurlPay({
    amountSats,
    payRequest,
    comment: optionalComment,
    validateSuccessActionUrl: optionalValidateSuccessActionUrl
  })

  // If the fees are acceptable, continue to create the LNURL Pay
  const feeSats = prepareResponse.feeSats
  console.log(`Fees: ${feeSats} sats`)
}
Flutter
/// Endpoint can also be of the form:
/// lnurlp://domain.com/lnurl-pay?key=val
/// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
String lnurlPayUrl = "lightning@address.com";

InputType inputType = await sdk.parse(input: lnurlPayUrl);
if (inputType is InputType_LightningAddress) {
  BigInt amountSats = BigInt.from(5000);
  String optionalComment = "<comment>";
  bool optionalValidateSuccessActionUrl = true;

  PrepareLnurlPayRequest request = PrepareLnurlPayRequest(
    amountSats: amountSats,
    payRequest: inputType.field0.payRequest,
    comment: optionalComment,
    validateSuccessActionUrl: optionalValidateSuccessActionUrl,
  );
  PrepareLnurlPayResponse prepareResponse =
      await sdk.prepareLnurlPay(request: request);

  // If the fees are acceptable, continue to create the LNURL Pay
  BigInt feeSats = prepareResponse.feeSats;
  print("Fees: $feeSats sats");
}
Python
# Endpoint can also be of the form:
# lnurlp://domain.com/lnurl-pay?key=val
# lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43r
#     vv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3k
#     vdnxx5crxwpjvyunsephsz36jf
lnurl_pay_url = "lightning@address.com"
try:
    parsed_input = await sdk.parse(lnurl_pay_url)
    if isinstance(parsed_input, InputType.LIGHTNING_ADDRESS):
        details = parsed_input[0]
        amount_sats = 5_000
        optional_comment = "<comment>"
        pay_request = details.pay_request
        optional_validate_success_action_url = True

        request = PrepareLnurlPayRequest(
            amount_sats=amount_sats,
            pay_request=pay_request,
            comment=optional_comment,
            validate_success_action_url=optional_validate_success_action_url,
        )
        prepare_response = await sdk.prepare_lnurl_pay(request=request)

        # If the fees are acceptable, continue to create the LNURL Pay
        fee_sats = prepare_response.fee_sats
        logging.debug(f"Fees: {fee_sats} sats")
        return prepare_response
except Exception as error:
    logging.error(error)
    raise
Go
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
lnurlPayUrl := "lightning@address.com"

input, err := sdk.Parse(lnurlPayUrl)

if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
	return nil, err
}

switch inputType := input.(type) {
case breez_sdk_spark.InputTypeLightningAddress:
	amountSats := uint64(5_000)
	optionalComment := "<comment>"
	optionalValidateSuccessActionUrl := true

	request := breez_sdk_spark.PrepareLnurlPayRequest{
		AmountSats:               amountSats,
		PayRequest:               inputType.Field0.PayRequest,
		Comment:                  &optionalComment,
		ValidateSuccessActionUrl: &optionalValidateSuccessActionUrl,
	}

	response, err := sdk.PrepareLnurlPay(request)

	if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
		return nil, err
	}

	// If the fees are acceptable, continue to create the LNURL Pay
	feeSats := response.FeeSats
	log.Printf("Fees: %v sats", feeSats)
	return &response, nil
}

LNURL Payments API docs

Once the payment has been prepared and the fees are accepted, the payment can be sent by passing:

  • Prepare Response - The response from the Preparing LNURL Payments step.
  • Idempotency Key - An optional UUID that identifies the payment. If set, providing the same idempotency key for multiple requests will ensure that only one payment is made.
Rust
let optional_idempotency_key = Some("<idempotency key uuid>".to_string());
let response = sdk
    .lnurl_pay(LnurlPayRequest {
        prepare_response,
        idempotency_key: optional_idempotency_key,
    })
    .await?;
Swift
let optionalIdempotencyKey = "<idempotency key uuid>"
let response = try await sdk.lnurlPay(
    request: LnurlPayRequest(
        prepareResponse: prepareResponse,
        idempotencyKey: optionalIdempotencyKey
    ))
Kotlin
try {
    val optionalIdempotencyKey = "<idempotency key uuid>"
    val response = sdk.lnurlPay(LnurlPayRequest(prepareResponse, optionalIdempotencyKey))
} catch (e: Exception) {
    // handle error
}
C#
var optionalIdempotencyKey = "<idempotency key uuid>";
var response = await sdk.LnurlPay(
    new LnurlPayRequest(
        prepareResponse: prepareResponse,
        idempotencyKey: optionalIdempotencyKey
    )
);
Javascript
const optionalIdempotencyKey = '<idempotency key uuid>'
const response = await sdk.lnurlPay({
  prepareResponse,
  idempotencyKey: optionalIdempotencyKey
})
React Native
const optionalIdempotencyKey = '<idempotency key uuid>'
const response = await sdk.lnurlPay({
  prepareResponse,
  idempotencyKey: optionalIdempotencyKey
})
Flutter
String? optionalIdempotencyKey = "<idempotency key uuid>";
LnurlPayResponse response = await sdk.lnurlPay(
  request: LnurlPayRequest(
    prepareResponse: prepareResponse,
    idempotencyKey: optionalIdempotencyKey),
);
Python
try:
    optional_idempotency_key = "<idempotency key uuid>"
    response = await sdk.lnurl_pay(
        LnurlPayRequest(
            prepare_response=prepare_response,
            idempotency_key=optional_idempotency_key,
        )
    )
except Exception as error:
    logging.error(error)
    raise
Go
optionalIdempotencyKey := "<idempotency key uuid>"
request := breez_sdk_spark.LnurlPayRequest{
	PrepareResponse: prepareResponse,
	IdempotencyKey:  &optionalIdempotencyKey,
}

response, err := sdk.LnurlPay(request)
if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
	return nil, err
}

payment := response.Payment

Developer note

By default when the LNURL-pay results in a success action with a URL, the URL is validated to check if there is a mismatch with the LNURL callback domain. You can disable this behaviour by setting the optional validation PrepareLnurlPayRequest param to false.

Supported Specs

  • LUD-01 LNURL bech32 encoding
  • LUD-06 payRequest spec
  • LUD-09 successAction field for payRequest
  • LUD-16 LN Address
  • LUD-17 Support for lnurlp prefix with non-bech32-encoded LNURL URLs