Skip to content

Commit

Permalink
Yield send_payment call after a timeout and return a pending payment
Browse files Browse the repository at this point in the history
  • Loading branch information
dangeross committed Apr 23, 2024
1 parent 10eb7aa commit 1128f90
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 35 deletions.
2 changes: 2 additions & 0 deletions libs/sdk-bindings/src/breez_sdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ dictionary Config {
string working_dir;
Network network;
u32 payment_timeout_sec;
u64 payment_request_yield_sec;
string? default_lsp_id;
string? api_key;
f64 maxfee_percent;
Expand Down Expand Up @@ -400,6 +401,7 @@ interface BreezEvent {
NewBlock(u32 block);
InvoicePaid(InvoicePaidDetails details);
Synced();
PaymentStarted(Payment details);
PaymentSucceed(Payment details);
PaymentFailed(PaymentFailedData details);
BackupStarted();
Expand Down
69 changes: 51 additions & 18 deletions libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ pub enum BreezEvent {
},
/// Indicates that the local SDK state has just been sync-ed with the remote components
Synced,
/// Indicates that an outgoing payment has started
PaymentStarted {
details: Payment,
},
/// Indicates that an outgoing payment has been completed successfully
PaymentSucceed {
details: Payment,
Expand Down Expand Up @@ -281,7 +285,7 @@ impl BreezServices {
/// Calling `send_payment` ensures that the payment is not already completed, if so it will result in an error.
/// If the invoice doesn't specify an amount, the amount is taken from the `amount_msat` arg.
pub async fn send_payment(
&self,
self: &Arc<BreezServices>,
req: SendPaymentRequest,
) -> Result<SendPaymentResponse, SendPaymentError> {
self.start_node().await?;
Expand Down Expand Up @@ -313,20 +317,41 @@ impl BreezServices {
{
Some(_) => Err(SendPaymentError::AlreadyPaid),
None => {
self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;
let payment_res = self
.node_api
.send_payment(parsed_invoice.bolt11.clone(), req.amount_msat, req.label)
.map_err(Into::into)
.await;
let payment = self
.on_payment_completed(
parsed_invoice.payee_pubkey.clone(),
Some(parsed_invoice),
payment_res,
)
.await?;
Ok(SendPaymentResponse { payment })
let pending_payment =
self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;
self.notify_event_listeners(BreezEvent::PaymentStarted {
details: pending_payment.clone(),
})
.await?;

let timeout = Duration::from_secs(self.config.payment_request_yield_sec);
let (tx, rx) = std::sync::mpsc::channel();
let cloned = self.clone();
tokio::spawn(async move {
let payment_res = cloned
.node_api
.send_payment(
parsed_invoice.bolt11.clone(),
req.amount_msat,
req.label.clone(),
)
.await;
let result = cloned
.on_payment_completed(
parsed_invoice.payee_pubkey.clone(),
Some(parsed_invoice),
payment_res.map_err(Into::into),
)
.await;
let _ = tx.send(result);
});

match rx.recv_timeout(timeout) {
Ok(result) => result.map(|payment| SendPaymentResponse { payment }),
Err(_) => Ok(SendPaymentResponse {
payment: pending_payment,
}),
}
}
}
}
Expand Down Expand Up @@ -361,7 +386,10 @@ impl BreezServices {
/// is made.
///
/// This method will return an [anyhow::Error] when any validation check fails.
pub async fn lnurl_pay(&self, req: LnUrlPayRequest) -> Result<LnUrlPayResult, LnUrlPayError> {
pub async fn lnurl_pay(
self: &Arc<BreezServices>,
req: LnUrlPayRequest,
) -> Result<LnUrlPayResult, LnUrlPayError> {
match validate_lnurl_pay(
req.amount_msat,
req.comment,
Expand Down Expand Up @@ -1165,7 +1193,7 @@ impl BreezServices {
invoice: &LNInvoice,
amount_msat: u64,
label: Option<String>,
) -> Result<(), SendPaymentError> {
) -> Result<Payment, SendPaymentError> {
self.persister.insert_or_update_payments(
&[Payment {
id: invoice.payment_hash.clone(),
Expand Down Expand Up @@ -1212,7 +1240,12 @@ impl BreezServices {
attempted_error: None,
},
)?;
Ok(())

self.persister
.get_payment_by_hash(&invoice.payment_hash)?
.ok_or(SendPaymentError::Generic {
err: "Payment not found".to_string(),
})
}

async fn on_payment_completed(
Expand Down
3 changes: 3 additions & 0 deletions libs/sdk-core/src/bridge_generated.io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ impl Wire2Api<Config> for wire_Config {
working_dir: self.working_dir.wire2api(),
network: self.network.wire2api(),
payment_timeout_sec: self.payment_timeout_sec.wire2api(),
payment_request_yield_sec: self.payment_request_yield_sec.wire2api(),
default_lsp_id: self.default_lsp_id.wire2api(),
api_key: self.api_key.wire2api(),
maxfee_percent: self.maxfee_percent.wire2api(),
Expand Down Expand Up @@ -1143,6 +1144,7 @@ pub struct wire_Config {
working_dir: *mut wire_uint_8_list,
network: i32,
payment_timeout_sec: u32,
payment_request_yield_sec: u64,
default_lsp_id: *mut wire_uint_8_list,
api_key: *mut wire_uint_8_list,
maxfee_percent: f64,
Expand Down Expand Up @@ -1515,6 +1517,7 @@ impl NewWithNullPtr for wire_Config {
working_dir: core::ptr::null_mut(),
network: Default::default(),
payment_timeout_sec: Default::default(),
payment_request_yield_sec: Default::default(),
default_lsp_id: core::ptr::null_mut(),
api_key: core::ptr::null_mut(),
maxfee_percent: Default::default(),
Expand Down
16 changes: 10 additions & 6 deletions libs/sdk-core/src/bridge_generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1105,19 +1105,22 @@ impl support::IntoDart for BreezEvent {
vec![1.into_dart(), details.into_into_dart().into_dart()]
}
Self::Synced => vec![2.into_dart()],
Self::PaymentSucceed { details } => {
Self::PaymentStarted { details } => {
vec![3.into_dart(), details.into_into_dart().into_dart()]
}
Self::PaymentFailed { details } => {
Self::PaymentSucceed { details } => {
vec![4.into_dart(), details.into_into_dart().into_dart()]
}
Self::BackupStarted => vec![5.into_dart()],
Self::BackupSucceeded => vec![6.into_dart()],
Self::PaymentFailed { details } => {
vec![5.into_dart(), details.into_into_dart().into_dart()]
}
Self::BackupStarted => vec![6.into_dart()],
Self::BackupSucceeded => vec![7.into_dart()],
Self::BackupFailed { details } => {
vec![7.into_dart(), details.into_into_dart().into_dart()]
vec![8.into_dart(), details.into_into_dart().into_dart()]
}
Self::SwapUpdated { details } => {
vec![8.into_dart(), details.into_into_dart().into_dart()]
vec![9.into_dart(), details.into_into_dart().into_dart()]
}
}
.into_dart()
Expand Down Expand Up @@ -1203,6 +1206,7 @@ impl support::IntoDart for Config {
self.working_dir.into_into_dart().into_dart(),
self.network.into_into_dart().into_dart(),
self.payment_timeout_sec.into_into_dart().into_dart(),
self.payment_request_yield_sec.into_into_dart().into_dart(),
self.default_lsp_id.into_dart(),
self.api_key.into_dart(),
self.maxfee_percent.into_into_dart().into_dart(),
Expand Down
6 changes: 6 additions & 0 deletions libs/sdk-core/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,11 @@ pub struct Config {
/// the folder should exist before starting the SDK.
pub working_dir: String,
pub network: Network,
/// Maps to the CLN `retry_for` config when paying invoices (`lightning-pay`)
pub payment_timeout_sec: u32,
/// The duration, in seconds, in which to return from a [crate::BreezServices::send_payment]
/// request with a pending payment if not already finished.
pub payment_request_yield_sec: u64,
pub default_lsp_id: Option<String>,
pub api_key: Option<String>,
/// Maps to the CLN `maxfeepercent` config when paying invoices (`lightning-pay`)
Expand All @@ -498,6 +502,7 @@ impl Config {
working_dir: ".".to_string(),
network: Bitcoin,
payment_timeout_sec: 60,
payment_request_yield_sec: 30,
default_lsp_id: None,
api_key: Some(api_key),
maxfee_percent: 1.0,
Expand All @@ -514,6 +519,7 @@ impl Config {
working_dir: ".".to_string(),
network: Bitcoin,
payment_timeout_sec: 60,
payment_request_yield_sec: 30,
default_lsp_id: None,
api_key: Some(api_key),
maxfee_percent: 0.5,
Expand Down
1 change: 1 addition & 0 deletions libs/sdk-flutter/ios/Classes/bridge_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ typedef struct wire_Config {
struct wire_uint_8_list *working_dir;
int32_t network;
uint32_t payment_timeout_sec;
uint64_t payment_request_yield_sec;
struct wire_uint_8_list *default_lsp_id;
struct wire_uint_8_list *api_key;
double maxfee_percent;
Expand Down
43 changes: 32 additions & 11 deletions libs/sdk-flutter/lib/bridge_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,11 @@ sealed class BreezEvent with _$BreezEvent {
/// Indicates that the local SDK state has just been sync-ed with the remote components
const factory BreezEvent.synced() = BreezEvent_Synced;

/// Indicates that an outgoing payment has started
const factory BreezEvent.paymentStarted({
required Payment details,
}) = BreezEvent_PaymentStarted;

/// Indicates that an outgoing payment has been completed successfully
const factory BreezEvent.paymentSucceed({
required Payment details,
Expand Down Expand Up @@ -520,7 +525,13 @@ class Config {
/// the folder should exist before starting the SDK.
final String workingDir;
final Network network;

/// Maps to the CLN `retry_for` config when paying invoices (`lightning-pay`)
final int paymentTimeoutSec;

/// The duration, in seconds, in which to return from a [crate::BreezServices::send_payment]
/// request with a pending payment if not already finished.
final int paymentRequestYieldSec;
final String? defaultLspId;
final String? apiKey;

Expand All @@ -538,6 +549,7 @@ class Config {
required this.workingDir,
required this.network,
required this.paymentTimeoutSec,
required this.paymentRequestYieldSec,
this.defaultLspId,
this.apiKey,
required this.maxfeePercent,
Expand Down Expand Up @@ -3313,22 +3325,26 @@ class BreezSdkCoreImpl implements BreezSdkCore {
case 2:
return BreezEvent_Synced();
case 3:
return BreezEvent_PaymentSucceed(
return BreezEvent_PaymentStarted(
details: _wire2api_box_autoadd_payment(raw[1]),
);
case 4:
return BreezEvent_PaymentSucceed(
details: _wire2api_box_autoadd_payment(raw[1]),
);
case 5:
return BreezEvent_PaymentFailed(
details: _wire2api_box_autoadd_payment_failed_data(raw[1]),
);
case 5:
return BreezEvent_BackupStarted();
case 6:
return BreezEvent_BackupSucceeded();
return BreezEvent_BackupStarted();
case 7:
return BreezEvent_BackupSucceeded();
case 8:
return BreezEvent_BackupFailed(
details: _wire2api_box_autoadd_backup_failed_data(raw[1]),
);
case 8:
case 9:
return BreezEvent_SwapUpdated(
details: _wire2api_box_autoadd_swap_info(raw[1]),
);
Expand Down Expand Up @@ -3371,19 +3387,20 @@ class BreezSdkCoreImpl implements BreezSdkCore {

Config _wire2api_config(dynamic raw) {
final arr = raw as List<dynamic>;
if (arr.length != 11) throw Exception('unexpected arr length: expect 11 but see ${arr.length}');
if (arr.length != 12) throw Exception('unexpected arr length: expect 12 but see ${arr.length}');
return Config(
breezserver: _wire2api_String(arr[0]),
chainnotifierUrl: _wire2api_String(arr[1]),
mempoolspaceUrl: _wire2api_opt_String(arr[2]),
workingDir: _wire2api_String(arr[3]),
network: _wire2api_network(arr[4]),
paymentTimeoutSec: _wire2api_u32(arr[5]),
defaultLspId: _wire2api_opt_String(arr[6]),
apiKey: _wire2api_opt_String(arr[7]),
maxfeePercent: _wire2api_f64(arr[8]),
exemptfeeMsat: _wire2api_u64(arr[9]),
nodeConfig: _wire2api_node_config(arr[10]),
paymentRequestYieldSec: _wire2api_u64(arr[6]),
defaultLspId: _wire2api_opt_String(arr[7]),
apiKey: _wire2api_opt_String(arr[8]),
maxfeePercent: _wire2api_f64(arr[9]),
exemptfeeMsat: _wire2api_u64(arr[10]),
nodeConfig: _wire2api_node_config(arr[11]),
);
}

Expand Down Expand Up @@ -4830,6 +4847,7 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase<BreezSdkCoreWire> {
wireObj.working_dir = api2wire_String(apiObj.workingDir);
wireObj.network = api2wire_network(apiObj.network);
wireObj.payment_timeout_sec = api2wire_u32(apiObj.paymentTimeoutSec);
wireObj.payment_request_yield_sec = api2wire_u64(apiObj.paymentRequestYieldSec);
wireObj.default_lsp_id = api2wire_opt_String(apiObj.defaultLspId);
wireObj.api_key = api2wire_opt_String(apiObj.apiKey);
wireObj.maxfee_percent = api2wire_f64(apiObj.maxfeePercent);
Expand Down Expand Up @@ -6504,6 +6522,9 @@ final class wire_Config extends ffi.Struct {
@ffi.Uint32()
external int payment_timeout_sec;

@ffi.Uint64()
external int payment_request_yield_sec;

external ffi.Pointer<wire_uint_8_list> default_lsp_id;

external ffi.Pointer<wire_uint_8_list> api_key;
Expand Down
Loading

0 comments on commit 1128f90

Please sign in to comment.