From 023629cdd868c90bfb7ddd33c81b595b5f18c9ad Mon Sep 17 00:00:00 2001 From: Ross Savage Date: Mon, 11 Dec 2023 21:17:17 +0100 Subject: [PATCH] Add optional extra TLVs to send spontaneous payment --- libs/sdk-bindings/src/breez_sdk.udl | 6 ++ libs/sdk-bindings/src/uniffi_binding.rs | 2 +- libs/sdk-core/src/breez_services.rs | 2 +- libs/sdk-core/src/bridge_generated.io.rs | 58 ++++++++++++++++ libs/sdk-core/src/bridge_generated.rs | 1 + libs/sdk-core/src/greenlight/node_api.rs | 11 +++- libs/sdk-core/src/models.rs | 11 ++++ libs/sdk-core/src/node_api.rs | 3 +- libs/sdk-core/src/test_utils.rs | 3 +- libs/sdk-flutter/lib/bridge_generated.dart | 66 +++++++++++++++++++ .../main/java/com/breezsdk/BreezSDKMapper.kt | 52 +++++++++++++++ .../sdk-react-native/ios/BreezSDKMapper.swift | 46 ++++++++++++- libs/sdk-react-native/src/index.ts | 6 ++ tools/sdk-cli/src/command_handlers.rs | 1 + 14 files changed, 262 insertions(+), 6 deletions(-) diff --git a/libs/sdk-bindings/src/breez_sdk.udl b/libs/sdk-bindings/src/breez_sdk.udl index 8efcf06d9..371d13a6a 100644 --- a/libs/sdk-bindings/src/breez_sdk.udl +++ b/libs/sdk-bindings/src/breez_sdk.udl @@ -237,6 +237,11 @@ interface PaymentDetails { ClosedChannel(ClosedChannelPaymentDetails data); }; +dictionary TlvEntry { + u64 field_number; + sequence value; +}; + [Enum] interface AesSuccessActionDataResult { Decrypted(AesSuccessActionDataDecrypted data); @@ -677,6 +682,7 @@ dictionary SendPaymentRequest { dictionary SendSpontaneousPaymentRequest { string node_id; u64 amount_msat; + sequence? extra_tlvs = null; }; dictionary SendPaymentResponse { diff --git a/libs/sdk-bindings/src/uniffi_binding.rs b/libs/sdk-bindings/src/uniffi_binding.rs index 4a47bcb90..48786e39f 100644 --- a/libs/sdk-bindings/src/uniffi_binding.rs +++ b/libs/sdk-bindings/src/uniffi_binding.rs @@ -22,7 +22,7 @@ use breez_sdk_core::{ SendPaymentRequest, SendPaymentResponse, SendSpontaneousPaymentRequest, ServiceHealthCheckResponse, SignMessageRequest, SignMessageResponse, StaticBackupRequest, StaticBackupResponse, SuccessActionProcessed, SwapInfo, SwapStatus, SweepRequest, - SweepResponse, Symbol, UnspentTransactionOutput, UrlSuccessActionData, + SweepResponse, Symbol, TlvEntry, UnspentTransactionOutput, UrlSuccessActionData, }; use log::{Level, LevelFilter, Metadata, Record}; use once_cell::sync::{Lazy, OnceCell}; diff --git a/libs/sdk-core/src/breez_services.rs b/libs/sdk-core/src/breez_services.rs index a95060d8f..94d497a1d 100644 --- a/libs/sdk-core/src/breez_services.rs +++ b/libs/sdk-core/src/breez_services.rs @@ -303,7 +303,7 @@ impl BreezServices { self.start_node().await?; let payment_res = self .node_api - .send_spontaneous_payment(req.node_id.clone(), req.amount_msat) + .send_spontaneous_payment(req.node_id.clone(), req.amount_msat, req.extra_tlvs) .map_err(Into::into) .await; let payment = self diff --git a/libs/sdk-core/src/bridge_generated.io.rs b/libs/sdk-core/src/bridge_generated.io.rs index 79017ff65..a73b2215b 100644 --- a/libs/sdk-core/src/bridge_generated.io.rs +++ b/libs/sdk-core/src/bridge_generated.io.rs @@ -428,6 +428,15 @@ pub extern "C" fn new_list_payment_type_filter_0(len: i32) -> *mut wire_list_pay support::new_leak_box_ptr(wrap) } +#[no_mangle] +pub extern "C" fn new_list_tlv_entry_0(len: i32) -> *mut wire_list_tlv_entry { + let wrap = wire_list_tlv_entry { + ptr: support::new_leak_vec_ptr(::new_with_null_ptr(), len), + len, + }; + support::new_leak_box_ptr(wrap) +} + #[no_mangle] pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list { let ans = wire_uint_8_list { @@ -697,6 +706,15 @@ impl Wire2Api for wire_ListPaymentsRequest { } } } +impl Wire2Api> for *mut wire_list_tlv_entry { + fn wire2api(self) -> Vec { + let vec = unsafe { + let wrap = support::box_from_leak_ptr(self); + support::vec_from_leak_ptr(wrap.ptr, wrap.len) + }; + vec.into_iter().map(Wire2Api::wire2api).collect() + } +} impl Wire2Api for wire_LnUrlAuthRequestData { fn wire2api(self) -> LnUrlAuthRequestData { LnUrlAuthRequestData { @@ -883,6 +901,7 @@ impl Wire2Api for wire_SendSpontaneousPaymentRequ SendSpontaneousPaymentRequest { node_id: self.node_id.wire2api(), amount_msat: self.amount_msat.wire2api(), + extra_tlvs: self.extra_tlvs.wire2api(), } } } @@ -908,6 +927,14 @@ impl Wire2Api for wire_SweepRequest { } } } +impl Wire2Api for wire_TlvEntry { + fn wire2api(self) -> TlvEntry { + TlvEntry { + field_number: self.field_number.wire2api(), + value: self.value.wire2api(), + } + } +} impl Wire2Api> for *mut wire_uint_8_list { fn wire2api(self) -> Vec { @@ -981,6 +1008,13 @@ pub struct wire_ListPaymentsRequest { limit: *mut u32, } +#[repr(C)] +#[derive(Clone)] +pub struct wire_list_tlv_entry { + ptr: *mut wire_TlvEntry, + len: i32, +} + #[repr(C)] #[derive(Clone)] pub struct wire_LnUrlAuthRequestData { @@ -1121,6 +1155,7 @@ pub struct wire_SendPaymentRequest { pub struct wire_SendSpontaneousPaymentRequest { node_id: *mut wire_uint_8_list, amount_msat: u64, + extra_tlvs: *mut wire_list_tlv_entry, } #[repr(C)] @@ -1142,6 +1177,13 @@ pub struct wire_SweepRequest { sat_per_vbyte: u32, } +#[repr(C)] +#[derive(Clone)] +pub struct wire_TlvEntry { + field_number: u64, + value: *mut wire_uint_8_list, +} + #[repr(C)] #[derive(Clone)] pub struct wire_uint_8_list { @@ -1616,6 +1658,7 @@ impl NewWithNullPtr for wire_SendSpontaneousPaymentRequest { Self { node_id: core::ptr::null_mut(), amount_msat: Default::default(), + extra_tlvs: core::ptr::null_mut(), } } } @@ -1669,6 +1712,21 @@ impl Default for wire_SweepRequest { } } +impl NewWithNullPtr for wire_TlvEntry { + fn new_with_null_ptr() -> Self { + Self { + field_number: Default::default(), + value: core::ptr::null_mut(), + } + } +} + +impl Default for wire_TlvEntry { + fn default() -> Self { + Self::new_with_null_ptr() + } +} + // Section: sync execution mode utility #[no_mangle] diff --git a/libs/sdk-core/src/bridge_generated.rs b/libs/sdk-core/src/bridge_generated.rs index 9d3b33f6f..817b6b8d6 100644 --- a/libs/sdk-core/src/bridge_generated.rs +++ b/libs/sdk-core/src/bridge_generated.rs @@ -113,6 +113,7 @@ use crate::models::SwapInfo; use crate::models::SwapStatus; use crate::models::SweepRequest; use crate::models::SweepResponse; +use crate::models::TlvEntry; use crate::models::UnspentTransactionOutput; // Section: wire functions diff --git a/libs/sdk-core/src/greenlight/node_api.rs b/libs/sdk-core/src/greenlight/node_api.rs index ed475ff30..ef15ebce6 100644 --- a/libs/sdk-core/src/greenlight/node_api.rs +++ b/libs/sdk-core/src/greenlight/node_api.rs @@ -907,6 +907,7 @@ impl NodeAPI for Greenlight { &self, node_id: String, amount_msat: u64, + extra_tlvs: Option>, ) -> NodeResult { let mut client: node::ClnClient = self.get_node_client().await?; let request = cln::KeysendRequest { @@ -916,7 +917,15 @@ impl NodeAPI for Greenlight { "breez-{}", SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() )), - extratlvs: None, + extratlvs: extra_tlvs.map(|tlvs| cln::TlvStream { + entries: tlvs + .into_iter() + .map(|tlv| cln::TlvEntry { + r#type: tlv.field_number, + value: tlv.value, + }) + .collect(), + }), routehints: None, maxfeepercent: Some(self.sdk_config.maxfee_percent), exemptfee: None, diff --git a/libs/sdk-core/src/models.rs b/libs/sdk-core/src/models.rs index aa7743e60..e3a709616 100644 --- a/libs/sdk-core/src/models.rs +++ b/libs/sdk-core/src/models.rs @@ -759,6 +759,15 @@ pub struct SendPaymentRequest { pub amount_msat: Option, } +/// Represents a TLV entry for a keysend payment. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TlvEntry { + /// The type field for the TLV + pub field_number: u64, + /// The value bytes for the TLV + pub value: Vec, +} + /// Represents a send spontaneous payment request. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SendSpontaneousPaymentRequest { @@ -766,6 +775,8 @@ pub struct SendSpontaneousPaymentRequest { pub node_id: String, /// The amount in millisatoshis for this payment pub amount_msat: u64, + // Optional extra TLVs + pub extra_tlvs: Option>, } /// Represents a send payment response. diff --git a/libs/sdk-core/src/node_api.rs b/libs/sdk-core/src/node_api.rs index 5968a3808..26cf07d1e 100644 --- a/libs/sdk-core/src/node_api.rs +++ b/libs/sdk-core/src/node_api.rs @@ -1,7 +1,7 @@ use crate::{ invoice::InvoiceError, persist::error::PersistError, CustomMessage, MaxChannelAmount, NodeCredentials, Payment, PaymentResponse, Peer, PrepareSweepRequest, PrepareSweepResponse, - RouteHintHop, SyncResponse, + RouteHintHop, SyncResponse, TlvEntry, }; use anyhow::Result; use bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey}; @@ -73,6 +73,7 @@ pub trait NodeAPI: Send + Sync { &self, node_id: String, amount_msat: u64, + extra_tlvs: Option>, ) -> NodeResult; async fn start(&self) -> NodeResult; diff --git a/libs/sdk-core/src/test_utils.rs b/libs/sdk-core/src/test_utils.rs index 7dca59c88..03c4b5c6d 100644 --- a/libs/sdk-core/src/test_utils.rs +++ b/libs/sdk-core/src/test_utils.rs @@ -33,7 +33,7 @@ use crate::fiat::{FiatCurrency, Rate}; use crate::grpc::{PaymentInformation, RegisterPaymentNotificationResponse, RegisterPaymentReply}; use crate::invoice::{InvoiceError, InvoiceResult}; use crate::lsp::LspInformation; -use crate::models::{FiatAPI, LspAPI, NodeState, Payment, Swap, SwapperAPI, SyncResponse}; +use crate::models::{FiatAPI, LspAPI, NodeState, Payment, Swap, SwapperAPI, SyncResponse, TlvEntry}; use crate::moonpay::MoonPayApi; use crate::node_api::{NodeAPI, NodeError, NodeResult}; use crate::swap_in::error::SwapResult; @@ -327,6 +327,7 @@ impl NodeAPI for MockNodeAPI { &self, _node_id: String, _amount_msat: u64, + _extra_tlvs: Option>, ) -> NodeResult { let payment = self.add_dummy_payment_rand().await?; Ok(payment) diff --git a/libs/sdk-flutter/lib/bridge_generated.dart b/libs/sdk-flutter/lib/bridge_generated.dart index cad8d9ba9..87ff8876c 100644 --- a/libs/sdk-flutter/lib/bridge_generated.dart +++ b/libs/sdk-flutter/lib/bridge_generated.dart @@ -1565,10 +1565,12 @@ class SendSpontaneousPaymentRequest { /// The amount in millisatoshis for this payment final int amountMsat; + final List? extraTlvs; const SendSpontaneousPaymentRequest({ required this.nodeId, required this.amountMsat, + this.extraTlvs, }); } @@ -1740,6 +1742,20 @@ class Symbol { }); } +/// Represents a TLV entry for a keysend payment. +class TlvEntry { + /// The type field for the TLV + final int fieldNumber; + + /// The value bytes for the TLV + final Uint8List value; + + const TlvEntry({ + required this.fieldNumber, + required this.value, + }); +} + /// UTXO known to the LN node class UnspentTransactionOutput { final Uint8List txid; @@ -4020,6 +4036,15 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { return ans; } + @protected + ffi.Pointer api2wire_list_tlv_entry(List raw) { + final ans = inner.new_list_tlv_entry_0(raw.length); + for (var i = 0; i < raw.length; ++i) { + _api_fill_to_wire_tlv_entry(raw[i], ans.ref.ptr[i]); + } + return ans; + } + @protected ffi.Pointer api2wire_opt_String(String? raw) { return raw == null ? ffi.nullptr : api2wire_String(raw); @@ -4062,6 +4087,11 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { return raw == null ? ffi.nullptr : api2wire_list_payment_type_filter(raw); } + @protected + ffi.Pointer api2wire_opt_list_tlv_entry(List? raw) { + return raw == null ? ffi.nullptr : api2wire_list_tlv_entry(raw); + } + @protected ffi.Pointer api2wire_opt_uint_8_list(Uint8List? raw) { return raw == null ? ffi.nullptr : api2wire_uint_8_list(raw); @@ -4392,6 +4422,7 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { SendSpontaneousPaymentRequest apiObj, wire_SendSpontaneousPaymentRequest wireObj) { wireObj.node_id = api2wire_String(apiObj.nodeId); wireObj.amount_msat = api2wire_u64(apiObj.amountMsat); + wireObj.extra_tlvs = api2wire_opt_list_tlv_entry(apiObj.extraTlvs); } void _api_fill_to_wire_sign_message_request(SignMessageRequest apiObj, wire_SignMessageRequest wireObj) { @@ -4406,6 +4437,11 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { wireObj.to_address = api2wire_String(apiObj.toAddress); wireObj.sat_per_vbyte = api2wire_u32(apiObj.satPerVbyte); } + + void _api_fill_to_wire_tlv_entry(TlvEntry apiObj, wire_TlvEntry wireObj) { + wireObj.field_number = api2wire_u64(apiObj.fieldNumber); + wireObj.value = api2wire_uint_8_list(apiObj.value); + } } // ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names @@ -5551,6 +5587,20 @@ class BreezSdkCoreWire implements FlutterRustBridgeWireBase { late final _new_list_payment_type_filter_0 = _new_list_payment_type_filter_0Ptr .asFunction Function(int)>(); + ffi.Pointer new_list_tlv_entry_0( + int len, + ) { + return _new_list_tlv_entry_0( + len, + ); + } + + late final _new_list_tlv_entry_0Ptr = + _lookup Function(ffi.Int32)>>( + 'new_list_tlv_entry_0'); + late final _new_list_tlv_entry_0 = + _new_list_tlv_entry_0Ptr.asFunction Function(int)>(); + ffi.Pointer new_uint_8_list_0( int len, ) { @@ -5701,11 +5751,27 @@ final class wire_SendPaymentRequest extends ffi.Struct { external ffi.Pointer amount_msat; } +final class wire_TlvEntry extends ffi.Struct { + @ffi.Uint64() + external int field_number; + + external ffi.Pointer value; +} + +final class wire_list_tlv_entry extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} + final class wire_SendSpontaneousPaymentRequest extends ffi.Struct { external ffi.Pointer node_id; @ffi.Uint64() external int amount_msat; + + external ffi.Pointer extra_tlvs; } final class wire_OpeningFeeParams extends ffi.Struct { diff --git a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt index 8a0ef26c6..348050684 100644 --- a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt +++ b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt @@ -2832,9 +2832,22 @@ fun asSendSpontaneousPaymentRequest(sendSpontaneousPaymentRequest: ReadableMap): } val nodeId = sendSpontaneousPaymentRequest.getString("nodeId")!! val amountMsat = sendSpontaneousPaymentRequest.getDouble("amountMsat").toULong() + val extraTlvs = + if (hasNonNullKey( + sendSpontaneousPaymentRequest, + "extraTlvs", + ) + ) { + sendSpontaneousPaymentRequest.getArray("extraTlvs")?.let { + asTlvEntryList(it) + } + } else { + null + } return SendSpontaneousPaymentRequest( nodeId, amountMsat, + extraTlvs, ) } @@ -2842,6 +2855,7 @@ fun readableMapOf(sendSpontaneousPaymentRequest: SendSpontaneousPaymentRequest): return readableMapOf( "nodeId" to sendSpontaneousPaymentRequest.nodeId, "amountMsat" to sendSpontaneousPaymentRequest.amountMsat, + "extraTlvs" to sendSpontaneousPaymentRequest.extraTlvs?.let { readableArrayOf(it) }, ) } @@ -3255,6 +3269,43 @@ fun asSymbolList(arr: ReadableArray): List { return list } +fun asTlvEntry(tlvEntry: ReadableMap): TlvEntry? { + if (!validateMandatoryFields( + tlvEntry, + arrayOf( + "fieldNumber", + "value", + ), + ) + ) { + return null + } + val fieldNumber = tlvEntry.getDouble("fieldNumber").toULong() + val value = tlvEntry.getArray("value")?.let { asUByteList(it) }!! + return TlvEntry( + fieldNumber, + value, + ) +} + +fun readableMapOf(tlvEntry: TlvEntry): ReadableMap { + return readableMapOf( + "fieldNumber" to tlvEntry.fieldNumber, + "value" to readableArrayOf(tlvEntry.value), + ) +} + +fun asTlvEntryList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toArrayList()) { + when (value) { + is ReadableMap -> list.add(asTlvEntry(value)!!) + else -> throw SdkException.Generic(errUnexpectedType("${value::class.java.name}")) + } + } + return list +} + fun asUnspentTransactionOutput(unspentTransactionOutput: ReadableMap): UnspentTransactionOutput? { if (!validateMandatoryFields( unspentTransactionOutput, @@ -4044,6 +4095,7 @@ fun pushToArray( is RouteHintHop -> array.pushMap(readableMapOf(value)) is String -> array.pushString(value) is SwapInfo -> array.pushMap(readableMapOf(value)) + is TlvEntry -> array.pushMap(readableMapOf(value)) is UByte -> array.pushInt(value.toInt()) is UnspentTransactionOutput -> array.pushMap(readableMapOf(value)) is Array<*> -> array.pushArray(readableArrayOf(value.asIterable())) diff --git a/libs/sdk-react-native/ios/BreezSDKMapper.swift b/libs/sdk-react-native/ios/BreezSDKMapper.swift index a6588345e..b8f0ca454 100644 --- a/libs/sdk-react-native/ios/BreezSDKMapper.swift +++ b/libs/sdk-react-native/ios/BreezSDKMapper.swift @@ -3078,10 +3078,15 @@ enum BreezSDKMapper { guard let amountMsat = sendSpontaneousPaymentRequest["amountMsat"] as? UInt64 else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "amountMsat", typeName: "SendSpontaneousPaymentRequest")) } + var extraTlvs: [TlvEntry]? + if let extraTlvsTmp = sendSpontaneousPaymentRequest["extraTlvs"] as? [[String: Any?]] { + extraTlvs = try asTlvEntryList(arr: extraTlvsTmp) + } return SendSpontaneousPaymentRequest( nodeId: nodeId, - amountMsat: amountMsat + amountMsat: amountMsat, + extraTlvs: extraTlvs ) } @@ -3089,6 +3094,7 @@ enum BreezSDKMapper { return [ "nodeId": sendSpontaneousPaymentRequest.nodeId, "amountMsat": sendSpontaneousPaymentRequest.amountMsat, + "extraTlvs": sendSpontaneousPaymentRequest.extraTlvs == nil ? nil : arrayOf(tlvEntryList: sendSpontaneousPaymentRequest.extraTlvs!), ] } @@ -3552,6 +3558,44 @@ enum BreezSDKMapper { return symbolList.map { v -> [String: Any?] in dictionaryOf(symbol: v) } } + static func asTlvEntry(tlvEntry: [String: Any?]) throws -> TlvEntry { + guard let fieldNumber = tlvEntry["fieldNumber"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "fieldNumber", typeName: "TlvEntry")) + } + guard let value = tlvEntry["value"] as? [UInt8] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "value", typeName: "TlvEntry")) + } + + return TlvEntry( + fieldNumber: fieldNumber, + value: value + ) + } + + static func dictionaryOf(tlvEntry: TlvEntry) -> [String: Any?] { + return [ + "fieldNumber": tlvEntry.fieldNumber, + "value": tlvEntry.value, + ] + } + + static func asTlvEntryList(arr: [Any]) throws -> [TlvEntry] { + var list = [TlvEntry]() + for value in arr { + if let val = value as? [String: Any?] { + var tlvEntry = try asTlvEntry(tlvEntry: val) + list.append(tlvEntry) + } else { + throw SdkError.Generic(message: errUnexpectedType(typeName: "TlvEntry")) + } + } + return list + } + + static func arrayOf(tlvEntryList: [TlvEntry]) -> [Any] { + return tlvEntryList.map { v -> [String: Any?] in dictionaryOf(tlvEntry: v) } + } + static func asUnspentTransactionOutput(unspentTransactionOutput: [String: Any?]) throws -> UnspentTransactionOutput { guard let txid = unspentTransactionOutput["txid"] as? [UInt8] else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "txid", typeName: "UnspentTransactionOutput")) diff --git a/libs/sdk-react-native/src/index.ts b/libs/sdk-react-native/src/index.ts index 84d6c53e3..f9c532fa4 100644 --- a/libs/sdk-react-native/src/index.ts +++ b/libs/sdk-react-native/src/index.ts @@ -433,6 +433,7 @@ export type SendPaymentResponse = { export type SendSpontaneousPaymentRequest = { nodeId: string amountMsat: number + extraTlvs?: TlvEntry[] } export type ServiceHealthCheckResponse = { @@ -495,6 +496,11 @@ export type SymbolType = { position?: number } +export type TlvEntry = { + fieldNumber: number + value: number[] +} + export type UnspentTransactionOutput = { txid: number[] outnum: number diff --git a/tools/sdk-cli/src/command_handlers.rs b/tools/sdk-cli/src/command_handlers.rs index ee573e515..39861b5cf 100644 --- a/tools/sdk-cli/src/command_handlers.rs +++ b/tools/sdk-cli/src/command_handlers.rs @@ -204,6 +204,7 @@ pub(crate) async fn handle_command( .send_spontaneous_payment(SendSpontaneousPaymentRequest { node_id, amount_msat, + extra_tlvs: None, }) .await?; serde_json::to_string_pretty(&response.payment).map_err(|e| e.into())