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 15, 2023
1 parent cd37b10 commit 335e61f
Show file tree
Hide file tree
Showing 9 changed files with 472 additions and 41 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 @@ -460,6 +460,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 @@ -514,7 +524,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
21 changes: 11 additions & 10 deletions libs/sdk-bindings/src/uniffi_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ use breez_sdk_core::{
ClosedChannelPaymentDetails, Config, CurrencyInfo, EnvironmentType, EventListener,
FeeratePreset, FiatCurrency, GreenlightCredentials, GreenlightNodeConfig, InputType,
InvoicePaidDetails, LNInvoice, LnPaymentDetails, LnUrlAuthRequestData, LnUrlCallbackStatus,
LnUrlErrorData, LnUrlPayRequestData, LnUrlPayResult, LnUrlWithdrawRequestData, LocaleOverrides,
LocalizedName, LogEntry, LogStream, LspInformation, MessageSuccessActionData, MetadataItem,
Network, NodeConfig, NodeState, OpenChannelFeeRequest, OpenChannelFeeResponse,
OpeningFeeParams, OpeningFeeParamsMenu, Payment, PaymentDetails, PaymentFailedData,
PaymentType, PaymentTypeFilter, Rate, ReceiveOnchainRequest, ReceivePaymentRequest,
ReceivePaymentResponse, RecommendedFees, ReverseSwapFeesRequest, ReverseSwapInfo,
ReverseSwapPairInfo, ReverseSwapStatus, RouteHint, RouteHintHop, SignMessageRequest,
SignMessageResponse, StaticBackupRequest, StaticBackupResponse, SuccessActionProcessed,
SwapInfo, SwapStatus, Symbol, UnspentTransactionOutput, UrlSuccessActionData,
LnUrlErrorData, LnUrlPayRequestData, LnUrlPayResult, LnUrlWithdrawCallbackStatus,
LnUrlWithdrawOkData, LnUrlWithdrawRequestData, LocaleOverrides, LocalizedName, LogEntry,
LogStream, LspInformation, MessageSuccessActionData, MetadataItem, Network, NodeConfig,
NodeState, OpenChannelFeeRequest, OpenChannelFeeResponse, OpeningFeeParams,
OpeningFeeParamsMenu, Payment, PaymentDetails, PaymentFailedData, PaymentType,
PaymentTypeFilter, Rate, ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse,
RecommendedFees, ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo,
ReverseSwapStatus, RouteHint, RouteHintHop, SignMessageRequest, SignMessageResponse,
StaticBackupRequest, StaticBackupResponse, SuccessActionProcessed, SwapInfo, SwapStatus,
Symbol, UnspentTransactionOutput, UrlSuccessActionData,
};

static RT: Lazy<tokio::runtime::Runtime> = Lazy::new(|| tokio::runtime::Runtime::new().unwrap());
Expand Down Expand Up @@ -191,7 +192,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
9 changes: 5 additions & 4 deletions libs/sdk-core/src/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ use crate::lsp::LspInformation;
use crate::models::{Config, LogEntry, NodeState, Payment, PaymentTypeFilter, SwapInfo};
use crate::{
BackupStatus, BuyBitcoinRequest, BuyBitcoinResponse, CheckMessageRequest, CheckMessageResponse,
EnvironmentType, LnUrlCallbackStatus, NodeConfig, ReceiveOnchainRequest, ReceivePaymentRequest,
ReceivePaymentResponse, ReverseSwapFeesRequest, ReverseSwapInfo, ReverseSwapPairInfo,
SignMessageRequest, SignMessageResponse, StaticBackupRequest, StaticBackupResponse,
EnvironmentType, LnUrlCallbackStatus, LnUrlWithdrawCallbackStatus, NodeConfig,
ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse, ReverseSwapFeesRequest,
ReverseSwapInfo, ReverseSwapPairInfo, SignMessageRequest, SignMessageResponse,
StaticBackupRequest, StaticBackupResponse,
};

static BREEZ_SERVICES_INSTANCE: OnceCell<Arc<BreezServices>> = OnceCell::new();
Expand Down Expand Up @@ -269,7 +270,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 @@ -61,6 +61,8 @@ use crate::models::GreenlightCredentials;
use crate::models::GreenlightNodeConfig;
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 @@ -1065,6 +1067,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 @@ -97,14 +107,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 @@ -135,7 +145,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 @@ -1074,7 +1074,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 @@ -1096,6 +1096,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 @@ -159,7 +159,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 @@ -731,6 +731,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 @@ -1831,7 +1849,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 @@ -1841,7 +1859,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 @@ -2190,6 +2208,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 @@ -2562,6 +2584,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 335e61f

Please sign in to comment.