LNURL-Pay

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 relative fees related to the payment so they can be confirmed. If the LNURL-pay invoice includes a Magic Routing Hint for a direct Liquid payment, the fees will reflect this.

Setting the receiver amount

When you want the payment receipient to receive a specific amount.

The SDK will also validate that the amount is within the sendable limits of the LNURL-pay request.

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::LnUrlPay { data, bip353_address }) = sdk.parse(lnurl_pay_url).await {
    let amount = PayAmount::Bitcoin {
        receiver_amount_sat: 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 {
            data,
            amount,
            bip353_address,
            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 fees_sat = prepare_response.fees_sat;
    info!("Fees: {} sats", fees_sat);
}
Swift
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
var response: PrepareLnUrlPayResponse?
let lnurlPayUrl = "lightning@address.com"
if let inputType = try? sdk.parse(input: lnurlPayUrl) {
    if case .lnUrlPay(let `data`, let bip353Address) = inputType {
        let amount = PayAmount.bitcoin(receiverAmountSat: 5_000)
        let optionalComment = "<comment>"
        let optionalValidateSuccessActionUrl = true

        let req = PrepareLnUrlPayRequest(
            data: data,
            amount: amount,
            bip353Address: bip353Address,
            comment: optionalComment,
            validateSuccessActionUrl: optionalValidateSuccessActionUrl
        )
        response = try? sdk.prepareLnurlPay(req: req)

        // If the fees are acceptable, continue to create the LNURL Pay
        let feesSat = response!.feesSat
        print("Fees: {} sats", feesSat)
    }
}
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.LnUrlPay) {
        val amount = PayAmount.Bitcoin(5_000.toULong())
        val optionalComment = "<comment>";
        val optionalValidateSuccessActionUrl = true;
        val lnurlData = inputType.data

        val req = PrepareLnUrlPayRequest(
            lnurlData, 
            amount,
            inputType.bip353Address, 
            optionalComment, 
            optionalValidateSuccessActionUrl)
        val prepareResponse = sdk.prepareLnurlPay(req)

        // If the fees are acceptable, continue to create the LNURL Pay
        val feesSat = prepareResponse.feesSat;
        // Log.v("Breez", "Fees: ${feesSat} sats")
    }
} catch (e: Exception) {
    // handle error
}
React Native
// Endpoint can also be of the
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
const lnurlPayUrl = 'lightning@address.com'

const input = await parse(lnurlPayUrl)
if (input.type === InputTypeVariant.LN_URL_PAY) {
  const amount: PayAmount = {
    type: PayAmountVariant.BITCOIN,
    receiverAmountSat: 5_000
  }
  const optionalComment = '<comment>'
  const optionalValidateSuccessActionUrl = true

  const prepareResponse = await prepareLnurlPay({
    data: input.data,
    amount,
    bip353Address: input.bip353Address,
    comment: optionalComment,
    validateSuccessActionUrl: optionalValidateSuccessActionUrl
  })

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

InputType inputType = await breezSDKLiquid.instance!.parse(input: lnurlPayUrl);
if (inputType is InputType_LnUrlPay) {
  PayAmount_Bitcoin amount = PayAmount_Bitcoin(receiverAmountSat: 5000 as BigInt);
  String optionalComment = "<comment>";
  bool optionalValidateSuccessActionUrl = true;

  PrepareLnUrlPayRequest req = PrepareLnUrlPayRequest(
    data: inputType.data,
    amount: amount,
    bip353Address: inputType.bip353Address,
    comment: optionalComment,
    validateSuccessActionUrl: optionalValidateSuccessActionUrl,
  );
  PrepareLnUrlPayResponse prepareResponse = await breezSDKLiquid.instance!.prepareLnurlPay(req: req);

  // If the fees are acceptable, continue to create the LNURL Pay
  BigInt feesSat = prepareResponse.feesSat;
  print("Fees: $feesSat sats");
}
Python
# Endpoint can also be of the form:
# lnurlp://domain.com/lnurl-pay?key=val
# lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
lnurl_pay_url = "lightning@address.com"
try: 
    parsed_input = sdk.parse(lnurl_pay_url)
    if isinstance(parsed_input, InputType.LN_URL_PAY):
        amount = PayAmount.BITCOIN(5_000)
        optional_comment = "<comment>"
        optional_validate_success_action_url = True

        req = PrepareLnUrlPayRequest(parsed_input.data,
                                     amount,
                                     parsed_input.bip353_address,
                                     optional_comment,
                                     optional_validate_success_action_url)
        prepare_response = sdk.prepare_lnurl_pay(req)

        # If the fees are acceptable, continue to create the LNURL Pay
        fees_sat = prepare_response.fees_sat
        logging.debug("Fees: ", fees_sat, " 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"

if input, err := sdk.Parse(lnurlPayUrl); err != nil {
    switch inputType := input.(type) {
    case breez_sdk_liquid.InputTypeLnUrlPay:
        var amount breez_sdk_liquid.PayAmount = breez_sdk_liquid.PayAmountBitcoin{
            ReceiverAmountSat: uint64(5_000),
        }
        optionalComment := "<comment>"
        optionalValidateSuccessActionUrl := true

        req := breez_sdk_liquid.PrepareLnUrlPayRequest{
            Data:                     inputType.Data,
            Amount:                   amount,
            Bip353Address:            inputType.Bip353Address,
            Comment:                  &optionalComment,
            ValidateSuccessActionUrl: &optionalValidateSuccessActionUrl,
        }
        prepareResponse, err := sdk.PrepareLnurlPay(req)
        if err != nil {
            log.Printf("Error: %#v", err)
            return
        }

        // If the fees are acceptable, continue to create the LNURL Pay
        feesSat := prepareResponse.FeesSat
        log.Printf("Fees: %v sats", feesSat)
    }
}
C#
// Endpoint can also be of the form:
// lnurlp://domain.com/lnurl-pay?key=val
// lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf
var lnurlPayUrl = "lightning@address.com";

try
{
    var input = sdk.Parse(lnurlPayUrl);
    if (input is InputType.LnUrlPay lnurlp)
    {
        var amount = new PayAmount.Bitcoin(5000);
        var optionalComment = "<comment>";
        var optionalValidateSuccessActionUrl = true;

        var req = new PrepareLnUrlPayRequest(
            lnurlp.data,
            amount,
            lnurlp.bip353Address,
            optionalComment,
            optionalValidateSuccessActionUrl);
        var prepareResponse = sdk.PrepareLnurlPay(req);

        // If the fees are acceptable, continue to create the LNURL Pay
        var feesSat = prepareResponse.feesSat;
        Console.WriteLine($"Fees: {feesSat} sats");
    }
}
catch (Exception)
{
    // Handle error
}

Draining all funds

When you want to send all funds from your wallet to the payment recipient.

Rust
let amount = PayAmount::Drain;
let optional_comment = Some("<comment>".to_string());
let optional_validate_success_action_url = Some(true);

let prepare_response = sdk
    .prepare_lnurl_pay(PrepareLnUrlPayRequest {
        data,
        amount,
        bip353_address: None,
        comment: optional_comment,
        validate_success_action_url: optional_validate_success_action_url,
    })
    .await?;
Swift
let amount = PayAmount.drain
let optionalComment = "<comment>"
let optionalValidateSuccessActionUrl = true

let req = PrepareLnUrlPayRequest(
    data: data,
    amount: amount,
    comment: optionalComment,
    validateSuccessActionUrl: optionalValidateSuccessActionUrl
)
let prepareResponse = try? sdk.prepareLnurlPay(req: req)
Kotlin
try {
    val amount = PayAmount.Drain
    val optionalComment = "<comment>";
    val optionalValidateSuccessActionUrl = true;

    val req = PrepareLnUrlPayRequest(
        lnurlData, 
        amount,
        null,
        optionalComment, 
        optionalValidateSuccessActionUrl)
    val prepareResponse = sdk.prepareLnurlPay(req)
} catch (e: Exception) {
    // handle error
}
React Native
const amount: PayAmount = {
  type: PayAmountVariant.DRAIN
}
const optionalComment = '<comment>'
const optionalValidateSuccessActionUrl = true

const prepareResponse = await prepareLnurlPay({
  data,
  amount,
  comment: optionalComment,
  validateSuccessActionUrl: optionalValidateSuccessActionUrl
})
Dart
PayAmount_Drain amount = PayAmount_Drain();
String optionalComment = "<comment>";
bool optionalValidateSuccessActionUrl = true;

PrepareLnUrlPayRequest req = PrepareLnUrlPayRequest(
  data: data,
  amount: amount,
  comment: optionalComment,
  validateSuccessActionUrl: optionalValidateSuccessActionUrl,
);
PrepareLnUrlPayResponse prepareResponse = await breezSDKLiquid.instance!.prepareLnurlPay(req: req);
Python
try: 
    amount = PayAmount.DRAIN
    optional_comment = "<comment>"
    optional_validate_success_action_url = True

    req = PrepareLnUrlPayRequest(data,
                                 amount, 
                                 optional_comment, 
                                 optional_validate_success_action_url)
    prepare_response = sdk.prepare_lnurl_pay(req)
    return prepare_response
except Exception as error:
    logging.error(error)
    raise 
Go
var amount breez_sdk_liquid.PayAmount = breez_sdk_liquid.PayAmountDrain{}
optionalComment := "<comment>"
optionalValidateSuccessActionUrl := true

req := breez_sdk_liquid.PrepareLnUrlPayRequest{
    Data:                     data,
    Amount:                   amount,
    Comment:                  &optionalComment,
    ValidateSuccessActionUrl: &optionalValidateSuccessActionUrl,
}
prepareResponse, err := sdk.PrepareLnurlPay(req)
if err != nil {
    log.Printf("Error: %#v", err)
    return
}
C#
try
{
    var amount = new PayAmount.Drain();
    var optionalComment = "<comment>";
    var optionalValidateSuccessActionUrl = true;

    var req = new PrepareLnUrlPayRequest(
        data,
        amount,
        null,
        optionalComment,
        optionalValidateSuccessActionUrl);
    var prepareResponse = sdk.PrepareLnurlPay(req);
}
catch (Exception)
{
    // Handle error
}

LNURL Payments API docs

Once the payment has been prepared and the fees are accepted, all you have to do is pass the prepare response as an argument to the LNURL pay method.

Rust
let result = sdk.lnurl_pay(LnUrlPayRequest { prepare_response }).await?;
Swift
let result = try? sdk.lnurlPay(
    req: LnUrlPayRequest(
        prepareResponse: prepareResponse
    ))
Kotlin
try {
    val result = sdk.lnurlPay(LnUrlPayRequest(prepareResponse))
} catch (e: Exception) {
    // handle error
}
React Native
const result = await lnurlPay({
  prepareResponse
})
Dart
LnUrlPayResult result = await breezSDKLiquid.instance!.lnurlPay(
  req: LnUrlPayRequest(prepareResponse: prepareResponse),
);
Python
try:
    result = sdk.lnurl_pay(LnUrlPayRequest(prepare_response))
except Exception as error:
    logging.error(error)
    raise
Go
req := breez_sdk_liquid.LnUrlPayRequest{
    PrepareResponse: prepareResponse,
}
if result, err := sdk.LnurlPay(req); err != nil {
    log.Printf("Result: %#v", result)
}
C#
try
{
    var result = sdk.LnurlPay(new LnUrlPayRequest(prepareResponse));
}
catch (Exception)
{
    // Handle error
}

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