Skip to content

Commit

Permalink
Merge pull request #436 from breez/ok300-lnurl-withdraw-extend-ok
Browse files Browse the repository at this point in the history
Return invoice on successful lnurl-withdraw
  • Loading branch information
ok300 authored Sep 23, 2023
2 parents b0d6be3 + d8ccf23 commit 9420ed5
Show file tree
Hide file tree
Showing 19 changed files with 729 additions and 54 deletions.
13 changes: 12 additions & 1 deletion libs/sdk-bindings/src/breez_sdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ dictionary LnPaymentDetails {
SuccessActionProcessed? lnurl_success_action;
string? lnurl_metadata;
string? ln_address;
string? lnurl_withdraw_endpoint;
};

dictionary ClosedChannelPaymentDetails {
Expand Down Expand Up @@ -473,6 +474,16 @@ interface LnUrlCallbackStatus {
ErrorStatus(LnUrlErrorData data);
};

[Enum]
interface LnUrlWithdrawResult {
Ok(LnUrlWithdrawSuccessData data);
ErrorStatus(LnUrlErrorData data);
};

dictionary LnUrlWithdrawSuccessData {
LNInvoice invoice;
};

dictionary LnUrlAuthRequestData {
string k1;
string? action;
Expand Down Expand Up @@ -527,7 +538,7 @@ interface BlockingBreezServices {
LnUrlPayResult pay_lnurl(LnUrlPayRequestData req_data, u64 amount_sats, string? comment);

[Throws=SdkError]
LnUrlCallbackStatus withdraw_lnurl(LnUrlWithdrawRequestData req_data, u64 amount_sats, string? description);
LnUrlWithdrawResult withdraw_lnurl(LnUrlWithdrawRequestData req_data, u64 amount_sats, string? description);

[Throws=SdkError]
LnUrlCallbackStatus lnurl_auth(LnUrlAuthRequestData req_data);
Expand Down
11 changes: 6 additions & 5 deletions libs/sdk-bindings/src/uniffi_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ use breez_sdk_core::{
FeeratePreset, FiatCurrency, GreenlightCredentials, GreenlightNodeConfig, InputType,
InvoicePaidDetails, LNInvoice, ListPaymentsRequest, LnPaymentDetails, LnUrlAuthRequestData,
LnUrlCallbackStatus, LnUrlErrorData, LnUrlPayRequestData, LnUrlPayResult,
LnUrlWithdrawRequestData, LocaleOverrides, LocalizedName, LogEntry, LogStream, LspInformation,
MessageSuccessActionData, MetadataItem, Network, NodeConfig, NodeState, OpenChannelFeeRequest,
OpenChannelFeeResponse, OpeningFeeParams, OpeningFeeParamsMenu, Payment, PaymentDetails,
PaymentFailedData, PaymentStatus, PaymentType, PaymentTypeFilter, Rate, ReceiveOnchainRequest,
LnUrlWithdrawRequestData, LnUrlWithdrawResult, LnUrlWithdrawSuccessData, LocaleOverrides,
LocalizedName, LogEntry, LogStream, LspInformation, MessageSuccessActionData, MetadataItem,
Network, NodeConfig, NodeState, OpenChannelFeeRequest, OpenChannelFeeResponse,
OpeningFeeParams, OpeningFeeParamsMenu, Payment, PaymentDetails, PaymentFailedData,
PaymentStatus, PaymentType, PaymentTypeFilter, Rate, ReceiveOnchainRequest,
ReceivePaymentRequest, ReceivePaymentResponse, RecommendedFees, ReverseSwapFeesRequest,
ReverseSwapInfo, ReverseSwapPairInfo, ReverseSwapStatus, RouteHint, RouteHintHop,
SignMessageRequest, SignMessageResponse, StaticBackupRequest, StaticBackupResponse,
Expand Down Expand Up @@ -184,7 +185,7 @@ impl BlockingBreezServices {
req_data: LnUrlWithdrawRequestData,
amount_sats: u64,
description: Option<String>,
) -> SdkResult<LnUrlCallbackStatus> {
) -> SdkResult<LnUrlWithdrawResult> {
rt().block_on(
self.breez_services
.lnurl_withdraw(req_data, amount_sats, description),
Expand Down
10 changes: 5 additions & 5 deletions libs/sdk-core/src/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ use crate::lsp::LspInformation;
use crate::models::{Config, LogEntry, NodeState, Payment, SwapInfo};
use crate::{
BackupStatus, BuyBitcoinRequest, BuyBitcoinResponse, CheckMessageRequest, CheckMessageResponse,
EnvironmentType, ListPaymentsRequest, LnUrlCallbackStatus, NodeConfig, OpenChannelFeeRequest,
OpenChannelFeeResponse, ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse,
ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo, SignMessageRequest,
SignMessageResponse, StaticBackupRequest, StaticBackupResponse,
EnvironmentType, ListPaymentsRequest, LnUrlCallbackStatus, LnUrlWithdrawResult, NodeConfig,
OpenChannelFeeRequest, OpenChannelFeeResponse, ReceiveOnchainRequest, ReceivePaymentRequest,
ReceivePaymentResponse, ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo,
SignMessageRequest, SignMessageResponse, StaticBackupRequest, StaticBackupResponse,
};

/*
Expand Down Expand Up @@ -274,7 +274,7 @@ pub fn lnurl_withdraw(
req_data: LnUrlWithdrawRequestData,
amount_sats: u64,
description: Option<String>,
) -> Result<LnUrlCallbackStatus> {
) -> Result<LnUrlWithdrawResult> {
block_on(async {
get_breez_services()
.await?
Expand Down
71 changes: 65 additions & 6 deletions libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ impl BreezServices {
maybe_sa_processed.as_ref(),
Some(req_data.metadata_str),
req_data.ln_address,
None,
)?;

Ok(LnUrlPayResult::EndpointSuccess {
Expand All @@ -350,7 +351,7 @@ impl BreezServices {
req_data: LnUrlWithdrawRequestData,
amount_sats: u64,
description: Option<String>,
) -> Result<LnUrlCallbackStatus> {
) -> Result<LnUrlWithdrawResult> {
let invoice = self
.receive_payment(ReceivePaymentRequest {
amount_sats,
Expand All @@ -364,7 +365,22 @@ impl BreezServices {
.await
.map_err(|_| anyhow!("Failed to receive payment"))?
.ln_invoice;
validate_lnurl_withdraw(req_data, invoice).await

let lnurl_w_endpoint = req_data.callback.clone();
let res = validate_lnurl_withdraw(req_data, invoice).await?;

if let LnUrlWithdrawResult::Ok { ref data } = res {
// If endpoint was successfully called, store the LNURL-withdraw endpoint URL as metadata linked to the invoice
self.persister.insert_lnurl_payment_external_info(
&data.invoice.payment_hash,
None,
None,
None,
Some(lnurl_w_endpoint),
)?;
}

Ok(res)
}

/// Third and last step of LNURL-auth. The first step is `parse()`, which also validates the LNURL destination
Expand Down Expand Up @@ -1879,12 +1895,14 @@ pub(crate) mod tests {

let lnurl_metadata = "{'key': 'sample-metadata-val'}";
let test_ln_address = "[email protected]";
let test_lnurl_withdraw_endpoint = "https://test.endpoint.lnurl-w";
let sa = SuccessActionProcessed::Message {
data: MessageSuccessActionData {
message: "test message".into(),
},
};

let payment_hash_lnurl_withdraw = "2222";
let payment_hash_with_lnurl_success_action = "3333";
let dummy_transactions = vec![
Payment {
Expand All @@ -1906,6 +1924,30 @@ pub(crate) mod tests {
lnurl_success_action: None,
lnurl_metadata: None,
ln_address: None,
lnurl_withdraw_endpoint: None,
},
},
},
Payment {
id: payment_hash_lnurl_withdraw.to_string(),
payment_type: PaymentType::Received,
payment_time: 150000,
amount_msat: 10,
fee_msat: 0,
status: PaymentStatus::Complete,
description: Some("test lnurl-withdraw receive".to_string()),
details: PaymentDetails::Ln {
data: LnPaymentDetails {
payment_hash: payment_hash_lnurl_withdraw.to_string(),
label: "".to_string(),
destination_pubkey: "1111".to_string(),
payment_preimage: "2222".to_string(),
keysend: false,
bolt11: "1111".to_string(),
lnurl_success_action: None,
lnurl_metadata: None,
ln_address: None,
lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()),
},
},
},
Expand All @@ -1928,6 +1970,7 @@ pub(crate) mod tests {
lnurl_success_action: Some(sa.clone()),
lnurl_metadata: Some(lnurl_metadata.to_string()),
ln_address: Some(test_ln_address.to_string()),
lnurl_withdraw_endpoint: None,
},
},
},
Expand All @@ -1943,6 +1986,14 @@ pub(crate) mod tests {
Some(&sa),
Some(lnurl_metadata.to_string()),
Some(test_ln_address.to_string()),
None,
)?;
persister.insert_lnurl_payment_external_info(
payment_hash_lnurl_withdraw,
None,
None,
None,
Some(test_lnurl_withdraw_endpoint.to_string()),
)?;

let mut builder = BreezServicesBuilder::new(test_config.clone());
Expand Down Expand Up @@ -1981,7 +2032,7 @@ pub(crate) mod tests {
include_failures: None,
})
.await?;
assert_eq!(received, vec![cloned[0].clone()]);
assert_eq!(received, vec![cloned[1].clone(), cloned[0].clone()]);

let sent = breez_services
.list_payments(ListPaymentsRequest {
Expand All @@ -1991,11 +2042,19 @@ pub(crate) mod tests {
include_failures: None,
})
.await?;
assert_eq!(sent, vec![cloned[1].clone()]);
assert_eq!(sent, vec![cloned[2].clone()]);
assert!(matches!(
&sent[0].details,
PaymentDetails::Ln {data: LnPaymentDetails {lnurl_success_action, ..}}
if lnurl_success_action == &Some(sa)));
assert!(matches!(
&sent[0].details, PaymentDetails::Ln {data: LnPaymentDetails {lnurl_success_action, ..}} if lnurl_success_action == &Some(sa)));
&sent[0].details,
PaymentDetails::Ln {data: LnPaymentDetails {ln_address, ..}}
if ln_address == &Some(test_ln_address.to_string())));
assert!(matches!(
&sent[0].details, PaymentDetails::Ln {data: LnPaymentDetails {ln_address, ..}} if ln_address == &Some(test_ln_address.to_string())));
&received[0].details,
PaymentDetails::Ln {data: LnPaymentDetails {lnurl_withdraw_endpoint, ..}}
if lnurl_withdraw_endpoint == &Some(test_lnurl_withdraw_endpoint.to_string())));

Ok(())
}
Expand Down
20 changes: 20 additions & 0 deletions libs/sdk-core/src/bridge_generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ use crate::models::GreenlightNodeConfig;
use crate::models::ListPaymentsRequest;
use crate::models::LnPaymentDetails;
use crate::models::LnUrlCallbackStatus;
use crate::models::LnUrlWithdrawResult;
use crate::models::LnUrlWithdrawSuccessData;
use crate::models::LogEntry;
use crate::models::Network;
use crate::models::NodeConfig;
Expand Down Expand Up @@ -1019,6 +1021,7 @@ impl support::IntoDart for LnPaymentDetails {
self.lnurl_success_action.into_dart(),
self.ln_address.into_dart(),
self.lnurl_metadata.into_dart(),
self.lnurl_withdraw_endpoint.into_dart(),
]
.into_dart()
}
Expand Down Expand Up @@ -1095,6 +1098,23 @@ impl support::IntoDart for LnUrlWithdrawRequestData {
}
impl support::IntoDartExceptPrimitive for LnUrlWithdrawRequestData {}

impl support::IntoDart for LnUrlWithdrawResult {
fn into_dart(self) -> support::DartAbi {
match self {
Self::Ok { data } => vec![0.into_dart(), data.into_dart()],
Self::ErrorStatus { data } => vec![1.into_dart(), data.into_dart()],
}
.into_dart()
}
}
impl support::IntoDartExceptPrimitive for LnUrlWithdrawResult {}
impl support::IntoDart for LnUrlWithdrawSuccessData {
fn into_dart(self) -> support::DartAbi {
vec![self.invoice.into_dart()].into_dart()
}
}
impl support::IntoDartExceptPrimitive for LnUrlWithdrawSuccessData {}

impl support::IntoDart for LocaleOverrides {
fn into_dart(self) -> support::DartAbi {
vec![
Expand Down
5 changes: 5 additions & 0 deletions libs/sdk-core/src/greenlight/node_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,7 @@ impl TryFrom<OffChainPayment> for Payment {
lnurl_success_action: None, // For received payments, this is None
lnurl_metadata: None, // For received payments, this is None
ln_address: None,
lnurl_withdraw_endpoint: None,
},
},
})
Expand Down Expand Up @@ -935,6 +936,7 @@ impl TryFrom<pb::Invoice> for Payment {
lnurl_success_action: None, // For received payments, this is None
lnurl_metadata: None, // For received payments, this is None
ln_address: None,
lnurl_withdraw_endpoint: None,
},
},
})
Expand Down Expand Up @@ -984,6 +986,7 @@ impl TryFrom<pb::Payment> for Payment {
lnurl_success_action: None,
lnurl_metadata: None,
ln_address: None,
lnurl_withdraw_endpoint: None,
},
},
})
Expand Down Expand Up @@ -1022,6 +1025,7 @@ impl TryFrom<ListinvoicesInvoices> for Payment {
lnurl_success_action: None, // For received payments, this is None
lnurl_metadata: None, // For received payments, this is None
ln_address: None,
lnurl_withdraw_endpoint: None,
},
},
})
Expand Down Expand Up @@ -1083,6 +1087,7 @@ impl TryFrom<ListpaysPays> for Payment {
lnurl_success_action: None,
lnurl_metadata: None,
ln_address: None,
lnurl_withdraw_endpoint: None,
},
},
})
Expand Down
53 changes: 31 additions & 22 deletions libs/sdk-core/src/lnurl/withdraw.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::str::FromStr;

use crate::input_parser::get_parse_and_log_response;
use crate::{lnurl::*, LnUrlCallbackStatus};
use crate::{lnurl::*, LnUrlCallbackStatus, LnUrlWithdrawResult, LnUrlWithdrawSuccessData};
use crate::{LNInvoice, LnUrlWithdrawRequestData};
use anyhow::{anyhow, Result};
use anyhow::{anyhow, ensure, Result};

/// Validates invoice and performs the second and last step of LNURL-withdraw, as per
/// <https://github.com/lnurl/luds/blob/luds/03.md>
Expand All @@ -16,23 +16,32 @@ use anyhow::{anyhow, Result};
pub(crate) async fn validate_lnurl_withdraw(
req_data: LnUrlWithdrawRequestData,
invoice: LNInvoice,
) -> Result<LnUrlCallbackStatus> {
match invoice
) -> Result<LnUrlWithdrawResult> {
let amount_msat = invoice
.amount_msat
.ok_or("Expected invoice amount, but found none")
.map_err(|e| anyhow!(e))?
{
n if n < req_data.min_withdrawable => Err(anyhow!(
"Amount is smaller than the minimum allowed by the LNURL-withdraw endpoint"
)),
n if n > req_data.max_withdrawable => Err(anyhow!(
"Amount is bigger than the maximum allowed by the LNURL-withdraw endpoint"
)),
_ => {
let callback_url = build_withdraw_callback_url(&req_data, &invoice)?;
get_parse_and_log_response(&callback_url).await
}
}
.map_err(|e| anyhow!(e))?;

ensure!(
amount_msat >= req_data.min_withdrawable,
"Amount is smaller than the minimum allowed by the LNURL-withdraw endpoint"
);
ensure!(
amount_msat <= req_data.max_withdrawable,
"Amount is bigger than the maximum allowed by the LNURL-withdraw endpoint"
);

// Send invoice to the LNURL-w endpoint via the callback
let callback_url = build_withdraw_callback_url(&req_data, &invoice)?;
let callback_res: LnUrlCallbackStatus = get_parse_and_log_response(&callback_url).await?;
let withdraw_status = match callback_res {
LnUrlCallbackStatus::Ok => LnUrlWithdrawResult::Ok {
data: LnUrlWithdrawSuccessData { invoice },
},
LnUrlCallbackStatus::ErrorStatus { data } => LnUrlWithdrawResult::ErrorStatus { data },
};

Ok(withdraw_status)
}

fn build_withdraw_callback_url(
Expand Down Expand Up @@ -101,14 +110,14 @@ mod tests {
#[tokio::test]
async fn test_lnurl_withdraw_success() -> Result<()> {
let invoice_str = "lnbc110n1p38q3gtpp5ypz09jrd8p993snjwnm68cph4ftwp22le34xd4r8ftspwshxhmnsdqqxqyjw5qcqpxsp5htlg8ydpywvsa7h3u4hdn77ehs4z4e844em0apjyvmqfkzqhhd2q9qgsqqqyssqszpxzxt9uuqzymr7zxcdccj5g69s8q7zzjs7sgxn9ejhnvdh6gqjcy22mss2yexunagm5r2gqczh8k24cwrqml3njskm548aruhpwssq9nvrvz";
let invoice = crate::invoice::parse_invoice(invoice_str)?;
let req_invoice = crate::invoice::parse_invoice(invoice_str)?;
let withdraw_req = get_test_withdraw_req_data(0, 100);

let _m = mock_lnurl_withdraw_callback(&withdraw_req, &invoice, None)?;
let _m = mock_lnurl_withdraw_callback(&withdraw_req, &req_invoice, None)?;

assert!(matches!(
validate_lnurl_withdraw(withdraw_req, invoice).await?,
LnUrlCallbackStatus::Ok
validate_lnurl_withdraw(withdraw_req, req_invoice.clone()).await?,
LnUrlWithdrawResult::Ok { data: LnUrlWithdrawSuccessData { invoice } } if invoice == req_invoice
));

Ok(())
Expand Down Expand Up @@ -139,7 +148,7 @@ mod tests {

assert!(matches!(
validate_lnurl_withdraw(withdraw_req, invoice).await?,
LnUrlCallbackStatus::ErrorStatus { data: _ }
LnUrlWithdrawResult::ErrorStatus { data: _ }
));

Ok(())
Expand Down
Loading

0 comments on commit 9420ed5

Please sign in to comment.