From d206646bab345ca5b9253d842c013c483131ae8c Mon Sep 17 00:00:00 2001 From: Aljaz Ceru Date: Fri, 18 Apr 2025 17:04:01 +0200 Subject: [PATCH 1/2] llm context files --- .../breez-nodeless-dart-llms.md | 813 ++++++++ .../breez-nodeless-reactnative-llms.md | 1832 +++++++++++++++++ .../breez-nodeless-rust-llms.md | 1398 +++++++++++++ .../breez-nodeless-wasm-llms.md | 1366 ++++++++++++ 4 files changed, 5409 insertions(+) create mode 100644 examples/breez-nodeless-llms/breez-nodeless-dart-llms.md create mode 100644 examples/breez-nodeless-llms/breez-nodeless-reactnative-llms.md create mode 100644 examples/breez-nodeless-llms/breez-nodeless-rust-llms.md create mode 100644 examples/breez-nodeless-llms/breez-nodeless-wasm-llms.md diff --git a/examples/breez-nodeless-llms/breez-nodeless-dart-llms.md b/examples/breez-nodeless-llms/breez-nodeless-dart-llms.md new file mode 100644 index 00000000..41e5923c --- /dev/null +++ b/examples/breez-nodeless-llms/breez-nodeless-dart-llms.md @@ -0,0 +1,813 @@ +# Breez SDK Nodeless (Liquid implementation) Documentation + +This comprehensive document provides all the context needed to build applications with the Dart bindings of Breez SDK Nodeless, a toolkit for integrating Bitcoin, Lightning Network, and Liquid Network functionality into your applications. + +## Overview + +Breez SDK Nodeless (Liquid implementation) is a powerful library that allows developers to integrate Bitcoin, Lightning Network, and Liquid Network functionality into their applications. The SDK abstracts many of the complexities of working with these technologies, providing a simple interface for common operations such as sending and receiving payments, managing wallets, and interacting with both Lightning and Liquid networks. + +Key capabilities include: +- Wallet management and balance tracking +- Lightning Network payments +- Liquid Network transactions +- Onchain Bitcoin operations +- LNURL support (auth, pay, withdraw) +- Non-Bitcoin asset management +- Webhook integration +- Event handling with listeners + +## Getting Started + +### Installation + +```yaml +# In your pubspec.yaml +dependencies: + flutter_breez_liquid: + git: + url: https://github.com/breez/breez-sdk-liquid-flutter + tag: 0.7.2 +``` + +### Guidelines +- **always make sure the sdk instance is synced before performing any actions** +- **Add logging**: Add sufficient logging into your application to diagnose any issues users are having. +- **Display pending payments**: Payments always contain a status field that can be used to determine if the payment was completed or not. Make sure you handle the case where the payment is still pending by showing the correct status to the user. +- **Enable swap refunds**: Swaps that are the result of receiving an On-Chain Transaction may not be completed and change to `Refundable` state. Make sure you handle this case correctly by allowing the user to retry the refund with different fees as long as the refund is not confirmed. +- **Expose swap fees**: When sending or receiving on-chain, make sure to clearly show the expected fees involved, as well as the send / receive amounts. + +### Initializing the SDK + +To get started with Breez SDK Nodeless (Liquid implementation), you need to initialize the SDK with your configuration: + +```dart +import 'package:flutter_breez_liquid/flutter_breez_liquid.dart'; + +Future initializeSDK() async { + // Your mnemonic seed phrase for wallet recovery + String mnemonic = ""; + + // Create the default config, providing your Breez API key + Config config = defaultConfig( + network: LiquidNetwork.mainnet, + breezApiKey: "" + ); + + // Customize the config object according to your needs + config = config.copyWith(workingDir: "path to an existing directory"); + + ConnectRequest connectRequest = ConnectRequest(mnemonic: mnemonic, config: config); + + try { + await breezSDKLiquid.connect(req: connectRequest); + } catch (error) { + print(error); + rethrow; + } +} +``` + +### Custom Signer Support + +If you prefer to manage your own keys, you can use a custom signer: + +```dart +import 'package:flutter_breez_liquid/flutter_breez_liquid.dart'; + +class CustomSigner implements Signer { + @override + Future sign(String message) async { + // Your custom signing implementation + return ""; + } +} + +Future connectWithCustomSigner() async { + CustomSigner signer = CustomSigner(); + + // Create the default config, providing your Breez API key + Config config = defaultConfig( + network: LiquidNetwork.mainnet, + breezApiKey: "" + ); + + // Customize the config object according to your needs + config = config.copyWith(workingDir: "path to an existing directory"); + + try { + ConnectWithSignerRequest request = ConnectWithSignerRequest(config: config); + await breezSDKLiquid.connectWithSigner(req: request, signer: signer); + } catch (error) { + print(error); + rethrow; + } +} +``` + +### Basic Operations + +#### Fetch Balance +- **always make sure the sdk instance is synced before performing any actions** + +```dart +Future fetchBalance() async { + try { + GetInfoResponse? info = await breezSDKLiquid.instance!.getInfo(); + BigInt balanceSat = info.walletInfo.balanceSat; + BigInt pendingSendSat = info.walletInfo.pendingSendSat; + BigInt pendingReceiveSat = info.walletInfo.pendingReceiveSat; + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Logging and Event Handling + +```dart +// Set up logging +void initializeLogStream() { + // Initialize the log stream + Stream? breezLogStream = breezLogStream().asBroadcastStream(); + + // Subscribe to the log stream + breezLogStream?.listen((logEntry) { + print("${logEntry.level}: ${logEntry.line}"); + }, onError: (error) { + print("Log stream error: $error"); + }); +} + +// Define an event listener +class SdkListener extends EventListener { + @override + void onEvent(SdkEvent sdkEvent) { + print("Received event: $sdkEvent"); + + if (sdkEvent is SdkEvent_Synced) { + print("SDK is synced"); + } else if (sdkEvent is SdkEvent_PaymentSucceeded) { + print("Payment succeeded: ${sdkEvent.details.paymentHash}"); + } else if (sdkEvent is SdkEvent_PaymentFailed) { + print("Payment failed: ${sdkEvent.details.error}"); + } + } +} + +// Add an event listener +Future addEventListeners() async { + try { + SdkListener listener = SdkListener(); + breezSDKLiquid.instance!.addEventListener().listen(listener.onEvent); + } catch (error) { + print(error); + rethrow; + } +} +``` + +## Core Features + +### Buying Bitcoin + +```dart +import 'package:flutter_breez_liquid/flutter_breez_liquid.dart'; + +Future fetchOnchainLimits() async { + try { + OnchainPaymentLimitsResponse currentLimits = await breezSDKLiquid.instance!.fetchOnchainLimits(); + print("Minimum amount: ${currentLimits.receive.minSat} sats"); + print("Maximum amount: ${currentLimits.receive.maxSat} sats"); + return currentLimits; + } catch (error) { + print(error); + rethrow; + } +} + +Future prepareBuyBitcoin(OnchainPaymentLimitsResponse currentLimits) async { + try { + PrepareBuyBitcoinRequest req = + PrepareBuyBitcoinRequest(provider: BuyBitcoinProvider.moonpay, amountSat: currentLimits.receive.minSat); + PrepareBuyBitcoinResponse prepareRes = await breezSDKLiquid.instance!.prepareBuyBitcoin(req: req); + + // Check the fees are acceptable before proceeding + BigInt receiveFeesSat = prepareRes.feesSat; + print("Fees: $receiveFeesSat sats"); + return prepareRes; + } catch (error) { + print(error); + rethrow; + } +} + +Future buyBitcoin(PrepareBuyBitcoinResponse prepareResponse) async { + try { + BuyBitcoinRequest req = BuyBitcoinRequest(prepareResponse: prepareResponse); + String url = await breezSDKLiquid.instance!.buyBitcoin(req: req); + return url; + } catch (error) { + print(error); + rethrow; + } +} +``` + +### Fiat Currencies + +```dart +Future> listFiatCurrencies() async { + try { + List fiatCurrencyList = await breezSDKLiquid.instance!.listFiatCurrencies(); + return fiatCurrencyList; + } catch (error) { + print(error); + rethrow; + } +} + +Future> fetchFiatRates() async { + try { + final List rates = await breezSDKLiquid.instance!.fetchFiatRates(); + final fiatRatesMap = rates.fold>({}, (map, rate) { + map[rate.coin] = rate; + return map; + }); + // print your desired rate + print(fiatRatesMap["USD"]?.value); + return fiatRatesMap; + } catch (error) { + print(error); + rethrow; + } +} +``` + +### Sending Payments + +#### Lightning Payments + +Sending Lightning payments involves a submarine swap and two Liquid on-chain transactions. The process is as follows: + +1. User broadcasts an L-BTC transaction to a Liquid lockup address. +2. Swapper pays the invoice, sending to the recipient, and then gets a preimage. +3. Swapper broadcasts an L-BTC transaction to claim the funds from the Liquid lockup address. + +The fee a user pays to send a Lightning payment is composed of three parts: + +1. **Lockup Transaction Fee:** ~34 sats (0.1 sat/discount vbyte). +2. **Claim Transaction Fee:** ~19 sats (0.1 sat/discount vbyte). +3. **Swap Service Fee:** 0.1% fee on the amount sent. + +Note: swap service fee is dynamic and can change. Currently, it is 0.1%. + +> **Example**: If the user sends 10k sats, the fee would be: +> +> - 34 sats [Lockup Transaction Fee] + 19 sats [Claim Transaction Fee] + 10 sats [Swapper Service Fee] = 63 sats + +```dart +Future prepareSendPaymentLightningBolt11() async { + // Set the bolt11 invoice you wish to pay + String destination = ""; + + try { + PrepareSendResponse prepareSendResponse = await breezSDKLiquid.instance!.prepareSendPayment( + req: PrepareSendRequest(destination: destination), + ); + + // If the fees are acceptable, continue to create the Send Payment + BigInt sendFeesSat = prepareSendResponse.feesSat; + print("Fees: $sendFeesSat sats"); + return prepareSendResponse; + } catch (error) { + print(error); + rethrow; + } +} + +Future prepareSendPaymentLightningBolt12() async { + // Set the bolt12 offer you wish to pay + String destination = ""; + + try { + PayAmount_Bitcoin optionalAmount = PayAmount_Bitcoin(receiverAmountSat: BigInt.from(5000)); + PrepareSendResponse prepareSendResponse = await breezSDKLiquid.instance!.prepareSendPayment( + req: PrepareSendRequest(destination: destination, amount: optionalAmount), + ); + return prepareSendResponse; + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Liquid Payments + +```dart +Future prepareSendPaymentLiquid() async { + // Set the Liquid BIP21 or Liquid address you wish to pay + String destination = ""; + + try { + PayAmount_Bitcoin optionalAmount = PayAmount_Bitcoin(receiverAmountSat: BigInt.from(5000)); + PrepareSendRequest prepareSendRequest = PrepareSendRequest( + destination: destination, + amount: optionalAmount, + ); + + PrepareSendResponse prepareSendResponse = await breezSDKLiquid.instance!.prepareSendPayment( + req: prepareSendRequest, + ); + + // If the fees are acceptable, continue to create the Send Payment + BigInt sendFeesSat = prepareSendResponse.feesSat; + print("Fees: $sendFeesSat sats"); + return prepareSendResponse; + } catch (error) { + print(error); + rethrow; + } +} + +Future prepareSendPaymentLiquidDrain() async { + // Set the Liquid BIP21 or Liquid address you wish to pay + String destination = ""; + + try { + PayAmount_Drain optionalAmount = PayAmount_Drain(); + PrepareSendRequest prepareSendRequest = PrepareSendRequest( + destination: destination, + amount: optionalAmount, + ); + + PrepareSendResponse prepareSendResponse = await breezSDKLiquid.instance!.prepareSendPayment( + req: prepareSendRequest, + ); + + // If the fees are acceptable, continue to create the Send Payment + BigInt sendFeesSat = prepareSendResponse.feesSat; + print("Fees: $sendFeesSat sats"); + return prepareSendResponse; + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Execute Payment +- **always make sure the sdk instance is synced before performing any actions** + +```dart +Future sendPayment({required PrepareSendResponse prepareResponse}) async { + try { + SendPaymentResponse sendPaymentResponse = await breezSDKLiquid.instance!.sendPayment( + req: SendPaymentRequest(prepareResponse: prepareResponse), + ); + Payment payment = sendPaymentResponse.payment; + return sendPaymentResponse; + } catch (error) { + print(error); + rethrow; + } +} +``` + +### Receiving Payments +**always make sure the sdk instance is synced before performing any actions** + +#### Lightning Payments + +Receiving Lightning payments involves a reverse submarine swap and requires two Liquid on-chain transactions. The process is as follows: + +1. Sender pays the Swapper invoice. +2. Swapper broadcasts an L-BTC transaction to a Liquid lockup address. +3. SDK claims the funds from the Liquid lockup address and then exposes the preimage. +4. Swapper uses the preimage to claim the funds from the Liquid lockup address. + +The fee a user pays to receive a Lightning payment is composed of three parts: + +1. **Lockup Transaction Fee:** ~27 sats (0.1 sat/discount vbyte). +2. **Claim Transaction Fee:** ~20 sats (0.1 sat/discount vbyte). +3. **Swap Service Fee:** 0.25% fee on the amount received. + +Note: swap service fee is dynamic and can change. Currently, it is 0.25%. + +> **Example**: If the sender sends 10k sats, the fee for the end-user would be: +> +> - 27 sats [Lockup Transaction Fee] + 20 sats [Claim Transaction Fee] + 25 sats [Swapper Service Fee] = 72 sats + +```dart +Future prepareReceivePaymentLightning() async { + try { + // Fetch the Receive lightning limits + LightningPaymentLimitsResponse currentLightningLimits = + await breezSDKLiquid.instance!.fetchLightningLimits(); + print("Minimum amount: ${currentLightningLimits.receive.minSat} sats"); + print("Maximum amount: ${currentLightningLimits.receive.maxSat} sats"); + + // Create an invoice and set the amount you wish the payer to send + ReceiveAmount_Bitcoin optionalAmount = ReceiveAmount_Bitcoin(payerAmountSat: BigInt.from(5000)); + PrepareReceiveResponse prepareResponse = await breezSDKLiquid.instance!.prepareReceivePayment( + req: PrepareReceiveRequest( + paymentMethod: PaymentMethod.lightning, + amount: optionalAmount, + ), + ); + + // If the fees are acceptable, continue to create the Receive Payment + BigInt receiveFeesSat = prepareResponse.feesSat; + print("Fees: $receiveFeesSat sats"); + return prepareResponse; + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Onchain Payments + +```dart +Future prepareReceivePaymentOnchain() async { + try { + // Fetch the Receive onchain limits + OnchainPaymentLimitsResponse currentOnchainLimits = await breezSDKLiquid.instance!.fetchOnchainLimits(); + print("Minimum amount: ${currentOnchainLimits.receive.minSat} sats"); + print("Maximum amount: ${currentOnchainLimits.receive.maxSat} sats"); + + // Or create a cross-chain transfer (Liquid to Bitcoin) via chain swap + ReceiveAmount_Bitcoin optionalAmount = ReceiveAmount_Bitcoin(payerAmountSat: BigInt.from(5000)); + PrepareReceiveResponse prepareResponse = await breezSDKLiquid.instance!.prepareReceivePayment( + req: PrepareReceiveRequest( + paymentMethod: PaymentMethod.bitcoinAddress, + amount: optionalAmount, + ), + ); + + // If the fees are acceptable, continue to create the Receive Payment + BigInt receiveFeesSat = prepareResponse.feesSat; + print("Fees: $receiveFeesSat sats"); + return prepareResponse; + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Liquid Payments + +```dart +Future prepareReceivePaymentLiquid() async { + try { + // Create a Liquid BIP21 URI/address to receive a payment to. + // There are no limits, but the payer amount should be greater than broadcast fees when specified + // Note: Not setting the amount will generate a plain Liquid address + ReceiveAmount_Bitcoin optionalAmount = ReceiveAmount_Bitcoin(payerAmountSat: BigInt.from(5000)); + PrepareReceiveResponse prepareResponse = await breezSDKLiquid.instance!.prepareReceivePayment( + req: PrepareReceiveRequest( + paymentMethod: PaymentMethod.liquidAddress, + amount: optionalAmount, + ), + ); + + // If the fees are acceptable, continue to create the Receive Payment + BigInt receiveFeesSat = prepareResponse.feesSat; + print("Fees: $receiveFeesSat sats"); + return prepareResponse; + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Execute Receive + +```dart +Future receivePayment(PrepareReceiveResponse prepareResponse) async { + try { + String optionalDescription = ""; + ReceivePaymentResponse res = await breezSDKLiquid.instance!.receivePayment( + req: ReceivePaymentRequest( + description: optionalDescription, + prepareResponse: prepareResponse, + ), + ); + + String destination = res.destination; + print(destination); + return res; + } catch (error) { + print(error); + rethrow; + } +} +``` + +### LNURL Operations + +#### LNURL Authentication + +```dart +Future lnurlAuth() async { + // Endpoint can also be of the form: + // keyauth://domain.com/auth?key=val + String lnurlAuthUrl = + "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttvdankjm3lw3skw0tvdankjm3xdvcn6vtp8q6n2dfsx5mrjwtrxdjnqvtzv56rzcnyv3jrxv3sxqmkyenrvv6kve3exv6nqdtyv43nqcmzvdsnvdrzx33rsenxx5unqc3cxgeqgntfgu"; + + try { + InputType inputType = await breezSDKLiquid.instance!.parse(input: lnurlAuthUrl); + if (inputType is InputType_LnUrlAuth) { + LnUrlCallbackStatus result = await breezSDKLiquid.instance!.lnurlAuth(reqData: inputType.data); + if (result is LnUrlCallbackStatus_Ok) { + print("Successfully authenticated"); + } else { + print("Failed to authenticate"); + } + } + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### LNURL Pay + +```dart +Future prepareLnurlPay() async { + // Endpoint can also be of the form: + // lnurlp://domain.com/lnurl-pay?key=val + String lnurlPayUrl = "lightning@address.com"; + + try { + InputType inputType = await breezSDKLiquid.instance!.parse(input: lnurlPayUrl); + if (inputType is InputType_LnUrlPay) { + PayAmount_Bitcoin amount = PayAmount_Bitcoin(receiverAmountSat: BigInt.from(5000)); + String optionalComment = ""; + 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"); + return prepareResponse; + } + } catch (error) { + print(error); + rethrow; + } +} + +Future lnurlPay({required PrepareLnUrlPayResponse prepareResponse}) async { + try { + LnUrlPayResult result = await breezSDKLiquid.instance!.lnurlPay( + req: LnUrlPayRequest(prepareResponse: prepareResponse), + ); + print(result); + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### LNURL Withdraw + +```dart +Future lnurlWithdraw() async { + // Endpoint can also be of the form: + // lnurlw://domain.com/lnurl-withdraw?key=val + String lnurlWithdrawUrl = + "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4exctthd96xserjv9mn7um9wdekjmmw843xxwpexdnxzen9vgunsvfexq6rvdecx93rgdmyxcuxverrvcursenpxvukzv3c8qunsdecx33nzwpnvg6ryc3hv93nzvecxgcxgwp3h33lxk"; + + try { + InputType inputType = await breezSDKLiquid.instance!.parse(input: lnurlWithdrawUrl); + if (inputType is InputType_LnUrlWithdraw) { + BigInt amountMsat = inputType.data.minWithdrawable; + LnUrlWithdrawRequest req = LnUrlWithdrawRequest( + data: inputType.data, + amountMsat: amountMsat, + description: "", + ); + LnUrlWithdrawResult result = await breezSDKLiquid.instance!.lnurlWithdraw(req: req); + print(result.data); + } + } catch (error) { + print(error); + rethrow; + } +} +``` + +### Onchain Operations + +#### Pay Onchain + +```dart +Future getCurrentLimits() async { + try { + OnchainPaymentLimitsResponse currentLimits = await breezSDKLiquid.instance!.fetchOnchainLimits(); + print("Minimum amount: ${currentLimits.send.minSat} sats"); + print("Maximum amount: ${currentLimits.send.maxSat} sats"); + return currentLimits; + } catch (error) { + print(error); + rethrow; + } +} + +Future preparePayOnchain() async { + try { + PreparePayOnchainRequest preparePayOnchainRequest = PreparePayOnchainRequest( + amount: PayAmount_Bitcoin(receiverAmountSat: BigInt.from(5000)), + ); + PreparePayOnchainResponse prepareRes = await breezSDKLiquid.instance!.preparePayOnchain( + req: preparePayOnchainRequest, + ); + + // Check if the fees are acceptable before proceeding + BigInt totalFeesSat = prepareRes.totalFeesSat; + print(totalFeesSat); + return prepareRes; + } catch (error) { + print(error); + rethrow; + } +} + +Future preparePayOnchainFeeRate() async { + try { + int optionalSatPerVbyte = 21; + + PreparePayOnchainRequest preparePayOnchainRequest = PreparePayOnchainRequest( + amount: PayAmount_Bitcoin(receiverAmountSat: BigInt.from(5000)), + feeRateSatPerVbyte: optionalSatPerVbyte, + ); + PreparePayOnchainResponse prepareRes = await breezSDKLiquid.instance!.preparePayOnchain( + req: preparePayOnchainRequest, + ); + + // Check if the fees are acceptable before proceeding + BigInt claimFeesSat = prepareRes.claimFeesSat; + BigInt totalFeesSat = prepareRes.totalFeesSat; + print(claimFeesSat); + print(totalFeesSat); + return prepareRes; + } catch (error) { + print(error); + rethrow; + } +} + +Future startReverseSwap({ + required PreparePayOnchainResponse prepareRes, +}) async { + try { + String destinationAddress = "bc1.."; + + PayOnchainRequest req = PayOnchainRequest( + address: destinationAddress, + prepareResponse: prepareRes, + ); + SendPaymentResponse res = await breezSDKLiquid.instance!.payOnchain(req: req); + return res; + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Drain Funds + +```dart +Future preparePayOnchainDrain() async { + try { + PreparePayOnchainRequest preparePayOnchainRequest = PreparePayOnchainRequest( + amount: PayAmount_Drain(), + ); + PreparePayOnchainResponse prepareRes = await breezSDKLiquid.instance!.preparePayOnchain( + req: preparePayOnchainRequest, + ); + + // Check if the fees are acceptable before proceeding + BigInt totalFeesSat = prepareRes.totalFeesSat; + print(totalFeesSat); + return prepareRes; + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Receive Onchain + +```dart +Future> listRefundables() async { + try { + List refundables = await breezSDKLiquid.instance!.listRefundables(); + return refundables; + } catch (error) { + print(error); + rethrow; + } +} + +Future executeRefund({ + required int refundTxFeeRate, + required RefundableSwap refundable, +}) async { + try { + String destinationAddress = "..."; + int feeRateSatPerVbyte = refundTxFeeRate; + + RefundRequest req = RefundRequest( + swapAddress: refundable.swapAddress, + refundAddress: destinationAddress, + feeRateSatPerVbyte: feeRateSatPerVbyte, + ); + RefundResponse resp = await breezSDKLiquid.instance!.refund(req: req); + print(resp.refundTxId); + return resp; + } catch (error) { + print(error); + rethrow; + } +} + +Future rescanSwaps() async { + try { + await breezSDKLiquid.instance!.rescanOnchainSwaps(); + } catch (error) { + print(error); + rethrow; + } +} + +Future recommendedFees() async { + try { + RecommendedFees fees = await breezSDKLiquid.instance!.recommendedFees(); + print(fees); + } catch (error) { + print(error); + rethrow; + } +} +``` + +#### Handle Fee Acceptance + +```dart +Future handlePaymentsWaitingFeeAcceptance() async { + try { + // Payments on hold waiting for fee acceptance have the state WaitingFeeAcceptance + List paymentsWaitingFeeAcceptance = await breezSDKLiquid.instance!.listPayments( + req: ListPaymentsRequest( + states: [PaymentState.waitingFeeAcceptance], + ), + ); + + for (Payment payment in paymentsWaitingFeeAcceptance) { + if (payment.details is! PaymentDetails_Bitcoin) { + // Only Bitcoin payments can be `WaitingFeeAcceptance` + continue; + } + + PaymentDetails_Bitcoin details = payment.details as PaymentDetails_Bitcoin; + FetchPaymentProposedFeesResponse fetchFeesResponse = + await breezSDKLiquid.instance!.fetchPaymentProposedFees( + req: FetchPaymentProposedFeesRequest( + swapId: details.swapId, + ), + ); + + print( + "Payer sent ${fetchFeesResponse.payerAmountSat} and currently proposed fees are ${fetchFeesResponse.feesSat}", + ); + + // If the user is ok with the fees, accept them, allowing the payment to proceed + await breezSDKLiquid.instance!.acceptPaymentProposedFees( + req: AcceptPaymentProposedFeesRequest( + response: fetchFeesResponse, + ), + ); + } + } catch (error) { + print(error); + rethrow; + } +} \ No newline at end of file diff --git a/examples/breez-nodeless-llms/breez-nodeless-reactnative-llms.md b/examples/breez-nodeless-llms/breez-nodeless-reactnative-llms.md new file mode 100644 index 00000000..e043b82c --- /dev/null +++ b/examples/breez-nodeless-llms/breez-nodeless-reactnative-llms.md @@ -0,0 +1,1832 @@ +# Breez SDK Nodeless (Liquid implementation) Documentation + +This comprehensive document provides all the context needed to build applications with the React Native bindings of Breez SDK Nodeless, a toolkit for integrating Bitcoin, Lightning Network, and Liquid Network functionality into your applications. + +## Overview + +Breez SDK Nodeless (Liquid implementation) is a powerful library that allows developers to integrate Bitcoin, Lightning Network, and Liquid Network functionality into their applications. The SDK abstracts many of the complexities of working with these technologies, providing a simple interface for common operations such as sending and receiving payments, managing wallets, and interacting with both Lightning and Liquid networks. + +Key capabilities include: +- Wallet management and balance tracking +- Lightning Network payments +- Liquid Network transactions +- Onchain Bitcoin operations +- LNURL support (auth, pay, withdraw) +- Non-Bitcoin asset management +- Webhook integration +- Event handling with listeners + +## Getting Started + +### Installation + +```bash +yarn add @breeztech/react-native-breez-sdk-liquid +``` + +### Guidelines +- **Always make sure the SDK instance is synced before performing any actions** +- **Add logging**: Add sufficient logging into your application to diagnose any issues users are having. +- **Display pending payments**: Payments always contain a status field that can be used to determine if the payment was completed or not. Make sure you handle the case where the payment is still pending by showing the correct status to the user. +- **Enable swap refunds**: Swaps that are the result of receiving an On-Chain Transaction may not be completed and change to `Refundable` state. Make sure you handle this case correctly by allowing the user to retry the refund with different fees as long as the refund is not confirmed. +- **Expose swap fees**: When sending or receiving on-chain, make sure to clearly show the expected fees involved, as well as the send/receive amounts. + +### Initializing the SDK + +To get started with Breez SDK Nodeless (Liquid implementation), you need to initialize the SDK with your configuration: + +```javascript +import { + addEventListener, + defaultConfig, + connect, + LiquidNetwork, + type LogEntry, + getInfo, + removeEventListener, + disconnect, + type SdkEvent, + setLogger +} from '@breeztech/react-native-breez-sdk-liquid' + +const initializeSDK = async () => { + // Your mnemonic seed phrase for wallet recovery + const mnemonic = '' + + // Create the default config, providing your Breez API key + const config = await defaultConfig( + LiquidNetwork.MAINNET, + '' + ) + + // By default in React Native the workingDir is set to: + // `//breezSdkLiquid` + // You can change this to another writable directory or a + // subdirectory of the workingDir if managing multiple mnemonics. + console.log(`Working directory: ${config.workingDir}`) + // config.workingDir = "path to writable directory" + + await connect({ mnemonic, config }) +} +``` + +### Basic Operations + +#### Fetch Balance + +```javascript +const fetchBalance = async () => { + try { + const info = await getInfo() + const balanceSat = info.walletInfo.balanceSat + const pendingSendSat = info.walletInfo.pendingSendSat + const pendingReceiveSat = info.walletInfo.pendingReceiveSat + + console.log(`Balance: ${balanceSat} sats`) + console.log(`Pending Send: ${pendingSendSat} sats`) + console.log(`Pending Receive: ${pendingReceiveSat} sats`) + } catch (err) { + console.error(err) + } +} + +const fetchAssetBalance = async () => { + try { + const info = await getInfo() + const assetBalances = info.walletInfo.assetBalances + return assetBalances + } catch (err) { + console.error(err) + } +} +``` + +### Messages and Signing + +```javascript +import { + checkMessage, + getInfo, + signMessage +} from '@breeztech/react-native-breez-sdk-liquid' + +const signMessageExample = async () => { + try { + const signMessageResponse = await signMessage({ + message: '' + }) + + // Get the wallet info for your pubkey + const info = await getInfo() + + const signature = signMessageResponse.signature + const pubkey = info.walletInfo.pubkey + + console.log(`Pubkey: ${pubkey}`) + console.log(`Signature: ${signature}`) + return { signature, pubkey } + } catch (err) { + console.error(err) + } +} + +const checkMessageExample = async () => { + try { + const checkMessageResponse = await checkMessage({ + message: '', + pubkey: '', + signature: '' + }) + const isValid = checkMessageResponse.isValid + + console.log(`Signature valid: ${isValid}`) + return isValid + } catch (err) { + console.error(err) + } +} +``` + +### List Payments + +```javascript +import { + getPayment, + GetPaymentRequestVariant, + ListPaymentDetailsVariant, + listPayments, + PaymentType +} from '@breeztech/react-native-breez-sdk-liquid' + +const getPaymentExample = async () => { + try { + const paymentHash = '' + const paymentByHash = await getPayment({ + type: GetPaymentRequestVariant.PAYMENT_HASH, + paymentHash + }) + + const swapId = '' + const paymentBySwapId = await getPayment({ + type: GetPaymentRequestVariant.SWAP_ID, + swapId + }) + + return { paymentByHash, paymentBySwapId } + } catch (err) { + console.error(err) + } +} + +const listAllPayments = async () => { + try { + const payments = await listPayments({}) + return payments + } catch (err) { + console.error(err) + } +} + +const listPaymentsFiltered = async () => { + try { + const payments = await listPayments({ + filters: [PaymentType.SEND], + fromTimestamp: 1696880000, + toTimestamp: 1696959200, + offset: 0, + limit: 50 + }) + return payments + } catch (err) { + console.error(err) + } +} + +const listPaymentsDetailsAddress = async () => { + try { + const payments = await listPayments({ + details: { + type: ListPaymentDetailsVariant.BITCOIN, + address: '' + } + }) + return payments + } catch (err) { + console.error(err) + } +} + +const listPaymentsDetailsDestination = async () => { + try { + const payments = await listPayments({ + details: { + type: ListPaymentDetailsVariant.LIQUID, + destination: '' + } + }) + return payments + } catch (err) { + console.error(err) + } +} +``` + +### Webhook Integration + +```javascript +import { registerWebhook, unregisterWebhook } from '@breeztech/react-native-breez-sdk-liquid' + +const registerWebhookExample = async () => { + try { + await registerWebhook('https://your-nds-service.com/notify?platform=ios&token=') + } catch (err) { + console.error(err) + } +} + +const unregisterWebhookExample = async () => { + try { + await unregisterWebhook() + } catch (err) { + console.error(err) + } +} +``` + +### Input Parsing + +```javascript +import { + InputTypeVariant, + parse +} from '@breeztech/react-native-breez-sdk-liquid' + +const parseInputExample = async () => { + const input = 'an input to be parsed...' + + try { + const parsed = await parse(input) + + switch (parsed.type) { + case InputTypeVariant.BITCOIN_ADDRESS: + console.log(`Input is Bitcoin address ${parsed.address.address}`) + break + + case InputTypeVariant.BOLT11: + console.log( + `Input is BOLT11 invoice for ${ + parsed.invoice.amountMsat != null + ? parsed.invoice.amountMsat.toString() + : 'unknown' + } msats` + ) + 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}` + ) + break + + case InputTypeVariant.LN_URL_WITHDRAW: + console.log( + `Input is LNURL-Withdraw for min/max ${parsed.data.minWithdrawable}/${parsed.data.maxWithdrawable} msats` + ) + break + + default: + // Other input types are available + break + } + + return parsed + } catch (err) { + console.error(err) + } +} + +const configureParsers = async () => { + try { + const mnemonic = '' + + // Create the default config, providing your Breez API key + const config = await defaultConfig( + LiquidNetwork.MAINNET, + '' + ) + + // Configure external parsers + config.externalInputParsers = [ + { + providerId: 'provider_a', + inputRegex: '^provider_a', + parserUrl: 'https://parser-domain.com/parser?input=' + }, + { + providerId: 'provider_b', + inputRegex: '^provider_b', + parserUrl: 'https://parser-domain.com/parser?input=' + } + ] + + await connect({ mnemonic, config }) + } catch (err) { + console.error(err) + } +} +``` + +## React Native Component Example + +Here's a complete React Native component example that implements basic wallet functionality: + +```javascript +import React, { useState, useEffect } from 'react'; +import { View, Text, ActivityIndicator, Button, StyleSheet, SafeAreaView, ScrollView, TextInput } from 'react-native'; +import { + connect, + defaultConfig, + disconnect, + getInfo, + LiquidNetwork, + PaymentMethod, + prepareReceivePayment, + receivePayment, + ReceiveAmountVariant, + prepareSendPayment, + sendPayment, + PayAmountVariant, + addEventListener, + removeEventListener +} from '@breeztech/react-native-breez-sdk-liquid'; + +const WalletApp = () => { + const [isLoading, setIsLoading] = useState(true); + const [walletInfo, setWalletInfo] = useState(null); + const [listenerId, setListenerId] = useState(null); + const [receiveAddress, setReceiveAddress] = useState(''); + const [sendAddress, setSendAddress] = useState(''); + const [amountSats, setAmountSats] = useState('1000'); + const [error, setError] = useState(''); + + useEffect(() => { + initializeSDK(); + + return () => { + cleanupSDK(); + }; + }, []); + + const initializeSDK = async () => { + try { + setIsLoading(true); + + // Initialize the SDK + const config = await defaultConfig( + LiquidNetwork.TESTNET, // Use TESTNET for development + 'your-api-key-here' + ); + + // Use a sample mnemonic for testing - in production you'd want to get this securely + const mnemonic = 'sample mnemonic words here for testing only'; + + await connect({ mnemonic, config }); + + // Set up event listener + const onEvent = (e) => { + console.log(`Event received: ${e.type}`); + // Refresh wallet info when we get a relevant event + if ( + e.type === 'PAYMENT_SUCCEEDED' || + e.type === 'PAYMENT_FAILED' || + e.type === 'SYNCED' + ) { + refreshWalletInfo(); + } + }; + + const id = await addEventListener(onEvent); + setListenerId(id); + + // Get initial wallet info + await refreshWalletInfo(); + + setIsLoading(false); + } catch (err) { + console.error('Initialization error:', err); + setError(`Initialization error: ${err.message}`); + setIsLoading(false); + } + }; + + const cleanupSDK = async () => { + try { + if (listenerId) { + await removeEventListener(listenerId); + } + await disconnect(); + } catch (err) { + console.error('Cleanup error:', err); + } + }; + + const refreshWalletInfo = async () => { + try { + const info = await getInfo(); + setWalletInfo(info.walletInfo); + } catch (err) { + console.error('Error getting wallet info:', err); + setError(`Error fetching wallet info: ${err.message}`); + } + }; + + const handleReceive = async () => { + try { + setIsLoading(true); + setError(''); + + // Prepare receive request + const amount = { + type: ReceiveAmountVariant.BITCOIN, + payerAmountSat: parseInt(amountSats, 10) + }; + + const prepareResponse = await prepareReceivePayment({ + paymentMethod: PaymentMethod.LIGHTNING, + amount + }); + + // Check fees + console.log(`Receive fees: ${prepareResponse.feesSat} sats`); + + // Create receive request + const receiveResponse = await receivePayment({ + prepareResponse, + description: 'Payment request from React Native app' + }); + + setReceiveAddress(receiveResponse.destination); + setIsLoading(false); + } catch (err) { + console.error('Error creating receive request:', err); + setError(`Error creating receive request: ${err.message}`); + setIsLoading(false); + } + }; + + const handleSend = async () => { + try { + if (!sendAddress) { + setError('Please enter a destination address'); + return; + } + + setIsLoading(true); + setError(''); + + // Prepare send payment + const amount = { + type: PayAmountVariant.BITCOIN, + receiverAmountSat: parseInt(amountSats, 10) + }; + + const prepareResponse = await prepareSendPayment({ + destination: sendAddress, + amount + }); + + // Check fees + console.log(`Send fees: ${prepareResponse.feesSat} sats`); + + // Confirm with user (in a real app) + // For this example, we'll just proceed + + // Send payment + const sendResponse = await sendPayment({ + prepareResponse + }); + + console.log('Payment sent:', sendResponse.payment); + + // Clear form fields + setSendAddress(''); + + // Refresh wallet info + await refreshWalletInfo(); + + setIsLoading(false); + } catch (err) { + console.error('Error sending payment:', err); + setError(`Error sending payment: ${err.message}`); + setIsLoading(false); + } + }; + + if (isLoading) { + return ( + + + Initializing wallet... + + ); + } + + return ( + + + Breez SDK Wallet + + {error ? {error} : null} + + + Balance: + + {walletInfo ? `${walletInfo.balanceSat} sats` : 'Unknown'} + + + + + Receive Payment + + + + +

Transaction History

+
    + {transactions.map((tx, index) => ( +
  • + {tx.paymentType === 'receive' ? 'Received: ' : 'Sent: '} + {tx.amountSat.toLocaleString()} sats + {tx.status !== 'complete' && ` (${tx.status})`} +
  • + ))} +
+ + )} + + ); +} + +export default WalletComponent; +``` + +## End-User Fees + +### Sending Lightning Payments + +Sending Lightning payments involves a submarine swap and two Liquid on-chain transactions: + +1. User broadcasts an L-BTC transaction to a Liquid lockup address +2. Swapper pays the invoice and gets a preimage +3. Swapper broadcasts an L-BTC transaction to claim funds + +Fee components: +- Lockup Transaction Fee: ~34 sats (0.1 sat/discount vbyte) +- Claim Transaction Fee: ~19 sats (0.1 sat/discount vbyte) +- Swap Service Fee: 0.1% of the amount sent + +Example: If sending 10k sats, total fee = 34 + 19 + 10 = 63 sats + +### Receiving Lightning Payments + +Receiving Lightning payments involves a reverse submarine swap and two Liquid on-chain transactions: + +1. Sender pays the Swapper invoice +2. Swapper broadcasts an L-BTC transaction to a Liquid lockup address +3. SDK claims the funds and exposes the preimage +4. Swapper uses the preimage to claim funds + +Fee components: +- Lockup Transaction Fee: ~27 sats (0.1 sat/discount vbyte) +- Claim Transaction Fee: ~20 sats (0.1 sat/discount vbyte) +- Swap Service Fee: 0.25% of the amount received + +Example: If receiving 10k sats, total fee = 27 + 20 + 25 = 72 sats + +### Sending to a Bitcoin Address + +Sending to a BTC address involves a trustless chain swap with 2 Liquid transactions and 2 BTC transactions: + +1. SDK broadcasts an L-BTC transaction to a Liquid lockup address +2. Swapper broadcasts a BTC transaction to a Bitcoin lockup address +3. Recipient claims the funds from the Bitcoin lockup address +4. Swapper claims the funds from the Liquid lockup address + +Fee components: +- L-BTC Lockup Transaction Fee: ~34 sats +- BTC Lockup Transaction Fee: based on current BTC mempool usage +- Swap Service Fee: 0.1% of the amount sent +- BTC Claim Transaction Fee: fees to claim BTC to destination address + +Example: For 100k sats with 2000 sats BTC mining fees and 1000 sats claim fees, total = 34 + 2000 + 100 + 1000 = 3134 sats + +### Receiving from a BTC Address + +Receiving from a BTC address also involves a trustless chain swap: + +1. Sender broadcasts a BTC transaction to the Bitcoin lockup address +2. Swapper broadcasts an L-BTC transaction to a Liquid lockup address +3. SDK claims the funds from the Liquid lockup address +4. Swapper claims the funds from the Bitcoin lockup address + +Fee components: +- L-BTC Claim Transaction Fee: ~20 sats +- BTC Claim Transaction Fee: based on Bitcoin mempool usage +- Swapper Service Fee: 0.1% of the amount received + +Example: For 100k sats with 2000 sats BTC mining fees, total = 20 + 100 + 2000 = 2120 sats + +## Best Practices + +### Syncing + +Always ensure the SDK instance is synced before performing actions: + +```typescript +const waitForSynced = async (sdk) => { + const eventPromise = new Promise((resolve) => { + const listener = { + onEvent: (event) => { + if (event.type === 'synced') { + resolve(); + } + } + }; + sdk.addEventListener(listener); + }); + + // Wait for sync event or timeout after 30 seconds + return Promise.race([ + eventPromise, + new Promise((_, reject) => setTimeout(() => reject(new Error('Sync timeout')), 30000)) + ]); +}; + +const performAction = async (sdk) => { + await waitForSynced(sdk); + + // Now it's safe to perform actions + const info = await sdk.getInfo(); + console.log(info); +}; +``` + +### Error Handling + +Implement robust error handling: + +```typescript +const safeOperation = async (operation) => { + try { + return await operation(); + } catch (error) { + console.error(`Operation failed: ${error.message}`); + // Handle specific error types appropriately + if (error.message.includes('insufficient funds')) { + // Handle insufficient funds error + } else if (error.message.includes('connection')) { + // Handle connection error + } + throw error; // Re-throw or handle at a higher level + } +}; + +// Usage +const result = await safeOperation(() => sdk.sendPayment({ + prepareResponse +})); +``` + +### Connection Lifecycle + +Manage the connection lifecycle properly: + +```typescript +// Initialize only once per session +const sdk = await connect(connectRequest); + +// Use SDK +// ... + +// Disconnect when done +try { + await sdk.disconnect(); +} catch (error) { + console.error(`Error disconnecting: ${error.message}`); +} +``` + +### Fee Handling + +Always check fees before executing payments: + +```typescript +const executeSafePayment = async (sdk, destination, maxAcceptableFee = 1000) => { + // Get fee information + const prepareResponse = await sdk.prepareSendPayment({ + destination + }); + + const feesSat = prepareResponse.feesSat || 0; + + // Check if fees are acceptable before proceeding + if (feesSat <= maxAcceptableFee) { + // Execute payment + return await sdk.sendPayment({ + prepareResponse + }); + } else { + // Fees are too high + throw new Error(`Fees too high: ${feesSat} sats (maximum: ${maxAcceptableFee} sats)`); + } +}; +``` + +### Browser Storage Considerations + +When working with browser storage, consider: + +```typescript +const secureMnemonicStorage = { + // Save mnemonic - in production, consider more secure options than localStorage + save: (mnemonic) => { + // For demonstration only - not secure for production + localStorage.setItem('encrypted_mnemonic', btoa(mnemonic)); + }, + + // Retrieve mnemonic + retrieve: () => { + const storedMnemonic = localStorage.getItem('encrypted_mnemonic'); + return storedMnemonic ? atob(storedMnemonic) : null; + }, + + // Clear mnemonic + clear: () => { + localStorage.removeItem('encrypted_mnemonic'); + } +}; +``` + +## Security Considerations + +1. **Protecting Mnemonics** + - Never hardcode mnemonics in your code + - Store encrypted or in secure storage + - Consider using a custom signer for production apps + - For web apps, consider using hardware wallet integration + +2. **API Key Security** + - Store API keys in environment variables + - Consider proxy server for production to avoid exposing keys in frontend code + - Don't commit API keys to source control + +3. **Validating Inputs** + - Always validate payment destinations + - Check amounts are within reasonable limits + - Sanitize and validate all external inputs + +4. **WASM-Specific Considerations** + - Load WASM files from a trusted source + - Be aware of cross-origin restrictions and CORS settings + - Consider using CSP (Content Security Policy) to restrict where WASM can be loaded from + +5. **Browser Environment** + - Use HTTPS for all connections + - Be aware of limited storage options in browsers + - Consider the implications of users clearing browser storage and provide recovery options \ No newline at end of file From e114af2b231944b886f046cc4d06044b77bf1ec5 Mon Sep 17 00:00:00 2001 From: Aljaz Ceru Date: Thu, 1 May 2025 10:44:24 +0200 Subject: [PATCH 2/2] adding python --- .../breez-nodeless-python-llms.md | 1502 +++++++++++++++++ 1 file changed, 1502 insertions(+) create mode 100644 examples/breez-nodeless-llms/breez-nodeless-python-llms.md diff --git a/examples/breez-nodeless-llms/breez-nodeless-python-llms.md b/examples/breez-nodeless-llms/breez-nodeless-python-llms.md new file mode 100644 index 00000000..33f4b83f --- /dev/null +++ b/examples/breez-nodeless-llms/breez-nodeless-python-llms.md @@ -0,0 +1,1502 @@ +# Breez SDK Nodeless (Liquid implementation) Documentation + +This comprehensive document provides all the context needed to build applications with the Python bindings of Breez SDK Nodeless, a toolkit for integrating Bitcoin, Lightning Network, and Liquid Network functionality into your applications. + +## Overview + +Breez SDK Nodeless (Liquid implementation) is a powerful library that allows developers to integrate Bitcoin, Lightning Network, and Liquid Network functionality into their applications. The SDK abstracts many of the complexities of working with these technologies, providing a simple interface for common operations such as sending and receiving payments, managing wallets, and interacting with both Lightning and Liquid networks. + +Key capabilities include: +- Wallet management and balance tracking +- Lightning Network payments +- Liquid Network transactions +- Onchain Bitcoin operations +- LNURL support (auth, pay, withdraw) +- Non-Bitcoin asset management +- Webhook integration +- Event handling with listeners + +## Getting Started + +### Installation + +```bash +pip install breez-sdk-liquid +``` + +### Guidelines +- **always make sure the sdk instance is synced before performing any actions** +- **Add logging**: Add sufficient logging into your application to diagnose any issues users are having. +- **Display pending payments**: Payments always contain a status field that can be used to determine if the payment was completed or not. Make sure you handle the case where the payment is still pending by showing the correct status to the user. + +- **Enable swap refunds**: Swaps that are the result of receiving an On-Chain Transaction may not be completed and change to `Refundable` state. Make sure you handle this case correctly by allowing the user to retry the refund with different fees as long as the refund is not confirmed. +- **Expose swap fees**: When sending or receiving on-chain, make sure to clearly show the expected fees involved, as well as the send / receive amounts. +### Initializing the SDK + +To get started with Breez SDK Nodeless (Liquid implementation), you need to initialize the SDK with your configuration: + +```python +import breez_sdk_liquid +from breez_sdk_liquid import connect, default_config, ConnectRequest, LiquidNetwork + +def start(): + # Your mnemonic seed phrase for wallet recovery + mnemonic = "" + + # Create the default config, providing your Breez API key + config = default_config( + network=LiquidNetwork.MAINNET, + breez_api_key="" + ) + + # Customize the config object according to your needs + config.working_dir = "path to an existing directory" + + try: + connect_request = ConnectRequest( + config=config, + mnemonic=mnemonic + ) + sdk = connect(connect_request) + return sdk + except Exception as error: + logging.error(error) + raise +``` + +### Custom Signer Support + +If you prefer to manage your own keys, you can use a custom signer: + +```python +import logging +import breez_sdk_liquid +from breez_sdk_liquid import connect_with_signer, default_config, LiquidNetwork, Signer, ConnectWithSignerRequest + +def connect_with_self_signer(signer: Signer): + + # Create the default config, providing your Breez API key + config = default_config( + network=LiquidNetwork.MAINNET, + breez_api_key="" + ) + + # Customize the config object according to your needs + config.working_dir = "path to an existing directory" + + try: + connect_request = ConnectWithSignerRequest(config=config) + sdk = connect_with_signer(connect_request, signer) + return sdk + except Exception as error: + logging.error(error) + raise +``` + +### Basic Operations + +#### Fetch Balance +- **always make sure the sdk instance is synced before performing any actions** + +```python +def fetch_balance(sdk): + try: + info = sdk.get_info() + balance_sat = info.wallet_info.balance_sat + pending_send_sat = info.wallet_info.pending_send_sat + pending_receive_sat = info.wallet_info.pending_receive_sat + except Exception as error: + logging.error(error) + raise +``` + +#### Logging and Event Handling + +```python +# Define a logger class +class SdkLogger(Logger): + def log(self, log_entry: LogEntry): + logging.debug(f"Received log [{log_entry.level}]: {log_entry.line}") + +# Set up logging +def set_logger(logger: SdkLogger): + try: + breez_sdk_liquid.set_logger(logger) + except Exception as error: + logging.error(error) + raise + +# Define an event listener +class SdkListener(EventListener): + def on_event(self, sdk_event: SdkEvent): + logging.debug(f"Received event {sdk_event}") + +# Add an event listener +def add_event_listener(sdk, listener: SdkListener): + try: + listener_id = sdk.add_event_listener(listener) + return listener_id + except Exception as error: + logging.error(error) + raise + +# Remove an event listener +def remove_event_listener(sdk, listener_id): + try: + sdk.remove_event_listener(listener_id) + except Exception as error: + logging.error(error) + raise +``` + +## Core Features + +### Buying Bitcoin + +```python +from breez_sdk_liquid import BindingLiquidSdk, OnchainPaymentLimitsResponse, BuyBitcoinProvider, PrepareBuyBitcoinRequest, PrepareBuyBitcoinResponse, BuyBitcoinRequest + +def fetch_onchain_limits(sdk: BindingLiquidSdk): + try: + current_limits = sdk.fetch_onchain_limits() + logging.debug( + "Minimum amount, in sats ", + current_limits.receive.min_sat + ) + logging.debug( + "Maximum amount, in sats ", + current_limits.receive.max_sat + ) + return current_limits + except Exception as error: + logging.error(error) + raise + +def prepare_buy_btc(sdk: BindingLiquidSdk, current_limits: OnchainPaymentLimitsResponse): + try: + req = PrepareBuyBitcoinRequest( + provider=BuyBitcoinProvider.MOONPAY, + amount_sat=current_limits.receive.min_sat + ) + prepare_response = sdk.prepare_buy_bitcoin(req) + + # Check the fees are acceptable before proceeding + receive_fees_sat = prepare_response.fees_sat + logging.debug("Fees: ", receive_fees_sat, " sats") + return prepare_response + except Exception as error: + logging.error(error) + raise + +def buy_btc(sdk: BindingLiquidSdk, prepare_response: PrepareBuyBitcoinResponse): + try: + req = BuyBitcoinRequest(prepare_response=prepare_response) + url = sdk.buy_bitcoin(req) + except Exception as error: + logging.error(error) + raise +``` + +### Fiat Currencies + +```python +def list_fiat_currencies(sdk): + try: + supported_fiat_currencies = sdk.list_fiat_currencies() + except Exception as error: + print(error) + raise + +def fetch_fiat_rates(sdk): + try: + fiat_rates = sdk.fetch_fiat_rates() + except Exception as error: + print(error) + raise +``` + +### Sending Payments + +#### Lightning Payments + +Sending Lightning payments involves a submarine swap and two Liquid on-chain transactions. The process is as follows: + +1. User broadcasts an L-BTC transaction to a Liquid lockup address. +2. Swapper pays the invoice, sending to the recipient, and then gets a preimage. +3. Swapper broadcasts an L-BTC transaction to claim the funds from the Liquid lockup address. + +The fee a user pays to send a Lightning payment is composed of three parts: + +1. **Lockup Transaction Fee:** ~34 sats (0.1 sat/discount vbyte). +2. **Claim Transaction Fee:** ~19 sats (0.1 sat/discount vbyte). +3. **Swap Service Fee:** 0.1% fee on the amount sent. + +Note: swap service fee is dynamic and can change. Currently, it is 0.1%. + +> **Example**: If the user sends 10k sats, the fee would be: +> +> - 34 sats [Lockup Transaction Fee] + 19 sats [Claim Transaction Fee] + 10 sats [Swapper Service Fee] = 63 sats +```python +def prepare_send_payment_lightning_bolt11(sdk: BindingLiquidSdk): + # Set the bolt11 invoice you wish to pay + destination = "" + try: + prepare_response = sdk.prepare_send_payment( + PrepareSendRequest( + destination=destination + ) + ) + + # If the fees are acceptable, continue to create the Send Payment + send_fees_sat = prepare_response.fees_sat + logging.debug(f"Fees: {send_fees_sat} sats") + return prepare_response + except Exception as error: + logging.error(error) + raise + +def prepare_send_payment_lightning_bolt12(sdk: BindingLiquidSdk): + # Set the bolt12 offer you wish to pay + destination = "" + try: + optional_amount = PayAmount.BITCOIN(5_000) + + prepare_response = sdk.prepare_send_payment( + PrepareSendRequest( + destination=destination, + amount=optional_amount + ) + ) + + return prepare_response + except Exception as error: + logging.error(error) + raise +``` + +#### Liquid Payments + +```python +def prepare_send_payment_liquid(sdk: BindingLiquidSdk): + # Set the Liquid BIP21 or Liquid address you wish to pay + destination = "" + try: + optional_amount = PayAmount.BITCOIN(5_000) + prepare_response = sdk.prepare_send_payment( + PrepareSendRequest( + destination=destination, + amount=optional_amount + ) + ) + + # If the fees are acceptable, continue to create the Send Payment + send_fees_sat = prepare_response.fees_sat + logging.debug(f"Fees: {send_fees_sat} sats") + return prepare_response + except Exception as error: + logging.error(error) + raise + +def prepare_send_payment_liquid_drain(sdk: BindingLiquidSdk): + # Set the Liquid BIP21 or Liquid address you wish to pay + destination = "" + try: + optional_amount = PayAmount.DRAIN + prepare_response = sdk.prepare_send_payment( + PrepareSendRequest( + destination=destination, + amount=optional_amount + ) + ) + + # If the fees are acceptable, continue to create the Send Payment + send_fees_sat = prepare_response.fees_sat + logging.debug(f"Fees: {send_fees_sat} sats") + return prepare_response + except Exception as error: + logging.error(error) + raise +``` + +#### Execute Payment +- **always make sure the sdk instance is synced before performing any actions** + +```python +def send_payment(sdk: BindingLiquidSdk, prepare_response: PrepareSendResponse): + try: + send_response = sdk.send_payment( + SendPaymentRequest( + prepare_response=prepare_response + ) + ) + payment = send_response.payment + except Exception as error: + logging.error(error) + raise +``` + +### Receiving Payments +**always make sure the sdk instance is synced before performing any actions** +#### Lightning Payments +Receiving Lightning payments involves a reverse submarine swap and requires two Liquid on-chain transactions. The process is as follows: + +1. Sender pays the Swapper invoice. +2. Swapper broadcasts an L-BTC transaction to a Liquid lockup address. +3. SDK claims the funds from the Liquid lockup address and then exposes the preimage. +4. Swapper uses the preimage to claim the funds from the Liquid lockup address. + +The fee a user pays to receive a Lightning payment is composed of three parts: + +1. **Lockup Transaction Fee:** ~27 sats (0.1 sat/discount vbyte). +2. **Claim Transaction Fee:** ~20 sats (0.1 sat/discount vbyte). +3. **Swap Service Fee:** 0.25% fee on the amount received. + +Note: swap service fee is dynamic and can change. Currently, it is 0.25%. + +> **Example**: If the sender sends 10k sats, the fee for the end-user would be: +> +> - 27 sats [Lockup Transaction Fee] + 20 sats [Claim Transaction Fee] + 25 sats [Swapper Service Fee] = 72 sats +```python +def prepare_receive_lightning(sdk: BindingLiquidSdk): + try: + # Fetch the lightning Receive limits + current_limits = sdk.fetch_lightning_limits() + logging.debug(f"Minimum amount allowed to deposit in sats {current_limits.receive.min_sat}") + logging.debug(f"Maximum amount allowed to deposit in sats {current_limits.receive.max_sat}") + + # 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, + amount=optional_amount + ) + prepare_response = sdk.prepare_receive_payment(prepare_request) + + # If the fees are acceptable, continue to create the Receive Payment + receive_fees_sat = prepare_response.fees_sat + logging.debug(f"Fees: {receive_fees_sat} sats") + return prepare_response + except Exception as error: + logging.error(error) + raise +``` + +#### Onchain Payments + +```python +def prepare_receive_onchain(sdk: BindingLiquidSdk): + try: + # Fetch the onchain Receive limits + current_limits = sdk.fetch_onchain_limits() + logging.debug(f"Minimum amount allowed to deposit in sats {current_limits.receive.min_sat}") + logging.debug(f"Maximum amount allowed to deposit in sats {current_limits.receive.max_sat}") + + # Set the onchain 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.BITCOIN_ADDRESS, + amount=optional_amount + ) + prepare_response = sdk.prepare_receive_payment(prepare_request) + + # If the fees are acceptable, continue to create the Receive Payment + receive_fees_sat = prepare_response.fees_sat + logging.debug(f"Fees: {receive_fees_sat} sats") + return prepare_response + except Exception as error: + logging.error(error) + raise +``` + +#### Liquid Payments + +```python +def prepare_receive_liquid(sdk: BindingLiquidSdk): + try: + # Create a Liquid BIP21 URI/address to receive a payment to. + # There are no limits, but the payer amount should be greater than broadcast fees when specified + # Note: Not setting the amount will generate a plain Liquid address + optional_amount = ReceiveAmount.BITCOIN(5_000) + prepare_request = PrepareReceiveRequest( + payment_method=PaymentMethod.LIQUID_ADDRESS, + amount=optional_amount + ) + prepare_response = sdk.prepare_receive_payment(prepare_request) + + # If the fees are acceptable, continue to create the Receive Payment + receive_fees_sat = prepare_response.fees_sat + logging.debug(f"Fees: {receive_fees_sat} sats") + return prepare_response + except Exception as error: + logging.error(error) + raise +``` + +#### Execute Receive + +```python +def receive_payment(sdk: BindingLiquidSdk, prepare_response: PrepareReceiveResponse): + try: + optional_description = "" + req = ReceivePaymentRequest( + prepare_response=prepare_response, + description=optional_description + ) + res = sdk.receive_payment(req) + destination = res.destination + except Exception as error: + logging.error(error) + raise +``` + +### LNURL Operations + +#### LNURL Authentication + +```python +def auth(sdk: BindingLiquidSdk): + # Endpoint can also be of the form: + # keyauth://domain.com/auth?key=val + lnurl_auth_url = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttvdankjm3lw3skw0tvdankjm3xdvcn6vtp8q6n2dfsx5mrjwtrxdjnqvtzv56rzcnyv3jrxv3sxqmkyenrvv6kve3exv6nqdtyv43nqcmzvdsnvdrzx33rsenxx5unqc3cxgeqgntfgu" + + try: + parsed_input = sdk.parse(lnurl_auth_url) + if isinstance(parsed_input, InputType.LN_URL_AUTH): + result = sdk.lnurl_auth(parsed_input.data) + if result.is_ok(): + logging.debug("Successfully authenticated") + else: + logging.debug("Failed to authenticate") + except Exception as error: + logging.error(error) + raise +``` + +#### LNURL Pay + +```python +def prepare_pay(sdk: BindingLiquidSdk): + # 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 = "" + optional_validate_success_action_url = True + + req = PrepareLnUrlPayRequest( + data=parsed_input.data, + amount=amount, + bip353_address=parsed_input.bip353_address, + comment=optional_comment, + validate_success_action_url=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 + +def pay(sdk: BindingLiquidSdk, prepare_response: PrepareLnUrlPayResponse): + try: + result = sdk.lnurl_pay(LnUrlPayRequest(prepare_response)) + except Exception as error: + logging.error(error) + raise +``` + +#### LNURL Withdraw + +```python +def withdraw(sdk: BindingLiquidSdk): + # Endpoint can also be of the form: + # lnurlw://domain.com/lnurl-withdraw?key=val + lnurl_withdraw_url = "lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4exctthd96xserjv9mn7um9wdekjmmw843xxwpexdnxzen9vgunsvfexq6rvdecx93rgdmyxcuxverrvcursenpxvukzv3c8qunsdecx33nzwpnvg6ryc3hv93nzvecxgcxgwp3h33lxk" + + try: + parsed_input = sdk.parse(lnurl_withdraw_url) + if isinstance(parsed_input, InputType.LN_URL_WITHDRAW): + amount_msat = parsed_input.data.min_withdrawable + result = sdk.lnurl_withdraw(parsed_input.data, amount_msat, "comment") + return result + except Exception as error: + logging.error(error) + raise +``` + +### Onchain Operations + +#### Pay Onchain + +```python +def fetch_pay_onchain_limits(sdk: BindingLiquidSdk): + try: + current_limits = sdk.fetch_onchain_limits() + logging.debug( + "Minimum amount, in sats ", + current_limits.send.min_sat + ) + logging.debug( + "Maximum amount, in sats ", + current_limits.send.max_sat + ) + return current_limits + except Exception as error: + logging.error(error) + raise + +def prepare_pay_onchain(sdk: BindingLiquidSdk): + try: + amount = PayAmount.BITCOIN(5_000) + prepare_request = PreparePayOnchainRequest(amount=amount) + prepare_response = sdk.prepare_pay_onchain(prepare_request) + + # Check if the fees are acceptable before proceeding + total_fees_sat = prepare_response.total_fees_sat + except Exception as error: + logging.error(error) + raise + +def prepare_pay_onchain_fee_rate(sdk: BindingLiquidSdk): + try: + amount = PayAmount.BITCOIN(5_000) + optional_sat_per_vbyte = 21 + + prepare_request = PreparePayOnchainRequest( + amount=amount, + fee_rate_sat_per_vbyte=optional_sat_per_vbyte + ) + prepare_response = sdk.prepare_pay_onchain(prepare_request) + + # Check if the fees are acceptable before proceeding + claim_fees_sat = prepare_response.claim_fees_sat + total_fees_sat = prepare_response.total_fees_sat + except Exception as error: + logging.error(error) + raise + +def start_pay_onchain(sdk: BindingLiquidSdk, prepare_response: PreparePayOnchainResponse): + address = "bc1.." + try: + sdk.pay_onchain(PayOnchainRequest( + address=address, + prepare_response=prepare_response + )) + except Exception as error: + logging.error(error) + raise +``` + +#### Drain Funds + +```python +def prepare_pay_onchain_drain(sdk: BindingLiquidSdk): + try: + amount = PayAmount.DRAIN + prepare_request = PreparePayOnchainRequest(amount=amount) + prepare_response = sdk.prepare_pay_onchain(prepare_request) + + # Check if the fees are acceptable before proceeding + total_fees_sat = prepare_response.total_fees_sat + except Exception as error: + logging.error(error) + raise +``` + +#### Receive Onchain + +```python +def list_refundables(sdk: BindingLiquidSdk): + try: + refundables = sdk.list_refundables() + return refundables + except Exception as error: + logging.error(error) + raise + +def execute_refund(sdk: BindingLiquidSdk, refund_tx_fee_rate: int, refundable: RefundableSwap): + destination_address = "..." + fee_rate_sat_per_vbyte = refund_tx_fee_rate + try: + sdk.refund(RefundRequest( + swap_address=refundable.swap_address, + refund_address=destination_address, + fee_rate_sat_per_vbyte=fee_rate_sat_per_vbyte + )) + except Exception as error: + logging.error(error) + raise + +def rescan_swaps(sdk: BindingLiquidSdk): + try: + sdk.rescan_onchain_swaps() + except Exception as error: + logging.error(error) + raise + +def recommended_fees(sdk: BindingLiquidSdk): + try: + fees = sdk.recommended_fees() + except Exception as error: + logging.error(error) + raise +``` + +#### Handle Fee Acceptance + +```python +def handle_payments_waiting_fee_acceptance(sdk: BindingLiquidSdk): + try: + # Payments on hold waiting for fee acceptance have the state WAITING_FEE_ACCEPTANCE + payments_waiting_fee_acceptance = sdk.list_payments( + ListPaymentsRequest( + states=[PaymentState.WAITING_FEE_ACCEPTANCE] + ) + ) + + for payment in payments_waiting_fee_acceptance: + if not isinstance(payment.details, PaymentDetails.BITCOIN): + # Only Bitcoin payments can be `WAITING_FEE_ACCEPTANCE` + continue + + fetch_fees_response = sdk.fetch_payment_proposed_fees( + FetchPaymentProposedFeesRequest( + swap_id=payment.details.swap_id + ) + ) + + logging.info( + f"Payer sent {fetch_fees_response.payer_amount_sat} " + f"and currently proposed fees are {fetch_fees_response.fees_sat}" + ) + + # If the user is ok with the fees, accept them, allowing the payment to proceed + sdk.accept_payment_proposed_fees( + AcceptPaymentProposedFeesRequest( + response=fetch_fees_response + ) + ) + + except Exception as error: + logging.error(error) + raise +``` + +### Non-Bitcoin Assets + +```python +def prepare_receive_asset(sdk: BindingLiquidSdk): + try: + # Create a Liquid BIP21 URI/address to receive an asset payment to. + # Note: Not setting the amount will generate an amountless BIP21 URI. + usdt_asset_id = "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2" + optional_amount = ReceiveAmount.ASSET(usdt_asset_id, 1.50) + prepare_request = PrepareReceiveRequest( + payment_method=PaymentMethod.LIQUID_ADDRESS, + amount=optional_amount + ) + prepare_response = sdk.prepare_receive_payment(prepare_request) + + # If the fees are acceptable, continue to create the Receive Payment + receive_fees_sat = prepare_response.fees_sat + logging.debug("Fees: ", receive_fees_sat, " sats") + return prepare_response + except Exception as error: + logging.error(error) + raise + +def prepare_send_payment_asset(sdk: BindingLiquidSdk): + # Set the Liquid BIP21 or Liquid address you wish to pay + destination = "" + try: + # If the destination is an address or an amountless BIP21 URI, + # you must specifiy an asset amount + usdt_asset_id = "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2" + optional_amount = PayAmount.ASSET(usdt_asset_id, 1.50) + prepare_response = sdk.prepare_send_payment(PrepareSendRequest(destination, optional_amount)) + + # If the fees are acceptable, continue to create the Send Payment + send_fees_sat = prepare_response.fees_sat + logging.debug("Fees: ", send_fees_sat, " sats") + return prepare_response + except Exception as error: + logging.error(error) + raise + +def configure_asset_metadata(): + # Create the default config + config = default_config( + network=LiquidNetwork.MAINNET, + breez_api_key="" + ) + + # Configure asset metadata + config.asset_metadata = [ + AssetMetadata( + asset_id="18729918ab4bca843656f08d4dd877bed6641fbd596a0a963abbf199cfeb3cec", + name="PEGx EUR", + ticker="EURx", + precision=8 + ) + ] + +def fetch_asset_balance(sdk: BindingLiquidSdk): + try: + info = sdk.get_info() + asset_balances = info.wallet_info.asset_balances + except Exception as error: + logging.error(error) + raise +``` + +### Messages and Signing + +```python +def sign_message(sdk: BindingLiquidSdk): + message = "" + try: + sign_message_request = SignMessageRequest(message=message) + sign_message_response = sdk.sign_message(sign_message_request) + + # Get the wallet info for your pubkey + info = sdk.get_info() + + signature = sign_message_response.signature + pubkey = info.wallet_info.pubkey + + logging.debug(f"Pubkey: {pubkey}") + logging.debug(f"Signature: {signature}") + except Exception as error: + logging.error(error) + raise + +def check_message(sdk: BindingLiquidSdk): + message = "" + pubkey = "" + signature = "" + try: + check_message_request = CheckMessageRequest( + message=message, + pubkey=pubkey, + signature=signature + ) + check_message_response = sdk.check_message(check_message_request) + + is_valid = check_message_response.is_valid + + logging.debug(f"Signature valid: {is_valid}") + except Exception as error: + logging.error(error) + raise +``` + +### List Payments + +```python +def get_payment(sdk: BindingLiquidSdk): + try: + payment_hash = "" + sdk.get_payment(GetPaymentRequest.PAYMENT_HASH(payment_hash)) + + swap_id = "" + sdk.get_payment(GetPaymentRequest.SWAP_ID(swap_id)) + except Exception as error: + logging.error(error) + raise + +def list_payments(sdk: BindingLiquidSdk): + try: + sdk.list_payments(ListPaymentsRequest()) + except Exception as error: + logging.error(error) + raise + +def list_payments_filtered(sdk: BindingLiquidSdk): + try: + req = ListPaymentsRequest( + filters = [PaymentType.SEND], + from_timestamp = 1696880000, + to_timestamp = 1696959200, + offset = 0, + limit = 50) + sdk.list_payments(req) + except Exception as error: + logging.error(error) + raise + +def list_payments_details_address(sdk: BindingLiquidSdk): + try: + address = "" + req = ListPaymentsRequest( + details = ListPaymentDetails.BITCOIN(address)) + sdk.list_payments(req) + except Exception as error: + logging.error(error) + raise + +def list_payments_details_destination(sdk: BindingLiquidSdk): + try: + destination = "" + req = ListPaymentsRequest( + details = ListPaymentDetails.LIQUID(destination=destination)) + sdk.list_payments(req) + except Exception as error: + logging.error(error) + raise +``` + +### Webhook Integration + +```python +def register_webhook(sdk: BindingLiquidSdk): + try: + sdk.register_webhook("https://your-nds-service.com/notify?platform=ios&token=") + except Exception as error: + logging.error(error) + raise + +def unregister_webhook(sdk: BindingLiquidSdk): + try: + sdk.unregister_webhook() + except Exception as error: + logging.error(error) + raise +``` + +### Input Parsing + +```python +def parse_input(sdk: BindingLiquidSdk): + input = "an input to be parsed..." + + try: + parsed_input = sdk.parse(input) + if isinstance(parsed_input, InputType.BITCOIN_ADDRESS): + logging.debug(f"Input is Bitcoin address {parsed_input.address.address}") + elif isinstance(parsed_input, InputType.BOLT11): + amount = "unknown" + 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.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): + logging.debug(f"Input is LNURL-Withdraw for min/max {parsed_input.data.min_withdrawable}/{parsed_input.data.max_withdrawable} msats") + # Other input types are available + except Exception as error: + logging.error(error) + raise + +def configure_parsers(): + mnemonic = "" + + # Create the default config, providing your Breez API key + config = default_config( + network=LiquidNetwork.MAINNET, + breez_api_key="" + ) + + # Configure external parsers + config.external_input_parsers = [ + ExternalInputParser( + provider_id="provider_a", + input_regex="^provider_a", + parser_url="https://parser-domain.com/parser?input=" + ), + ExternalInputParser( + provider_id="provider_b", + input_regex="^provider_b", + parser_url="https://parser-domain.com/parser?input=" + ) + ] + + try: + connect_request = ConnectRequest( + config=config, + mnemonic=mnemonic + ) + sdk = connect(connect_request) + return sdk + except Exception as error: + logging.error(error) + raise +``` + +## Complete CLI Example + +The SDK includes a complete CLI example that shows how to build a functional application: + +```python +from typing import Optional +from colorama import init as colorama_init, Style +from mnemonic import Mnemonic +from os.path import exists +from qrcode.main import QRCode +from qrcode.constants import ERROR_CORRECT_L +from breez_sdk_liquid import LiquidNetwork, PayAmount, ReceiveAmount +import argparse +import breez_sdk_liquid +import sys +import time +import os +import json + +colorama_init() + +class Sdk: + """ + A wrapper class for the Breez SDK. + + This class initializes the Breez SDK, manages the mnemonic phrase, + and provides methods to interact with the SDK. + + Attributes: + instance: The Breez SDK instance. + listener: An instance of SdkListener to handle SDK events. + """ + + def __init__(self, network: Optional[LiquidNetwork] = None, debug: Optional[bool] = False): + if debug: + breez_sdk_liquid.set_logger(SdkLogListener()) + + api_key = os.getenv('BREEZ_API_KEY') + mnemonic = self.read_mnemonic() + config = breez_sdk_liquid.default_config(network or LiquidNetwork.TESTNET, api_key) + connect_request = breez_sdk_liquid.ConnectRequest(config=config, mnemonic=mnemonic) + self.instance = breez_sdk_liquid.connect(connect_request) + self.listener = SdkListener() + self.instance.add_event_listener(self.listener) + + def is_paid(self, destination: str): + return self.listener.is_paid(destination) + + def is_synced(self): + return self.listener.is_synced() + + def read_mnemonic(self): + if exists('phrase'): + with open('phrase') as f: + mnemonic = f.readline() + f.close() + return mnemonic + else: + with open('phrase', 'w') as f: + mnemo = Mnemonic("english") + words = mnemo.generate(strength=128) + f.write(words) + f.close() + return words + + +class SdkLogListener(breez_sdk_liquid.Logger): + def log(self, log_entry): + if log_entry.level != "TRACE": + print_dim("{}: {}".format(log_entry.level, log_entry.line)) + + +class SdkListener(breez_sdk_liquid.EventListener): + """ + A listener class for handling Breez SDK events. + + This class extends the EventListener from breez_sdk_liquid and implements + custom event handling logic, particularly for tracking successful payments. + + Attributes: + paid (list): A list to store destinations of successful payments. + """ + + def __init__(self): + """ + Initialize the SdkListener. + + Sets up an empty list to track paid destinations. + """ + self.synced = False + self.paid = [] + + def on_event(self, event): + if isinstance(event, breez_sdk_liquid.SdkEvent.SYNCED): + self.synced = True + else: + print_dim(event) + if isinstance(event, breez_sdk_liquid.SdkEvent.PAYMENT_SUCCEEDED): + if event.details.destination: + self.paid.append(event.details.destination) + + def is_paid(self, destination: str): + return destination in self.paid + + def is_synced(self): + return self.synced + +def parse_network(network_str: str) -> LiquidNetwork: + if network_str == 'TESTNET': + return LiquidNetwork.TESTNET + elif network_str == 'MAINNET': + return LiquidNetwork.MAINNET + + raise Exception("Invalid network specified") + +def parse_pay_amount(params) -> Optional[PayAmount]: + amount_sat = getattr(params, 'amount_sat', None) + amount = getattr(params, 'amount', None) + asset_id = getattr(params, 'asset_id', None) + drain = getattr(params, 'drain', None) + asset_params = (asset_id, amount) + if drain is True: + return PayAmount.DRAIN + elif amount_sat is not None: + return PayAmount.BITCOIN(amount_sat) + elif any(asset_params): + if not all(asset_params): + raise ValueError('Sending an asset requires both `asset_id` and `amount`') + return PayAmount.ASSET(asset_id, amount) + return None + +def parse_receive_amount(params) -> Optional[ReceiveAmount]: + amount_sat = getattr(params, 'amount_sat', None) + amount = getattr(params, 'amount', None) + asset_id = getattr(params, 'asset_id', None) + if amount_sat is not None: + return ReceiveAmount.BITCOIN(amount_sat) + elif asset_id is not None: + return ReceiveAmount.ASSET(asset_id, amount) + return None + +def list_payments(params): + """ + List payments using the Breez SDK. + + This function initializes the Breez SDK and retrieves a list of payments + based on the provided parameters. It then prints the results. + + Args: + params (argparse.Namespace): Command-line arguments containing: + - from_timestamp (int, optional): The start timestamp for filtering payments. + - to_timestamp (int, optional): The end timestamp for filtering payments. + - offset (int, optional): The number of payments to skip before starting to return results. + - limit (int, optional): The maximum number of payments to return. + + Raises: + Exception: If any error occurs during the process of listing payments. + """ + sdk = Sdk(params.network, params.debug) + try: + req = breez_sdk_liquid.ListPaymentsRequest(from_timestamp=params.from_timestamp, + to_timestamp=params.to_timestamp, + offset=params.offset, + limit=params.limit) + res = sdk.instance.list_payments(req) + print(*res, sep='\n\n') + except Exception as error: + print(error) + +def receive_payment(params): + """ + Handles the process of receiving a payment using the Breez SDK. + + This function performs the following steps: + 1. Initializes the SDK + 2. Prepares a receive request to get fee information + 3. Prompts the user to accept the fees + 4. If accepted, initiates the receive payment process + 5. Displays a QR code for the payment destination + 6. Waits for the payment to be received + + 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 + - asset_id (str): The optional id of the asset to receive + - amount (float): The optional amount to receive of the asset + + Raises: + Exception: If any error occurs during the payment receiving process + """ + sdk = Sdk(params.network, params.debug) + try: + # Prepare the receive request to get fees + receive_amount = parse_receive_amount(params) + prepare_req = breez_sdk_liquid.PrepareReceiveRequest(payment_method=getattr(breez_sdk_liquid.PaymentMethod, params.method), amount=receive_amount) + prepare_res = sdk.instance.prepare_receive_payment(prepare_req) + # Prompt to accept fees + accepted = input(f"Fees: {prepare_res.fees_sat} sat. Are the fees acceptable? (y/N)? : ") + if accepted in ["Y", "y"]: + # Receive payment + req = breez_sdk_liquid.ReceivePaymentRequest(prepare_response=prepare_res) + res = sdk.instance.receive_payment(req) + if res.destination: + print_qr(res.destination) + print('Waiting for payment:', res.destination) + wait_for_payment(sdk, res.destination) + except Exception as error: + print(error) + +def send_payment(params): + """ + Handles the process of sending a payment using the Breez SDK. + + This function performs the following steps: + 1. Initializes the SDK + 2. Prepares a send request to get fee information + 3. Prompts the user to accept the fees + 4. If accepted, initiates the send payment process + 5. Waits for the payment to be completed + + Args: + params (argparse.Namespace): Command-line arguments containing: + - destination (str): The bolt11 or Liquid BIP21 URI/address + - amount_sats (int): The amount to send in satoshis + - asset_id (str): The optional id of the asset to send + - amount (float): The optional amount to send of the asset + - drain: Drain all funds when sending + + Raises: + Exception: If any error occurs during the payment sending process + """ + sdk = Sdk(params.network, params.debug) + try: + # Prepare the send request to get fees + pay_amount = parse_pay_amount(params) + prepare_req = breez_sdk_liquid.PrepareSendRequest(destination=params.destination, amount=pay_amount) + prepare_res = sdk.instance.prepare_send_payment(prepare_req) + # Prompt to accept fees + accepted = input(f"Fees: {prepare_res.fees_sat} sat. Are the fees acceptable? (Y/n)? : ") + if accepted == "Y": + # Send payment + req = breez_sdk_liquid.SendPaymentRequest(prepare_response=prepare_res) + res = sdk.instance.send_payment(req) + if res.payment.destination: + print('Sending payment:', res.payment.destination) + wait_for_payment(sdk, res.payment.destination) + except Exception as error: + print(error) + +def print_dim(data): + print(Style.DIM) + print(data) + print(Style.RESET_ALL) + +def print_qr(text: str): + qr = QRCode( + version=1, + error_correction=ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(text) + qr.make(fit=True) + qr.print_ascii() + +def wait_for_payment(sdk: Sdk, destination: str): + while True: + if sdk.is_paid(destination): + break + time.sleep(1) + +def wait_for_synced(sdk: Sdk): + while True: + if sdk.is_synced(): + break + time.sleep(1) + +def get_info(params): + sdk = Sdk(params.network, params.debug) + wait_for_synced(sdk) + res = sdk.instance.get_info() + print(json.dumps(res, default=lambda x: x.__dict__, indent=2)) + +def main(): + if len(sys.argv) <= 1: + sys.argv.append('--help') + + parser = argparse.ArgumentParser('Pythod SDK Example', description='Simple CLI to receive/send payments') + parser.add_argument('-d', '--debug', + default=False, + action='store_true', + help='Output SDK logs') + parser.add_argument('-n', '--network', + help='The network the SDK runs on, either "MAINNET" or "TESTNET"', + type=parse_network) + subparser = parser.add_subparsers(title='subcommands') + # info + info_parser = subparser.add_parser('info', help='Get wallet info') + info_parser.set_defaults(run=get_info) + # list + list_parser = subparser.add_parser('list', help='List payments') + list_parser.add_argument('-f', '--from_timestamp', + type=int, + help='The optional from unix timestamp') + list_parser.add_argument('-t', '--to_timestamp', + type=int, + help='The optional to unix timestamp') + list_parser.add_argument('-o', '--offset', + type=int, + help='The optional offset of payments') + list_parser.add_argument('-l', '--limit', + type=int, + help='The optional limit of listed payments') + list_parser.set_defaults(run=list_payments) + # receive + receive_parser = subparser.add_parser('receive', help='Receive a payment') + receive_parser.add_argument('-m', '--method', + choices=['LIGHTNING', 'BITCOIN_ADDRESS', 'LIQUID_ADDRESS'], + help='The payment method', + required=True) + receive_parser.add_argument('-a', '--amount_sat', + type=int, + help='The optional amount to receive in sats') + receive_parser.add_argument('--asset_id', help='The optional id of the asset to receive') + receive_parser.add_argument('--amount', type=float, help='The optional amount to receive of the asset') + receive_parser.set_defaults(run=receive_payment) + # send + send_parser = subparser.add_parser('send', help='Send a payment') + send_parser.add_argument('-d', '--destination', help='The bolt11 or Liquid BIP21 URI/address', required=True) + send_parser.add_argument('-a', '--amount_sat', type=int, help='The optional amount to send in sats') + send_parser.add_argument('--asset_id', help='The optional id of the asset to send') + send_parser.add_argument('--amount', type=float, help='The optional amount to send of the asset') + send_parser.add_argument('--drain', default=False, action='store_true', help='Drain all funds when sending') + send_parser.set_defaults(run=send_payment) + + args = parser.parse_args() + args.run(args) +``` + + +### End-User fees +#### Sending Lightning Payments + +Sending Lightning payments involves a submarine swap and two Liquid on-chain transactions. The process is as follows: + +1. User broadcasts an L-BTC transaction to a Liquid lockup address. +2. Swapper pays the invoice, sending to the recipient, and then gets a preimage. +3. Swapper broadcasts an L-BTC transaction to claim the funds from the Liquid lockup address. + +The fee a user pays to send a Lightning payment is composed of three parts: + +1. **Lockup Transaction Fee:** ~34 sats (0.1 sat/discount vbyte). +2. **Claim Transaction Fee:** ~19 sats (0.1 sat/discount vbyte). +3. **Swap Service Fee:** 0.1% fee on the amount sent. + +Note: swap service fee is dynamic and can change. Currently, it is 0.1%. + +> **Example**: If the user sends 10k sats, the fee would be: +> +> - 34 sats \[Lockup Transaction Fee\] + 19 sats \[Claim Transaction Fee\] + 10 sats \[Swapper Service Fee\] = 63 sats + +#### Receiving Lightning Payments + +Receiving Lightning payments involves a reverse submarine swap and requires two Liquid on-chain transactions. The process is as follows: + +1. Sender pays the Swapper invoice. +2. Swapper broadcasts an L-BTC transaction to a Liquid lockup address. +3. SDK claims the funds from the Liquid lockup address and then exposes the preimage. +4. Swapper uses the preimage to claim the funds from the Liquid lockup address. + +The fee a user pays to receive a Lightning payment is composed of three parts: + +1. **Lockup Transaction Fee:** ~27 sats (0.1 sat/discount vbyte). +2. **Claim Transaction Fee:** ~20 sats (0.1 sat/discount vbyte). +3. **Swap Service Fee:** 0.25% fee on the amount received. + +Note: swap service fee is dynamic and can change. Currently, it is 0.25%. + +> **Example**: If the sender sends 10k sats, the fee for the end-user would be: +> +> - 27 sats \[Lockup Transaction Fee\] + 20 sats \[Claim Transaction Fee\] + 25 sats \[Swapper Service Fee\] = 72 sats + +#### Sending to a bitcoin address + +Sending to a BTC address involves a trustless chain swap, 2 Liquid on-chain transactions, and 2 BTC on-chain transactions. The process is as follows: + +1. SDK broadcasts an L-BTC transaction to a Liquid lockup address. +2. Swapper broadcasts a BTC transaction to a Bitcoin lockup address. +3. Recipient claims the funds from the Bitcoin lockup address. +4. Swapper claims the funds from the Liquid lockup address. + +The fee to send to a BTC address is composed of four parts: + +1. **L-BTC Lockup Transaction Fee**: ~34 sats (0.1 sat/discount vbyte). +2. **BTC Lockup Transaction Fee**: the swapper charges a mining fee based on the current bitcoin mempool usage. +3. **Swap Service Fee:** 0.1% fee on the amount sent. +4. **BTC Claim Transaction Fee:** the SDK fees to claim BTC funds to the destination address, based on the current Bitcoin mempool usage. + +Note: swap service fee is dynamic and can change. Currently, it is 0.1%. + +> **Example**: If the user sends 100k sats, the mining fees returned by the Swapper are 2000 sats, and the claim fees for the user are 1000 sats—the fee would be: +> +> - 34 sats \[Lockup Transaction Fee\] + 2000 sats \[BTC Claim Transaction Fee\] + 100 sats \[Swapper Service Fee\] + 1000 sats \[BTC Lockup Transaction Fee\] = 3132 sats + +### Receiving from a BTC Address + +Receiving from a BTC address involves a trustless chain swap, 2 Liquid on-chain transactions, and 2 BTC on-chain transactions. + +The process is as follows: + +1. Sender broadcasts a BTC transaction to the Bitcoin lockup address. +2. Swapper broadcasts an L-BTC transaction to a Liquid lockup address. +3. SDK claims the funds from the Liquid lockup address. +4. Swapper claims the funds from the Bitcoin lockup address. + +The fee to receive from a BTC address is composed of three parts: + +1. **L-BTC Claim Transaction Fee:** ~20 sats (0.1 sat/discount vbyte). +2. **BTC Claim Transaction Fee:** the swapper charges a mining fee based on the Bitcoin mempool usage at the time of the swap. +3. **Swapper Service Fee:** the swapper charges a 0.1% fee on the amount received. + +Note: swapper service see is dynamic and can change. Currently, it is 0.1%. + +> **Example**: If the sender sends 100k sats and the mining fees returned by the Swapper are 2000 sats—the fee for the end-user would be: +> +> - 20 sats \[Claim Transaction Fee\] + 100 sats \[Swapper Service Fee\] + 2000 sats \[BTC Claim Transaction Fee\] = 2120 sats +## Best Practices + +### Syncing +- **Always make sure the sdk instance is synced before performing any actions** + +```python +def wait_for_synced(sdk: Sdk): + while True: + if sdk.is_synced(): + break + time.sleep(1) + + +def main(): + sdk = Sdk(params.network, params.debug) + wait_for_synced(sdk) + res = sdk.instance.get_info() + print(json.dumps({ + "walletInfo": res.wallet_info.__dict__, + "blockchainInfo": res.blockchain_info.__dict__, + }, indent=2)) + +``` + +### Error Handling + +Always wrap your SDK method calls in try-except blocks to properly handle errors: + +```python +try: + # Call SDK method + result = sdk.some_method() + # Process result +except Exception as error: + logging.error(f"An error occurred: {error}") + # Handle the error appropriately +``` + +### Connection Lifecycle + +Manage the connection lifecycle properly: + +1. Initialize only once per session +2. Properly handle events +3. Disconnect when done + +```python +# Initialize +sdk = connect(connect_request) + +# Use SDK +# ... + +# Disconnect +try: + sdk.disconnect() +except Exception as error: + logging.error(f"Error disconnecting: {error}") +``` + +### Fee Handling + +Always check fees before executing payments: + +```python +# Get fee information +prepare_response = sdk.prepare_send_payment(prepare_request) +fees_sat = prepare_response.fees_sat + +# Check if fees are acceptable before proceeding +if fees_sat <= max_acceptable_fee: + # Execute payment + sdk.send_payment(SendPaymentRequest(prepare_response=prepare_response)) +else: + # Fees are too high + logging.warning(f"Fees too high: {fees_sat} sats") +``` + +### Event Handling + +Implement a robust event listener to handle asynchronous events: + +```python +class MyEventListener(EventListener): + def on_event(self, event): + if isinstance(event, SdkEvent.SYNCED): + logging.info("SDK is synced") + elif isinstance(event, SdkEvent.PAYMENT_SUCCEEDED): + logging.info(f"Payment succeeded: {event.details.payment_hash}") + elif isinstance(event, SdkEvent.PAYMENT_FAILED): + logging.error(f"Payment failed: {event.details.error}") + # Handle other event types +``` + +## Troubleshooting + +### Common Issues + +1. **Connection Problems** + - Check your API key + - Verify network selection (MAINNET vs TESTNET) + - Confirm working directory permissions + +2. **Payment Issues** + - Verify amount is within allowed limits + - Check fee settings + - Ensure destination is valid + - Verify sufficient balance + +3. **Event Listener Not Triggering** + - Confirm listener is registered with `add_event_listener` + - Check event type matching in your handler + - Add debug logging to trace events + +### Debugging + +Use the SDK's built-in logging system: + +```python +class DetailedLogListener(Logger): + def log(self, log_entry): + print(f"[{log_entry.level}] {log_entry.line}") + +# Set as logger +breez_sdk_liquid.set_logger(DetailedLogListener()) +``` + +## Security Considerations + +1. **Protecting Mnemonics** + - Never hardcode mnemonics in your code + - Store encrypted or in secure storage + - Consider using a custom signer for production apps + +2. **API Key Security** + - Store API keys in environment variables + - Don't commit API keys to source control + - Use different keys for production and testing + +3. **Validating Inputs** + - Always validate payment destinations + - Check amounts are within reasonable limits + - Sanitize and validate all external inputs +