SDK events framework (#193)

* Add events framework

* Adapt RN codegen and add synced event

* Only use get_connection internally
This commit is contained in:
Ross Savage
2024-05-25 06:20:14 +02:00
committed by GitHub
parent 5143aeb1dd
commit 06b848a8f3
58 changed files with 2527 additions and 376 deletions

6
cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
target
liquid*/
history.txt
phrase
*.sql
*.log

19
cli/Cargo.lock generated
View File

@@ -401,6 +401,7 @@ dependencies = [
"rustyline",
"serde",
"serde_json",
"tokio",
]
[[package]]
@@ -416,6 +417,7 @@ dependencies = [
"lwk_common",
"lwk_signer",
"lwk_wollet",
"once_cell",
"openssl",
"rusqlite",
"rusqlite_migration",
@@ -424,6 +426,7 @@ dependencies = [
"serde",
"serde_json",
"thiserror",
"tokio",
"tungstenite",
]
@@ -441,9 +444,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5"
[[package]]
name = "byteorder"
@@ -2399,9 +2402,21 @@ dependencies = [
"num_cpus",
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-rustls"
version = "0.25.0"

View File

@@ -16,6 +16,7 @@ qrcode-rs = { version = "0.1", default-features = false }
rustyline = { version = "13.0.0", features = ["derive"] }
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115"
tokio = { version = "1", features = ["macros"] }
[patch.crates-io]
# https://github.com/BlockstreamResearch/rust-secp256k1-zkp/pull/48/commits

View File

@@ -38,6 +38,8 @@ pub(crate) enum Command {
ListPayments,
/// Get the balance and general info of the current instance
GetInfo,
/// Sync local data with mempool and onchain data
Sync,
/// Empties the encrypted transaction cache
EmptyCache,
/// Backs up the current pending swaps
@@ -89,7 +91,7 @@ macro_rules! wait_confirmation {
};
}
pub(crate) fn handle_command(
pub(crate) async fn handle_command(
_rl: &mut Editor<CliHelper, DefaultHistory>,
sdk: &Arc<LiquidSdk>,
command: Command,
@@ -116,8 +118,9 @@ pub(crate) fn handle_command(
result
}
Command::SendPayment { bolt11, delay } => {
let prepare_response =
sdk.prepare_send_payment(&PrepareSendRequest { invoice: bolt11 })?;
let prepare_response = sdk
.prepare_send_payment(&PrepareSendRequest { invoice: bolt11 })
.await?;
wait_confirmation!(
format!(
@@ -131,23 +134,27 @@ pub(crate) fn handle_command(
let sdk_cloned = sdk.clone();
let prepare_cloned = prepare_response.clone();
thread::spawn(move || {
tokio::spawn(async move {
thread::sleep(Duration::from_secs(delay));
sdk_cloned.send_payment(&prepare_cloned).unwrap();
sdk_cloned.send_payment(&prepare_cloned).await.unwrap();
});
command_result!(prepare_response)
} else {
let response = sdk.send_payment(&prepare_response)?;
let response = sdk.send_payment(&prepare_response).await?;
command_result!(response)
}
}
Command::GetInfo => {
command_result!(sdk.get_info(GetInfoRequest { with_scan: true })?)
command_result!(sdk.get_info(GetInfoRequest { with_scan: true }).await?)
}
Command::ListPayments => {
let payments = sdk.list_payments()?;
command_result!(payments)
}
Command::Sync => {
sdk.sync().await?;
command_result!("Synced successfully")
}
Command::EmptyCache => {
sdk.empty_wallet_cache()?;
command_result!("Cache emptied successfully")

View File

@@ -45,7 +45,16 @@ fn show_results(result: Result<String>) -> Result<()> {
Ok(println!("{result_str}"))
}
fn main() -> Result<()> {
struct CliEventListener {}
impl EventListener for CliEventListener {
fn on_event(&self, e: LiquidSdkEvent) {
info!("Received event: {:?}", e);
}
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let data_dir_str = args.data_dir.unwrap_or(DEFAULT_DATA_DIR.to_string());
@@ -66,6 +75,8 @@ fn main() -> Result<()> {
env_logger::builder()
.target(env_logger::Target::Pipe(Box::new(log_file)))
.filter(None, log::LevelFilter::Debug)
.filter(Some("rustyline"), log::LevelFilter::Warn)
.init();
let persistence = CliPersistence { data_dir };
@@ -86,7 +97,11 @@ fn main() -> Result<()> {
mnemonic: mnemonic.to_string(),
data_dir: Some(data_dir_str),
network,
})?;
})
.await?;
let listener_id = sdk
.add_event_listener(Box::new(CliEventListener {}))
.await?;
let cli_prompt = match network {
Network::Liquid => "breez-liquid-cli [mainnet]> ",
@@ -105,7 +120,7 @@ fn main() -> Result<()> {
println!("{}", cli_res.unwrap_err());
continue;
}
let res = handle_command(rl, &sdk, cli_res.unwrap());
let res = handle_command(rl, &sdk, cli_res.unwrap()).await;
show_results(res)?;
}
Err(ReadlineError::Interrupted) => {
@@ -123,5 +138,6 @@ fn main() -> Result<()> {
}
}
sdk.remove_event_listener(listener_id).await?;
rl.save_history(history_file).map_err(|e| anyhow!(e))
}

4
lib/Cargo.lock generated
View File

@@ -520,6 +520,7 @@ dependencies = [
"lwk_common",
"lwk_signer",
"lwk_wollet",
"once_cell",
"openssl",
"rusqlite",
"rusqlite_migration",
@@ -529,6 +530,7 @@ dependencies = [
"serde_json",
"tempdir",
"thiserror",
"tokio",
"tungstenite",
"uuid",
]
@@ -541,7 +543,9 @@ dependencies = [
"breez-liquid-sdk",
"camino",
"glob",
"once_cell",
"thiserror",
"tokio",
"uniffi 0.27.1",
"uniffi-kotlin-multiplatform",
"uniffi_bindgen 0.25.3",

View File

@@ -11,7 +11,6 @@ path = "uniffi-bindgen.rs"
name = "breez_liquid_sdk_bindings"
crate-type = ["staticlib", "cdylib", "lib"]
[dependencies]
anyhow = { workspace = true }
breez-liquid-sdk = { path = "../core" }
@@ -21,6 +20,8 @@ uniffi_bindgen = "0.25.2"
uniffi-kotlin-multiplatform = { git = "https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings", rev = "55d51f3abf9819b32bd81756053dcfc10f8d5522" }
camino = "1.1.1"
thiserror = { workspace = true }
tokio = { version = "1", features = ["rt"] }
once_cell = "*"
[build-dependencies]
uniffi = { workspace = true, features = [ "build" ] }

View File

@@ -20,6 +20,11 @@ typedef struct _Dart_Handle* Dart_Handle;
*/
#define LIQUID_CLAIM_TX_FEERATE_MSAT 100.0
typedef struct wire_cst_list_prim_u_8_strict {
uint8_t *ptr;
int32_t len;
} wire_cst_list_prim_u_8_strict;
typedef struct wire_cst_get_info_request {
bool with_scan;
} wire_cst_get_info_request;
@@ -28,11 +33,6 @@ typedef struct wire_cst_prepare_receive_request {
uint64_t payer_amount_sat;
} wire_cst_prepare_receive_request;
typedef struct wire_cst_list_prim_u_8_strict {
uint8_t *ptr;
int32_t len;
} wire_cst_list_prim_u_8_strict;
typedef struct wire_cst_prepare_send_request {
struct wire_cst_list_prim_u_8_strict *invoice;
} wire_cst_prepare_send_request;
@@ -93,6 +93,44 @@ typedef struct wire_cst_liquid_sdk_error {
union LiquidSdkErrorKind kind;
} wire_cst_liquid_sdk_error;
typedef struct wire_cst_LiquidSdkEvent_PaymentFailed {
struct wire_cst_payment *details;
} wire_cst_LiquidSdkEvent_PaymentFailed;
typedef struct wire_cst_LiquidSdkEvent_PaymentPending {
struct wire_cst_payment *details;
} wire_cst_LiquidSdkEvent_PaymentPending;
typedef struct wire_cst_LiquidSdkEvent_PaymentRefunded {
struct wire_cst_payment *details;
} wire_cst_LiquidSdkEvent_PaymentRefunded;
typedef struct wire_cst_LiquidSdkEvent_PaymentRefundPending {
struct wire_cst_payment *details;
} wire_cst_LiquidSdkEvent_PaymentRefundPending;
typedef struct wire_cst_LiquidSdkEvent_PaymentSucceed {
struct wire_cst_payment *details;
} wire_cst_LiquidSdkEvent_PaymentSucceed;
typedef struct wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation {
struct wire_cst_payment *details;
} wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation;
typedef union LiquidSdkEventKind {
struct wire_cst_LiquidSdkEvent_PaymentFailed PaymentFailed;
struct wire_cst_LiquidSdkEvent_PaymentPending PaymentPending;
struct wire_cst_LiquidSdkEvent_PaymentRefunded PaymentRefunded;
struct wire_cst_LiquidSdkEvent_PaymentRefundPending PaymentRefundPending;
struct wire_cst_LiquidSdkEvent_PaymentSucceed PaymentSucceed;
struct wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation PaymentWaitingConfirmation;
} LiquidSdkEventKind;
typedef struct wire_cst_liquid_sdk_event {
int32_t tag;
union LiquidSdkEventKind kind;
} wire_cst_liquid_sdk_event;
typedef struct wire_cst_PaymentError_Generic {
struct wire_cst_list_prim_u_8_strict *err;
} wire_cst_PaymentError_Generic;
@@ -136,6 +174,10 @@ typedef struct wire_cst_send_payment_response {
struct wire_cst_list_prim_u_8_strict *txid;
} wire_cst_send_payment_response;
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener(int64_t port_,
uintptr_t that,
struct wire_cst_list_prim_u_8_strict *listener);
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_backup(int64_t port_,
uintptr_t that);
@@ -183,6 +225,8 @@ struct wire_cst_connect_request *frbgen_breez_liquid_cst_new_box_autoadd_connect
struct wire_cst_get_info_request *frbgen_breez_liquid_cst_new_box_autoadd_get_info_request(void);
struct wire_cst_payment *frbgen_breez_liquid_cst_new_box_autoadd_payment(void);
struct wire_cst_prepare_receive_request *frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request(void);
struct wire_cst_prepare_receive_response *frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_response(void);
@@ -202,6 +246,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
int64_t dummy_var = 0;
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_connect_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_get_info_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_payment);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_response);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_send_request);
@@ -212,6 +257,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_prim_u_8_strict);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_backup);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_empty_wallet_cache);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_get_info);

View File

@@ -101,10 +101,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.0"
http_parser:
dependency: transitive
description:
@@ -309,10 +309,10 @@ packages:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.4.2"
yaml:
dependency: transitive
description:
@@ -330,4 +330,4 @@ packages:
source: hosted
version: "2.2.0"
sdks:
dart: ">=3.3.0 <4.0.0"
dart: ">=3.2.0 <4.0.0"

View File

@@ -46,7 +46,8 @@ Future<void> mainImpl(List<String> args) async {
final triple = target.triple;
final flutterIdentifier = target.flutterIdentifier;
await run('rustup target add $triple');
await run('${target.compiler} --target $triple $profileArg', args: compilerOpts);
await run('${target.compiler} --package breez-liquid-sdk --target $triple $profileArg',
args: compilerOpts);
await run('mkdir -p $flutterIdentifier');
await run('cp ../../../../target/$triple/$profile/${target.libName} $flutterIdentifier/');
}

View File

@@ -10,7 +10,7 @@ pub use uniffi_bindgen::bindings::kotlin::gen_kotlin::*;
use crate::generator::RNConfig;
static IGNORED_FUNCTIONS: Lazy<HashSet<String>> = Lazy::new(|| {
let list: Vec<&str> = vec!["connect"];
let list: Vec<&str> = vec!["connect", "add_event_listener"];
HashSet::from_iter(list.into_iter().map(|s| s.to_string()))
});

View File

@@ -5,7 +5,9 @@
{% let obj = ci.get_object_definition(name).unwrap() %}
{% let obj_interface = "getBindingLiquidSdk()." %}
{%- for func in obj.methods() -%}
{%- if func.name()|ignored_function == false -%}
{%- include "TopLevelFunctionTemplate.kt" %}
{% endif -%}
{% endfor %}
{%- else -%}
{%- endmatch -%}

View File

@@ -67,6 +67,22 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext
}
}
}
@ReactMethod
fun addEventListener(promise: Promise) {
executor.execute {
try {
val emitter = reactApplicationContext.getJSModule(RCTDeviceEventEmitter::class.java)
var eventListener = BreezLiquidSDKEventListener(emitter)
val res = getBindingLiquidSdk().addEventListener(eventListener)
eventListener.setId(res)
promise.resolve(res)
} catch (e: Exception) {
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
}
}
}
{%- include "Objects.kt" %}
}

View File

@@ -9,7 +9,7 @@ use crate::generator::RNConfig;
pub use uniffi_bindgen::bindings::swift::gen_swift::*;
static IGNORED_FUNCTIONS: Lazy<HashSet<String>> = Lazy::new(|| {
let list: Vec<&str> = vec!["connect"];
let list: Vec<&str> = vec!["connect", "add_event_listener"];
HashSet::from_iter(list.into_iter().map(|s| s.to_string()))
});

View File

@@ -5,7 +5,9 @@
{% let obj = ci.get_object_definition(name).unwrap() %}
{% let obj_interface = "getBindingLiquidSdk()." %}
{%- for func in obj.methods() -%}
{%- if func.name()|ignored_function == false -%}
{%- include "TopLevelFunctionTemplate.swift" %}
{% endif -%}
{% endfor %}
{%- else -%}
{%- endmatch -%}

View File

@@ -12,13 +12,20 @@ RCT_EXTERN_METHOD(
resolve: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD(
addEventListener: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
{%- for type_ in ci.iter_types() %}
{%- let type_name = type_|type_name %}
{%- match type_ %}
{%- when Type::Object ( name ) %}
{% let obj = ci.get_object_definition(name).unwrap() %}
{%- for func in obj.methods() -%}
{%- if func.name()|ignored_function == false -%}
{%- include "ExternFunctionTemplate.m" %}
{% endif -%}
{% endfor %}
{%- else -%}
{%- endmatch -%}

View File

@@ -7,6 +7,7 @@ class RNBreezLiquidSDK: RCTEventEmitter {
public static var emitter: RCTEventEmitter!
public static var hasListeners: Bool = false
public static var supportedEvents: [String] = []
private var bindingLiquidSdk: BindingLiquidSdk!
@@ -26,8 +27,12 @@ class RNBreezLiquidSDK: RCTEventEmitter {
TAG
}
static func addSupportedEvent(name: String) {
RNBreezLiquidSDK.supportedEvents.append(name)
}
override func supportedEvents() -> [String]! {
return []
return RNBreezLiquidSDK.supportedEvents
}
override func startObserving() {
@@ -73,6 +78,19 @@ class RNBreezLiquidSDK: RCTEventEmitter {
rejectErr(err: err, reject: reject)
}
}
@objc(addEventListener:reject:)
func addEventListener(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {
var eventListener = BreezLiquidSDKEventListener()
var res = try getBindingLiquidSdk().addEventListener(listener: eventListener)
eventListener.setId(id: res)
resolve(res)
} catch let err {
rejectErr(err: err, reject: reject)
}
}
{%- include "Objects.swift" %}
func rejectErr(err: Error, reject: @escaping RCTPromiseRejectBlock) {

View File

@@ -26,7 +26,7 @@ static KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| {
});
static IGNORED_FUNCTIONS: Lazy<HashSet<String>> = Lazy::new(|| {
let list: Vec<&str> = vec!["connect"];
let list: Vec<&str> = vec!["connect", "add_event_listener"];
HashSet::from_iter(list.into_iter().map(|s| s.to_string()))
});

View File

@@ -1,5 +1,14 @@
export type EventListener = (e: LiquidSdkEvent) => void
export const connect = async (req: ConnectRequest): Promise<void> => {
const response = await BreezLiquidSDK.connect(req)
return response
}
export const addEventListener = async (listener: EventListener): Promise<string> => {
const response = await BreezLiquidSDK.addEventListener()
BreezLiquidSDKEmitter.addListener(`event-${response}`, listener)
return response
}

View File

@@ -4,7 +4,9 @@
{%- when Type::Object ( name ) %}
{% let obj = ci.get_object_definition(name).unwrap() %}
{%- for func in obj.methods() -%}
{%- if func.name()|ignored_function == false -%}
{%- include "TopLevelFunctionTemplate.ts" %}
{% endif -%}
{% endfor %}
{%- else -%}
{%- endmatch -%}

View File

@@ -1,4 +1,4 @@
import { NativeModules, Platform } from "react-native"
import { NativeModules, Platform, NativeEventEmitter } from "react-native"
const LINKING_ERROR =
`The package 'react-native-breez-liquid-sdk' doesn't seem to be linked. Make sure: \n\n` +
@@ -17,6 +17,7 @@ const BreezLiquidSDK = NativeModules.RNBreezLiquidSDK
}
)
const BreezLiquidSDKEmitter = new NativeEventEmitter(BreezLiquidSDK)
{%- import "macros.ts" as ts %}
{%- include "Types.ts" %}
{% include "Helpers.ts" -%}

View File

@@ -96,12 +96,33 @@ enum PaymentState {
"Failed",
};
[Enum]
interface LiquidSdkEvent {
PaymentFailed(Payment details);
PaymentPending(Payment details);
PaymentRefunded(Payment details);
PaymentRefundPending(Payment details);
PaymentSucceed(Payment details);
PaymentWaitingConfirmation(Payment details);
Synced();
};
callback interface EventListener {
void on_event(LiquidSdkEvent e);
};
namespace breez_liquid_sdk {
[Throws=LiquidSdkError]
BindingLiquidSdk connect(ConnectRequest req);
};
interface BindingLiquidSdk {
[Throws=LiquidSdkError]
string add_event_listener(EventListener listener);
[Throws=LiquidSdkError]
void remove_event_listener(string id);
[Throws=LiquidSdkError]
GetInfoResponse get_info(GetInfoRequest req);

View File

@@ -2,10 +2,20 @@ use std::sync::Arc;
use anyhow::Result;
use breez_liquid_sdk::{error::*, model::*, sdk::LiquidSdk};
use once_cell::sync::Lazy;
use tokio::runtime::Runtime;
static RT: Lazy<Runtime> = Lazy::new(|| Runtime::new().unwrap());
fn rt() -> &'static Runtime {
&RT
}
pub fn connect(req: ConnectRequest) -> Result<Arc<BindingLiquidSdk>, LiquidSdkError> {
let ln_sdk = LiquidSdk::connect(req)?;
Ok(Arc::from(BindingLiquidSdk { sdk: ln_sdk }))
rt().block_on(async {
let sdk = LiquidSdk::connect(req).await?;
Ok(Arc::from(BindingLiquidSdk { sdk }))
})
}
pub struct BindingLiquidSdk {
@@ -13,22 +23,30 @@ pub struct BindingLiquidSdk {
}
impl BindingLiquidSdk {
pub fn add_event_listener(&self, listener: Box<dyn EventListener>) -> LiquidSdkResult<String> {
rt().block_on(self.sdk.add_event_listener(listener))
}
pub fn remove_event_listener(&self, id: String) -> LiquidSdkResult<()> {
rt().block_on(self.sdk.remove_event_listener(id))
}
pub fn get_info(&self, req: GetInfoRequest) -> Result<GetInfoResponse, LiquidSdkError> {
self.sdk.get_info(req).map_err(Into::into)
rt().block_on(self.sdk.get_info(req)).map_err(Into::into)
}
pub fn prepare_send_payment(
&self,
req: PrepareSendRequest,
) -> Result<PrepareSendResponse, PaymentError> {
self.sdk.prepare_send_payment(&req)
rt().block_on(self.sdk.prepare_send_payment(&req))
}
pub fn send_payment(
&self,
req: PrepareSendResponse,
) -> Result<SendPaymentResponse, PaymentError> {
self.sdk.send_payment(&req)
rt().block_on(self.sdk.send_payment(&req))
}
pub fn prepare_receive_payment(
@@ -49,15 +67,19 @@ impl BindingLiquidSdk {
self.sdk.list_payments()
}
pub fn sync(&self) -> Result<(), LiquidSdkError> {
self.sdk.sync().map_err(Into::into)
pub fn sync(&self) -> LiquidSdkResult<()> {
rt().block_on(self.sdk.sync()).map_err(Into::into)
}
pub fn backup(&self) -> Result<(), LiquidSdkError> {
pub fn empty_wallet_cache(&self) -> LiquidSdkResult<()> {
self.sdk.empty_wallet_cache().map_err(Into::into)
}
pub fn backup(&self) -> LiquidSdkResult<()> {
self.sdk.backup().map_err(Into::into)
}
pub fn restore(&self, req: RestoreRequest) -> Result<(), LiquidSdkError> {
pub fn restore(&self, req: RestoreRequest) -> LiquidSdkResult<()> {
self.sdk.restore(req).map_err(Into::into)
}
}

View File

@@ -1,12 +1,23 @@
class SDKListener: breez_liquid_sdk.EventListener {
override fun onEvent(e: breez_liquid_sdk.LiquidSdkEvent) {
println(e.toString());
}
}
try {
var mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
var connectReq = breez_liquid_sdk.ConnectRequest(mnemonic, breez_liquid_sdk.Network.LIQUID_TESTNET)
var sdk = breez_liquid_sdk.connect(connectReq)
var listenerId = sdk.addEventListener(SDKListener())
var getInfoReq = breez_liquid_sdk.GetInfoRequest(false)
var nodeInfo = sdk.getInfo(getInfoReq)
sdk.removeEventListener(listenerId)
println("$nodeInfo")
assert(nodeInfo.pubkey.equals("03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494"))
} catch (ex: Exception) {

View File

@@ -1,14 +1,24 @@
import breez_liquid_sdk
class SDKListener(breez_liquid_sdk.EventListener):
def on_event(self, event):
print(event)
def test():
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
connect_req = breez_liquid_sdk.ConnectRequest(mnemonic=mnemonic, network=breez_liquid_sdk.Network.LIQUID_TESTNET)
sdk = breez_liquid_sdk.connect(connect_req)
listener_id = sdk.add_event_listener(SDKListener())
get_info_req = breez_liquid_sdk.GetInfoRequest(with_scan=False)
node_info = sdk.get_info(get_info_req)
sdk.remove_event_listener(listener_id)
print(node_info)
assert node_info.pubkey == "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494"

View File

@@ -1,12 +1,22 @@
import breez_liquid_sdk
class SDKListener: EventListener {
func onEvent(e: LiquidSdkEvent) {
print("Received event ", e);
}
}
let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
let connectReq = breez_liquid_sdk.ConnectRequest(mnemonic: mnemonic, network: .liquidTestnet);
let sdk = try breez_liquid_sdk.connect(req: connectReq);
let listenerId = try sdk.addEventListener(listener: SDKListener());
let getInfoReq = breez_liquid_sdk.GetInfoRequest(withScan: false);
let nodeInfo = try sdk.getInfo(req: getInfoReq);
try sdk.removeEventListener(id: listenerId);
print(nodeInfo);
assert(nodeInfo.pubkey == "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494", "nodeInfo.pubkey");

View File

@@ -29,7 +29,9 @@ serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.116"
thiserror = { workspace = true }
tungstenite = { version = "0.21.0", features = ["native-tls-vendored"] }
once_cell = "1"
openssl = { version = "0.10", features = ["vendored"] }
tokio = { version = "1", features = ["rt"] }
# Pin these versions to fix iOS build issues
security-framework = "=2.10.0"

View File

@@ -1,10 +1,30 @@
use crate::{error::*, model::*, sdk::LiquidSdk};
use crate::{error::*, frb::bridge::StreamSink, model::*, sdk::LiquidSdk};
use anyhow::Result;
use once_cell::sync::Lazy;
use std::sync::Arc;
use tokio::runtime::Runtime;
static RT: Lazy<Runtime> = Lazy::new(|| Runtime::new().unwrap());
pub(crate) fn rt() -> &'static Runtime {
&RT
}
struct BindingEventListener {
stream: StreamSink<LiquidSdkEvent>,
}
impl EventListener for BindingEventListener {
fn on_event(&self, e: LiquidSdkEvent) {
let _ = self.stream.add(e);
}
}
pub fn connect(req: ConnectRequest) -> Result<BindingLiquidSdk, LiquidSdkError> {
let ln_sdk = LiquidSdk::connect(req)?;
rt().block_on(async {
let ln_sdk = LiquidSdk::connect(req).await?;
Ok(BindingLiquidSdk { sdk: ln_sdk })
})
}
pub struct BindingLiquidSdk {
@@ -13,21 +33,31 @@ pub struct BindingLiquidSdk {
impl BindingLiquidSdk {
pub fn get_info(&self, req: GetInfoRequest) -> Result<GetInfoResponse, LiquidSdkError> {
self.sdk.get_info(req).map_err(Into::into)
rt().block_on(self.sdk.get_info(req)).map_err(Into::into)
}
pub fn add_event_listener(
&self,
listener: StreamSink<LiquidSdkEvent>,
) -> Result<String, LiquidSdkError> {
rt().block_on(
self.sdk
.add_event_listener(Box::new(BindingEventListener { stream: listener })),
)
}
pub fn prepare_send_payment(
&self,
req: PrepareSendRequest,
) -> Result<PrepareSendResponse, PaymentError> {
self.sdk.prepare_send_payment(&req)
rt().block_on(self.sdk.prepare_send_payment(&req))
}
pub fn send_payment(
&self,
req: PrepareSendResponse,
) -> Result<SendPaymentResponse, PaymentError> {
self.sdk.send_payment(&req)
rt().block_on(self.sdk.send_payment(&req))
}
pub fn prepare_receive_payment(
@@ -49,17 +79,17 @@ impl BindingLiquidSdk {
}
pub fn sync(&self) -> Result<(), LiquidSdkError> {
self.sdk.sync().map_err(Into::into)
}
pub fn backup(&self) -> Result<(), LiquidSdkError> {
self.sdk.backup().map_err(Into::into)
rt().block_on(self.sdk.sync()).map_err(Into::into)
}
pub fn empty_wallet_cache(&self) -> Result<(), LiquidSdkError> {
self.sdk.empty_wallet_cache().map_err(Into::into)
}
pub fn backup(&self) -> Result<(), LiquidSdkError> {
self.sdk.backup().map_err(Into::into)
}
pub fn restore(&self, req: RestoreRequest) -> Result<(), LiquidSdkError> {
self.sdk.restore(req).map_err(Into::into)
}

View File

@@ -69,7 +69,7 @@ impl BoltzStatusStream {
Ok(socket)
}
pub(super) fn track_pending_swaps(sdk: Arc<LiquidSdk>) -> Result<()> {
pub(super) async fn track_pending_swaps(sdk: Arc<LiquidSdk>) -> Result<()> {
let mut socket = Self::connect(sdk.clone())?;
let reconnect_delay = Duration::from_secs(15);
@@ -77,7 +77,8 @@ impl BoltzStatusStream {
let mut keep_alive_last_ping_ts = Instant::now();
// Outer loop: reconnects in case the connection is lost
thread::spawn(move || loop {
tokio::spawn(async move {
loop {
// Initially subscribe to all ongoing swaps
match sdk.list_ongoing_swaps() {
Ok(initial_ongoing_swaps) => {
@@ -138,7 +139,8 @@ impl BoltzStatusStream {
channel: _,
args,
}) => {
for boltz_client::swaps::boltzv2::Update { id, status } in args
for boltz_client::swaps::boltzv2::Update { id, status } in
args
{
if Self::is_tracked_send_swap(&id) {
match SubSwapStates::from_str(&status) {
@@ -146,7 +148,7 @@ impl BoltzStatusStream {
let res = sdk.try_handle_send_swap_boltz_status(
new_state,
&id,
);
).await;
info!("Handled new Send Swap status from Boltz, result: {res:?}");
}
Err(_) => error!("Received invalid SubSwapState for Send Swap {id}: {status}")
@@ -156,7 +158,7 @@ impl BoltzStatusStream {
Ok(new_state) => {
let res = sdk.try_handle_receive_swap_boltz_status(
new_state, &id,
);
).await;
info!("Handled new Receive Swap status from Boltz, result: {res:?}");
}
Err(_) => error!("Received invalid RevSwapState for Receive Swap {id}: {status}"),
@@ -212,6 +214,7 @@ impl BoltzStatusStream {
}
}
}
}
});
Ok(())

View File

@@ -1,5 +1,7 @@
use anyhow::Error;
pub type LiquidSdkResult<T, E = LiquidSdkError> = Result<T, E>;
#[macro_export]
macro_rules! ensure_sdk {
($cond:expr, $err:expr) => {

38
lib/core/src/event.rs Normal file
View File

@@ -0,0 +1,38 @@
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::Result;
use tokio::sync::RwLock;
use crate::model::{EventListener, LiquidSdkEvent};
pub(crate) struct EventManager {
listeners: RwLock<HashMap<String, Box<dyn EventListener>>>,
}
impl EventManager {
pub fn new() -> Self {
Self {
listeners: Default::default(),
}
}
pub async fn add(&self, listener: Box<dyn EventListener>) -> Result<String> {
let id = format!(
"{:X}",
SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis()
);
(*self.listeners.write().await).insert(id.clone(), listener);
Ok(id)
}
pub async fn remove(&self, id: String) {
(*self.listeners.write().await).remove(&id);
}
pub async fn notify(&self, e: LiquidSdkEvent) {
for listener in (*self.listeners.read().await).values() {
listener.on_event(e.clone());
}
}
}

View File

@@ -39,6 +39,20 @@ impl
unsafe { decode_rust_opaque_nom(self as _) }
}
}
impl
CstDecode<
StreamSink<crate::model::LiquidSdkEvent, flutter_rust_bridge::for_generated::DcoCodec>,
> for *mut wire_cst_list_prim_u_8_strict
{
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(
self,
) -> StreamSink<crate::model::LiquidSdkEvent, flutter_rust_bridge::for_generated::DcoCodec>
{
let raw: String = self.cst_decode();
StreamSink::deserialize(raw)
}
}
impl CstDecode<String> for *mut wire_cst_list_prim_u_8_strict {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> String {
@@ -60,6 +74,13 @@ impl CstDecode<crate::model::GetInfoRequest> for *mut wire_cst_get_info_request
CstDecode::<crate::model::GetInfoRequest>::cst_decode(*wrap).into()
}
}
impl CstDecode<crate::model::Payment> for *mut wire_cst_payment {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::Payment {
let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) };
CstDecode::<crate::model::Payment>::cst_decode(*wrap).into()
}
}
impl CstDecode<crate::model::PrepareReceiveRequest> for *mut wire_cst_prepare_receive_request {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::PrepareReceiveRequest {
@@ -144,6 +165,51 @@ impl CstDecode<crate::error::LiquidSdkError> for wire_cst_liquid_sdk_error {
}
}
}
impl CstDecode<crate::model::LiquidSdkEvent> for wire_cst_liquid_sdk_event {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::LiquidSdkEvent {
match self.tag {
0 => {
let ans = unsafe { self.kind.PaymentFailed };
crate::model::LiquidSdkEvent::PaymentFailed {
details: ans.details.cst_decode(),
}
}
1 => {
let ans = unsafe { self.kind.PaymentPending };
crate::model::LiquidSdkEvent::PaymentPending {
details: ans.details.cst_decode(),
}
}
2 => {
let ans = unsafe { self.kind.PaymentRefunded };
crate::model::LiquidSdkEvent::PaymentRefunded {
details: ans.details.cst_decode(),
}
}
3 => {
let ans = unsafe { self.kind.PaymentRefundPending };
crate::model::LiquidSdkEvent::PaymentRefundPending {
details: ans.details.cst_decode(),
}
}
4 => {
let ans = unsafe { self.kind.PaymentSucceed };
crate::model::LiquidSdkEvent::PaymentSucceed {
details: ans.details.cst_decode(),
}
}
5 => {
let ans = unsafe { self.kind.PaymentWaitingConfirmation };
crate::model::LiquidSdkEvent::PaymentWaitingConfirmation {
details: ans.details.cst_decode(),
}
}
6 => crate::model::LiquidSdkEvent::Synced,
_ => unreachable!(),
}
}
}
impl CstDecode<Vec<crate::model::Payment>> for *mut wire_cst_list_payment {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> Vec<crate::model::Payment> {
@@ -338,6 +404,19 @@ impl Default for wire_cst_liquid_sdk_error {
Self::new_with_null_ptr()
}
}
impl NewWithNullPtr for wire_cst_liquid_sdk_event {
fn new_with_null_ptr() -> Self {
Self {
tag: -1,
kind: LiquidSdkEventKind { nil__: () },
}
}
}
impl Default for wire_cst_liquid_sdk_event {
fn default() -> Self {
Self::new_with_null_ptr()
}
}
impl NewWithNullPtr for wire_cst_payment {
fn new_with_null_ptr() -> Self {
Self {
@@ -458,6 +537,15 @@ impl Default for wire_cst_send_payment_response {
}
}
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener(
port_: i64,
that: usize,
listener: *mut wire_cst_list_prim_u_8_strict,
) {
wire__crate__bindings__BindingLiquidSdk_add_event_listener_impl(port_, that, listener)
}
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_backup(
port_: i64,
@@ -586,6 +674,11 @@ pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_get_info_request(
)
}
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_payment() -> *mut wire_cst_payment {
flutter_rust_bridge::for_generated::new_leak_box_ptr(wire_cst_payment::new_with_null_ptr())
}
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request(
) -> *mut wire_cst_prepare_receive_request {
@@ -693,6 +786,53 @@ pub struct wire_cst_LiquidSdkError_Generic {
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_liquid_sdk_event {
tag: i32,
kind: LiquidSdkEventKind,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union LiquidSdkEventKind {
PaymentFailed: wire_cst_LiquidSdkEvent_PaymentFailed,
PaymentPending: wire_cst_LiquidSdkEvent_PaymentPending,
PaymentRefunded: wire_cst_LiquidSdkEvent_PaymentRefunded,
PaymentRefundPending: wire_cst_LiquidSdkEvent_PaymentRefundPending,
PaymentSucceed: wire_cst_LiquidSdkEvent_PaymentSucceed,
PaymentWaitingConfirmation: wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation,
nil__: (),
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_LiquidSdkEvent_PaymentFailed {
details: *mut wire_cst_payment,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_LiquidSdkEvent_PaymentPending {
details: *mut wire_cst_payment,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_LiquidSdkEvent_PaymentRefunded {
details: *mut wire_cst_payment,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_LiquidSdkEvent_PaymentRefundPending {
details: *mut wire_cst_payment,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_LiquidSdkEvent_PaymentSucceed {
details: *mut wire_cst_payment,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation {
details: *mut wire_cst_payment,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_list_payment {
ptr: *mut wire_cst_payment,
len: i32,

View File

@@ -33,7 +33,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueNom,
);
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.0.0-dev.35";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1284301568;
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 692273053;
// Section: executor
@@ -41,6 +41,46 @@ flutter_rust_bridge::frb_generated_default_handler!();
// Section: wire_funcs
fn wire__crate__bindings__BindingLiquidSdk_add_event_listener_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
that: impl CstDecode<
RustOpaqueNom<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BindingLiquidSdk>>,
>,
listener: impl CstDecode<
StreamSink<crate::model::LiquidSdkEvent, flutter_rust_bridge::for_generated::DcoCodec>,
>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "BindingLiquidSdk_add_event_listener",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let api_that = that.cst_decode();
let api_listener = listener.cst_decode();
move |context| {
transform_result_dco((move || {
let mut api_that_decoded = None;
let decode_indices_ =
flutter_rust_bridge::for_generated::rust_auto_opaque_decode_compute_order(
vec![api_that.rust_auto_opaque_lock_order_info(0, false)],
);
for i in decode_indices_ {
match i {
0 => {
api_that_decoded = Some(api_that.rust_auto_opaque_decode_sync_ref())
}
_ => unreachable!(),
}
}
let api_that = api_that_decoded.unwrap();
crate::bindings::BindingLiquidSdk::add_event_listener(&api_that, api_listener)
})())
}
},
)
}
fn wire__crate__bindings__BindingLiquidSdk_backup_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
that: impl CstDecode<
@@ -520,6 +560,16 @@ impl SseDecode
}
}
impl SseDecode
for StreamSink<crate::model::LiquidSdkEvent, flutter_rust_bridge::for_generated::DcoCodec>
{
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut inner = <String>::sse_decode(deserializer);
return StreamSink::deserialize(inner);
}
}
impl SseDecode for String {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -598,6 +648,57 @@ impl SseDecode for crate::error::LiquidSdkError {
}
}
impl SseDecode for crate::model::LiquidSdkEvent {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut tag_ = <i32>::sse_decode(deserializer);
match tag_ {
0 => {
let mut var_details = <crate::model::Payment>::sse_decode(deserializer);
return crate::model::LiquidSdkEvent::PaymentFailed {
details: var_details,
};
}
1 => {
let mut var_details = <crate::model::Payment>::sse_decode(deserializer);
return crate::model::LiquidSdkEvent::PaymentPending {
details: var_details,
};
}
2 => {
let mut var_details = <crate::model::Payment>::sse_decode(deserializer);
return crate::model::LiquidSdkEvent::PaymentRefunded {
details: var_details,
};
}
3 => {
let mut var_details = <crate::model::Payment>::sse_decode(deserializer);
return crate::model::LiquidSdkEvent::PaymentRefundPending {
details: var_details,
};
}
4 => {
let mut var_details = <crate::model::Payment>::sse_decode(deserializer);
return crate::model::LiquidSdkEvent::PaymentSucceed {
details: var_details,
};
}
5 => {
let mut var_details = <crate::model::Payment>::sse_decode(deserializer);
return crate::model::LiquidSdkEvent::PaymentWaitingConfirmation {
details: var_details,
};
}
6 => {
return crate::model::LiquidSdkEvent::Synced;
}
_ => {
unimplemented!("");
}
}
}
}
impl SseDecode for Vec<crate::model::Payment> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -987,6 +1088,40 @@ impl flutter_rust_bridge::IntoIntoDart<crate::error::LiquidSdkError>
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::model::LiquidSdkEvent {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
match self {
crate::model::LiquidSdkEvent::PaymentFailed { details } => {
[0.into_dart(), details.into_into_dart().into_dart()].into_dart()
}
crate::model::LiquidSdkEvent::PaymentPending { details } => {
[1.into_dart(), details.into_into_dart().into_dart()].into_dart()
}
crate::model::LiquidSdkEvent::PaymentRefunded { details } => {
[2.into_dart(), details.into_into_dart().into_dart()].into_dart()
}
crate::model::LiquidSdkEvent::PaymentRefundPending { details } => {
[3.into_dart(), details.into_into_dart().into_dart()].into_dart()
}
crate::model::LiquidSdkEvent::PaymentSucceed { details } => {
[4.into_dart(), details.into_into_dart().into_dart()].into_dart()
}
crate::model::LiquidSdkEvent::PaymentWaitingConfirmation { details } => {
[5.into_dart(), details.into_into_dart().into_dart()].into_dart()
}
crate::model::LiquidSdkEvent::Synced => [6.into_dart()].into_dart(),
}
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::model::LiquidSdkEvent {}
impl flutter_rust_bridge::IntoIntoDart<crate::model::LiquidSdkEvent>
for crate::model::LiquidSdkEvent
{
fn into_into_dart(self) -> crate::model::LiquidSdkEvent {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::model::Network {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
match self {
@@ -1241,6 +1376,15 @@ impl SseEncode
}
}
impl SseEncode
for StreamSink<crate::model::LiquidSdkEvent, flutter_rust_bridge::for_generated::DcoCodec>
{
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
unimplemented!("")
}
}
impl SseEncode for String {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -1300,6 +1444,41 @@ impl SseEncode for crate::error::LiquidSdkError {
}
}
impl SseEncode for crate::model::LiquidSdkEvent {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
match self {
crate::model::LiquidSdkEvent::PaymentFailed { details } => {
<i32>::sse_encode(0, serializer);
<crate::model::Payment>::sse_encode(details, serializer);
}
crate::model::LiquidSdkEvent::PaymentPending { details } => {
<i32>::sse_encode(1, serializer);
<crate::model::Payment>::sse_encode(details, serializer);
}
crate::model::LiquidSdkEvent::PaymentRefunded { details } => {
<i32>::sse_encode(2, serializer);
<crate::model::Payment>::sse_encode(details, serializer);
}
crate::model::LiquidSdkEvent::PaymentRefundPending { details } => {
<i32>::sse_encode(3, serializer);
<crate::model::Payment>::sse_encode(details, serializer);
}
crate::model::LiquidSdkEvent::PaymentSucceed { details } => {
<i32>::sse_encode(4, serializer);
<crate::model::Payment>::sse_encode(details, serializer);
}
crate::model::LiquidSdkEvent::PaymentWaitingConfirmation { details } => {
<i32>::sse_encode(5, serializer);
<crate::model::Payment>::sse_encode(details, serializer);
}
crate::model::LiquidSdkEvent::Synced => {
<i32>::sse_encode(6, serializer);
}
}
}
}
impl SseEncode for Vec<crate::model::Payment> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {

View File

@@ -1 +1 @@
mod bridge;
pub(crate) mod bridge;

View File

@@ -2,6 +2,7 @@
pub mod bindings;
pub(crate) mod boltz_status_stream;
pub mod error;
pub(crate) mod event;
#[cfg(feature = "frb")]
pub mod frb;
pub mod model;

View File

@@ -49,7 +49,24 @@ impl TryFrom<&str> for Network {
}
}
#[derive(Debug)]
/// Trait that can be used to react to various [LiquidSdkEvent]s emitted by the SDK.
pub trait EventListener: Send + Sync {
fn on_event(&self, e: LiquidSdkEvent);
}
/// Event emitted by the SDK. To listen for and react to these events, use an [EventListener] when
/// initializing the [LiquidSdk].
#[derive(Clone, Debug, PartialEq)]
pub enum LiquidSdkEvent {
PaymentFailed { details: Payment },
PaymentPending { details: Payment },
PaymentRefunded { details: Payment },
PaymentRefundPending { details: Payment },
PaymentSucceed { details: Payment },
PaymentWaitingConfirmation { details: Payment },
Synced,
}
pub struct LiquidSdkOptions {
pub signer: SwSigner,
pub network: Network,
@@ -66,6 +83,7 @@ pub struct LiquidSdkOptions {
/// If not set, it defaults to a Blockstream instance.
pub electrum_url: Option<ElectrumUrl>,
}
impl LiquidSdkOptions {
pub(crate) fn get_electrum_url(&self) -> ElectrumUrl {
self.electrum_url.clone().unwrap_or({
@@ -440,7 +458,7 @@ pub struct PaymentSwapData {
/// Represents an SDK payment.
///
/// By default, this is an onchain tx. It may represent a swap, if swap metadata is available.
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Payment {
/// The tx ID of the onchain transaction
pub tx_id: String,

View File

@@ -7,7 +7,7 @@ use std::{collections::HashMap, fs::create_dir_all, path::PathBuf, str::FromStr}
use anyhow::Result;
use migrations::current_migrations;
use rusqlite::{params, Connection};
use rusqlite::{params, Connection, OptionalExtension, Row};
use rusqlite_migration::{Migrations, M};
use crate::model::{Network::*, *};
@@ -92,12 +92,8 @@ impl Persister {
Ok([ongoing_send_swaps, ongoing_receive_swaps].concat())
}
pub fn get_payments(&self) -> Result<HashMap<String, Payment>> {
let con = self.get_connection()?;
// TODO For refund txs, do not create a new Payment
// Assumes there is no swap chaining (send swap lockup tx = receive swap claim tx)
let mut stmt = con.prepare(
fn select_payment_query(&self, where_clause: Option<&str>) -> String {
format!(
"
SELECT
ptx.tx_id,
@@ -121,11 +117,13 @@ impl Persister {
ON ptx.tx_id = rs.claim_tx_id
LEFT JOIN send_swaps AS ss
ON ptx.tx_id = ss.lockup_tx_id
WHERE {}
",
)?;
where_clause.unwrap_or("true")
)
}
let data = stmt
.query_map(params![], |row| {
fn sql_row_to_payment(&self, row: &Row) -> Result<Payment, rusqlite::Error> {
let tx = PaymentTxData {
tx_id: row.get(0)?,
timestamp: row.get(1)?,
@@ -165,7 +163,30 @@ impl Persister {
}),
};
Ok((tx.tx_id.clone(), Payment::from(tx, swap)))
Ok(Payment::from(tx, swap))
}
pub fn get_payment(&self, id: String) -> Result<Option<Payment>> {
Ok(self
.get_connection()?
.query_row(
&self.select_payment_query(Some(&format!("ptx.tx_id = ?1"))),
params![id],
|row| self.sql_row_to_payment(row),
)
.optional()?)
}
pub fn get_payments(&self) -> Result<HashMap<String, Payment>> {
let con = self.get_connection()?;
// TODO For refund txs, do not create a new Payment
// Assumes there is no swap chaining (send swap lockup tx = receive swap claim tx)
let mut stmt = con.prepare(&self.select_payment_query(None))?;
let data = stmt
.query_map(params![], |row| {
self.sql_row_to_payment(row).map(|p| (p.tx_id.clone(), p))
})?
.map(|i| i.unwrap())
.collect();

View File

@@ -7,7 +7,7 @@ use crate::persist::Persister;
use anyhow::Result;
use boltz_client::swaps::boltzv2::CreateReverseResponse;
use rusqlite::{named_params, params, Connection, OptionalExtension, Row};
use rusqlite::{named_params, params, Connection, Row};
use serde::{Deserialize, Serialize};
impl Persister {
@@ -73,13 +73,12 @@ impl Persister {
)
}
pub(crate) fn fetch_receive_swap(
con: &Connection,
id: &str,
) -> rusqlite::Result<Option<ReceiveSwap>> {
pub(crate) fn fetch_receive_swap(&self, id: &str) -> Result<Option<ReceiveSwap>> {
let con: Connection = self.get_connection()?;
let query = Self::list_receive_swaps_query(vec!["id = ?1".to_string()]);
con.query_row(&query, [id], Self::sql_row_to_receive_swap)
.optional()
let res = con.query_row(&query, [id], Self::sql_row_to_receive_swap);
Ok(res.ok())
}
fn sql_row_to_receive_swap(row: &Row) -> rusqlite::Result<ReceiveSwap> {
@@ -128,10 +127,8 @@ impl Persister {
self.list_receive_swaps(con, where_clause)
}
pub(crate) fn list_pending_receive_swaps(
&self,
con: &Connection,
) -> rusqlite::Result<Vec<ReceiveSwap>> {
pub(crate) fn list_pending_receive_swaps(&self) -> Result<Vec<ReceiveSwap>> {
let con: Connection = self.get_connection()?;
let query = Self::list_receive_swaps_query(vec!["state = ?1".to_string()]);
let res = con
.prepare(&query)?
@@ -147,10 +144,9 @@ impl Persister {
/// Pending Receive Swaps, indexed by claim_tx_id
pub(crate) fn list_pending_receive_swaps_by_claim_tx_id(
&self,
con: &Connection,
) -> rusqlite::Result<HashMap<String, ReceiveSwap>> {
) -> Result<HashMap<String, ReceiveSwap>> {
let res = self
.list_pending_receive_swaps(con)?
.list_pending_receive_swaps()?
.iter()
.filter_map(|pending_receive_swap| {
pending_receive_swap
@@ -164,12 +160,12 @@ impl Persister {
pub(crate) fn try_handle_receive_swap_update(
&self,
con: &Connection,
swap_id: &str,
to_state: PaymentState,
claim_tx_id: Option<&str>,
) -> Result<(), PaymentError> {
// Do not overwrite claim_tx_id
let con: Connection = self.get_connection()?;
con.execute(
"UPDATE receive_swaps
SET

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use anyhow::Result;
use boltz_client::swaps::boltzv2::CreateSubmarineResponse;
use rusqlite::{named_params, params, Connection, OptionalExtension, Row};
use rusqlite::{named_params, params, Connection, Row};
use serde::{Deserialize, Serialize};
use crate::ensure_sdk;
@@ -70,13 +70,12 @@ impl Persister {
)
}
pub(crate) fn fetch_send_swap(
con: &Connection,
id: &str,
) -> rusqlite::Result<Option<SendSwap>> {
pub(crate) fn fetch_send_swap(&self, id: &str) -> Result<Option<SendSwap>> {
let con: Connection = self.get_connection()?;
let query = Self::list_send_swaps_query(vec!["id = ?1".to_string()]);
con.query_row(&query, [id], Self::sql_row_to_send_swap)
.optional()
let res = con.query_row(&query, [id], Self::sql_row_to_send_swap);
Ok(res.ok())
}
fn sql_row_to_send_swap(row: &Row) -> rusqlite::Result<SendSwap> {
@@ -124,10 +123,8 @@ impl Persister {
self.list_send_swaps(con, where_clause)
}
pub(crate) fn list_pending_send_swaps(
&self,
con: &Connection,
) -> rusqlite::Result<Vec<SendSwap>> {
pub(crate) fn list_pending_send_swaps(&self) -> Result<Vec<SendSwap>> {
let con: Connection = self.get_connection()?;
let query = Self::list_send_swaps_query(vec!["state = ?1".to_string()]);
let res = con
.prepare(&query)?
@@ -140,10 +137,9 @@ impl Persister {
/// Pending Send swaps, indexed by refund tx id
pub(crate) fn list_pending_send_swaps_by_refund_tx_id(
&self,
con: &Connection,
) -> rusqlite::Result<HashMap<String, SendSwap>> {
) -> Result<HashMap<String, SendSwap>> {
let res: HashMap<String, SendSwap> = self
.list_pending_send_swaps(con)?
.list_pending_send_swaps()?
.iter()
.filter_map(|pending_send_swap| {
pending_send_swap
@@ -157,7 +153,6 @@ impl Persister {
pub(crate) fn try_handle_send_swap_update(
&self,
con: &Connection,
swap_id: &str,
to_state: PaymentState,
preimage: Option<&str>,
@@ -165,6 +160,7 @@ impl Persister {
refund_tx_id: Option<&str>,
) -> Result<(), PaymentError> {
// Do not overwrite preimage, lockup_tx_id, refund_tx_id
let con: Connection = self.get_connection()?;
con.execute(
"UPDATE send_swaps
SET

View File

@@ -1,14 +1,3 @@
use std::collections::HashMap;
use std::time::Instant;
use std::{
fs,
path::PathBuf,
str::FromStr,
sync::{Arc, Mutex},
thread,
time::Duration,
};
use anyhow::{anyhow, Result};
use boltz_client::{
network::electrum::ElectrumConfig,
@@ -31,12 +20,21 @@ use lwk_wollet::{
BlockchainBackend, ElectrumClient, ElectrumUrl, ElementsNetwork, FsPersister,
Wollet as LwkWollet, WolletDescriptor,
};
use std::time::Instant;
use std::{fs, path::PathBuf, str::FromStr, sync::Arc, time::Duration};
use tokio::sync::Mutex;
use crate::boltz_status_stream::set_stream_nonblocking;
use crate::model::PaymentState::*;
use crate::{
boltz_status_stream::BoltzStatusStream, ensure_sdk, error::PaymentError, get_invoice_amount,
model::*, persist::Persister, utils,
boltz_status_stream::BoltzStatusStream,
ensure_sdk,
error::{LiquidSdkResult, PaymentError},
event::EventManager,
get_invoice_amount,
model::*,
persist::Persister,
utils,
};
/// Claim tx feerate, in sats per vbyte.
@@ -54,10 +52,11 @@ pub struct LiquidSdk {
lwk_signer: SwSigner,
persister: Persister,
data_dir_path: String,
event_manager: EventManager,
}
impl LiquidSdk {
pub fn connect(req: ConnectRequest) -> Result<Arc<LiquidSdk>> {
pub async fn connect(req: ConnectRequest) -> Result<Arc<LiquidSdk>> {
let is_mainnet = req.network == Network::Liquid;
let signer = SwSigner::new(&req.mnemonic, is_mainnet)?;
let descriptor = LiquidSdk::get_descriptor(&signer, req.network)?;
@@ -70,17 +69,19 @@ impl LiquidSdk {
network: req.network,
})?;
BoltzStatusStream::track_pending_swaps(sdk.clone())?;
BoltzStatusStream::track_pending_swaps(sdk.clone()).await?;
// Periodically run sync() in the background
let sdk_clone = sdk.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_secs(30));
_ = sdk_clone.sync();
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(30)).await;
_ = sdk_clone.sync().await;
}
});
// Initial sync() before returning the instance
sdk.sync()?;
sdk.sync().await?;
Ok(sdk)
}
@@ -103,6 +104,8 @@ impl LiquidSdk {
let persister = Persister::new(&data_dir_path, network)?;
persister.init()?;
let event_manager = EventManager::new();
let sdk = Arc::new(LiquidSdk {
lwk_wollet,
network,
@@ -110,11 +113,29 @@ impl LiquidSdk {
lwk_signer: opts.signer,
persister,
data_dir_path,
event_manager,
});
Ok(sdk)
}
async fn notify_event_listeners(&self, e: LiquidSdkEvent) -> Result<()> {
self.event_manager.notify(e).await;
Ok(())
}
pub async fn add_event_listener(
&self,
listener: Box<dyn EventListener>,
) -> LiquidSdkResult<String> {
Ok(self.event_manager.add(listener).await?)
}
pub async fn remove_event_listener(&self, id: String) -> LiquidSdkResult<()> {
self.event_manager.remove(id).await;
Ok(())
}
fn get_descriptor(signer: &SwSigner, network: Network) -> Result<WolletDescriptor> {
let is_mainnet = network == Network::Liquid;
let descriptor_str = singlesig_desc(
@@ -168,7 +189,7 @@ impl LiquidSdk {
}
/// Transitions a Receive swap to a new state
pub(crate) fn try_handle_receive_swap_update(
pub(crate) async fn try_handle_receive_swap_update(
&self,
swap_id: &str,
to_state: PaymentState,
@@ -178,20 +199,24 @@ impl LiquidSdk {
"Transitioning Receive swap {swap_id} to {to_state:?} (claim_tx_id = {claim_tx_id:?})"
);
let con = self.persister.get_connection()?;
let swap = Persister::fetch_receive_swap(&con, swap_id)
let swap = self
.persister
.fetch_receive_swap(swap_id)
.map_err(|_| PaymentError::PersistError)?
.ok_or(PaymentError::Generic {
err: format!("Receive Swap not found {swap_id}"),
})?;
let payment_id = claim_tx_id.map(|c| c.to_string()).or(swap.claim_tx_id);
Self::validate_state_transition(swap.state, to_state)?;
self.persister
.try_handle_receive_swap_update(&con, swap_id, to_state, claim_tx_id)
.try_handle_receive_swap_update(swap_id, to_state, claim_tx_id)?;
Ok(self.emit_payment_updated(payment_id).await?)
}
/// Transitions a Send swap to a new state
pub(crate) fn try_handle_send_swap_update(
pub(crate) async fn try_handle_send_swap_update(
&self,
swap_id: &str,
to_state: PaymentState,
@@ -201,36 +226,132 @@ impl LiquidSdk {
) -> Result<(), PaymentError> {
info!("Transitioning Send swap {swap_id} to {to_state:?} (lockup_tx_id = {lockup_tx_id:?}, refund_tx_id = {refund_tx_id:?})");
let con = self.persister.get_connection()?;
let swap = Persister::fetch_send_swap(&con, swap_id)
let swap: SendSwap = self
.persister
.fetch_send_swap(swap_id)
.map_err(|_| PaymentError::PersistError)?
.ok_or(PaymentError::Generic {
err: format!("Send Swap not found {swap_id}"),
})?;
let payment_id = lockup_tx_id.map(|c| c.to_string()).or(swap.lockup_tx_id);
Self::validate_state_transition(swap.state, to_state)?;
self.persister.try_handle_send_swap_update(
&con,
swap_id,
to_state,
preimage,
lockup_tx_id,
refund_tx_id,
)?;
Ok(self.emit_payment_updated(payment_id).await?)
}
async fn emit_payment_updated(&self, payment_id: Option<String>) -> Result<()> {
if let Some(id) = payment_id {
match self.persister.get_payment(id.clone())? {
Some(payment) => {
match payment.status {
Complete => {
self.notify_event_listeners(LiquidSdkEvent::PaymentSucceed {
details: payment,
})
.await?
}
Pending => {
// The swap state has changed to Pending
match payment.swap_id.clone() {
Some(swap_id) => match payment.payment_type {
PaymentType::Receive => {
match self.persister.fetch_receive_swap(&swap_id)? {
Some(swap) => match swap.claim_tx_id {
Some(_) => {
// The claim tx has now been broadcast
self.notify_event_listeners(
LiquidSdkEvent::PaymentWaitingConfirmation {
details: payment,
},
)
.await?
}
None => {
// The lockup tx is in the mempool/confirmed
self.notify_event_listeners(
LiquidSdkEvent::PaymentPending {
details: payment,
},
)
.await?
}
},
None => debug!("Swap not found: {swap_id}"),
}
}
PaymentType::Send => {
match self.persister.fetch_send_swap(&swap_id)? {
Some(swap) => match swap.refund_tx_id {
Some(_) => {
// The refund tx has now been broadcast
self.notify_event_listeners(
LiquidSdkEvent::PaymentRefundPending {
details: payment,
},
)
.await?
}
None => {
// The lockup tx is in the mempool/confirmed
self.notify_event_listeners(
LiquidSdkEvent::PaymentPending {
details: payment,
},
)
.await?
}
},
None => debug!("Swap not found: {swap_id}"),
}
}
},
None => debug!("Payment has no swap id"),
}
}
Failed => match payment.payment_type {
PaymentType::Receive => {
self.notify_event_listeners(LiquidSdkEvent::PaymentFailed {
details: payment,
})
.await?
}
PaymentType::Send => {
// The refund tx is confirmed
self.notify_event_listeners(LiquidSdkEvent::PaymentRefunded {
details: payment,
})
.await?
}
},
_ => (),
};
}
None => debug!("Payment not found: {id}"),
}
}
Ok(())
}
/// Handles status updates from Boltz for Receive swaps
pub(crate) fn try_handle_receive_swap_boltz_status(
pub(crate) async fn try_handle_receive_swap_boltz_status(
&self,
swap_state: RevSwapStates,
id: &str,
) -> Result<()> {
self.sync()?;
self.sync().await?;
info!("Handling Receive Swap transition to {swap_state:?} for swap {id}");
let con = self.persister.get_connection()?;
let receive_swap = Persister::fetch_receive_swap(&con, id)?
let receive_swap = self
.persister
.fetch_receive_swap(id)?
.ok_or(anyhow!("No ongoing Receive Swap found for ID {id}"))?;
match swap_state {
@@ -239,7 +360,7 @@ impl LiquidSdk {
| RevSwapStates::TransactionFailed
| RevSwapStates::TransactionRefunded => {
error!("Swap {id} entered into an unrecoverable state: {swap_state:?}");
self.try_handle_receive_swap_update(id, Failed, None)?;
self.try_handle_receive_swap_update(id, Failed, None).await?;
}
// The lockup tx is in the mempool and we accept 0-conf => try to claim
@@ -251,7 +372,7 @@ impl LiquidSdk {
Some(claim_tx_id) => {
warn!("Claim tx for Receive Swap {id} was already broadcast: txid {claim_tx_id}")
}
None => match self.try_claim(&receive_swap) {
None => match self.try_claim(&receive_swap).await {
Ok(()) => {}
Err(err) => match err {
PaymentError::AlreadyClaimed => warn!("Funds already claimed for Receive Swap {id}"),
@@ -272,17 +393,18 @@ impl LiquidSdk {
}
/// Handles status updates from Boltz for Send swaps
pub(crate) fn try_handle_send_swap_boltz_status(
pub(crate) async fn try_handle_send_swap_boltz_status(
&self,
swap_state: SubSwapStates,
id: &str,
) -> Result<()> {
self.sync()?;
self.sync().await?;
info!("Handling Send Swap transition to {swap_state:?} for swap {id}");
let con = self.persister.get_connection()?;
let ongoing_send_swap = Persister::fetch_send_swap(&con, id)?
let ongoing_send_swap = self
.persister
.fetch_send_swap(id)?
.ok_or(anyhow!("No ongoing Send Swap found for ID {id}"))?;
let create_response: CreateSubmarineResponse =
ongoing_send_swap.get_boltz_create_response()?;
@@ -308,6 +430,7 @@ impl LiquidSdk {
&ongoing_send_swap.invoice,
&keypair,
)
.await
.map_err(|e| anyhow!("Could not post claim details. Err: {e:?}"))?;
// We insert a pseudo-lockup-tx in case LWK fails to pick up the new mempool tx for a while
@@ -327,7 +450,8 @@ impl LiquidSdk {
warn!("Swap-in {id} has already been claimed");
let preimage =
self.get_preimage_from_script_path_claim_spend(&ongoing_send_swap)?;
self.validate_send_swap_preimage(id, &ongoing_send_swap.invoice, &preimage)?;
self.validate_send_swap_preimage(id, &ongoing_send_swap.invoice, &preimage)
.await?;
Ok(())
}
@@ -343,10 +467,12 @@ impl LiquidSdk {
)
.map_err(|e| anyhow!("Could not rebuild refund details for swap-in {id}: {e:?}"))?;
let refund_tx_id =
self.try_refund(id, &swap_script, &keypair, receiver_amount_sat)?;
let refund_tx_id = self
.try_refund(id, &swap_script, &keypair, receiver_amount_sat)
.await?;
info!("Broadcast refund tx for Swap-in {id}. Tx id: {refund_tx_id}");
self.try_handle_send_swap_update(id, Pending, None, None, Some(&refund_tx_id))?;
self.try_handle_send_swap_update(id, Pending, None, None, Some(&refund_tx_id))
.await?;
Ok(())
}
@@ -359,16 +485,16 @@ impl LiquidSdk {
}
/// Gets the next unused onchain Liquid address
fn next_unused_address(&self) -> Result<Address, lwk_wollet::Error> {
let lwk_wollet = self.lwk_wollet.lock().unwrap();
async fn next_unused_address(&self) -> Result<Address, lwk_wollet::Error> {
let lwk_wollet = self.lwk_wollet.lock().await;
Ok(lwk_wollet.address(None)?.address().clone())
}
pub fn get_info(&self, req: GetInfoRequest) -> Result<GetInfoResponse> {
debug!("next_unused_address: {}", self.next_unused_address()?);
pub async fn get_info(&self, req: GetInfoRequest) -> Result<GetInfoResponse> {
debug!("next_unused_address: {}", self.next_unused_address().await?);
if req.with_scan {
self.sync()?;
self.sync().await?;
}
let mut pending_send_sat = 0;
@@ -420,13 +546,13 @@ impl LiquidSdk {
)
}
fn build_tx(
async fn build_tx(
&self,
fee_rate: Option<f32>,
recipient_address: &str,
amount_sat: u64,
) -> Result<Transaction, PaymentError> {
let lwk_wollet = self.lwk_wollet.lock().unwrap();
let lwk_wollet = self.lwk_wollet.lock().await;
let mut pset = lwk_wollet::TxBuilder::new(self.network.into())
.add_lbtc_recipient(
&ElementsAddress::from_str(recipient_address).map_err(|e| {
@@ -483,7 +609,7 @@ impl LiquidSdk {
Ok(lbtc_pair)
}
fn get_broadcast_fee_estimation(&self, amount_sat: u64) -> Result<u64> {
async fn get_broadcast_fee_estimation(&self, amount_sat: u64) -> Result<u64> {
// TODO Replace this with own address when LWK supports taproot
// https://github.com/Blockstream/lwk/issues/31
let temp_p2tr_addr = match self.network {
@@ -493,13 +619,14 @@ impl LiquidSdk {
// Create a throw-away tx similar to the lockup tx, in order to estimate fees
Ok(self
.build_tx(None, temp_p2tr_addr, amount_sat)?
.build_tx(None, temp_p2tr_addr, amount_sat)
.await?
.all_fees()
.values()
.sum())
}
pub fn prepare_send_payment(
pub async fn prepare_send_payment(
&self,
req: &PrepareSendRequest,
) -> Result<PrepareSendResponse, PaymentError> {
@@ -512,7 +639,9 @@ impl LiquidSdk {
let client = self.boltz_client_v2();
let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?;
let broadcast_fees_sat = self.get_broadcast_fee_estimation(receiver_amount_sat)?;
let broadcast_fees_sat = self
.get_broadcast_fee_estimation(receiver_amount_sat)
.await?;
Ok(PrepareSendResponse {
invoice: req.invoice.clone(),
@@ -531,8 +660,11 @@ impl LiquidSdk {
.ok_or(PaymentError::InvalidPreimage)
}
fn new_refund_tx(&self, swap_script: &LBtcSwapScriptV2) -> Result<LBtcSwapTxV2, PaymentError> {
let wallet = self.lwk_wollet.lock().unwrap();
async fn new_refund_tx(
&self,
swap_script: &LBtcSwapScriptV2,
) -> Result<LBtcSwapTxV2, PaymentError> {
let wallet = self.lwk_wollet.lock().await;
let output_address = wallet.address(Some(0))?.address().to_string();
let network_config = self.network_config();
Ok(LBtcSwapTxV2::new_refund(
@@ -542,16 +674,17 @@ impl LiquidSdk {
)?)
}
fn try_refund(
async fn try_refund(
&self,
swap_id: &str,
swap_script: &LBtcSwapScriptV2,
keypair: &Keypair,
amount_sat: u64,
) -> Result<String, PaymentError> {
let refund_tx = self.new_refund_tx(swap_script)?;
let refund_tx = self.new_refund_tx(swap_script).await?;
let broadcast_fees_sat = Amount::from_sat(self.get_broadcast_fee_estimation(amount_sat)?);
let broadcast_fees_sat =
Amount::from_sat(self.get_broadcast_fee_estimation(amount_sat).await?);
let client = self.boltz_client_v2();
let is_lowball = Some((&client, boltz_client::network::Chain::from(self.network)));
@@ -578,7 +711,7 @@ impl LiquidSdk {
}
/// Check if the provided preimage matches our invoice. If so, mark the Send payment as [Complete].
fn validate_send_swap_preimage(
async fn validate_send_swap_preimage(
&self,
swap_id: &str,
invoice: &str,
@@ -587,10 +720,11 @@ impl LiquidSdk {
Self::verify_payment_hash(preimage, invoice)?;
info!("Preimage is valid for Send Swap {swap_id}");
self.try_handle_send_swap_update(swap_id, Complete, Some(preimage), None, None)
.await
}
/// Interact with Boltz to assist in them doing a cooperative claim
fn cooperate_send_swap_claim(
async fn cooperate_send_swap_claim(
&self,
swap_id: &str,
swap_script: &LBtcSwapScriptV2,
@@ -599,12 +733,13 @@ impl LiquidSdk {
) -> Result<(), PaymentError> {
debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim");
let client = self.boltz_client_v2();
let refund_tx = self.new_refund_tx(swap_script)?;
let refund_tx = self.new_refund_tx(swap_script).await?;
let claim_tx_response = client.get_claim_tx_details(&swap_id.to_string())?;
debug!("Received claim tx details: {:?}", &claim_tx_response);
self.validate_send_swap_preimage(swap_id, invoice, &claim_tx_response.preimage)?;
self.validate_send_swap_preimage(swap_id, invoice, &claim_tx_response.preimage)
.await?;
let (partial_sig, pub_nonce) =
refund_tx.submarine_partial_sig(keypair, &claim_tx_response)?;
@@ -614,7 +749,7 @@ impl LiquidSdk {
Ok(())
}
fn lockup_funds(
async fn lockup_funds(
&self,
swap_id: &str,
create_response: &CreateSubmarineResponse,
@@ -624,11 +759,13 @@ impl LiquidSdk {
create_response.expected_amount, create_response.address
);
let lockup_tx = self.build_tx(
let lockup_tx = self
.build_tx(
None,
&create_response.address,
create_response.expected_amount,
)?;
)
.await?;
let electrum_client = ElectrumClient::new(&self.electrum_url)?;
let lockup_tx_id = electrum_client.broadcast(&lockup_tx)?.to_string();
@@ -639,7 +776,7 @@ impl LiquidSdk {
Ok(lockup_tx_id)
}
pub fn send_payment(
pub async fn send_payment(
&self,
req: &PrepareSendResponse,
) -> Result<SendPaymentResponse, PaymentError> {
@@ -648,7 +785,9 @@ impl LiquidSdk {
let client = self.boltz_client_v2();
let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?;
let broadcast_fees_sat = self.get_broadcast_fee_estimation(receiver_amount_sat)?;
let broadcast_fees_sat = self
.get_broadcast_fee_estimation(receiver_amount_sat)
.await?;
ensure_sdk!(
req.fees_sat == lbtc_pair.fees.total(receiver_amount_sat) + broadcast_fees_sat,
PaymentError::InvalidOrExpiredFees
@@ -719,16 +858,16 @@ impl LiquidSdk {
})?;
// Sync before handling new state
self.sync()?;
self.sync().await?;
// See https://docs.boltz.exchange/v/api/lifecycle#normal-submarine-swaps
match state {
// Boltz has locked the HTLC, we proceed with locking up the funds
SubSwapStates::InvoiceSet => {
// Check that we have not persisted the swap already
let con = self.persister.get_connection()?;
if let Some(ongoing_swap) = Persister::fetch_send_swap(&con, swap_id)
if let Some(ongoing_swap) = self
.persister
.fetch_send_swap(swap_id)
.map_err(|_| PaymentError::PersistError)?
{
if ongoing_swap.lockup_tx_id.is_some() {
@@ -736,14 +875,15 @@ impl LiquidSdk {
}
};
lockup_tx_id = self.lockup_funds(swap_id, &create_response)?;
lockup_tx_id = self.lockup_funds(swap_id, &create_response).await?;
self.try_handle_send_swap_update(
swap_id,
Pending,
None,
Some(&lockup_tx_id),
None,
)?;
)
.await?;
}
// Boltz has detected the lockup in the mempool, we can speed up
@@ -751,7 +891,8 @@ impl LiquidSdk {
SubSwapStates::TransactionClaimPending => {
// TODO Consolidate status handling: merge with and reuse try_handle_send_swap_boltz_status
self.cooperate_send_swap_claim(swap_id, &swap_script, &req.invoice, &keypair)?;
self.cooperate_send_swap_claim(swap_id, &swap_script, &req.invoice, &keypair)
.await?;
debug!("Boltz successfully claimed the funds");
BoltzStatusStream::unmark_swap_as_tracked(swap_id, SwapType::Submarine);
@@ -770,8 +911,9 @@ impl LiquidSdk {
SubSwapStates::InvoiceFailedToPay
| SubSwapStates::SwapExpired
| SubSwapStates::TransactionLockupFailed => {
let refund_tx_id =
self.try_refund(swap_id, &swap_script, &keypair, receiver_amount_sat)?;
let refund_tx_id = self
.try_refund(swap_id, &swap_script, &keypair, receiver_amount_sat)
.await?;
result = Err(PaymentError::Refunded {
err: format!(
@@ -790,7 +932,7 @@ impl LiquidSdk {
result
}
fn try_claim(&self, ongoing_receive_swap: &ReceiveSwap) -> Result<(), PaymentError> {
async fn try_claim(&self, ongoing_receive_swap: &ReceiveSwap) -> Result<(), PaymentError> {
ensure_sdk!(
ongoing_receive_swap.claim_tx_id.is_none(),
PaymentError::AlreadyClaimed
@@ -799,6 +941,9 @@ impl LiquidSdk {
let swap_id = &ongoing_receive_swap.id;
debug!("Trying to claim Receive Swap {swap_id}",);
self.try_handle_receive_swap_update(swap_id, Pending, None)
.await?;
let lsk = self.get_liquid_swap_key()?;
let our_keys = lsk.keypair;
@@ -808,7 +953,7 @@ impl LiquidSdk {
our_keys.public_key().into(),
)?;
let claim_address = self.next_unused_address()?.to_string();
let claim_address = self.next_unused_address().await?.to_string();
let claim_tx_wrapper = LBtcSwapTxV2::new_claim(
swap_script,
claim_address,
@@ -834,18 +979,19 @@ impl LiquidSdk {
info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {swap_id}");
debug!("Claim Tx {:?}", claim_tx);
self.try_handle_receive_swap_update(swap_id, Pending, Some(&claim_tx_id))?;
// We insert a pseudo-claim-tx in case LWK fails to pick up the new mempool tx for a while
// This makes the tx known to the SDK (get_info, list_payments) instantly
self.persister.insert_or_update_payment(PaymentTxData {
tx_id: claim_tx_id,
tx_id: claim_tx_id.clone(),
timestamp: None,
amount_sat: ongoing_receive_swap.receiver_amount_sat,
payment_type: PaymentType::Receive,
is_confirmed: false,
})?;
self.try_handle_receive_swap_update(swap_id, Pending, Some(&claim_tx_id))
.await?;
Ok(())
}
@@ -954,22 +1100,19 @@ impl LiquidSdk {
/// This method fetches the chain tx data (onchain and mempool) using LWK. For every wallet tx,
/// it inserts or updates a corresponding entry in our Payments table.
fn sync_payments_with_chain_data(&self, with_scan: bool) -> Result<()> {
async fn sync_payments_with_chain_data(&self, with_scan: bool) -> Result<()> {
if with_scan {
let mut electrum_client = ElectrumClient::new(&self.electrum_url)?;
let mut lwk_wollet = self.lwk_wollet.lock().unwrap();
let mut lwk_wollet = self.lwk_wollet.lock().await;
lwk_wollet::full_scan_with_electrum_client(&mut lwk_wollet, &mut electrum_client)?;
}
let con = self.persister.get_connection()?;
let pending_receive_swaps_by_claim_tx_id: HashMap<String, ReceiveSwap> = self
.persister
.list_pending_receive_swaps_by_claim_tx_id(&con)?;
let pending_send_swaps_by_refund_tx_id: HashMap<String, SendSwap> = self
.persister
.list_pending_send_swaps_by_refund_tx_id(&con)?;
let pending_receive_swaps_by_claim_tx_id =
self.persister.list_pending_receive_swaps_by_claim_tx_id()?;
let pending_send_swaps_by_refund_tx_id =
self.persister.list_pending_send_swaps_by_refund_tx_id()?;
for tx in self.lwk_wollet.lock().unwrap().transactions()? {
for tx in self.lwk_wollet.lock().await.transactions()? {
let tx_id = tx.txid.to_string();
let is_tx_confirmed = tx.height.is_some();
let amount_sat = tx.balance.values().sum::<i64>();
@@ -977,10 +1120,12 @@ impl LiquidSdk {
// Transition the swaps whose state depends on this tx being confirmed
if is_tx_confirmed {
if let Some(swap) = pending_receive_swaps_by_claim_tx_id.get(&tx_id) {
self.try_handle_receive_swap_update(&swap.id, Complete, None)?;
self.try_handle_receive_swap_update(&swap.id, Complete, None)
.await?;
}
if let Some(swap) = pending_send_swaps_by_refund_tx_id.get(&tx_id) {
self.try_handle_send_swap_update(&swap.id, Failed, None, None, None)?;
self.try_handle_send_swap_update(&swap.id, Failed, None, None, None)
.await?;
}
}
@@ -1096,12 +1241,13 @@ impl LiquidSdk {
}
/// Synchronize the DB with mempool and onchain data
pub fn sync(&self) -> Result<()> {
pub async fn sync(&self) -> Result<()> {
let t0 = Instant::now();
self.sync_payments_with_chain_data(true)?;
self.sync_payments_with_chain_data(true).await?;
let duration_ms = Instant::now().duration_since(t0).as_millis();
info!("Synchronized with mempool and onchain data (t = {duration_ms} ms)");
self.notify_event_listeners(LiquidSdkEvent::Synced).await?;
Ok(())
}
@@ -1155,30 +1301,33 @@ mod tests {
.collect())
}
#[test]
fn normal_submarine_swap() -> Result<()> {
#[tokio::test]
async fn normal_submarine_swap() -> Result<()> {
let (_data_dir, data_dir_str) = create_temp_dir()?;
let sdk = LiquidSdk::connect(ConnectRequest {
mnemonic: TEST_MNEMONIC.to_string(),
data_dir: Some(data_dir_str),
network: Network::LiquidTestnet,
})?;
})
.await?;
let invoice = "lntb10u1pnqwkjrpp5j8ucv9mgww0ajk95yfpvuq0gg5825s207clrzl5thvtuzfn68h0sdqqcqzzsxqr23srzjqv8clnrfs9keq3zlg589jvzpw87cqh6rjks0f9g2t9tvuvcqgcl45f6pqqqqqfcqqyqqqqlgqqqqqqgq2qsp5jnuprlxrargr6hgnnahl28nvutj3gkmxmmssu8ztfhmmey3gq2ss9qyyssq9ejvcp6frwklf73xvskzdcuhnnw8dmxag6v44pffwqrxznsly4nqedem3p3zhn6u4ln7k79vk6zv55jjljhnac4gnvr677fyhfgn07qp4x6wrq".to_string();
sdk.prepare_send_payment(&PrepareSendRequest { invoice })?;
sdk.prepare_send_payment(&PrepareSendRequest { invoice })
.await?;
assert!(!list_pending(&sdk)?.is_empty());
Ok(())
}
#[test]
fn reverse_submarine_swap() -> Result<()> {
#[tokio::test]
async fn reverse_submarine_swap() -> Result<()> {
let (_data_dir, data_dir_str) = create_temp_dir()?;
let sdk = LiquidSdk::connect(ConnectRequest {
mnemonic: TEST_MNEMONIC.to_string(),
data_dir: Some(data_dir_str),
network: Network::LiquidTestnet,
})?;
})
.await?;
let prepare_response = sdk.prepare_receive_payment(&PrepareReceiveRequest {
payer_amount_sat: 1_000,

View File

@@ -8,6 +8,8 @@ import 'frb_generated.dart';
import 'model.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// The type `BindingEventListener` is not used by any `pub` functions, thus it is ignored.
Future<BindingLiquidSdk> connect({required ConnectRequest req, dynamic hint}) =>
RustLib.instance.api.crateBindingsConnect(req: req, hint: hint);
@@ -25,6 +27,9 @@ class BindingLiquidSdk extends RustOpaque {
rustArcDecrementStrongCountPtr: RustLib.instance.api.rust_arc_decrement_strong_count_BindingLiquidSdkPtr,
);
Stream<LiquidSdkEvent> addEventListener({dynamic hint}) =>
RustLib.instance.api.crateBindingsBindingLiquidSdkAddEventListener(that: this, hint: hint);
Future<void> backup({dynamic hint}) =>
RustLib.instance.api.crateBindingsBindingLiquidSdkBackup(that: this, hint: hint);

View File

@@ -53,7 +53,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.0.0-dev.35';
@override
int get rustContentHash => 1284301568;
int get rustContentHash => 692273053;
static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig(
stem: 'breez_liquid_sdk',
@@ -63,6 +63,9 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
}
abstract class RustLibApi extends BaseApi {
Stream<LiquidSdkEvent> crateBindingsBindingLiquidSdkAddEventListener(
{required BindingLiquidSdk that, dynamic hint});
Future<void> crateBindingsBindingLiquidSdkBackup({required BindingLiquidSdk that, dynamic hint});
Future<void> crateBindingsBindingLiquidSdkEmptyWalletCache({required BindingLiquidSdk that, dynamic hint});
@@ -107,6 +110,35 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
required super.portManager,
});
@override
Stream<LiquidSdkEvent> crateBindingsBindingLiquidSdkAddEventListener(
{required BindingLiquidSdk that, dynamic hint}) {
final listener = RustStreamSink<LiquidSdkEvent>();
unawaited(handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 =
cst_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk(
that);
var arg1 = cst_encode_StreamSink_liquid_sdk_event_Dco(listener);
return wire.wire__crate__bindings__BindingLiquidSdk_add_event_listener(port_, arg0, arg1);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_String,
decodeErrorData: dco_decode_liquid_sdk_error,
),
constMeta: kCrateBindingsBindingLiquidSdkAddEventListenerConstMeta,
argValues: [that, listener],
apiImpl: this,
hint: hint,
)));
return listener.stream;
}
TaskConstMeta get kCrateBindingsBindingLiquidSdkAddEventListenerConstMeta => const TaskConstMeta(
debugName: "BindingLiquidSdk_add_event_listener",
argNames: ["that", "listener"],
);
@override
Future<void> crateBindingsBindingLiquidSdkBackup({required BindingLiquidSdk that, dynamic hint}) {
return handler.executeNormal(NormalTask(
@@ -423,6 +455,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return BindingLiquidSdk.dcoDecode(raw as List<dynamic>);
}
@protected
RustStreamSink<LiquidSdkEvent> dco_decode_StreamSink_liquid_sdk_event_Dco(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
throw UnimplementedError();
}
@protected
String dco_decode_String(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -447,6 +485,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return dco_decode_get_info_request(raw);
}
@protected
Payment dco_decode_box_autoadd_payment(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_payment(raw);
}
@protected
PrepareReceiveRequest dco_decode_box_autoadd_prepare_receive_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -537,6 +581,41 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
LiquidSdkEvent dco_decode_liquid_sdk_event(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
switch (raw[0]) {
case 0:
return LiquidSdkEvent_PaymentFailed(
details: dco_decode_box_autoadd_payment(raw[1]),
);
case 1:
return LiquidSdkEvent_PaymentPending(
details: dco_decode_box_autoadd_payment(raw[1]),
);
case 2:
return LiquidSdkEvent_PaymentRefunded(
details: dco_decode_box_autoadd_payment(raw[1]),
);
case 3:
return LiquidSdkEvent_PaymentRefundPending(
details: dco_decode_box_autoadd_payment(raw[1]),
);
case 4:
return LiquidSdkEvent_PaymentSucceed(
details: dco_decode_box_autoadd_payment(raw[1]),
);
case 5:
return LiquidSdkEvent_PaymentWaitingConfirmation(
details: dco_decode_box_autoadd_payment(raw[1]),
);
case 6:
return LiquidSdkEvent_Synced();
default:
throw Exception("unreachable");
}
}
@protected
List<Payment> dco_decode_list_payment(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -768,6 +847,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return BindingLiquidSdk.sseDecode(sse_decode_usize(deserializer), sse_decode_i_32(deserializer));
}
@protected
RustStreamSink<LiquidSdkEvent> sse_decode_StreamSink_liquid_sdk_event_Dco(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
throw UnimplementedError('Unreachable ()');
}
@protected
String sse_decode_String(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -793,6 +878,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (sse_decode_get_info_request(deserializer));
}
@protected
Payment sse_decode_box_autoadd_payment(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_payment(deserializer));
}
@protected
PrepareReceiveRequest sse_decode_box_autoadd_prepare_receive_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -879,6 +970,37 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
LiquidSdkEvent sse_decode_liquid_sdk_event(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var tag_ = sse_decode_i_32(deserializer);
switch (tag_) {
case 0:
var var_details = sse_decode_box_autoadd_payment(deserializer);
return LiquidSdkEvent_PaymentFailed(details: var_details);
case 1:
var var_details = sse_decode_box_autoadd_payment(deserializer);
return LiquidSdkEvent_PaymentPending(details: var_details);
case 2:
var var_details = sse_decode_box_autoadd_payment(deserializer);
return LiquidSdkEvent_PaymentRefunded(details: var_details);
case 3:
var var_details = sse_decode_box_autoadd_payment(deserializer);
return LiquidSdkEvent_PaymentRefundPending(details: var_details);
case 4:
var var_details = sse_decode_box_autoadd_payment(deserializer);
return LiquidSdkEvent_PaymentSucceed(details: var_details);
case 5:
var var_details = sse_decode_box_autoadd_payment(deserializer);
return LiquidSdkEvent_PaymentWaitingConfirmation(details: var_details);
case 6:
return LiquidSdkEvent_Synced();
default:
throw UnimplementedError('');
}
}
@protected
List<Payment> sse_decode_list_payment(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1186,6 +1308,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_usize(self.sseEncode(move: null), serializer);
}
@protected
void sse_encode_StreamSink_liquid_sdk_event_Dco(
RustStreamSink<LiquidSdkEvent> self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_String(
self.setupAndSerialize(
codec: DcoCodec(decodeSuccessData: dco_decode_liquid_sdk_event, decodeErrorData: null)),
serializer);
}
@protected
void sse_encode_String(String self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1210,6 +1342,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_get_info_request(self, serializer);
}
@protected
void sse_encode_box_autoadd_payment(Payment self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_payment(self, serializer);
}
@protected
void sse_encode_box_autoadd_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1286,6 +1424,33 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_liquid_sdk_event(LiquidSdkEvent self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
switch (self) {
case LiquidSdkEvent_PaymentFailed(details: final details):
sse_encode_i_32(0, serializer);
sse_encode_box_autoadd_payment(details, serializer);
case LiquidSdkEvent_PaymentPending(details: final details):
sse_encode_i_32(1, serializer);
sse_encode_box_autoadd_payment(details, serializer);
case LiquidSdkEvent_PaymentRefunded(details: final details):
sse_encode_i_32(2, serializer);
sse_encode_box_autoadd_payment(details, serializer);
case LiquidSdkEvent_PaymentRefundPending(details: final details):
sse_encode_i_32(3, serializer);
sse_encode_box_autoadd_payment(details, serializer);
case LiquidSdkEvent_PaymentSucceed(details: final details):
sse_encode_i_32(4, serializer);
sse_encode_box_autoadd_payment(details, serializer);
case LiquidSdkEvent_PaymentWaitingConfirmation(details: final details):
sse_encode_i_32(5, serializer);
sse_encode_box_autoadd_payment(details, serializer);
case LiquidSdkEvent_Synced():
sse_encode_i_32(6, serializer);
}
}
@protected
void sse_encode_list_payment(List<Payment> self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs

View File

@@ -37,6 +37,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
BindingLiquidSdk dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk(
dynamic raw);
@protected
RustStreamSink<LiquidSdkEvent> dco_decode_StreamSink_liquid_sdk_event_Dco(dynamic raw);
@protected
String dco_decode_String(dynamic raw);
@@ -49,6 +52,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
GetInfoRequest dco_decode_box_autoadd_get_info_request(dynamic raw);
@protected
Payment dco_decode_box_autoadd_payment(dynamic raw);
@protected
PrepareReceiveRequest dco_decode_box_autoadd_prepare_receive_request(dynamic raw);
@@ -82,6 +88,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
LiquidSdkError dco_decode_liquid_sdk_error(dynamic raw);
@protected
LiquidSdkEvent dco_decode_liquid_sdk_event(dynamic raw);
@protected
List<Payment> dco_decode_list_payment(dynamic raw);
@@ -159,6 +168,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
BindingLiquidSdk sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk(
SseDeserializer deserializer);
@protected
RustStreamSink<LiquidSdkEvent> sse_decode_StreamSink_liquid_sdk_event_Dco(SseDeserializer deserializer);
@protected
String sse_decode_String(SseDeserializer deserializer);
@@ -171,6 +183,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
GetInfoRequest sse_decode_box_autoadd_get_info_request(SseDeserializer deserializer);
@protected
Payment sse_decode_box_autoadd_payment(SseDeserializer deserializer);
@protected
PrepareReceiveRequest sse_decode_box_autoadd_prepare_receive_request(SseDeserializer deserializer);
@@ -204,6 +219,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
LiquidSdkError sse_decode_liquid_sdk_error(SseDeserializer deserializer);
@protected
LiquidSdkEvent sse_decode_liquid_sdk_event(SseDeserializer deserializer);
@protected
List<Payment> sse_decode_list_payment(SseDeserializer deserializer);
@@ -267,6 +285,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int sse_decode_usize(SseDeserializer deserializer);
@protected
ffi.Pointer<wire_cst_list_prim_u_8_strict> cst_encode_StreamSink_liquid_sdk_event_Dco(
RustStreamSink<LiquidSdkEvent> raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return cst_encode_String(raw.setupAndSerialize(
codec: DcoCodec(decodeSuccessData: dco_decode_liquid_sdk_event, decodeErrorData: null)));
}
@protected
ffi.Pointer<wire_cst_list_prim_u_8_strict> cst_encode_String(String raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
@@ -289,6 +315,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return ptr;
}
@protected
ffi.Pointer<wire_cst_payment> cst_encode_box_autoadd_payment(Payment raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
final ptr = wire.cst_new_box_autoadd_payment();
cst_api_fill_to_wire_payment(raw, ptr.ref);
return ptr;
}
@protected
ffi.Pointer<wire_cst_prepare_receive_request> cst_encode_box_autoadd_prepare_receive_request(
PrepareReceiveRequest raw) {
@@ -387,6 +421,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
cst_api_fill_to_wire_get_info_request(apiObj, wireObj.ref);
}
@protected
void cst_api_fill_to_wire_box_autoadd_payment(Payment apiObj, ffi.Pointer<wire_cst_payment> wireObj) {
cst_api_fill_to_wire_payment(apiObj, wireObj.ref);
}
@protected
void cst_api_fill_to_wire_box_autoadd_prepare_receive_request(
PrepareReceiveRequest apiObj, ffi.Pointer<wire_cst_prepare_receive_request> wireObj) {
@@ -447,6 +486,50 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
}
}
@protected
void cst_api_fill_to_wire_liquid_sdk_event(LiquidSdkEvent apiObj, wire_cst_liquid_sdk_event wireObj) {
if (apiObj is LiquidSdkEvent_PaymentFailed) {
var pre_details = cst_encode_box_autoadd_payment(apiObj.details);
wireObj.tag = 0;
wireObj.kind.PaymentFailed.details = pre_details;
return;
}
if (apiObj is LiquidSdkEvent_PaymentPending) {
var pre_details = cst_encode_box_autoadd_payment(apiObj.details);
wireObj.tag = 1;
wireObj.kind.PaymentPending.details = pre_details;
return;
}
if (apiObj is LiquidSdkEvent_PaymentRefunded) {
var pre_details = cst_encode_box_autoadd_payment(apiObj.details);
wireObj.tag = 2;
wireObj.kind.PaymentRefunded.details = pre_details;
return;
}
if (apiObj is LiquidSdkEvent_PaymentRefundPending) {
var pre_details = cst_encode_box_autoadd_payment(apiObj.details);
wireObj.tag = 3;
wireObj.kind.PaymentRefundPending.details = pre_details;
return;
}
if (apiObj is LiquidSdkEvent_PaymentSucceed) {
var pre_details = cst_encode_box_autoadd_payment(apiObj.details);
wireObj.tag = 4;
wireObj.kind.PaymentSucceed.details = pre_details;
return;
}
if (apiObj is LiquidSdkEvent_PaymentWaitingConfirmation) {
var pre_details = cst_encode_box_autoadd_payment(apiObj.details);
wireObj.tag = 5;
wireObj.kind.PaymentWaitingConfirmation.details = pre_details;
return;
}
if (apiObj is LiquidSdkEvent_Synced) {
wireObj.tag = 6;
return;
}
}
@protected
void cst_api_fill_to_wire_payment(Payment apiObj, wire_cst_payment wireObj) {
wireObj.tx_id = cst_encode_String(apiObj.txId);
@@ -622,6 +705,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk(
BindingLiquidSdk self, SseSerializer serializer);
@protected
void sse_encode_StreamSink_liquid_sdk_event_Dco(
RustStreamSink<LiquidSdkEvent> self, SseSerializer serializer);
@protected
void sse_encode_String(String self, SseSerializer serializer);
@@ -634,6 +721,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_box_autoadd_get_info_request(GetInfoRequest self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_payment(Payment self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer);
@@ -667,6 +757,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_liquid_sdk_error(LiquidSdkError self, SseSerializer serializer);
@protected
void sse_encode_liquid_sdk_event(LiquidSdkEvent self, SseSerializer serializer);
@protected
void sse_encode_list_payment(List<Payment> self, SseSerializer serializer);
@@ -766,6 +859,26 @@ class RustLibWire implements BaseWire {
late final _store_dart_post_cobject =
_store_dart_post_cobjectPtr.asFunction<void Function(DartPostCObjectFnType)>();
void wire__crate__bindings__BindingLiquidSdk_add_event_listener(
int port_,
int that,
ffi.Pointer<wire_cst_list_prim_u_8_strict> listener,
) {
return _wire__crate__bindings__BindingLiquidSdk_add_event_listener(
port_,
that,
listener,
);
}
late final _wire__crate__bindings__BindingLiquidSdk_add_event_listenerPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Int64, ffi.UintPtr, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener');
late final _wire__crate__bindings__BindingLiquidSdk_add_event_listener =
_wire__crate__bindings__BindingLiquidSdk_add_event_listenerPtr
.asFunction<void Function(int, int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void wire__crate__bindings__BindingLiquidSdk_backup(
int port_,
int that,
@@ -1018,6 +1131,16 @@ class RustLibWire implements BaseWire {
late final _cst_new_box_autoadd_get_info_request = _cst_new_box_autoadd_get_info_requestPtr
.asFunction<ffi.Pointer<wire_cst_get_info_request> Function()>();
ffi.Pointer<wire_cst_payment> cst_new_box_autoadd_payment() {
return _cst_new_box_autoadd_payment();
}
late final _cst_new_box_autoadd_paymentPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<wire_cst_payment> Function()>>(
'frbgen_breez_liquid_cst_new_box_autoadd_payment');
late final _cst_new_box_autoadd_payment =
_cst_new_box_autoadd_paymentPtr.asFunction<ffi.Pointer<wire_cst_payment> Function()>();
ffi.Pointer<wire_cst_prepare_receive_request> cst_new_box_autoadd_prepare_receive_request() {
return _cst_new_box_autoadd_prepare_receive_request();
}
@@ -1127,6 +1250,13 @@ typedef DartDartPostCObjectFnTypeFunction = bool Function(
typedef DartPort = ffi.Int64;
typedef DartDartPort = int;
final class wire_cst_list_prim_u_8_strict extends ffi.Struct {
external ffi.Pointer<ffi.Uint8> ptr;
@ffi.Int32()
external int len;
}
final class wire_cst_get_info_request extends ffi.Struct {
@ffi.Bool()
external bool with_scan;
@@ -1137,13 +1267,6 @@ final class wire_cst_prepare_receive_request extends ffi.Struct {
external int payer_amount_sat;
}
final class wire_cst_list_prim_u_8_strict extends ffi.Struct {
external ffi.Pointer<ffi.Uint8> ptr;
@ffi.Int32()
external int len;
}
final class wire_cst_prepare_send_request extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> invoice;
}
@@ -1233,6 +1356,51 @@ final class wire_cst_liquid_sdk_error extends ffi.Struct {
external LiquidSdkErrorKind kind;
}
final class wire_cst_LiquidSdkEvent_PaymentFailed extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentPending extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentRefunded extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentRefundPending extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentSucceed extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class LiquidSdkEventKind extends ffi.Union {
external wire_cst_LiquidSdkEvent_PaymentFailed PaymentFailed;
external wire_cst_LiquidSdkEvent_PaymentPending PaymentPending;
external wire_cst_LiquidSdkEvent_PaymentRefunded PaymentRefunded;
external wire_cst_LiquidSdkEvent_PaymentRefundPending PaymentRefundPending;
external wire_cst_LiquidSdkEvent_PaymentSucceed PaymentSucceed;
external wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation PaymentWaitingConfirmation;
}
final class wire_cst_liquid_sdk_event extends ffi.Struct {
@ffi.Int32()
external int tag;
external LiquidSdkEventKind kind;
}
final class wire_cst_PaymentError_Generic extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> err;
}

View File

@@ -5,6 +5,8 @@
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
part 'model.freezed.dart';
class ConnectRequest {
final String mnemonic;
@@ -79,6 +81,31 @@ class GetInfoResponse {
pubkey == other.pubkey;
}
@freezed
sealed class LiquidSdkEvent with _$LiquidSdkEvent {
const LiquidSdkEvent._();
const factory LiquidSdkEvent.paymentFailed({
required Payment details,
}) = LiquidSdkEvent_PaymentFailed;
const factory LiquidSdkEvent.paymentPending({
required Payment details,
}) = LiquidSdkEvent_PaymentPending;
const factory LiquidSdkEvent.paymentRefunded({
required Payment details,
}) = LiquidSdkEvent_PaymentRefunded;
const factory LiquidSdkEvent.paymentRefundPending({
required Payment details,
}) = LiquidSdkEvent_PaymentRefundPending;
const factory LiquidSdkEvent.paymentSucceed({
required Payment details,
}) = LiquidSdkEvent_PaymentSucceed;
const factory LiquidSdkEvent.paymentWaitingConfirmation({
required Payment details,
}) = LiquidSdkEvent_PaymentWaitingConfirmation;
const factory LiquidSdkEvent.synced() = LiquidSdkEvent_Synced;
}
enum Network {
liquid,
liquidTestnet,

View File

@@ -0,0 +1,522 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$LiquidSdkEvent {}
/// @nodoc
abstract class $LiquidSdkEventCopyWith<$Res> {
factory $LiquidSdkEventCopyWith(LiquidSdkEvent value, $Res Function(LiquidSdkEvent) then) =
_$LiquidSdkEventCopyWithImpl<$Res, LiquidSdkEvent>;
}
/// @nodoc
class _$LiquidSdkEventCopyWithImpl<$Res, $Val extends LiquidSdkEvent>
implements $LiquidSdkEventCopyWith<$Res> {
_$LiquidSdkEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
}
/// @nodoc
abstract class _$$LiquidSdkEvent_PaymentFailedImplCopyWith<$Res> {
factory _$$LiquidSdkEvent_PaymentFailedImplCopyWith(
_$LiquidSdkEvent_PaymentFailedImpl value, $Res Function(_$LiquidSdkEvent_PaymentFailedImpl) then) =
__$$LiquidSdkEvent_PaymentFailedImplCopyWithImpl<$Res>;
@useResult
$Res call({Payment details});
}
/// @nodoc
class __$$LiquidSdkEvent_PaymentFailedImplCopyWithImpl<$Res>
extends _$LiquidSdkEventCopyWithImpl<$Res, _$LiquidSdkEvent_PaymentFailedImpl>
implements _$$LiquidSdkEvent_PaymentFailedImplCopyWith<$Res> {
__$$LiquidSdkEvent_PaymentFailedImplCopyWithImpl(
_$LiquidSdkEvent_PaymentFailedImpl _value, $Res Function(_$LiquidSdkEvent_PaymentFailedImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? details = null,
}) {
return _then(_$LiquidSdkEvent_PaymentFailedImpl(
details: null == details
? _value.details
: details // ignore: cast_nullable_to_non_nullable
as Payment,
));
}
}
/// @nodoc
class _$LiquidSdkEvent_PaymentFailedImpl extends LiquidSdkEvent_PaymentFailed {
const _$LiquidSdkEvent_PaymentFailedImpl({required this.details}) : super._();
@override
final Payment details;
@override
String toString() {
return 'LiquidSdkEvent.paymentFailed(details: $details)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LiquidSdkEvent_PaymentFailedImpl &&
(identical(other.details, details) || other.details == details));
}
@override
int get hashCode => Object.hash(runtimeType, details);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$LiquidSdkEvent_PaymentFailedImplCopyWith<_$LiquidSdkEvent_PaymentFailedImpl> get copyWith =>
__$$LiquidSdkEvent_PaymentFailedImplCopyWithImpl<_$LiquidSdkEvent_PaymentFailedImpl>(this, _$identity);
}
abstract class LiquidSdkEvent_PaymentFailed extends LiquidSdkEvent {
const factory LiquidSdkEvent_PaymentFailed({required final Payment details}) =
_$LiquidSdkEvent_PaymentFailedImpl;
const LiquidSdkEvent_PaymentFailed._() : super._();
Payment get details;
@JsonKey(ignore: true)
_$$LiquidSdkEvent_PaymentFailedImplCopyWith<_$LiquidSdkEvent_PaymentFailedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$LiquidSdkEvent_PaymentPendingImplCopyWith<$Res> {
factory _$$LiquidSdkEvent_PaymentPendingImplCopyWith(_$LiquidSdkEvent_PaymentPendingImpl value,
$Res Function(_$LiquidSdkEvent_PaymentPendingImpl) then) =
__$$LiquidSdkEvent_PaymentPendingImplCopyWithImpl<$Res>;
@useResult
$Res call({Payment details});
}
/// @nodoc
class __$$LiquidSdkEvent_PaymentPendingImplCopyWithImpl<$Res>
extends _$LiquidSdkEventCopyWithImpl<$Res, _$LiquidSdkEvent_PaymentPendingImpl>
implements _$$LiquidSdkEvent_PaymentPendingImplCopyWith<$Res> {
__$$LiquidSdkEvent_PaymentPendingImplCopyWithImpl(
_$LiquidSdkEvent_PaymentPendingImpl _value, $Res Function(_$LiquidSdkEvent_PaymentPendingImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? details = null,
}) {
return _then(_$LiquidSdkEvent_PaymentPendingImpl(
details: null == details
? _value.details
: details // ignore: cast_nullable_to_non_nullable
as Payment,
));
}
}
/// @nodoc
class _$LiquidSdkEvent_PaymentPendingImpl extends LiquidSdkEvent_PaymentPending {
const _$LiquidSdkEvent_PaymentPendingImpl({required this.details}) : super._();
@override
final Payment details;
@override
String toString() {
return 'LiquidSdkEvent.paymentPending(details: $details)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LiquidSdkEvent_PaymentPendingImpl &&
(identical(other.details, details) || other.details == details));
}
@override
int get hashCode => Object.hash(runtimeType, details);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$LiquidSdkEvent_PaymentPendingImplCopyWith<_$LiquidSdkEvent_PaymentPendingImpl> get copyWith =>
__$$LiquidSdkEvent_PaymentPendingImplCopyWithImpl<_$LiquidSdkEvent_PaymentPendingImpl>(
this, _$identity);
}
abstract class LiquidSdkEvent_PaymentPending extends LiquidSdkEvent {
const factory LiquidSdkEvent_PaymentPending({required final Payment details}) =
_$LiquidSdkEvent_PaymentPendingImpl;
const LiquidSdkEvent_PaymentPending._() : super._();
Payment get details;
@JsonKey(ignore: true)
_$$LiquidSdkEvent_PaymentPendingImplCopyWith<_$LiquidSdkEvent_PaymentPendingImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$LiquidSdkEvent_PaymentRefundedImplCopyWith<$Res> {
factory _$$LiquidSdkEvent_PaymentRefundedImplCopyWith(_$LiquidSdkEvent_PaymentRefundedImpl value,
$Res Function(_$LiquidSdkEvent_PaymentRefundedImpl) then) =
__$$LiquidSdkEvent_PaymentRefundedImplCopyWithImpl<$Res>;
@useResult
$Res call({Payment details});
}
/// @nodoc
class __$$LiquidSdkEvent_PaymentRefundedImplCopyWithImpl<$Res>
extends _$LiquidSdkEventCopyWithImpl<$Res, _$LiquidSdkEvent_PaymentRefundedImpl>
implements _$$LiquidSdkEvent_PaymentRefundedImplCopyWith<$Res> {
__$$LiquidSdkEvent_PaymentRefundedImplCopyWithImpl(
_$LiquidSdkEvent_PaymentRefundedImpl _value, $Res Function(_$LiquidSdkEvent_PaymentRefundedImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? details = null,
}) {
return _then(_$LiquidSdkEvent_PaymentRefundedImpl(
details: null == details
? _value.details
: details // ignore: cast_nullable_to_non_nullable
as Payment,
));
}
}
/// @nodoc
class _$LiquidSdkEvent_PaymentRefundedImpl extends LiquidSdkEvent_PaymentRefunded {
const _$LiquidSdkEvent_PaymentRefundedImpl({required this.details}) : super._();
@override
final Payment details;
@override
String toString() {
return 'LiquidSdkEvent.paymentRefunded(details: $details)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LiquidSdkEvent_PaymentRefundedImpl &&
(identical(other.details, details) || other.details == details));
}
@override
int get hashCode => Object.hash(runtimeType, details);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$LiquidSdkEvent_PaymentRefundedImplCopyWith<_$LiquidSdkEvent_PaymentRefundedImpl> get copyWith =>
__$$LiquidSdkEvent_PaymentRefundedImplCopyWithImpl<_$LiquidSdkEvent_PaymentRefundedImpl>(
this, _$identity);
}
abstract class LiquidSdkEvent_PaymentRefunded extends LiquidSdkEvent {
const factory LiquidSdkEvent_PaymentRefunded({required final Payment details}) =
_$LiquidSdkEvent_PaymentRefundedImpl;
const LiquidSdkEvent_PaymentRefunded._() : super._();
Payment get details;
@JsonKey(ignore: true)
_$$LiquidSdkEvent_PaymentRefundedImplCopyWith<_$LiquidSdkEvent_PaymentRefundedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$LiquidSdkEvent_PaymentRefundPendingImplCopyWith<$Res> {
factory _$$LiquidSdkEvent_PaymentRefundPendingImplCopyWith(_$LiquidSdkEvent_PaymentRefundPendingImpl value,
$Res Function(_$LiquidSdkEvent_PaymentRefundPendingImpl) then) =
__$$LiquidSdkEvent_PaymentRefundPendingImplCopyWithImpl<$Res>;
@useResult
$Res call({Payment details});
}
/// @nodoc
class __$$LiquidSdkEvent_PaymentRefundPendingImplCopyWithImpl<$Res>
extends _$LiquidSdkEventCopyWithImpl<$Res, _$LiquidSdkEvent_PaymentRefundPendingImpl>
implements _$$LiquidSdkEvent_PaymentRefundPendingImplCopyWith<$Res> {
__$$LiquidSdkEvent_PaymentRefundPendingImplCopyWithImpl(_$LiquidSdkEvent_PaymentRefundPendingImpl _value,
$Res Function(_$LiquidSdkEvent_PaymentRefundPendingImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? details = null,
}) {
return _then(_$LiquidSdkEvent_PaymentRefundPendingImpl(
details: null == details
? _value.details
: details // ignore: cast_nullable_to_non_nullable
as Payment,
));
}
}
/// @nodoc
class _$LiquidSdkEvent_PaymentRefundPendingImpl extends LiquidSdkEvent_PaymentRefundPending {
const _$LiquidSdkEvent_PaymentRefundPendingImpl({required this.details}) : super._();
@override
final Payment details;
@override
String toString() {
return 'LiquidSdkEvent.paymentRefundPending(details: $details)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LiquidSdkEvent_PaymentRefundPendingImpl &&
(identical(other.details, details) || other.details == details));
}
@override
int get hashCode => Object.hash(runtimeType, details);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$LiquidSdkEvent_PaymentRefundPendingImplCopyWith<_$LiquidSdkEvent_PaymentRefundPendingImpl>
get copyWith =>
__$$LiquidSdkEvent_PaymentRefundPendingImplCopyWithImpl<_$LiquidSdkEvent_PaymentRefundPendingImpl>(
this, _$identity);
}
abstract class LiquidSdkEvent_PaymentRefundPending extends LiquidSdkEvent {
const factory LiquidSdkEvent_PaymentRefundPending({required final Payment details}) =
_$LiquidSdkEvent_PaymentRefundPendingImpl;
const LiquidSdkEvent_PaymentRefundPending._() : super._();
Payment get details;
@JsonKey(ignore: true)
_$$LiquidSdkEvent_PaymentRefundPendingImplCopyWith<_$LiquidSdkEvent_PaymentRefundPendingImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$LiquidSdkEvent_PaymentSucceedImplCopyWith<$Res> {
factory _$$LiquidSdkEvent_PaymentSucceedImplCopyWith(_$LiquidSdkEvent_PaymentSucceedImpl value,
$Res Function(_$LiquidSdkEvent_PaymentSucceedImpl) then) =
__$$LiquidSdkEvent_PaymentSucceedImplCopyWithImpl<$Res>;
@useResult
$Res call({Payment details});
}
/// @nodoc
class __$$LiquidSdkEvent_PaymentSucceedImplCopyWithImpl<$Res>
extends _$LiquidSdkEventCopyWithImpl<$Res, _$LiquidSdkEvent_PaymentSucceedImpl>
implements _$$LiquidSdkEvent_PaymentSucceedImplCopyWith<$Res> {
__$$LiquidSdkEvent_PaymentSucceedImplCopyWithImpl(
_$LiquidSdkEvent_PaymentSucceedImpl _value, $Res Function(_$LiquidSdkEvent_PaymentSucceedImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? details = null,
}) {
return _then(_$LiquidSdkEvent_PaymentSucceedImpl(
details: null == details
? _value.details
: details // ignore: cast_nullable_to_non_nullable
as Payment,
));
}
}
/// @nodoc
class _$LiquidSdkEvent_PaymentSucceedImpl extends LiquidSdkEvent_PaymentSucceed {
const _$LiquidSdkEvent_PaymentSucceedImpl({required this.details}) : super._();
@override
final Payment details;
@override
String toString() {
return 'LiquidSdkEvent.paymentSucceed(details: $details)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LiquidSdkEvent_PaymentSucceedImpl &&
(identical(other.details, details) || other.details == details));
}
@override
int get hashCode => Object.hash(runtimeType, details);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$LiquidSdkEvent_PaymentSucceedImplCopyWith<_$LiquidSdkEvent_PaymentSucceedImpl> get copyWith =>
__$$LiquidSdkEvent_PaymentSucceedImplCopyWithImpl<_$LiquidSdkEvent_PaymentSucceedImpl>(
this, _$identity);
}
abstract class LiquidSdkEvent_PaymentSucceed extends LiquidSdkEvent {
const factory LiquidSdkEvent_PaymentSucceed({required final Payment details}) =
_$LiquidSdkEvent_PaymentSucceedImpl;
const LiquidSdkEvent_PaymentSucceed._() : super._();
Payment get details;
@JsonKey(ignore: true)
_$$LiquidSdkEvent_PaymentSucceedImplCopyWith<_$LiquidSdkEvent_PaymentSucceedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWith<$Res> {
factory _$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWith(
_$LiquidSdkEvent_PaymentWaitingConfirmationImpl value,
$Res Function(_$LiquidSdkEvent_PaymentWaitingConfirmationImpl) then) =
__$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWithImpl<$Res>;
@useResult
$Res call({Payment details});
}
/// @nodoc
class __$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWithImpl<$Res>
extends _$LiquidSdkEventCopyWithImpl<$Res, _$LiquidSdkEvent_PaymentWaitingConfirmationImpl>
implements _$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWith<$Res> {
__$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWithImpl(
_$LiquidSdkEvent_PaymentWaitingConfirmationImpl _value,
$Res Function(_$LiquidSdkEvent_PaymentWaitingConfirmationImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? details = null,
}) {
return _then(_$LiquidSdkEvent_PaymentWaitingConfirmationImpl(
details: null == details
? _value.details
: details // ignore: cast_nullable_to_non_nullable
as Payment,
));
}
}
/// @nodoc
class _$LiquidSdkEvent_PaymentWaitingConfirmationImpl extends LiquidSdkEvent_PaymentWaitingConfirmation {
const _$LiquidSdkEvent_PaymentWaitingConfirmationImpl({required this.details}) : super._();
@override
final Payment details;
@override
String toString() {
return 'LiquidSdkEvent.paymentWaitingConfirmation(details: $details)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LiquidSdkEvent_PaymentWaitingConfirmationImpl &&
(identical(other.details, details) || other.details == details));
}
@override
int get hashCode => Object.hash(runtimeType, details);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWith<_$LiquidSdkEvent_PaymentWaitingConfirmationImpl>
get copyWith => __$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWithImpl<
_$LiquidSdkEvent_PaymentWaitingConfirmationImpl>(this, _$identity);
}
abstract class LiquidSdkEvent_PaymentWaitingConfirmation extends LiquidSdkEvent {
const factory LiquidSdkEvent_PaymentWaitingConfirmation({required final Payment details}) =
_$LiquidSdkEvent_PaymentWaitingConfirmationImpl;
const LiquidSdkEvent_PaymentWaitingConfirmation._() : super._();
Payment get details;
@JsonKey(ignore: true)
_$$LiquidSdkEvent_PaymentWaitingConfirmationImplCopyWith<_$LiquidSdkEvent_PaymentWaitingConfirmationImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$LiquidSdkEvent_SyncedImplCopyWith<$Res> {
factory _$$LiquidSdkEvent_SyncedImplCopyWith(
_$LiquidSdkEvent_SyncedImpl value, $Res Function(_$LiquidSdkEvent_SyncedImpl) then) =
__$$LiquidSdkEvent_SyncedImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LiquidSdkEvent_SyncedImplCopyWithImpl<$Res>
extends _$LiquidSdkEventCopyWithImpl<$Res, _$LiquidSdkEvent_SyncedImpl>
implements _$$LiquidSdkEvent_SyncedImplCopyWith<$Res> {
__$$LiquidSdkEvent_SyncedImplCopyWithImpl(
_$LiquidSdkEvent_SyncedImpl _value, $Res Function(_$LiquidSdkEvent_SyncedImpl) _then)
: super(_value, _then);
}
/// @nodoc
class _$LiquidSdkEvent_SyncedImpl extends LiquidSdkEvent_Synced {
const _$LiquidSdkEvent_SyncedImpl() : super._();
@override
String toString() {
return 'LiquidSdkEvent.synced()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LiquidSdkEvent_SyncedImpl);
}
@override
int get hashCode => runtimeType.hashCode;
}
abstract class LiquidSdkEvent_Synced extends LiquidSdkEvent {
const factory LiquidSdkEvent_Synced() = _$LiquidSdkEvent_SyncedImpl;
const LiquidSdkEvent_Synced._() : super._();
}

View File

@@ -21,7 +21,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
flutter_breez_liquid: 90494dd8df26d6258f0d2a90663204ee6257ede2
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7

View File

@@ -37,6 +37,26 @@ class FlutterBreezLiquidBindings {
late final _store_dart_post_cobject =
_store_dart_post_cobjectPtr.asFunction<void Function(DartPostCObjectFnType)>();
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener(
int port_,
int that,
ffi.Pointer<wire_cst_list_prim_u_8_strict> listener,
) {
return _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener(
port_,
that,
listener,
);
}
late final _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listenerPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Int64, ffi.UintPtr, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener');
late final _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listener =
_frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_add_event_listenerPtr
.asFunction<void Function(int, int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_backup(
int port_,
int that,
@@ -297,6 +317,17 @@ class FlutterBreezLiquidBindings {
_frbgen_breez_liquid_cst_new_box_autoadd_get_info_requestPtr
.asFunction<ffi.Pointer<wire_cst_get_info_request> Function()>();
ffi.Pointer<wire_cst_payment> frbgen_breez_liquid_cst_new_box_autoadd_payment() {
return _frbgen_breez_liquid_cst_new_box_autoadd_payment();
}
late final _frbgen_breez_liquid_cst_new_box_autoadd_paymentPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<wire_cst_payment> Function()>>(
'frbgen_breez_liquid_cst_new_box_autoadd_payment');
late final _frbgen_breez_liquid_cst_new_box_autoadd_payment =
_frbgen_breez_liquid_cst_new_box_autoadd_paymentPtr
.asFunction<ffi.Pointer<wire_cst_payment> Function()>();
ffi.Pointer<wire_cst_prepare_receive_request>
frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request() {
return _frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request();
@@ -426,6 +457,13 @@ typedef DartDartPort = int;
final class _Dart_Handle extends ffi.Opaque {}
final class wire_cst_list_prim_u_8_strict extends ffi.Struct {
external ffi.Pointer<ffi.Uint8> ptr;
@ffi.Int32()
external int len;
}
final class wire_cst_get_info_request extends ffi.Struct {
@ffi.Bool()
external bool with_scan;
@@ -436,13 +474,6 @@ final class wire_cst_prepare_receive_request extends ffi.Struct {
external int payer_amount_sat;
}
final class wire_cst_list_prim_u_8_strict extends ffi.Struct {
external ffi.Pointer<ffi.Uint8> ptr;
@ffi.Int32()
external int len;
}
final class wire_cst_prepare_send_request extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> invoice;
}
@@ -532,6 +563,51 @@ final class wire_cst_liquid_sdk_error extends ffi.Struct {
external LiquidSdkErrorKind kind;
}
final class wire_cst_LiquidSdkEvent_PaymentFailed extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentPending extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentRefunded extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentRefundPending extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentSucceed extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation extends ffi.Struct {
external ffi.Pointer<wire_cst_payment> details;
}
final class LiquidSdkEventKind extends ffi.Union {
external wire_cst_LiquidSdkEvent_PaymentFailed PaymentFailed;
external wire_cst_LiquidSdkEvent_PaymentPending PaymentPending;
external wire_cst_LiquidSdkEvent_PaymentRefunded PaymentRefunded;
external wire_cst_LiquidSdkEvent_PaymentRefundPending PaymentRefundPending;
external wire_cst_LiquidSdkEvent_PaymentSucceed PaymentSucceed;
external wire_cst_LiquidSdkEvent_PaymentWaitingConfirmation PaymentWaitingConfirmation;
}
final class wire_cst_liquid_sdk_event extends ffi.Struct {
@ffi.Int32()
external int tag;
external LiquidSdkEventKind kind;
}
final class wire_cst_PaymentError_Generic extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> err;
}

View File

@@ -0,0 +1,19 @@
package com.breezliquidsdk
import breez_liquid_sdk.LiquidSdkEvent
import breez_liquid_sdk.EventListener
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
class BreezLiquidSDKEventListener(private val emitter: RCTDeviceEventEmitter) : EventListener {
private var id: String? = null
fun setId(id: String) {
this.id = id
}
override fun onEvent(e: LiquidSdkEvent) {
this.id?.let {
emitter.emit("event-$it", readableMapOf(e))
}
}
}

View File

@@ -420,6 +420,78 @@ fun asSendPaymentResponseList(arr: ReadableArray): List<SendPaymentResponse> {
return list
}
fun asLiquidSdkEvent(liquidSdkEvent: ReadableMap): LiquidSdkEvent? {
val type = liquidSdkEvent.getString("type")
if (type == "paymentFailed") {
return LiquidSdkEvent.PaymentFailed(liquidSdkEvent.getMap("details")?.let { asPayment(it) }!!)
}
if (type == "paymentPending") {
return LiquidSdkEvent.PaymentPending(liquidSdkEvent.getMap("details")?.let { asPayment(it) }!!)
}
if (type == "paymentRefunded") {
return LiquidSdkEvent.PaymentRefunded(liquidSdkEvent.getMap("details")?.let { asPayment(it) }!!)
}
if (type == "paymentRefundPending") {
return LiquidSdkEvent.PaymentRefundPending(liquidSdkEvent.getMap("details")?.let { asPayment(it) }!!)
}
if (type == "paymentSucceed") {
return LiquidSdkEvent.PaymentSucceed(liquidSdkEvent.getMap("details")?.let { asPayment(it) }!!)
}
if (type == "paymentWaitingConfirmation") {
return LiquidSdkEvent.PaymentWaitingConfirmation(liquidSdkEvent.getMap("details")?.let { asPayment(it) }!!)
}
if (type == "synced") {
return LiquidSdkEvent.Synced
}
return null
}
fun readableMapOf(liquidSdkEvent: LiquidSdkEvent): ReadableMap? {
val map = Arguments.createMap()
when (liquidSdkEvent) {
is LiquidSdkEvent.PaymentFailed -> {
pushToMap(map, "type", "paymentFailed")
pushToMap(map, "details", readableMapOf(liquidSdkEvent.details))
}
is LiquidSdkEvent.PaymentPending -> {
pushToMap(map, "type", "paymentPending")
pushToMap(map, "details", readableMapOf(liquidSdkEvent.details))
}
is LiquidSdkEvent.PaymentRefunded -> {
pushToMap(map, "type", "paymentRefunded")
pushToMap(map, "details", readableMapOf(liquidSdkEvent.details))
}
is LiquidSdkEvent.PaymentRefundPending -> {
pushToMap(map, "type", "paymentRefundPending")
pushToMap(map, "details", readableMapOf(liquidSdkEvent.details))
}
is LiquidSdkEvent.PaymentSucceed -> {
pushToMap(map, "type", "paymentSucceed")
pushToMap(map, "details", readableMapOf(liquidSdkEvent.details))
}
is LiquidSdkEvent.PaymentWaitingConfirmation -> {
pushToMap(map, "type", "paymentWaitingConfirmation")
pushToMap(map, "details", readableMapOf(liquidSdkEvent.details))
}
is LiquidSdkEvent.Synced -> {
pushToMap(map, "type", "synced")
}
}
return map
}
fun asLiquidSdkEventList(arr: ReadableArray): List<LiquidSdkEvent> {
val list = ArrayList<LiquidSdkEvent>()
for (value in arr.toArrayList()) {
when (value) {
is ReadableMap -> list.add(asLiquidSdkEvent(value)!!)
else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}"))
}
}
return list
}
fun asNetwork(type: String): Network {
return Network.valueOf(camelToUpperSnakeCase(type))
}

View File

@@ -2,6 +2,7 @@ package com.breezliquidsdk
import breez_liquid_sdk.*
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@@ -66,6 +67,37 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext
}
}
@ReactMethod
fun addEventListener(promise: Promise) {
executor.execute {
try {
val emitter = reactApplicationContext.getJSModule(RCTDeviceEventEmitter::class.java)
var eventListener = BreezLiquidSDKEventListener(emitter)
val res = getBindingLiquidSdk().addEventListener(eventListener)
eventListener.setId(res)
promise.resolve(res)
} catch (e: Exception) {
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
}
}
}
@ReactMethod
fun removeEventListener(
id: String,
promise: Promise,
) {
executor.execute {
try {
getBindingLiquidSdk().removeEventListener(id)
promise.resolve(readableMapOf("status" to "ok"))
} catch (e: Exception) {
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
}
}
}
@ReactMethod
fun getInfo(
req: ReadableMap,

View File

@@ -8,7 +8,7 @@
import React, { useState } from "react"
import { SafeAreaView, ScrollView, StatusBar, Text, TouchableOpacity, View } from "react-native"
import { Network, getInfo, connect } from "@breeztech/react-native-breez-liquid-sdk"
import { addEventListener, Network, getInfo, connect, removeEventListener } from "@breeztech/react-native-breez-liquid-sdk"
import { generateMnemonic } from "@dreson4/react-native-quick-bip39"
import { getSecureItem, setSecureItem } from "./utils/storage"
@@ -33,7 +33,13 @@ const App = () => {
console.log(`${title}${text && text.length > 0 ? ": " + text : ""}`)
}
const eventHandler = (e) => {
addLine("event", JSON.stringify(e))
}
React.useEffect(() => {
let listenerId = null
const asyncFn = async () => {
try {
let mnemonic = await getSecureItem(MNEMONIC_STORE)
@@ -43,10 +49,13 @@ const App = () => {
setSecureItem(MNEMONIC_STORE, mnemonic)
}
await connect({mnemonic, network: Network.LIQUID_TESTNET})
await connect({ mnemonic, network: Network.LIQUID_TESTNET })
addLine("connect", null)
let walletInfo = await getInfo({withScan: false})
listenerId = await addEventListener(eventHandler)
addLine("addEventListener", listenerId)
let walletInfo = await getInfo({ withScan: false })
addLine("getInfo", JSON.stringify(walletInfo))
} catch (e) {
addLine("error", e.toString())
@@ -55,6 +64,12 @@ const App = () => {
}
asyncFn()
return () => {
if (listenerId) {
removeEventListener(listenerId)
}
}
}, [])
return (

View File

@@ -383,8 +383,13 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.breezliquidsdk;
PRODUCT_NAME = BreezLiquidSDKExample;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -485,7 +490,12 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.breezliquidsdk;
PRODUCT_NAME = BreezLiquidSDKExample;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;

View File

@@ -552,7 +552,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: 9fa78656d705f55b1220151d997e57e2a3f2cde0
BreezLiquidSDK: 4bca3771d7dfe9c834dbe26836ffadfe8f283c76
BreezLiquidSDK: 287a36ed15beff2b9ba61a869e1868f790b4fa20
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 9cf707e46f9bd90816b7c91b2c1c8b8a2f549527

View File

@@ -0,0 +1,20 @@
import Foundation
import BreezLiquidSDK
class BreezLiquidSDKEventListener: EventListener {
private var id: String?
func setId(id: String) {
self.id = id
RNBreezLiquidSDK.addSupportedEvent(name: "event-\(id)")
}
func onEvent(e: LiquidSdkEvent) {
if let id = self.id {
if RNBreezLiquidSDK.hasListeners {
RNBreezLiquidSDK.emitter.sendEvent(withName: "event-\(id)",
body: BreezLiquidSDKMapper.dictionaryOf(liquidSdkEvent: e))
}
}
}
}

View File

@@ -460,6 +460,137 @@ enum BreezLiquidSDKMapper {
return sendPaymentResponseList.map { v -> [String: Any?] in dictionaryOf(sendPaymentResponse: v) }
}
static func asLiquidSdkEvent(liquidSdkEvent: [String: Any?]) throws -> LiquidSdkEvent {
let type = liquidSdkEvent["type"] as! String
if type == "paymentFailed" {
guard let detailsTmp = liquidSdkEvent["details"] as? [String: Any?] else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "details", typeName: "LiquidSdkEvent"))
}
let _details = try asPayment(payment: detailsTmp)
return LiquidSdkEvent.paymentFailed(details: _details)
}
if type == "paymentPending" {
guard let detailsTmp = liquidSdkEvent["details"] as? [String: Any?] else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "details", typeName: "LiquidSdkEvent"))
}
let _details = try asPayment(payment: detailsTmp)
return LiquidSdkEvent.paymentPending(details: _details)
}
if type == "paymentRefunded" {
guard let detailsTmp = liquidSdkEvent["details"] as? [String: Any?] else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "details", typeName: "LiquidSdkEvent"))
}
let _details = try asPayment(payment: detailsTmp)
return LiquidSdkEvent.paymentRefunded(details: _details)
}
if type == "paymentRefundPending" {
guard let detailsTmp = liquidSdkEvent["details"] as? [String: Any?] else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "details", typeName: "LiquidSdkEvent"))
}
let _details = try asPayment(payment: detailsTmp)
return LiquidSdkEvent.paymentRefundPending(details: _details)
}
if type == "paymentSucceed" {
guard let detailsTmp = liquidSdkEvent["details"] as? [String: Any?] else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "details", typeName: "LiquidSdkEvent"))
}
let _details = try asPayment(payment: detailsTmp)
return LiquidSdkEvent.paymentSucceed(details: _details)
}
if type == "paymentWaitingConfirmation" {
guard let detailsTmp = liquidSdkEvent["details"] as? [String: Any?] else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "details", typeName: "LiquidSdkEvent"))
}
let _details = try asPayment(payment: detailsTmp)
return LiquidSdkEvent.paymentWaitingConfirmation(details: _details)
}
if type == "synced" {
return LiquidSdkEvent.synced
}
throw LiquidSdkError.Generic(message: "Unexpected type \(type) for enum LiquidSdkEvent")
}
static func dictionaryOf(liquidSdkEvent: LiquidSdkEvent) -> [String: Any?] {
switch liquidSdkEvent {
case let .paymentFailed(
details
):
return [
"type": "paymentFailed",
"details": dictionaryOf(payment: details),
]
case let .paymentPending(
details
):
return [
"type": "paymentPending",
"details": dictionaryOf(payment: details),
]
case let .paymentRefunded(
details
):
return [
"type": "paymentRefunded",
"details": dictionaryOf(payment: details),
]
case let .paymentRefundPending(
details
):
return [
"type": "paymentRefundPending",
"details": dictionaryOf(payment: details),
]
case let .paymentSucceed(
details
):
return [
"type": "paymentSucceed",
"details": dictionaryOf(payment: details),
]
case let .paymentWaitingConfirmation(
details
):
return [
"type": "paymentWaitingConfirmation",
"details": dictionaryOf(payment: details),
]
case .synced:
return [
"type": "synced",
]
}
}
static func arrayOf(liquidSdkEventList: [LiquidSdkEvent]) -> [Any] {
return liquidSdkEventList.map { v -> [String: Any?] in dictionaryOf(liquidSdkEvent: v) }
}
static func asLiquidSdkEventList(arr: [Any]) throws -> [LiquidSdkEvent] {
var list = [LiquidSdkEvent]()
for value in arr {
if let val = value as? [String: Any?] {
var liquidSdkEvent = try asLiquidSdkEvent(liquidSdkEvent: val)
list.append(liquidSdkEvent)
} else {
throw LiquidSdkError.Generic(message: errUnexpectedType(typeName: "LiquidSdkEvent"))
}
}
return list
}
static func asNetwork(network: String) throws -> Network {
switch network {
case "liquid":

View File

@@ -9,6 +9,17 @@ RCT_EXTERN_METHOD(
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD(
addEventListener: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD(
removeEventListener: (NSString*)id
resolve: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD(
getInfo: (NSDictionary*)req
resolve: (RCTPromiseResolveBlock)resolve

View File

@@ -7,6 +7,7 @@ class RNBreezLiquidSDK: RCTEventEmitter {
public static var emitter: RCTEventEmitter!
public static var hasListeners: Bool = false
public static var supportedEvents: [String] = []
private var bindingLiquidSdk: BindingLiquidSdk!
@@ -26,8 +27,12 @@ class RNBreezLiquidSDK: RCTEventEmitter {
TAG
}
static func addSupportedEvent(name: String) {
RNBreezLiquidSDK.supportedEvents.append(name)
}
override func supportedEvents() -> [String]! {
return []
return RNBreezLiquidSDK.supportedEvents
}
override func startObserving() {
@@ -68,6 +73,29 @@ class RNBreezLiquidSDK: RCTEventEmitter {
}
}
@objc(addEventListener:reject:)
func addEventListener(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {
var eventListener = BreezLiquidSDKEventListener()
var res = try getBindingLiquidSdk().addEventListener(listener: eventListener)
eventListener.setId(id: res)
resolve(res)
} catch let err {
rejectErr(err: err, reject: reject)
}
}
@objc(removeEventListener:resolve:reject:)
func removeEventListener(_ id: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {
try getBindingLiquidSdk().removeEventListener(id: id)
resolve(["status": "ok"])
} catch let err {
rejectErr(err: err, reject: reject)
}
}
@objc(getInfo:resolve:reject:)
func getInfo(_ req: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {

View File

@@ -1,4 +1,4 @@
import { NativeModules, Platform } from "react-native"
import { NativeModules, Platform, NativeEventEmitter } from "react-native"
const LINKING_ERROR =
`The package 'react-native-breez-liquid-sdk' doesn't seem to be linked. Make sure: \n\n` +
@@ -17,6 +17,8 @@ const BreezLiquidSDK = NativeModules.RNBreezLiquidSDK
}
)
const BreezLiquidSDKEmitter = new NativeEventEmitter(BreezLiquidSDK)
export interface ConnectRequest {
mnemonic: string
network: Network
@@ -76,6 +78,38 @@ export interface SendPaymentResponse {
txid: string
}
export enum LiquidSdkEventVariant {
PAYMENT_FAILED = "paymentFailed",
PAYMENT_PENDING = "paymentPending",
PAYMENT_REFUNDED = "paymentRefunded",
PAYMENT_REFUND_PENDING = "paymentRefundPending",
PAYMENT_SUCCEED = "paymentSucceed",
PAYMENT_WAITING_CONFIRMATION = "paymentWaitingConfirmation",
SYNCED = "synced"
}
export type LiquidSdkEvent = {
type: LiquidSdkEventVariant.PAYMENT_FAILED,
details: Payment
} | {
type: LiquidSdkEventVariant.PAYMENT_PENDING,
details: Payment
} | {
type: LiquidSdkEventVariant.PAYMENT_REFUNDED,
details: Payment
} | {
type: LiquidSdkEventVariant.PAYMENT_REFUND_PENDING,
details: Payment
} | {
type: LiquidSdkEventVariant.PAYMENT_SUCCEED,
details: Payment
} | {
type: LiquidSdkEventVariant.PAYMENT_WAITING_CONFIRMATION,
details: Payment
} | {
type: LiquidSdkEventVariant.SYNCED
}
export enum Network {
LIQUID = "liquid",
LIQUID_TESTNET = "liquidTestnet"
@@ -93,11 +127,24 @@ export enum PaymentType {
SEND = "send"
}
export type EventListener = (e: LiquidSdkEvent) => void
export const connect = async (req: ConnectRequest): Promise<void> => {
const response = await BreezLiquidSDK.connect(req)
return response
}
export const addEventListener = async (listener: EventListener): Promise<string> => {
const response = await BreezLiquidSDK.addEventListener()
BreezLiquidSDKEmitter.addListener(`event-${response}`, listener)
return response
}
export const removeEventListener = async (id: string): Promise<void> => {
await BreezLiquidSDK.removeEventListener(id)
}
export const getInfo = async (req: GetInfoRequest): Promise<GetInfoResponse> => {
const response = await BreezLiquidSDK.getInfo(req)
return response