Skip to content

Commit

Permalink
Return invoice on successful lnurl-withdraw
Browse files Browse the repository at this point in the history
  • Loading branch information
ok300 committed Sep 19, 2023
1 parent 392b8e2 commit 834fd0b
Show file tree
Hide file tree
Showing 9 changed files with 458 additions and 29 deletions.
12 changes: 11 additions & 1 deletion libs/sdk-bindings/src/breez_sdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,16 @@ interface LnUrlCallbackStatus {
ErrorStatus(LnUrlErrorData data);
};

[Enum]
interface LnUrlWithdrawCallbackStatus {
Ok(LnUrlWithdrawOkData data);
ErrorStatus(LnUrlErrorData data);
};

dictionary LnUrlWithdrawOkData {
LNInvoice invoice;
};

dictionary LnUrlAuthRequestData {
string k1;
string? action;
Expand Down Expand Up @@ -527,7 +537,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);
LnUrlWithdrawCallbackStatus withdraw_lnurl(LnUrlWithdrawRequestData req_data, u64 amount_sats, string? description);

[Throws=SdkError]
LnUrlCallbackStatus lnurl_auth(LnUrlAuthRequestData req_data);
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-bindings/src/uniffi_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ impl BlockingBreezServices {
req_data: LnUrlWithdrawRequestData,
amount_sats: u64,
description: Option<String>,
) -> SdkResult<LnUrlCallbackStatus> {
) -> SdkResult<LnUrlWithdrawCallbackStatus> {
rt().block_on(
self.breez_services
.lnurl_withdraw(req_data, amount_sats, description),
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-core/src/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ pub fn lnurl_withdraw(
req_data: LnUrlWithdrawRequestData,
amount_sats: u64,
description: Option<String>,
) -> Result<LnUrlCallbackStatus> {
) -> Result<LnUrlWithdrawCallbackStatus> {
block_on(async {
get_breez_services()?
.lnurl_withdraw(req_data, amount_sats, description)
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ impl BreezServices {
req_data: LnUrlWithdrawRequestData,
amount_sats: u64,
description: Option<String>,
) -> Result<LnUrlCallbackStatus> {
) -> Result<LnUrlWithdrawCallbackStatus> {
let invoice = self
.receive_payment(ReceivePaymentRequest {
amount_sats,
Expand Down
19 changes: 19 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::LnUrlWithdrawCallbackStatus;
use crate::models::LnUrlWithdrawOkData;
use crate::models::LogEntry;
use crate::models::Network;
use crate::models::NodeConfig;
Expand Down Expand Up @@ -1063,6 +1065,23 @@ impl support::IntoDart for LnUrlPayResult {
}
}
impl support::IntoDartExceptPrimitive for LnUrlPayResult {}
impl support::IntoDart for LnUrlWithdrawCallbackStatus {
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 LnUrlWithdrawCallbackStatus {}
impl support::IntoDart for LnUrlWithdrawOkData {
fn into_dart(self) -> support::DartAbi {
vec![self.invoice.into_dart()].into_dart()
}
}
impl support::IntoDartExceptPrimitive for LnUrlWithdrawOkData {}

impl support::IntoDart for LnUrlWithdrawRequestData {
fn into_dart(self) -> support::DartAbi {
vec![
Expand Down
52 changes: 31 additions & 21 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, LnUrlWithdrawCallbackStatus, LnUrlWithdrawOkData};
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,33 @@ use anyhow::{anyhow, Result};
pub(crate) async fn validate_lnurl_withdraw(
req_data: LnUrlWithdrawRequestData,
invoice: LNInvoice,
) -> Result<LnUrlCallbackStatus> {
match invoice
) -> Result<LnUrlWithdrawCallbackStatus> {
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"
);

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 => LnUrlWithdrawCallbackStatus::Ok {
data: LnUrlWithdrawOkData { invoice },
},
LnUrlCallbackStatus::ErrorStatus { data } => {
LnUrlWithdrawCallbackStatus::ErrorStatus { data }
}
}
};

Ok(withdraw_status)
}

fn build_withdraw_callback_url(
Expand Down Expand Up @@ -101,14 +111,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?,
LnUrlWithdrawCallbackStatus::Ok { data: LnUrlWithdrawOkData { invoice } } if invoice == req_invoice
));

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

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

Ok(())
Expand Down
14 changes: 13 additions & 1 deletion libs/sdk-core/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ pub struct UnspentTransactionOutput {
pub reserved_to_block: u32,
}

//// Contains the result of the entire LNURL interaction, as reported by the LNURL endpoint.
/// Contains the result of the entire LNURL interaction, as reported by the LNURL endpoint.
///
/// * `Ok` indicates the interaction with the endpoint was valid, and the endpoint
/// - started to pay the invoice asynchronously in the case of LNURL-withdraw,
Expand All @@ -1114,6 +1114,18 @@ pub enum LnUrlCallbackStatus {
},
}

/// [LnUrlCallbackStatus] specific to LNURL-withdraw, where the success case contains the invoice.
#[derive(Serialize)]
pub enum LnUrlWithdrawCallbackStatus {
Ok { data: LnUrlWithdrawOkData },
ErrorStatus { data: LnUrlErrorData },
}

#[derive(Deserialize, Debug, Serialize)]
pub struct LnUrlWithdrawOkData {
pub invoice: LNInvoice,
}

/// Different providers will demand different behaviours when the user is trying to buy bitcoin.
#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "buy_bitcoin_provider")]
Expand Down
51 changes: 48 additions & 3 deletions libs/sdk-flutter/lib/bridge_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ abstract class BreezSdkCore {
FlutterRustBridgeTaskConstMeta get kLnurlPayConstMeta;

/// See [BreezServices::lnurl_withdraw]
Future<LnUrlCallbackStatus> lnurlWithdraw(
Future<LnUrlWithdrawCallbackStatus> lnurlWithdraw(
{required LnUrlWithdrawRequestData reqData,
required int amountSats,
String? description,
Expand Down Expand Up @@ -745,6 +745,24 @@ class LnUrlPayResult with _$LnUrlPayResult {
}) = LnUrlPayResult_EndpointError;
}

@freezed
class LnUrlWithdrawCallbackStatus with _$LnUrlWithdrawCallbackStatus {
const factory LnUrlWithdrawCallbackStatus.ok({
required LnUrlWithdrawOkData data,
}) = LnUrlWithdrawCallbackStatus_Ok;
const factory LnUrlWithdrawCallbackStatus.errorStatus({
required LnUrlErrorData data,
}) = LnUrlWithdrawCallbackStatus_ErrorStatus;
}

class LnUrlWithdrawOkData {
final LNInvoice invoice;

const LnUrlWithdrawOkData({
required this.invoice,
});
}

/// Wrapped in a [LnUrlWithdraw], this is the result of [parse] when given a LNURL-withdraw endpoint.
///
/// It represents the endpoint's parameters for the LNURL workflow.
Expand Down Expand Up @@ -1849,7 +1867,7 @@ class BreezSdkCoreImpl implements BreezSdkCore {
argNames: ["userAmountSat", "comment", "reqData"],
);

Future<LnUrlCallbackStatus> lnurlWithdraw(
Future<LnUrlWithdrawCallbackStatus> lnurlWithdraw(
{required LnUrlWithdrawRequestData reqData,
required int amountSats,
String? description,
Expand All @@ -1859,7 +1877,7 @@ class BreezSdkCoreImpl implements BreezSdkCore {
var arg2 = _platform.api2wire_opt_String(description);
return _platform.executeNormal(FlutterRustBridgeTask(
callFfi: (port_) => _platform.inner.wire_lnurl_withdraw(port_, arg0, arg1, arg2),
parseSuccessData: _wire2api_ln_url_callback_status,
parseSuccessData: _wire2api_ln_url_withdraw_callback_status,
constMeta: kLnurlWithdrawConstMeta,
argValues: [reqData, amountSats, description],
hint: hint,
Expand Down Expand Up @@ -2208,6 +2226,10 @@ class BreezSdkCoreImpl implements BreezSdkCore {
return _wire2api_ln_url_pay_request_data(raw);
}

LnUrlWithdrawOkData _wire2api_box_autoadd_ln_url_withdraw_ok_data(dynamic raw) {
return _wire2api_ln_url_withdraw_ok_data(raw);
}

LnUrlWithdrawRequestData _wire2api_box_autoadd_ln_url_withdraw_request_data(dynamic raw) {
return _wire2api_ln_url_withdraw_request_data(raw);
}
Expand Down Expand Up @@ -2580,6 +2602,29 @@ class BreezSdkCoreImpl implements BreezSdkCore {
}
}

LnUrlWithdrawCallbackStatus _wire2api_ln_url_withdraw_callback_status(dynamic raw) {
switch (raw[0]) {
case 0:
return LnUrlWithdrawCallbackStatus_Ok(
data: _wire2api_box_autoadd_ln_url_withdraw_ok_data(raw[1]),
);
case 1:
return LnUrlWithdrawCallbackStatus_ErrorStatus(
data: _wire2api_box_autoadd_ln_url_error_data(raw[1]),
);
default:
throw Exception("unreachable");
}
}

LnUrlWithdrawOkData _wire2api_ln_url_withdraw_ok_data(dynamic raw) {
final arr = raw as List<dynamic>;
if (arr.length != 1) throw Exception('unexpected arr length: expect 1 but see ${arr.length}');
return LnUrlWithdrawOkData(
invoice: _wire2api_ln_invoice(arr[0]),
);
}

LnUrlWithdrawRequestData _wire2api_ln_url_withdraw_request_data(dynamic raw) {
final arr = raw as List<dynamic>;
if (arr.length != 5) throw Exception('unexpected arr length: expect 5 but see ${arr.length}');
Expand Down
Loading

0 comments on commit 834fd0b

Please sign in to comment.