Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return invoice on successful lnurl-withdraw #436

Merged
merged 8 commits into from
Sep 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1880,12 +1896,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 @@ -1907,6 +1925,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 @@ -1929,6 +1971,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 @@ -1944,6 +1987,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 @@ -1982,7 +2033,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 @@ -1992,11 +2043,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
Loading