Skip to content

Commit

Permalink
Multi-asset send/receive
Browse files Browse the repository at this point in the history
  • Loading branch information
dangeross committed Jan 20, 2025
1 parent 8076549 commit 4dfef3b
Show file tree
Hide file tree
Showing 25 changed files with 2,315 additions and 473 deletions.
80 changes: 60 additions & 20 deletions cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ pub(crate) enum Command {
#[arg(long)]
address: Option<String>,

/// The amount in satoshi to pay, in case of a direct Liquid address
/// or amount-less BIP21
/// The amount to pay, in case of a direct Liquid address or amount-less BIP21.
/// If an asset id is provided, it is the base unit of that asset depending on its
/// precision, otherwise in satoshi.
#[arg(short, long)]
amount_sat: Option<u64>,
receiver_amount: Option<u64>,

/// Optional id of the asset, in case of a direct Liquid address
/// or amount-less BIP21
#[clap(long = "asset")]
asset_id: Option<String>,

/// Whether or not this is a drain operation. If true, all available funds will be used.
#[arg(short, long)]
Expand Down Expand Up @@ -72,10 +78,15 @@ pub(crate) enum Command {
#[arg(short = 'm', long = "method")]
payment_method: Option<PaymentMethod>,

/// Amount the payer will send, in satoshi
/// If not specified, it will generate a BIP21 URI/Liquid address with no amount
/// The amount the payer should send. If an asset id is provided, it is the base
/// unit of that asset depending on its precision, otherwise in satoshi.
/// If not specified, it will generate a BIP21 URI/address with no amount.
#[arg(short, long)]
payer_amount_sat: Option<u64>,
payer_amount: Option<u64>,

/// Optional id of the asset to receive when the 'payment_method' is "liquid"
#[clap(long = "asset")]
asset_id: Option<String>,

/// Optional description for the invoice
#[clap(short = 'd', long = "description")]
Expand Down Expand Up @@ -118,6 +129,10 @@ pub(crate) enum Command {
#[clap(short = 'o', long = "offset")]
offset: Option<u32>,

/// Optional id of the asset for Liquid payment method
#[clap(long = "asset")]
asset_id: Option<String>,

/// Optional Liquid BIP21 URI/address for Liquid payment method
#[clap(short = 'd', long = "destination")]
destination: Option<String>,
Expand Down Expand Up @@ -266,19 +281,29 @@ pub(crate) async fn handle_command(
Ok(match command {
Command::ReceivePayment {
payment_method,
payer_amount_sat,
payer_amount,
asset_id,
description,
use_description_hash,
} => {
let amount = match asset_id {
Some(asset_id) => Some(ReceiveAmount::Asset {
asset_id,
payer_amount,
}),
None => {
payer_amount.map(|payer_amount_sat| ReceiveAmount::Bitcoin { payer_amount_sat })
}
};
let prepare_response = sdk
.prepare_receive_payment(&PrepareReceiveRequest {
payer_amount_sat,
payment_method: payment_method.unwrap_or(PaymentMethod::Lightning),
amount: amount.clone(),
})
.await?;

let fees = prepare_response.fees_sat;
let confirmation_msg = match payer_amount_sat {
let confirmation_msg = match amount {
Some(_) => format!("Fees: {fees} sat. Are the fees acceptable? (y/N)"),
None => {
let min = prepare_response.min_payer_amount_sat;
Expand Down Expand Up @@ -332,13 +357,14 @@ pub(crate) async fn handle_command(
invoice,
offer,
address,
amount_sat,
receiver_amount,
asset_id,
drain,
delay,
} => {
let destination = match (invoice, offer, address) {
(Some(invoice), None, None) => Ok(invoice),
(None, Some(offer), None) => match amount_sat {
(None, Some(offer), None) => match receiver_amount {
Some(_) => Ok(offer),
None => Err(anyhow!(
"Must specify an amount for a BOLT12 offer."
Expand All @@ -354,10 +380,16 @@ pub(crate) async fn handle_command(
"Must specify either a BOLT11 invoice, a BOLT12 offer or a direct/BIP21 address."
))
}?;
let amount = match (amount_sat, drain.unwrap_or(false)) {
(Some(amount_sat), _) => Some(PayAmount::Receiver { amount_sat }),
(_, true) => Some(PayAmount::Drain),
(_, _) => None,
let amount = match (asset_id, receiver_amount, drain.unwrap_or(false)) {
(Some(asset_id), Some(receiver_amount), _) => Some(PayAmount::Asset {
asset_id,
receiver_amount,
}),
(None, Some(receiver_amount_sat), _) => Some(PayAmount::Bitcoin {
receiver_amount_sat,
}),
(_, _, true) => Some(PayAmount::Drain),
_ => None,
};

let prepare_response = sdk
Expand Down Expand Up @@ -400,8 +432,8 @@ pub(crate) async fn handle_command(
} => {
let amount = match drain.unwrap_or(false) {
true => PayAmount::Drain,
false => PayAmount::Receiver {
amount_sat: receiver_amount_sat.ok_or(anyhow::anyhow!(
false => PayAmount::Bitcoin {
receiver_amount_sat: receiver_amount_sat.ok_or(anyhow::anyhow!(
"Must specify `receiver_amount_sat` if not draining"
))?,
},
Expand Down Expand Up @@ -488,12 +520,20 @@ pub(crate) async fn handle_command(
to_timestamp,
limit,
offset,
asset_id,
destination,
address,
} => {
let details = match (destination, address) {
(Some(destination), None) => Some(ListPaymentDetails::Liquid { destination }),
(None, Some(address)) => Some(ListPaymentDetails::Bitcoin { address }),
let details = match (asset_id.clone(), destination.clone(), address) {
(None, Some(_), None) | (Some(_), None, None) | (Some(_), Some(_), None) => {
Some(ListPaymentDetails::Liquid {
asset_id,
destination,
})
}
(None, None, Some(address)) => Some(ListPaymentDetails::Bitcoin {
address: Some(address),
}),
_ => None,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import breez_sdk_liquid.BindingLiquidSdk
import breez_sdk_liquid.PaymentMethod
import breez_sdk_liquid.PrepareReceiveRequest
import breez_sdk_liquid.ReceiveAmount
import breez_sdk_liquid.ReceivePaymentRequest
import breez_sdk_liquid_notification.Constants.DEFAULT_LNURL_PAY_INVOICE_NOTIFICATION_TITLE
import breez_sdk_liquid_notification.Constants.DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT
Expand Down Expand Up @@ -62,7 +63,7 @@ class LnurlPayInvoiceJob(
DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT
)
val prepareReceivePaymentRes = liquidSDK.prepareReceivePayment(
PrepareReceiveRequest(PaymentMethod.LIGHTNING, amountSat)
PrepareReceiveRequest(PaymentMethod.LIGHTNING, ReceiveAmount.Bitcoin(amountSat))
)
val receivePaymentResponse = liquidSDK.receivePayment(
ReceivePaymentRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ typedef struct wire_cst_list_payment_state {
} wire_cst_list_payment_state;

typedef struct wire_cst_ListPaymentDetails_Liquid {
struct wire_cst_list_prim_u_8_strict *asset_id;
struct wire_cst_list_prim_u_8_strict *destination;
} wire_cst_ListPaymentDetails_Liquid;

Expand Down Expand Up @@ -352,12 +353,18 @@ typedef struct wire_cst_prepare_ln_url_pay_request {
bool *validate_success_action_url;
} wire_cst_prepare_ln_url_pay_request;

typedef struct wire_cst_PayAmount_Receiver {
uint64_t amount_sat;
} wire_cst_PayAmount_Receiver;
typedef struct wire_cst_PayAmount_Bitcoin {
uint64_t receiver_amount_sat;
} wire_cst_PayAmount_Bitcoin;

typedef struct wire_cst_PayAmount_Asset {
struct wire_cst_list_prim_u_8_strict *asset_id;
uint64_t receiver_amount;
} wire_cst_PayAmount_Asset;

typedef union PayAmountKind {
struct wire_cst_PayAmount_Receiver Receiver;
struct wire_cst_PayAmount_Bitcoin Bitcoin;
struct wire_cst_PayAmount_Asset Asset;
} PayAmountKind;

typedef struct wire_cst_pay_amount {
Expand All @@ -370,9 +377,28 @@ typedef struct wire_cst_prepare_pay_onchain_request {
uint32_t *fee_rate_sat_per_vbyte;
} wire_cst_prepare_pay_onchain_request;

typedef struct wire_cst_ReceiveAmount_Bitcoin {
uint64_t payer_amount_sat;
} wire_cst_ReceiveAmount_Bitcoin;

typedef struct wire_cst_ReceiveAmount_Asset {
struct wire_cst_list_prim_u_8_strict *asset_id;
uint64_t *payer_amount;
} wire_cst_ReceiveAmount_Asset;

typedef union ReceiveAmountKind {
struct wire_cst_ReceiveAmount_Bitcoin Bitcoin;
struct wire_cst_ReceiveAmount_Asset Asset;
} ReceiveAmountKind;

typedef struct wire_cst_receive_amount {
int32_t tag;
union ReceiveAmountKind kind;
} wire_cst_receive_amount;

typedef struct wire_cst_prepare_receive_request {
uint64_t *payer_amount_sat;
int32_t payment_method;
struct wire_cst_receive_amount *amount;
} wire_cst_prepare_receive_request;

typedef struct wire_cst_prepare_refund_request {
Expand All @@ -388,7 +414,7 @@ typedef struct wire_cst_prepare_send_request {

typedef struct wire_cst_prepare_receive_response {
int32_t payment_method;
uint64_t *payer_amount_sat;
struct wire_cst_receive_amount *amount;
uint64_t fees_sat;
uint64_t *min_payer_amount_sat;
uint64_t *max_payer_amount_sat;
Expand Down Expand Up @@ -501,6 +527,7 @@ typedef struct wire_cst_PaymentDetails_Lightning {
typedef struct wire_cst_PaymentDetails_Liquid {
struct wire_cst_list_prim_u_8_strict *destination;
struct wire_cst_list_prim_u_8_strict *description;
struct wire_cst_list_prim_u_8_strict *asset_id;
} wire_cst_PaymentDetails_Liquid;

typedef struct wire_cst_PaymentDetails_Bitcoin {
Expand Down Expand Up @@ -645,6 +672,16 @@ typedef struct wire_cst_symbol {
uint32_t *position;
} wire_cst_symbol;

typedef struct wire_cst_asset_balance {
struct wire_cst_list_prim_u_8_strict *asset_id;
uint64_t balance;
} wire_cst_asset_balance;

typedef struct wire_cst_list_asset_balance {
struct wire_cst_asset_balance *ptr;
int32_t len;
} wire_cst_list_asset_balance;

typedef struct wire_cst_localized_name {
struct wire_cst_list_prim_u_8_strict *locale;
struct wire_cst_list_prim_u_8_strict *name;
Expand Down Expand Up @@ -727,6 +764,7 @@ typedef struct wire_cst_wallet_info {
uint64_t pending_receive_sat;
struct wire_cst_list_prim_u_8_strict *fingerprint;
struct wire_cst_list_prim_u_8_strict *pubkey;
struct wire_cst_list_asset_balance *asset_balances;
} wire_cst_wallet_info;

typedef struct wire_cst_get_info_response {
Expand Down Expand Up @@ -1335,6 +1373,8 @@ struct wire_cst_prepare_refund_request *frbgen_breez_liquid_cst_new_box_autoadd_

struct wire_cst_prepare_send_request *frbgen_breez_liquid_cst_new_box_autoadd_prepare_send_request(void);

struct wire_cst_receive_amount *frbgen_breez_liquid_cst_new_box_autoadd_receive_amount(void);

struct wire_cst_receive_payment_request *frbgen_breez_liquid_cst_new_box_autoadd_receive_payment_request(void);

struct wire_cst_refund_request *frbgen_breez_liquid_cst_new_box_autoadd_refund_request(void);
Expand All @@ -1361,6 +1401,8 @@ struct wire_cst_url_success_action_data *frbgen_breez_liquid_cst_new_box_autoadd

struct wire_cst_list_String *frbgen_breez_liquid_cst_new_list_String(int32_t len);

struct wire_cst_list_asset_balance *frbgen_breez_liquid_cst_new_list_asset_balance(int32_t len);

struct wire_cst_list_external_input_parser *frbgen_breez_liquid_cst_new_list_external_input_parser(int32_t len);

struct wire_cst_list_fiat_currency *frbgen_breez_liquid_cst_new_list_fiat_currency(int32_t len);
Expand Down Expand Up @@ -1429,6 +1471,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_refund_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_send_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_receive_amount);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_receive_payment_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_refund_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_restore_request);
Expand All @@ -1442,6 +1485,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_u_64);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_url_success_action_data);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_String);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_asset_balance);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_external_input_parser);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_fiat_currency);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_ln_offer_blinded_path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ class LnurlPayInvoiceTask : LnurlPayTask {
}
let plainTextMetadata = ResourceHelper.shared.getString(key: Constants.LNURL_PAY_METADATA_PLAIN_TEXT, fallback: Constants.DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT)
let metadata = "[[\"text/plain\",\"\(plainTextMetadata)\"]]"
let prepareReceivePaymentRes = try liquidSDK.prepareReceivePayment(req: PrepareReceiveRequest(paymentMethod: PaymentMethod.lightning, payerAmountSat: amountSat))
let amount = ReceiveAmount.bitcoin(payerAmountSat: amountSat)
let prepareReceivePaymentRes = try liquidSDK.prepareReceivePayment(req: PrepareReceiveRequest(paymentMethod: PaymentMethod.lightning, amount: amount))
let receivePaymentRes = try liquidSDK.receivePayment(req: ReceivePaymentRequest(prepareResponse: prepareReceivePaymentRes, description: metadata, useDescriptionHash: true))
self.replyServer(encodable: LnurlInvoiceResponse(pr: receivePaymentRes.destination, routes: []), replyURL: request!.reply_url)
} catch let e {
Expand Down
25 changes: 19 additions & 6 deletions lib/bindings/src/breez_sdk_liquid.udl
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ dictionary ConnectWithSignerRequest {
Config config;
};

dictionary AssetBalance {
string asset_id;
u64 balance;
};

dictionary BlockchainInfo {
u32 liquid_tip;
u32 bitcoin_tip;
Expand All @@ -366,6 +371,7 @@ dictionary WalletInfo {
u64 pending_receive_sat;
string fingerprint;
string pubkey;
sequence<AssetBalance> asset_balances;
};

dictionary GetInfoResponse {
Expand Down Expand Up @@ -441,15 +447,21 @@ enum PaymentMethod {
"LiquidAddress",
};

[Enum]
interface ReceiveAmount {
Bitcoin(u64 payer_amount_sat);
Asset(string asset_id, u64? payer_amount);
};

dictionary PrepareReceiveRequest {
PaymentMethod payment_method;
u64? payer_amount_sat = null;
ReceiveAmount? amount = null;
};

dictionary PrepareReceiveResponse {
PaymentMethod payment_method;
u64 fees_sat;
u64? payer_amount_sat;
ReceiveAmount? amount;
u64? min_payer_amount_sat;
u64? max_payer_amount_sat;
f64? swapper_feerate;
Expand Down Expand Up @@ -483,7 +495,8 @@ dictionary OnchainPaymentLimitsResponse {

[Enum]
interface PayAmount {
Receiver(u64 amount_sat);
Bitcoin(u64 receiver_amount_sat);
Asset(string asset_id, u64 receiver_amount);
Drain();
};

Expand Down Expand Up @@ -543,8 +556,8 @@ dictionary ListPaymentsRequest {

[Enum]
interface ListPaymentDetails {
Liquid(string destination);
Bitcoin(string address);
Liquid(string? asset_id, string? destination);
Bitcoin(string? address);
};

[Enum]
Expand Down Expand Up @@ -580,7 +593,7 @@ dictionary LnUrlInfo {
[Enum]
interface PaymentDetails {
Lightning(string swap_id, string description, u32 liquid_expiration_blockheight, string? preimage, string? invoice, string? bolt12_offer, string? payment_hash, string? destination_pubkey, LnUrlInfo? lnurl_info, string? refund_tx_id, u64? refund_tx_amount_sat);
Liquid(string destination, string description);
Liquid(string asset_id, string destination, string description);
Bitcoin(string swap_id, string description, u32? bitcoin_expiration_blockheight, u32? liquid_expiration_blockheight, string? refund_tx_id, u64? refund_tx_amount_sat);
};

Expand Down
Loading

0 comments on commit 4dfef3b

Please sign in to comment.