From ce0cd99b860a2d11aa046a559b930fbbaa91bfcd Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Wed, 15 May 2024 09:45:50 +0200 Subject: [PATCH] Update RN bindings --- .../breezliquidsdk/BreezLiquidSDKMapper.kt | 248 +++++++++++------- .../breezliquidsdk/BreezLiquidSDKModule.kt | 95 +++++-- packages/react-native/src/index.ts | 20 +- 3 files changed, 236 insertions(+), 127 deletions(-) diff --git a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt index ab0b438..2f04e6f 100644 --- a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt +++ b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKMapper.kt @@ -1,33 +1,34 @@ package com.breezliquidsdk import breez_liquid_sdk.* import com.facebook.react.bridge.* -import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter -import java.io.File import java.util.* -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors - + fun asConnectRequest(connectRequest: ReadableMap): ConnectRequest? { - if (!validateMandatoryFields(connectRequest, arrayOf( - "mnemonic", - "network", - ))) { + if (!validateMandatoryFields( + connectRequest, + arrayOf( + "mnemonic", + "network", + ), + ) + ) { return null } val mnemonic = connectRequest.getString("mnemonic")!! - val network = connectRequest.getString("network")?.let { asNetwork(it)}!! + val network = connectRequest.getString("network")?.let { asNetwork(it) }!! val dataDir = if (hasNonNullKey(connectRequest, "dataDir")) connectRequest.getString("dataDir") else null return ConnectRequest( mnemonic, network, - dataDir,) + dataDir, + ) } fun readableMapOf(connectRequest: ConnectRequest): ReadableMap { return readableMapOf( - "mnemonic" to connectRequest.mnemonic, - "network" to connectRequest.network.name.lowercase(), - "dataDir" to connectRequest.dataDir, + "mnemonic" to connectRequest.mnemonic, + "network" to connectRequest.network.name.lowercase(), + "dataDir" to connectRequest.dataDir, ) } @@ -35,26 +36,32 @@ fun asConnectRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asConnectRequest(value)!!) + is ReadableMap -> list.add(asConnectRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asGetInfoRequest(getInfoRequest: ReadableMap): GetInfoRequest? { - if (!validateMandatoryFields(getInfoRequest, arrayOf( - "withScan", - ))) { + if (!validateMandatoryFields( + getInfoRequest, + arrayOf( + "withScan", + ), + ) + ) { return null } val withScan = getInfoRequest.getBoolean("withScan") return GetInfoRequest( - withScan,) + withScan, + ) } fun readableMapOf(getInfoRequest: GetInfoRequest): ReadableMap { return readableMapOf( - "withScan" to getInfoRequest.withScan, + "withScan" to getInfoRequest.withScan, ) } @@ -62,30 +69,36 @@ fun asGetInfoRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asGetInfoRequest(value)!!) + is ReadableMap -> list.add(asGetInfoRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asGetInfoResponse(getInfoResponse: ReadableMap): GetInfoResponse? { - if (!validateMandatoryFields(getInfoResponse, arrayOf( - "balanceSat", - "pubkey", - ))) { + if (!validateMandatoryFields( + getInfoResponse, + arrayOf( + "balanceSat", + "pubkey", + ), + ) + ) { return null } val balanceSat = getInfoResponse.getDouble("balanceSat").toULong() val pubkey = getInfoResponse.getString("pubkey")!! return GetInfoResponse( balanceSat, - pubkey,) + pubkey, + ) } fun readableMapOf(getInfoResponse: GetInfoResponse): ReadableMap { return readableMapOf( - "balanceSat" to getInfoResponse.balanceSat, - "pubkey" to getInfoResponse.pubkey, + "balanceSat" to getInfoResponse.balanceSat, + "pubkey" to getInfoResponse.pubkey, ) } @@ -93,26 +106,32 @@ fun asGetInfoResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asGetInfoResponse(value)!!) + is ReadableMap -> list.add(asGetInfoResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asPrepareReceiveRequest(prepareReceiveRequest: ReadableMap): PrepareReceiveRequest? { - if (!validateMandatoryFields(prepareReceiveRequest, arrayOf( - "payerAmountSat", - ))) { + if (!validateMandatoryFields( + prepareReceiveRequest, + arrayOf( + "payerAmountSat", + ), + ) + ) { return null } val payerAmountSat = prepareReceiveRequest.getDouble("payerAmountSat").toULong() return PrepareReceiveRequest( - payerAmountSat,) + payerAmountSat, + ) } fun readableMapOf(prepareReceiveRequest: PrepareReceiveRequest): ReadableMap { return readableMapOf( - "payerAmountSat" to prepareReceiveRequest.payerAmountSat, + "payerAmountSat" to prepareReceiveRequest.payerAmountSat, ) } @@ -120,30 +139,36 @@ fun asPrepareReceiveRequestList(arr: ReadableArray): List val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareReceiveRequest(value)!!) + is ReadableMap -> list.add(asPrepareReceiveRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiveResponse? { - if (!validateMandatoryFields(prepareReceiveResponse, arrayOf( - "payerAmountSat", - "feesSat", - ))) { + if (!validateMandatoryFields( + prepareReceiveResponse, + arrayOf( + "payerAmountSat", + "feesSat", + ), + ) + ) { return null } val payerAmountSat = prepareReceiveResponse.getDouble("payerAmountSat").toULong() val feesSat = prepareReceiveResponse.getDouble("feesSat").toULong() return PrepareReceiveResponse( payerAmountSat, - feesSat,) + feesSat, + ) } fun readableMapOf(prepareReceiveResponse: PrepareReceiveResponse): ReadableMap { return readableMapOf( - "payerAmountSat" to prepareReceiveResponse.payerAmountSat, - "feesSat" to prepareReceiveResponse.feesSat, + "payerAmountSat" to prepareReceiveResponse.payerAmountSat, + "feesSat" to prepareReceiveResponse.feesSat, ) } @@ -151,26 +176,32 @@ fun asPrepareReceiveResponseList(arr: ReadableArray): List() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareReceiveResponse(value)!!) + is ReadableMap -> list.add(asPrepareReceiveResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asPrepareSendRequest(prepareSendRequest: ReadableMap): PrepareSendRequest? { - if (!validateMandatoryFields(prepareSendRequest, arrayOf( - "invoice", - ))) { + if (!validateMandatoryFields( + prepareSendRequest, + arrayOf( + "invoice", + ), + ) + ) { return null } val invoice = prepareSendRequest.getString("invoice")!! return PrepareSendRequest( - invoice,) + invoice, + ) } fun readableMapOf(prepareSendRequest: PrepareSendRequest): ReadableMap { return readableMapOf( - "invoice" to prepareSendRequest.invoice, + "invoice" to prepareSendRequest.invoice, ) } @@ -178,30 +209,36 @@ fun asPrepareSendRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareSendRequest(value)!!) + is ReadableMap -> list.add(asPrepareSendRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asPrepareSendResponse(prepareSendResponse: ReadableMap): PrepareSendResponse? { - if (!validateMandatoryFields(prepareSendResponse, arrayOf( - "invoice", - "feesSat", - ))) { + if (!validateMandatoryFields( + prepareSendResponse, + arrayOf( + "invoice", + "feesSat", + ), + ) + ) { return null } val invoice = prepareSendResponse.getString("invoice")!! val feesSat = prepareSendResponse.getDouble("feesSat").toULong() return PrepareSendResponse( invoice, - feesSat,) + feesSat, + ) } fun readableMapOf(prepareSendResponse: PrepareSendResponse): ReadableMap { return readableMapOf( - "invoice" to prepareSendResponse.invoice, - "feesSat" to prepareSendResponse.feesSat, + "invoice" to prepareSendResponse.invoice, + "feesSat" to prepareSendResponse.feesSat, ) } @@ -209,30 +246,36 @@ fun asPrepareSendResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asPrepareSendResponse(value)!!) + is ReadableMap -> list.add(asPrepareSendResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asReceivePaymentResponse(receivePaymentResponse: ReadableMap): ReceivePaymentResponse? { - if (!validateMandatoryFields(receivePaymentResponse, arrayOf( - "id", - "invoice", - ))) { + if (!validateMandatoryFields( + receivePaymentResponse, + arrayOf( + "id", + "invoice", + ), + ) + ) { return null } val id = receivePaymentResponse.getString("id")!! val invoice = receivePaymentResponse.getString("invoice")!! return ReceivePaymentResponse( id, - invoice,) + invoice, + ) } fun readableMapOf(receivePaymentResponse: ReceivePaymentResponse): ReadableMap { return readableMapOf( - "id" to receivePaymentResponse.id, - "invoice" to receivePaymentResponse.invoice, + "id" to receivePaymentResponse.id, + "invoice" to receivePaymentResponse.invoice, ) } @@ -240,25 +283,30 @@ fun asReceivePaymentResponseList(arr: ReadableArray): List() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asReceivePaymentResponse(value)!!) + is ReadableMap -> list.add(asReceivePaymentResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asRestoreRequest(restoreRequest: ReadableMap): RestoreRequest? { - if (!validateMandatoryFields(restoreRequest, arrayOf( - ))) { + if (!validateMandatoryFields( + restoreRequest, + arrayOf(), + ) + ) { return null } val backupPath = if (hasNonNullKey(restoreRequest, "backupPath")) restoreRequest.getString("backupPath") else null return RestoreRequest( - backupPath,) + backupPath, + ) } fun readableMapOf(restoreRequest: RestoreRequest): ReadableMap { return readableMapOf( - "backupPath" to restoreRequest.backupPath, + "backupPath" to restoreRequest.backupPath, ) } @@ -266,26 +314,32 @@ fun asRestoreRequestList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asRestoreRequest(value)!!) + is ReadableMap -> list.add(asRestoreRequest(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } return list } + fun asSendPaymentResponse(sendPaymentResponse: ReadableMap): SendPaymentResponse? { - if (!validateMandatoryFields(sendPaymentResponse, arrayOf( - "txid", - ))) { + if (!validateMandatoryFields( + sendPaymentResponse, + arrayOf( + "txid", + ), + ) + ) { return null } val txid = sendPaymentResponse.getString("txid")!! return SendPaymentResponse( - txid,) + txid, + ) } fun readableMapOf(sendPaymentResponse: SendPaymentResponse): ReadableMap { return readableMapOf( - "txid" to sendPaymentResponse.txid, + "txid" to sendPaymentResponse.txid, ) } @@ -293,7 +347,7 @@ fun asSendPaymentResponseList(arr: ReadableArray): List { val list = ArrayList() for (value in arr.toArrayList()) { when (value) { - is ReadableMap -> list.add(asSendPaymentResponse(value)!!) + is ReadableMap -> list.add(asSendPaymentResponse(value)!!) else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}")) } } @@ -313,7 +367,9 @@ fun asNetworkList(arr: ReadableArray): List { } } return list -}fun readableMapOf(vararg values: Pair): ReadableMap { +} + +fun readableMapOf(vararg values: Pair): ReadableMap { val map = Arguments.createMap() for ((key, value) in values) { pushToMap(map, key, value) @@ -321,11 +377,17 @@ fun asNetworkList(arr: ReadableArray): List { return map } -fun hasNonNullKey(map: ReadableMap, key: String): Boolean { +fun hasNonNullKey( + map: ReadableMap, + key: String, +): Boolean { return map.hasKey(key) && !map.isNull(key) } -fun validateMandatoryFields(map: ReadableMap, keys: Array): Boolean { +fun validateMandatoryFields( + map: ReadableMap, + keys: Array, +): Boolean { for (k in keys) { if (!hasNonNullKey(map, k)) return false } @@ -333,7 +395,10 @@ fun validateMandatoryFields(map: ReadableMap, keys: Array): Boolean { return true } -fun pushToArray(array: WritableArray, value: Any?) { +fun pushToArray( + array: WritableArray, + value: Any?, +) { when (value) { null -> array.pushNull() is Array<*> -> array.pushArray(readableArrayOf(value.asIterable())) @@ -342,7 +407,11 @@ fun pushToArray(array: WritableArray, value: Any?) { } } -fun pushToMap(map: WritableMap, key: String, value: Any?) { +fun pushToMap( + map: WritableMap, + key: String, + value: Any?, +) { when (value) { null -> map.putNull(key) is Boolean -> map.putBoolean(key, value) @@ -395,19 +464,22 @@ fun asStringList(arr: ReadableArray): List { return list } -fun errMissingMandatoryField(fieldName: String, typeName: String): String { - return "Missing mandatory field ${fieldName} for type ${typeName}" - } +fun errMissingMandatoryField( + fieldName: String, + typeName: String, +): String { + return "Missing mandatory field $fieldName for type $typeName" +} fun errUnexpectedType(typeName: String): String { - return "Unexpected type ${typeName}" - } + return "Unexpected type $typeName" +} fun errUnexpectedValue(fieldName: String): String { - return "Unexpected value for optional field ${fieldName}" + return "Unexpected value for optional field $fieldName" } fun camelToUpperSnakeCase(str: String): String { val pattern = "(?<=.)[A-Z]".toRegex() return str.replace(pattern, "_$0").uppercase() -} \ No newline at end of file +} diff --git a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt index 630eb81..e9def90 100644 --- a/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt +++ b/packages/react-native/android/src/main/java/com/breezliquidsdk/BreezLiquidSDKModule.kt @@ -2,13 +2,10 @@ package com.breezliquidsdk import breez_liquid_sdk.* import com.facebook.react.bridge.* -import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter -import java.io.File import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors - class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private lateinit var executor: ExecutorService private var bindingLiquidSdk: BindingLiquidSdk? = null @@ -42,10 +39,11 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext @ReactMethod fun removeListeners(count: Int) {} - - @ReactMethod - fun connect(req: ReadableMap, promise: Promise) { + fun connect( + req: ReadableMap, + promise: Promise, + ) { if (bindingLiquidSdk != null) { promise.reject("Generic", "Already initialized") return @@ -53,8 +51,13 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext executor.execute { try { - var connectRequest = asConnectRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "ConnectRequest")) } - connectRequest.dataDir = connectRequest.dataDir?.takeUnless { it.isEmpty() } ?: run { reactApplicationContext.filesDir.toString() + "/breezLiquidSdk" } + var connectRequest = + asConnectRequest( + req, + ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "ConnectRequest")) } + connectRequest.dataDir = connectRequest.dataDir?.takeUnless { + it.isEmpty() + } ?: run { reactApplicationContext.filesDir.toString() + "/breezLiquidSdk" } bindingLiquidSdk = connect(connectRequest) promise.resolve(readableMapOf("status" to "ok")) } catch (e: Exception) { @@ -63,12 +66,17 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } - @ReactMethod - fun getInfo(req: ReadableMap, promise: Promise) { + fun getInfo( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val getInfoRequest = asGetInfoRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "GetInfoRequest")) } + val getInfoRequest = + asGetInfoRequest( + req, + ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "GetInfoRequest")) } val res = getBindingLiquidSdk().getInfo(getInfoRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -76,12 +84,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun prepareSendPayment(req: ReadableMap, promise: Promise) { + fun prepareSendPayment( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val prepareSendRequest = asPrepareSendRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendRequest")) } + val prepareSendRequest = + asPrepareSendRequest(req) ?: run { + throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendRequest")) + } val res = getBindingLiquidSdk().prepareSendPayment(prepareSendRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -89,12 +103,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun sendPayment(req: ReadableMap, promise: Promise) { + fun sendPayment( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val prepareSendResponse = asPrepareSendResponse(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendResponse")) } + val prepareSendResponse = + asPrepareSendResponse(req) ?: run { + throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareSendResponse")) + } val res = getBindingLiquidSdk().sendPayment(prepareSendResponse) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -102,12 +122,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun prepareReceivePayment(req: ReadableMap, promise: Promise) { + fun prepareReceivePayment( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val prepareReceiveRequest = asPrepareReceiveRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveRequest")) } + val prepareReceiveRequest = + asPrepareReceiveRequest(req) ?: run { + throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveRequest")) + } val res = getBindingLiquidSdk().prepareReceivePayment(prepareReceiveRequest) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -115,12 +141,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun receivePayment(req: ReadableMap, promise: Promise) { + fun receivePayment( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val prepareReceiveResponse = asPrepareReceiveResponse(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveResponse")) } + val prepareReceiveResponse = + asPrepareReceiveResponse(req) ?: run { + throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PrepareReceiveResponse")) + } val res = getBindingLiquidSdk().receivePayment(prepareReceiveResponse) promise.resolve(readableMapOf(res)) } catch (e: Exception) { @@ -128,7 +160,7 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod fun backup(promise: Promise) { executor.execute { @@ -140,12 +172,18 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - + @ReactMethod - fun restore(req: ReadableMap, promise: Promise) { + fun restore( + req: ReadableMap, + promise: Promise, + ) { executor.execute { try { - val restoreRequest = asRestoreRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "RestoreRequest")) } + val restoreRequest = + asRestoreRequest( + req, + ) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "RestoreRequest")) } getBindingLiquidSdk().restore(restoreRequest) promise.resolve(readableMapOf("status" to "ok")) } catch (e: Exception) { @@ -153,5 +191,4 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext } } } - -} \ No newline at end of file +} diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 4b7161d..8bf5d88 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -17,49 +17,49 @@ const BreezLiquidSDK = NativeModules.RNBreezLiquidSDK } ) -export type ConnectRequest = { +export interface ConnectRequest { mnemonic: string network: Network dataDir?: string } -export type GetInfoRequest = { +export interface GetInfoRequest { withScan: boolean } -export type GetInfoResponse = { +export interface GetInfoResponse { balanceSat: number pubkey: string } -export type PrepareReceiveRequest = { +export interface PrepareReceiveRequest { payerAmountSat: number } -export type PrepareReceiveResponse = { +export interface PrepareReceiveResponse { payerAmountSat: number feesSat: number } -export type PrepareSendRequest = { +export interface PrepareSendRequest { invoice: string } -export type PrepareSendResponse = { +export interface PrepareSendResponse { invoice: string feesSat: number } -export type ReceivePaymentResponse = { +export interface ReceivePaymentResponse { id: string invoice: string } -export type RestoreRequest = { +export interface RestoreRequest { backupPath?: string } -export type SendPaymentResponse = { +export interface SendPaymentResponse { txid: string }