mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2025-12-17 22:14:24 +01:00
Add Config (#267)
* Add config * Add rustdocs to Config, send_payment (#271) --------- Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com>
This commit is contained in:
@@ -74,10 +74,11 @@ async fn main() -> Result<()> {
|
||||
|
||||
let mnemonic = persistence.get_or_create_mnemonic()?;
|
||||
let network = args.network.unwrap_or(Network::Testnet);
|
||||
let mut config = LiquidSdk::default_config(network);
|
||||
config.working_dir = data_dir_str;
|
||||
let sdk = LiquidSdk::connect(ConnectRequest {
|
||||
mnemonic: mnemonic.to_string(),
|
||||
data_dir: Some(data_dir_str),
|
||||
network,
|
||||
config,
|
||||
})
|
||||
.await?;
|
||||
let listener_id = sdk
|
||||
|
||||
@@ -55,10 +55,17 @@ typedef struct wire_cst_prepare_send_response {
|
||||
uint64_t fees_sat;
|
||||
} wire_cst_prepare_send_response;
|
||||
|
||||
typedef struct wire_cst_config {
|
||||
struct wire_cst_list_prim_u_8_strict *boltz_url;
|
||||
struct wire_cst_list_prim_u_8_strict *electrum_url;
|
||||
struct wire_cst_list_prim_u_8_strict *working_dir;
|
||||
int32_t network;
|
||||
uint64_t payment_timeout_sec;
|
||||
} wire_cst_config;
|
||||
|
||||
typedef struct wire_cst_connect_request {
|
||||
struct wire_cst_list_prim_u_8_strict *mnemonic;
|
||||
struct wire_cst_list_prim_u_8_strict *data_dir;
|
||||
int32_t network;
|
||||
struct wire_cst_config config;
|
||||
} wire_cst_connect_request;
|
||||
|
||||
typedef struct wire_cst_payment {
|
||||
@@ -271,6 +278,8 @@ void frbgen_breez_liquid_wire__crate__bindings__breez_log_stream(int64_t port_,
|
||||
void frbgen_breez_liquid_wire__crate__bindings__connect(int64_t port_,
|
||||
struct wire_cst_connect_request *req);
|
||||
|
||||
void frbgen_breez_liquid_wire__crate__bindings__default_config(int64_t port_, int32_t network);
|
||||
|
||||
void frbgen_breez_liquid_wire__crate__bindings__parse_invoice(int64_t port_,
|
||||
struct wire_cst_list_prim_u_8_strict *input);
|
||||
|
||||
@@ -337,6 +346,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_sync);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__breez_log_stream);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__connect);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__default_config);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__parse_invoice);
|
||||
dummy_var ^= ((int64_t) (void*) store_dart_post_cobject);
|
||||
return dummy_var;
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
{%- match func.return_type() -%}
|
||||
{%- when Some with (return_type) %}
|
||||
val res = {{ obj_interface }}{{ func.name()|fn_name|unquote }}({%- call kt::arg_list(func) -%})
|
||||
{%- if func.name() == "default_config" %}
|
||||
val workingDir = File(reactApplicationContext.filesDir.toString() + "/breezLiquidSdk")
|
||||
|
||||
res.workingDir = workingDir.absolutePath
|
||||
{%- endif -%}
|
||||
{%- match return_type %}
|
||||
{%- when Type::Optional(inner) %}
|
||||
{%- let unboxed = inner.as_ref() %}
|
||||
|
||||
@@ -36,6 +36,19 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext
|
||||
throw LiquidSdkException.Generic("Not initialized")
|
||||
}
|
||||
|
||||
@Throws(LiquidSdkException::class)
|
||||
private fun ensureWorkingDir(workingDir: String) {
|
||||
try {
|
||||
val workingDirFile = File(workingDir)
|
||||
|
||||
if (!workingDirFile.exists() && !workingDirFile.mkdirs()) {
|
||||
throw LiquidSdkException.Generic("Mandatory field workingDir must contain a writable directory")
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
throw LiquidSdkException.Generic("Mandatory field workingDir must contain a writable directory")
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun addListener(eventName: String) {}
|
||||
|
||||
@@ -73,7 +86,9 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext
|
||||
executor.execute {
|
||||
try {
|
||||
var connectRequest = asConnectRequest(req) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "ConnectRequest")) }
|
||||
connectRequest.dataDir = connectRequest.dataDir?.takeUnless { it.isEmpty() } ?: run { reactApplicationContext.filesDir.toString() + "/breezLiquidSdk" }
|
||||
|
||||
ensureWorkingDir(connectRequest.config.workingDir)
|
||||
|
||||
bindingLiquidSdk = connect(connectRequest)
|
||||
promise.resolve(readableMapOf("status" to "ok"))
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
{%- match func.return_type() -%}
|
||||
{%- when Some with (return_type) %}
|
||||
var res = {%- call swift::throws_decl(func) -%}{{ obj_interface }}{{ func.name()|fn_name|unquote }}({%- call swift::arg_list(func) -%})
|
||||
{%- if func.name() == "default_config" %}
|
||||
res.workingDir = RNBreezLiquidSDK.breezLiquidSdkDirectory.path
|
||||
{%- endif -%}
|
||||
{%- match return_type %}
|
||||
{%- when Type::Optional(inner) %}
|
||||
{%- let unboxed = inner.as_ref() %}
|
||||
|
||||
@@ -11,10 +11,16 @@ class RNBreezLiquidSDK: RCTEventEmitter {
|
||||
|
||||
private var bindingLiquidSdk: BindingLiquidSdk!
|
||||
|
||||
static var defaultDataDir: URL {
|
||||
let applicationDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
|
||||
return applicationDirectory.appendingPathComponent("breezLiquidSdk", isDirectory: true)
|
||||
static var breezLiquidSdkDirectory: URL {
|
||||
let applicationDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
let breezLiquidSdkDirectory = applicationDirectory.appendingPathComponent("breezLiquidSdk", isDirectory: true)
|
||||
|
||||
if !FileManager.default.fileExists(atPath: breezLiquidSdkDirectory.path) {
|
||||
try! FileManager.default.createDirectory(atPath: breezLiquidSdkDirectory.path, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
return breezLiquidSdkDirectory
|
||||
}
|
||||
|
||||
override init() {
|
||||
@@ -56,6 +62,16 @@ class RNBreezLiquidSDK: RCTEventEmitter {
|
||||
throw LiquidSdkError.Generic(message: "Not initialized")
|
||||
}
|
||||
|
||||
private func ensureWorkingDir(workingDir: String) throws {
|
||||
do {
|
||||
if !FileManager.default.fileExists(atPath: workingDir) {
|
||||
try FileManager.default.createDirectory(atPath: workingDir, withIntermediateDirectories: true)
|
||||
}
|
||||
} catch {
|
||||
throw LiquidSdkError.Generic(message: "Mandatory field workingDir must contain a writable directory")
|
||||
}
|
||||
}
|
||||
|
||||
{% let obj_interface = "BreezLiquidSDK." -%}
|
||||
{% for func in ci.function_definitions() %}
|
||||
{%- if func.name()|ignored_function == false -%}
|
||||
@@ -81,7 +97,8 @@ class RNBreezLiquidSDK: RCTEventEmitter {
|
||||
|
||||
do {
|
||||
var connectRequest = try BreezLiquidSDKMapper.asConnectRequest(connectRequest: req)
|
||||
connectRequest.dataDir = connectRequest.dataDir == nil || connectRequest.dataDir!.isEmpty ? RNBreezLiquidSDK.defaultDataDir.path : connectRequest.dataDir
|
||||
try ensureWorkingDir(workingDir: connectRequest.config.workingDir)
|
||||
|
||||
bindingLiquidSdk = try BreezLiquidSDK.connect(req: connectRequest)
|
||||
resolve(["status": "ok"])
|
||||
} catch let err {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NativeModules, Platform, NativeEventEmitter } from "react-native"
|
||||
import { NativeModules, Platform, EmitterSubscription, NativeEventEmitter } from "react-native"
|
||||
|
||||
const LINKING_ERROR =
|
||||
`The package 'react-native-breez-liquid-sdk' doesn't seem to be linked. Make sure: \n\n` +
|
||||
@@ -20,7 +20,7 @@ const BreezLiquidSDK = NativeModules.RNBreezLiquidSDK
|
||||
const BreezLiquidSDKEmitter = new NativeEventEmitter(BreezLiquidSDK)
|
||||
{%- import "macros.ts" as ts %}
|
||||
{%- include "Types.ts" %}
|
||||
{% include "Helpers.ts" -%}
|
||||
{% include "Helpers.ts" %}
|
||||
{% for func in ci.function_definitions() %}
|
||||
{%- if func.name()|ignored_function == false -%}
|
||||
{%- include "TopLevelFunctionTemplate.ts" %}
|
||||
|
||||
@@ -23,15 +23,22 @@ enum PaymentError {
|
||||
"SignerError",
|
||||
};
|
||||
|
||||
dictionary Config {
|
||||
string boltz_url;
|
||||
string electrum_url;
|
||||
string working_dir;
|
||||
Network network;
|
||||
u64 payment_timeout_sec;
|
||||
};
|
||||
|
||||
enum Network {
|
||||
"Mainnet",
|
||||
"Testnet",
|
||||
};
|
||||
|
||||
dictionary ConnectRequest {
|
||||
Config config;
|
||||
string mnemonic;
|
||||
Network network;
|
||||
string? data_dir = null;
|
||||
};
|
||||
|
||||
dictionary GetInfoRequest {
|
||||
@@ -166,6 +173,8 @@ namespace breez_liquid_sdk {
|
||||
[Throws=LiquidSdkError]
|
||||
void set_logger(Logger logger);
|
||||
|
||||
Config default_config(Network network);
|
||||
|
||||
[Throws=PaymentError]
|
||||
LNInvoice parse_invoice(string invoice);
|
||||
};
|
||||
|
||||
@@ -58,6 +58,10 @@ pub fn connect(req: ConnectRequest) -> Result<Arc<BindingLiquidSdk>, LiquidSdkEr
|
||||
})
|
||||
}
|
||||
|
||||
pub fn default_config(network: Network) -> Config {
|
||||
LiquidSdk::default_config(network)
|
||||
}
|
||||
|
||||
pub fn parse_invoice(input: String) -> Result<LNInvoice, PaymentError> {
|
||||
LiquidSdk::parse_invoice(&input)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ class SDKListener: breez_liquid_sdk.EventListener {
|
||||
|
||||
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 config = breez_liquid_sdk.defaultConfig(breez_liquid_sdk.Network.TESTNET)
|
||||
var connectRequest = breez_liquid_sdk.ConnectRequest(config, mnemonic)
|
||||
var sdk = breez_liquid_sdk.connect(connectRequest)
|
||||
|
||||
var listenerId = sdk.addEventListener(SDKListener())
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ class SDKListener(breez_liquid_sdk.EventListener):
|
||||
|
||||
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)
|
||||
config = breez_liquid_sdk.default_config(breez_liquid_sdk.Network.TESTNET)
|
||||
connect_request = breez_liquid_sdk.ConnectRequest(config=config, mnemonic=mnemonic)
|
||||
sdk = breez_liquid_sdk.connect(connect_request)
|
||||
|
||||
listener_id = sdk.add_event_listener(SDKListener())
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ class SDKListener: EventListener {
|
||||
}
|
||||
|
||||
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 config = breez_liquid_sdk.defaultConfig(network: .testnet);
|
||||
let connectRequest = breez_liquid_sdk.ConnectRequest(config: config, mnemonic: mnemonic);
|
||||
let sdk = try breez_liquid_sdk.connect(req: connectRequest);
|
||||
|
||||
let listenerId = try sdk.addEventListener(listener: SDKListener());
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ pub fn breez_log_stream(s: StreamSink<LogEntry>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn default_config(network: Network) -> Config {
|
||||
LiquidSdk::default_config(network)
|
||||
}
|
||||
|
||||
pub fn parse_invoice(input: String) -> Result<LNInvoice, PaymentError> {
|
||||
LiquidSdk::parse_invoice(&input)
|
||||
}
|
||||
|
||||
@@ -157,13 +157,24 @@ impl CstDecode<u64> for *mut u64 {
|
||||
unsafe { *flutter_rust_bridge::for_generated::box_from_leak_ptr(self) }
|
||||
}
|
||||
}
|
||||
impl CstDecode<crate::model::Config> for wire_cst_config {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::Config {
|
||||
crate::model::Config {
|
||||
boltz_url: self.boltz_url.cst_decode(),
|
||||
electrum_url: self.electrum_url.cst_decode(),
|
||||
working_dir: self.working_dir.cst_decode(),
|
||||
network: self.network.cst_decode(),
|
||||
payment_timeout_sec: self.payment_timeout_sec.cst_decode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CstDecode<crate::model::ConnectRequest> for wire_cst_connect_request {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::ConnectRequest {
|
||||
crate::model::ConnectRequest {
|
||||
mnemonic: self.mnemonic.cst_decode(),
|
||||
data_dir: self.data_dir.cst_decode(),
|
||||
network: self.network.cst_decode(),
|
||||
config: self.config.cst_decode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,12 +483,27 @@ impl Default for wire_cst_backup_request {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
impl NewWithNullPtr for wire_cst_config {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
boltz_url: core::ptr::null_mut(),
|
||||
electrum_url: core::ptr::null_mut(),
|
||||
working_dir: core::ptr::null_mut(),
|
||||
network: Default::default(),
|
||||
payment_timeout_sec: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for wire_cst_config {
|
||||
fn default() -> Self {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
impl NewWithNullPtr for wire_cst_connect_request {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
mnemonic: core::ptr::null_mut(),
|
||||
data_dir: core::ptr::null_mut(),
|
||||
network: Default::default(),
|
||||
config: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -844,6 +870,14 @@ pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__connect(
|
||||
wire__crate__bindings__connect_impl(port_, req)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__default_config(
|
||||
port_: i64,
|
||||
network: i32,
|
||||
) {
|
||||
wire__crate__bindings__default_config_impl(port_, network)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__parse_invoice(
|
||||
port_: i64,
|
||||
@@ -1002,10 +1036,18 @@ pub struct wire_cst_backup_request {
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_config {
|
||||
boltz_url: *mut wire_cst_list_prim_u_8_strict,
|
||||
electrum_url: *mut wire_cst_list_prim_u_8_strict,
|
||||
working_dir: *mut wire_cst_list_prim_u_8_strict,
|
||||
network: i32,
|
||||
payment_timeout_sec: u64,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_connect_request {
|
||||
mnemonic: *mut wire_cst_list_prim_u_8_strict,
|
||||
data_dir: *mut wire_cst_list_prim_u_8_strict,
|
||||
network: i32,
|
||||
config: wire_cst_config,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -34,7 +34,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.36";
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -532134055;
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1028270774;
|
||||
|
||||
// Section: executor
|
||||
|
||||
@@ -390,6 +390,26 @@ fn wire__crate__bindings__connect_impl(
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__bindings__default_config_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
network: impl CstDecode<crate::model::Network>,
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "default_config",
|
||||
port: Some(port_),
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||
},
|
||||
move || {
|
||||
let api_network = network.cst_decode();
|
||||
move |context| {
|
||||
transform_result_dco((move || {
|
||||
Result::<_, ()>::Ok(crate::bindings::default_config(api_network))
|
||||
})())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__bindings__parse_invoice_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
input: impl CstDecode<String>,
|
||||
@@ -553,16 +573,32 @@ impl SseDecode for bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::model::Config {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut var_boltzUrl = <String>::sse_decode(deserializer);
|
||||
let mut var_electrumUrl = <String>::sse_decode(deserializer);
|
||||
let mut var_workingDir = <String>::sse_decode(deserializer);
|
||||
let mut var_network = <crate::model::Network>::sse_decode(deserializer);
|
||||
let mut var_paymentTimeoutSec = <u64>::sse_decode(deserializer);
|
||||
return crate::model::Config {
|
||||
boltz_url: var_boltzUrl,
|
||||
electrum_url: var_electrumUrl,
|
||||
working_dir: var_workingDir,
|
||||
network: var_network,
|
||||
payment_timeout_sec: var_paymentTimeoutSec,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::model::ConnectRequest {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut var_mnemonic = <String>::sse_decode(deserializer);
|
||||
let mut var_dataDir = <Option<String>>::sse_decode(deserializer);
|
||||
let mut var_network = <crate::model::Network>::sse_decode(deserializer);
|
||||
let mut var_config = <crate::model::Config>::sse_decode(deserializer);
|
||||
return crate::model::ConnectRequest {
|
||||
mnemonic: var_mnemonic,
|
||||
data_dir: var_dataDir,
|
||||
network: var_network,
|
||||
config: var_config,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1113,12 +1149,30 @@ impl flutter_rust_bridge::IntoIntoDart<crate::model::BackupRequest>
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::model::Config {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.boltz_url.into_into_dart().into_dart(),
|
||||
self.electrum_url.into_into_dart().into_dart(),
|
||||
self.working_dir.into_into_dart().into_dart(),
|
||||
self.network.into_into_dart().into_dart(),
|
||||
self.payment_timeout_sec.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::model::Config {}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::model::Config> for crate::model::Config {
|
||||
fn into_into_dart(self) -> crate::model::Config {
|
||||
self
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::model::ConnectRequest {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.mnemonic.into_into_dart().into_dart(),
|
||||
self.data_dir.into_into_dart().into_dart(),
|
||||
self.network.into_into_dart().into_dart(),
|
||||
self.config.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
@@ -1603,12 +1657,22 @@ impl SseEncode for bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::model::Config {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<String>::sse_encode(self.boltz_url, serializer);
|
||||
<String>::sse_encode(self.electrum_url, serializer);
|
||||
<String>::sse_encode(self.working_dir, serializer);
|
||||
<crate::model::Network>::sse_encode(self.network, serializer);
|
||||
<u64>::sse_encode(self.payment_timeout_sec, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::model::ConnectRequest {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<String>::sse_encode(self.mnemonic, serializer);
|
||||
<Option<String>>::sse_encode(self.data_dir, serializer);
|
||||
<crate::model::Network>::sse_encode(self.network, serializer);
|
||||
<crate::model::Config>::sse_encode(self.config, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use boltz_client::network::electrum::ElectrumConfig;
|
||||
use boltz_client::network::Chain;
|
||||
use boltz_client::swaps::boltzv2::{
|
||||
CreateReverseResponse, CreateSubmarineResponse, Leaf, SwapTree,
|
||||
CreateReverseResponse, CreateSubmarineResponse, Leaf, SwapTree, BOLTZ_MAINNET_URL_V2,
|
||||
BOLTZ_TESTNET_URL_V2,
|
||||
};
|
||||
use boltz_client::{Keypair, LBtcSwapScriptV2, ToHex};
|
||||
use lwk_signer::SwSigner;
|
||||
use lwk_wollet::{ElectrumUrl, ElementsNetwork, WolletDescriptor};
|
||||
use lwk_wollet::{ElectrumClient, ElectrumUrl, ElementsNetwork, WolletDescriptor};
|
||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
|
||||
use rusqlite::ToSql;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -13,6 +15,49 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::error::PaymentError;
|
||||
use crate::utils;
|
||||
|
||||
/// Configuration for the Liquid SDK
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Config {
|
||||
pub boltz_url: String,
|
||||
pub electrum_url: String,
|
||||
/// Directory in which all SDK files (DB, log, cache) are stored.
|
||||
///
|
||||
/// Prefix can be a relative or absolute path to this directory.
|
||||
pub working_dir: String,
|
||||
pub network: Network,
|
||||
/// Send payment timeout. See [crate::sdk::LiquidSdk::send_payment]
|
||||
pub payment_timeout_sec: u64,
|
||||
}
|
||||
impl Config {
|
||||
pub(crate) fn get_electrum_client(&self) -> Result<ElectrumClient, lwk_wollet::Error> {
|
||||
ElectrumClient::new(&ElectrumUrl::new(&self.electrum_url, true, true))
|
||||
}
|
||||
|
||||
pub(crate) fn get_electrum_config(&self) -> ElectrumConfig {
|
||||
ElectrumConfig::new(self.network.into(), &self.electrum_url, true, true, 100)
|
||||
}
|
||||
|
||||
pub fn mainnet() -> Self {
|
||||
Config {
|
||||
boltz_url: BOLTZ_MAINNET_URL_V2.to_owned(),
|
||||
electrum_url: "blockstream.info:995".to_string(),
|
||||
working_dir: ".".to_string(),
|
||||
network: Network::Mainnet,
|
||||
payment_timeout_sec: 15,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn testnet() -> Self {
|
||||
Config {
|
||||
boltz_url: BOLTZ_TESTNET_URL_V2.to_owned(),
|
||||
electrum_url: "blockstream.info:465".to_string(),
|
||||
working_dir: ".".to_string(),
|
||||
network: Network::Testnet,
|
||||
payment_timeout_sec: 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize)]
|
||||
pub enum Network {
|
||||
/// Mainnet Bitcoin and Liquid chains
|
||||
@@ -84,39 +129,18 @@ pub enum LiquidSdkEvent {
|
||||
}
|
||||
|
||||
pub struct LiquidSdkOptions {
|
||||
pub config: Config,
|
||||
pub signer: SwSigner,
|
||||
pub network: Network,
|
||||
/// Output script descriptor
|
||||
///
|
||||
/// See <https://github.com/bitcoin/bips/pull/1143>
|
||||
pub descriptor: WolletDescriptor,
|
||||
/// Absolute or relative path to the data dir, including the dir name.
|
||||
///
|
||||
/// If not set, it defaults to [crate::DEFAULT_DATA_DIR].
|
||||
pub data_dir_path: Option<String>,
|
||||
/// Custom Electrum URL. If set, it must match the specified network.
|
||||
///
|
||||
/// 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({
|
||||
let (url, validate_domain, tls) = match &self.network {
|
||||
Network::Mainnet => ("blockstream.info:995", true, true),
|
||||
Network::Testnet => ("blockstream.info:465", true, true),
|
||||
};
|
||||
ElectrumUrl::new(url, tls, validate_domain)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ConnectRequest {
|
||||
pub mnemonic: String,
|
||||
pub data_dir: Option<String>,
|
||||
pub network: Network,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
||||
@@ -13,7 +13,6 @@ use boltz_client::network::Chain;
|
||||
use boltz_client::swaps::boltzv2;
|
||||
use boltz_client::ToHex;
|
||||
use boltz_client::{
|
||||
network::electrum::ElectrumConfig,
|
||||
swaps::{
|
||||
boltz::{RevSwapStates, SubSwapStates},
|
||||
boltzv2::*,
|
||||
@@ -30,8 +29,7 @@ use lwk_wollet::elements::LockTime;
|
||||
use lwk_wollet::hashes::{sha256, Hash};
|
||||
use lwk_wollet::{
|
||||
elements::{Address, Transaction},
|
||||
BlockchainBackend, ElectrumClient, ElectrumUrl, ElementsNetwork, FsPersister,
|
||||
Wollet as LwkWollet, WolletDescriptor,
|
||||
BlockchainBackend, ElementsNetwork, FsPersister, Wollet as LwkWollet, WolletDescriptor,
|
||||
};
|
||||
use tokio::sync::{watch, Mutex, RwLock};
|
||||
use tokio::time::MissedTickBehavior;
|
||||
@@ -56,14 +54,12 @@ pub const LIQUID_CLAIM_TX_FEERATE_MSAT: f32 = 100.0;
|
||||
pub const DEFAULT_DATA_DIR: &str = ".data";
|
||||
|
||||
pub struct LiquidSdk {
|
||||
electrum_url: ElectrumUrl,
|
||||
network: Network,
|
||||
config: Config,
|
||||
/// LWK Wollet, a watch-only Liquid wallet for this instance
|
||||
lwk_wollet: Arc<Mutex<LwkWollet>>,
|
||||
/// LWK Signer, for signing Liquid transactions
|
||||
lwk_signer: SwSigner,
|
||||
persister: Arc<Persister>,
|
||||
data_dir_path: String,
|
||||
event_manager: Arc<EventManager>,
|
||||
status_stream: Arc<BoltzStatusStream>,
|
||||
is_started: RwLock<bool>,
|
||||
@@ -73,16 +69,15 @@ pub struct LiquidSdk {
|
||||
|
||||
impl LiquidSdk {
|
||||
pub async fn connect(req: ConnectRequest) -> Result<Arc<LiquidSdk>> {
|
||||
let is_mainnet = req.network == Network::Mainnet;
|
||||
let config = req.config;
|
||||
let is_mainnet = config.network == Network::Mainnet;
|
||||
let signer = SwSigner::new(&req.mnemonic, is_mainnet)?;
|
||||
let descriptor = LiquidSdk::get_descriptor(&signer, req.network)?;
|
||||
let descriptor = LiquidSdk::get_descriptor(&signer, config.network)?;
|
||||
|
||||
let sdk = LiquidSdk::new(LiquidSdkOptions {
|
||||
signer,
|
||||
descriptor,
|
||||
electrum_url: None,
|
||||
data_dir_path: req.data_dir,
|
||||
network: req.network,
|
||||
config,
|
||||
})?;
|
||||
sdk.start().await?;
|
||||
|
||||
@@ -90,33 +85,30 @@ impl LiquidSdk {
|
||||
}
|
||||
|
||||
fn new(opts: LiquidSdkOptions) -> Result<Arc<Self>> {
|
||||
let network = opts.network;
|
||||
let elements_network: ElementsNetwork = opts.network.into();
|
||||
let electrum_url = opts.get_electrum_url();
|
||||
let data_dir_path = opts.data_dir_path.unwrap_or(DEFAULT_DATA_DIR.to_string());
|
||||
let config = opts.config;
|
||||
let elements_network: ElementsNetwork = config.network.into();
|
||||
|
||||
let lwk_persister = FsPersister::new(&data_dir_path, network.into(), &opts.descriptor)?;
|
||||
fs::create_dir_all(&config.working_dir)?;
|
||||
|
||||
let lwk_persister = FsPersister::new(
|
||||
config.working_dir.clone(),
|
||||
elements_network,
|
||||
&opts.descriptor,
|
||||
)?;
|
||||
let lwk_wollet = LwkWollet::new(elements_network, lwk_persister, opts.descriptor)?;
|
||||
|
||||
fs::create_dir_all(&data_dir_path)?;
|
||||
|
||||
let persister = Arc::new(Persister::new(&data_dir_path, network)?);
|
||||
let persister = Arc::new(Persister::new(&config.working_dir, config.network)?);
|
||||
persister.init()?;
|
||||
|
||||
let event_manager = Arc::new(EventManager::new());
|
||||
let status_stream = Arc::new(BoltzStatusStream::new(
|
||||
Self::boltz_url_v2(network),
|
||||
persister.clone(),
|
||||
));
|
||||
let status_stream = Arc::new(BoltzStatusStream::new(&config.boltz_url, persister.clone()));
|
||||
let (shutdown_sender, shutdown_receiver) = watch::channel::<()>(());
|
||||
|
||||
let sdk = Arc::new(LiquidSdk {
|
||||
config,
|
||||
lwk_wollet: Arc::new(Mutex::new(lwk_wollet)),
|
||||
network,
|
||||
electrum_url,
|
||||
lwk_signer: opts.signer,
|
||||
persister,
|
||||
data_dir_path,
|
||||
event_manager,
|
||||
status_stream,
|
||||
is_started: RwLock::new(false),
|
||||
@@ -729,24 +721,7 @@ impl LiquidSdk {
|
||||
}
|
||||
|
||||
pub(crate) fn boltz_client_v2(&self) -> BoltzApiClientV2 {
|
||||
BoltzApiClientV2::new(Self::boltz_url_v2(self.network))
|
||||
}
|
||||
|
||||
pub(crate) fn boltz_url_v2(network: Network) -> &'static str {
|
||||
match network {
|
||||
Network::Testnet => BOLTZ_TESTNET_URL_V2,
|
||||
Network::Mainnet => BOLTZ_MAINNET_URL_V2,
|
||||
}
|
||||
}
|
||||
|
||||
fn network_config(&self) -> ElectrumConfig {
|
||||
ElectrumConfig::new(
|
||||
self.network.into(),
|
||||
&self.electrum_url.to_string(),
|
||||
true,
|
||||
true,
|
||||
100,
|
||||
)
|
||||
BoltzApiClientV2::new(&self.config.boltz_url)
|
||||
}
|
||||
|
||||
async fn build_tx(
|
||||
@@ -756,7 +731,7 @@ impl LiquidSdk {
|
||||
amount_sat: u64,
|
||||
) -> Result<Transaction, PaymentError> {
|
||||
let lwk_wollet = self.lwk_wollet.lock().await;
|
||||
let mut pset = lwk_wollet::TxBuilder::new(self.network.into())
|
||||
let mut pset = lwk_wollet::TxBuilder::new(self.config.network.into())
|
||||
.add_lbtc_recipient(
|
||||
&ElementsAddress::from_str(recipient_address).map_err(|e| {
|
||||
PaymentError::Generic {
|
||||
@@ -780,7 +755,7 @@ impl LiquidSdk {
|
||||
.parse::<Bolt11Invoice>()
|
||||
.map_err(|_| PaymentError::InvalidInvoice)?;
|
||||
|
||||
match (invoice.network().to_string().as_str(), self.network) {
|
||||
match (invoice.network().to_string().as_str(), self.config.network) {
|
||||
("bitcoin", Network::Mainnet) => {}
|
||||
("testnet", Network::Testnet) => {}
|
||||
_ => return Err(PaymentError::InvalidInvoice),
|
||||
@@ -815,7 +790,7 @@ impl LiquidSdk {
|
||||
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 {
|
||||
let temp_p2tr_addr = match self.config.network {
|
||||
Network::Mainnet => "lq1pqvzxvqhrf54dd4sny4cag7497pe38252qefk46t92frs7us8r80ja9ha8r5me09nn22m4tmdqp5p4wafq3s59cql3v9n45t5trwtxrmxfsyxjnstkctj",
|
||||
Network::Testnet => "tlq1pq0wqu32e2xacxeyps22x8gjre4qk3u6r70pj4r62hzczxeyz8x3yxucrpn79zy28plc4x37aaf33kwt6dz2nn6gtkya6h02mwpzy4eh69zzexq7cf5y5"
|
||||
};
|
||||
@@ -871,12 +846,12 @@ impl LiquidSdk {
|
||||
swap_script: &LBtcSwapScriptV2,
|
||||
) -> Result<LBtcSwapTxV2, PaymentError> {
|
||||
let output_address = self.next_unused_address().await?.to_string();
|
||||
let network_config = self.network_config();
|
||||
let network_config = self.config.get_electrum_config();
|
||||
Ok(LBtcSwapTxV2::new_refund(
|
||||
swap_script.clone(),
|
||||
&output_address,
|
||||
&network_config,
|
||||
Self::boltz_url_v2(self.network).to_string(),
|
||||
self.config.clone().boltz_url,
|
||||
swap_id.to_string(),
|
||||
)?)
|
||||
}
|
||||
@@ -895,7 +870,8 @@ impl LiquidSdk {
|
||||
Some((&self.boltz_client_v2(), &swap.id)),
|
||||
)?;
|
||||
|
||||
let refund_tx_id = refund_tx.broadcast(&tx, &self.network_config(), is_lowball)?;
|
||||
let refund_tx_id =
|
||||
refund_tx.broadcast(&tx, &self.config.get_electrum_config(), is_lowball)?;
|
||||
info!(
|
||||
"Successfully broadcast cooperative refund for Send Swap {}",
|
||||
&swap.id
|
||||
@@ -925,9 +901,9 @@ impl LiquidSdk {
|
||||
info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", swap_script.locktime);
|
||||
match utils::is_locktime_expired(locktime_from_height, swap_script.locktime) {
|
||||
true => {
|
||||
let tx =
|
||||
refund_tx.sign_refund(&swap.get_refund_keypair()?, broadcast_fees_sat, None)?;
|
||||
let refund_tx_id = refund_tx.broadcast(&tx, &self.network_config(), is_lowball)?;
|
||||
let tx = refund_tx.sign_refund(&swap.get_refund_keypair()?, broadcast_fees_sat, None)?;
|
||||
let refund_tx_id =
|
||||
refund_tx.broadcast(&tx, &self.config.get_electrum_config(), is_lowball)?;
|
||||
info!(
|
||||
"Successfully broadcast non-cooperative refund for Send Swap {}",
|
||||
swap.id
|
||||
@@ -950,7 +926,7 @@ impl LiquidSdk {
|
||||
let broadcast_fees_sat =
|
||||
Amount::from_sat(self.get_broadcast_fee_estimation(amount_sat).await?);
|
||||
let client = self.boltz_client_v2();
|
||||
let is_lowball = match self.network {
|
||||
let is_lowball = match self.config.network {
|
||||
Network::Mainnet => None,
|
||||
Network::Testnet => Some((&client, boltz_client::network::Chain::LiquidTestnet)),
|
||||
};
|
||||
@@ -1031,7 +1007,7 @@ impl LiquidSdk {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let electrum_client = ElectrumClient::new(&self.electrum_url)?;
|
||||
let electrum_client = self.config.get_electrum_client()?;
|
||||
let lockup_tx_id = electrum_client.broadcast(&lockup_tx)?.to_string();
|
||||
|
||||
debug!(
|
||||
@@ -1040,6 +1016,13 @@ impl LiquidSdk {
|
||||
Ok(lockup_tx_id)
|
||||
}
|
||||
|
||||
/// Creates, initiates and starts monitoring the progress of a Send Payment.
|
||||
///
|
||||
/// Depending on [Config]'s `payment_timeout_sec`, this function will return:
|
||||
/// - a [PaymentError::PaymentTimeout], if the payment could not be initiated in this time
|
||||
/// - a [PaymentState::Pending] payment, if the payment could be initiated, but didn't yet
|
||||
/// complete in this time
|
||||
/// - a [PaymentState::Complete] payment, if the payment was successfully completed in this time
|
||||
pub async fn send_payment(
|
||||
&self,
|
||||
req: &PrepareSendResponse,
|
||||
@@ -1110,7 +1093,7 @@ impl LiquidSdk {
|
||||
swap_id: String,
|
||||
accept_zero_conf: bool,
|
||||
) -> Result<Payment, PaymentError> {
|
||||
let timeout_fut = tokio::time::sleep(Duration::from_secs(15));
|
||||
let timeout_fut = tokio::time::sleep(Duration::from_secs(self.config.payment_timeout_sec));
|
||||
tokio::pin!(timeout_fut);
|
||||
|
||||
let mut events_stream = self.event_manager.subscribe();
|
||||
@@ -1178,8 +1161,8 @@ impl LiquidSdk {
|
||||
let claim_tx_wrapper = LBtcSwapTxV2::new_claim(
|
||||
swap_script,
|
||||
claim_address,
|
||||
&self.network_config(),
|
||||
Self::boltz_url_v2(self.network).into(),
|
||||
&self.config.get_electrum_config(),
|
||||
self.config.clone().boltz_url,
|
||||
ongoing_receive_swap.id.clone(),
|
||||
)?;
|
||||
|
||||
@@ -1194,8 +1177,8 @@ impl LiquidSdk {
|
||||
|
||||
let claim_tx_id = claim_tx_wrapper.broadcast(
|
||||
&claim_tx,
|
||||
&self.network_config(),
|
||||
Some((&self.boltz_client_v2(), self.network.into())),
|
||||
&self.config.get_electrum_config(),
|
||||
Some((&self.boltz_client_v2(), self.config.network.into())),
|
||||
)?;
|
||||
info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {swap_id}");
|
||||
debug!("Claim Tx {:?}", claim_tx);
|
||||
@@ -1329,7 +1312,7 @@ impl LiquidSdk {
|
||||
/// it inserts or updates a corresponding entry in our Payments table.
|
||||
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 electrum_client = self.config.get_electrum_client()?;
|
||||
let mut lwk_wollet = self.lwk_wollet.lock().await;
|
||||
lwk_wollet::full_scan_with_electrum_client(&mut lwk_wollet, &mut electrum_client)?;
|
||||
}
|
||||
@@ -1378,9 +1361,11 @@ impl LiquidSdk {
|
||||
info!("Retrieving preimage from non-cooperative claim tx");
|
||||
|
||||
let id = &swap.id;
|
||||
let electrum_client = ElectrumClient::new(&self.electrum_url)?;
|
||||
let electrum_client = self.config.get_electrum_client()?;
|
||||
let swap_script = swap.get_swap_script()?;
|
||||
let swap_script_pk = swap_script.to_address(self.network.into())?.script_pubkey();
|
||||
let swap_script_pk = swap_script
|
||||
.to_address(self.config.network.into())?
|
||||
.script_pubkey();
|
||||
debug!("Found Send Swap swap_script_pk: {swap_script_pk:?}");
|
||||
|
||||
// Get tx history of the swap script (lockup address)
|
||||
@@ -1445,8 +1430,8 @@ impl LiquidSdk {
|
||||
|
||||
/// Empties all Liquid Wallet caches for this network type.
|
||||
pub fn empty_wallet_cache(&self) -> Result<()> {
|
||||
let mut path = PathBuf::from(self.data_dir_path.clone());
|
||||
path.push(Into::<ElementsNetwork>::into(self.network).as_str());
|
||||
let mut path = PathBuf::from(self.config.working_dir.clone());
|
||||
path.push(Into::<ElementsNetwork>::into(self.config.network).as_str());
|
||||
path.push("enc_cache");
|
||||
|
||||
fs::remove_dir_all(&path)?;
|
||||
@@ -1484,6 +1469,13 @@ impl LiquidSdk {
|
||||
self.persister.restore_from_backup(backup_path)
|
||||
}
|
||||
|
||||
pub fn default_config(network: Network) -> Config {
|
||||
match network {
|
||||
Network::Mainnet => Config::mainnet(),
|
||||
Network::Testnet => Config::testnet(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_invoice(input: &str) -> Result<LNInvoice, PaymentError> {
|
||||
let input = input
|
||||
.strip_prefix("lightning:")
|
||||
@@ -1566,7 +1558,7 @@ mod tests {
|
||||
use tempdir::TempDir;
|
||||
|
||||
use crate::model::*;
|
||||
use crate::sdk::{LiquidSdk, Network};
|
||||
use crate::sdk::LiquidSdk;
|
||||
|
||||
const TEST_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
||||
|
||||
@@ -1594,10 +1586,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn normal_submarine_swap() -> Result<()> {
|
||||
let (_data_dir, data_dir_str) = create_temp_dir()?;
|
||||
let mut config = Config::testnet();
|
||||
config.working_dir = data_dir_str;
|
||||
let sdk = LiquidSdk::connect(ConnectRequest {
|
||||
mnemonic: TEST_MNEMONIC.to_string(),
|
||||
data_dir: Some(data_dir_str),
|
||||
network: Network::Testnet,
|
||||
config,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1612,10 +1605,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn reverse_submarine_swap() -> Result<()> {
|
||||
let (_data_dir, data_dir_str) = create_temp_dir()?;
|
||||
let mut config = Config::testnet();
|
||||
config.working_dir = data_dir_str;
|
||||
let sdk = LiquidSdk::connect(ConnectRequest {
|
||||
mnemonic: TEST_MNEMONIC.to_string(),
|
||||
data_dir: Some(data_dir_str),
|
||||
network: Network::Testnet,
|
||||
config,
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ Future<BindingLiquidSdk> connect({required ConnectRequest req, dynamic hint}) =>
|
||||
Stream<LogEntry> breezLogStream({dynamic hint}) =>
|
||||
RustLib.instance.api.crateBindingsBreezLogStream(hint: hint);
|
||||
|
||||
Future<Config> defaultConfig({required Network network, dynamic hint}) =>
|
||||
RustLib.instance.api.crateBindingsDefaultConfig(network: network, hint: hint);
|
||||
|
||||
Future<LNInvoice> parseInvoice({required String input, dynamic hint}) =>
|
||||
RustLib.instance.api.crateBindingsParseInvoice(input: input, hint: hint);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
String get codegenVersion => '2.0.0-dev.36';
|
||||
|
||||
@override
|
||||
int get rustContentHash => -532134055;
|
||||
int get rustContentHash => 1028270774;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig(
|
||||
stem: 'breez_liquid_sdk',
|
||||
@@ -100,6 +100,8 @@ abstract class RustLibApi extends BaseApi {
|
||||
|
||||
Future<BindingLiquidSdk> crateBindingsConnect({required ConnectRequest req, dynamic hint});
|
||||
|
||||
Future<Config> crateBindingsDefaultConfig({required Network network, dynamic hint});
|
||||
|
||||
Future<LNInvoice> crateBindingsParseInvoice({required String input, dynamic hint});
|
||||
|
||||
RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_BindingLiquidSdk;
|
||||
@@ -485,6 +487,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
argNames: ["req"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Config> crateBindingsDefaultConfig({required Network network, dynamic hint}) {
|
||||
return handler.executeNormal(NormalTask(
|
||||
callFfi: (port_) {
|
||||
var arg0 = cst_encode_network(network);
|
||||
return wire.wire__crate__bindings__default_config(port_, arg0);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_config,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateBindingsDefaultConfigConstMeta,
|
||||
argValues: [network],
|
||||
apiImpl: this,
|
||||
hint: hint,
|
||||
));
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateBindingsDefaultConfigConstMeta => const TaskConstMeta(
|
||||
debugName: "default_config",
|
||||
argNames: ["network"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<LNInvoice> crateBindingsParseInvoice({required String input, dynamic hint}) {
|
||||
return handler.executeNormal(NormalTask(
|
||||
@@ -637,15 +662,28 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return dco_decode_u_64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
Config dco_decode_config(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 5) throw Exception('unexpected arr length: expect 5 but see ${arr.length}');
|
||||
return Config(
|
||||
boltzUrl: dco_decode_String(arr[0]),
|
||||
electrumUrl: dco_decode_String(arr[1]),
|
||||
workingDir: dco_decode_String(arr[2]),
|
||||
network: dco_decode_network(arr[3]),
|
||||
paymentTimeoutSec: dco_decode_u_64(arr[4]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
ConnectRequest dco_decode_connect_request(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
|
||||
if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}');
|
||||
return ConnectRequest(
|
||||
mnemonic: dco_decode_String(arr[0]),
|
||||
dataDir: dco_decode_opt_String(arr[1]),
|
||||
network: dco_decode_network(arr[2]),
|
||||
config: dco_decode_config(arr[1]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1134,13 +1172,28 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return (sse_decode_u_64(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
Config sse_decode_config(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_boltzUrl = sse_decode_String(deserializer);
|
||||
var var_electrumUrl = sse_decode_String(deserializer);
|
||||
var var_workingDir = sse_decode_String(deserializer);
|
||||
var var_network = sse_decode_network(deserializer);
|
||||
var var_paymentTimeoutSec = sse_decode_u_64(deserializer);
|
||||
return Config(
|
||||
boltzUrl: var_boltzUrl,
|
||||
electrumUrl: var_electrumUrl,
|
||||
workingDir: var_workingDir,
|
||||
network: var_network,
|
||||
paymentTimeoutSec: var_paymentTimeoutSec);
|
||||
}
|
||||
|
||||
@protected
|
||||
ConnectRequest sse_decode_connect_request(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_mnemonic = sse_decode_String(deserializer);
|
||||
var var_dataDir = sse_decode_opt_String(deserializer);
|
||||
var var_network = sse_decode_network(deserializer);
|
||||
return ConnectRequest(mnemonic: var_mnemonic, dataDir: var_dataDir, network: var_network);
|
||||
var var_config = sse_decode_config(deserializer);
|
||||
return ConnectRequest(mnemonic: var_mnemonic, config: var_config);
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -1719,12 +1772,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_u_64(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_config(Config self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_String(self.boltzUrl, serializer);
|
||||
sse_encode_String(self.electrumUrl, serializer);
|
||||
sse_encode_String(self.workingDir, serializer);
|
||||
sse_encode_network(self.network, serializer);
|
||||
sse_encode_u_64(self.paymentTimeoutSec, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_connect_request(ConnectRequest self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_String(self.mnemonic, serializer);
|
||||
sse_encode_opt_String(self.dataDir, serializer);
|
||||
sse_encode_network(self.network, serializer);
|
||||
sse_encode_config(self.config, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
|
||||
@@ -85,6 +85,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
BigInt dco_decode_box_autoadd_u_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
Config dco_decode_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
ConnectRequest dco_decode_connect_request(dynamic raw);
|
||||
|
||||
@@ -246,6 +249,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Config sse_decode_config(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
ConnectRequest sse_decode_connect_request(SseDeserializer deserializer);
|
||||
|
||||
@@ -575,11 +581,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
cst_api_fill_to_wire_restore_request(apiObj, wireObj.ref);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_config(Config apiObj, wire_cst_config wireObj) {
|
||||
wireObj.boltz_url = cst_encode_String(apiObj.boltzUrl);
|
||||
wireObj.electrum_url = cst_encode_String(apiObj.electrumUrl);
|
||||
wireObj.working_dir = cst_encode_String(apiObj.workingDir);
|
||||
wireObj.network = cst_encode_network(apiObj.network);
|
||||
wireObj.payment_timeout_sec = cst_encode_u_64(apiObj.paymentTimeoutSec);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_connect_request(ConnectRequest apiObj, wire_cst_connect_request wireObj) {
|
||||
wireObj.mnemonic = cst_encode_String(apiObj.mnemonic);
|
||||
wireObj.data_dir = cst_encode_opt_String(apiObj.dataDir);
|
||||
wireObj.network = cst_encode_network(apiObj.network);
|
||||
cst_api_fill_to_wire_config(apiObj.config, wireObj.config);
|
||||
}
|
||||
|
||||
@protected
|
||||
@@ -922,6 +936,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_config(Config self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_connect_request(ConnectRequest self, SseSerializer serializer);
|
||||
|
||||
@@ -1308,6 +1325,22 @@ class RustLibWire implements BaseWire {
|
||||
late final _wire__crate__bindings__connect = _wire__crate__bindings__connectPtr
|
||||
.asFunction<void Function(int, ffi.Pointer<wire_cst_connect_request>)>();
|
||||
|
||||
void wire__crate__bindings__default_config(
|
||||
int port_,
|
||||
int network,
|
||||
) {
|
||||
return _wire__crate__bindings__default_config(
|
||||
port_,
|
||||
network,
|
||||
);
|
||||
}
|
||||
|
||||
late final _wire__crate__bindings__default_configPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64, ffi.Int32)>>(
|
||||
'frbgen_breez_liquid_wire__crate__bindings__default_config');
|
||||
late final _wire__crate__bindings__default_config =
|
||||
_wire__crate__bindings__default_configPtr.asFunction<void Function(int, int)>();
|
||||
|
||||
void wire__crate__bindings__parse_invoice(
|
||||
int port_,
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> input,
|
||||
@@ -1577,13 +1610,24 @@ final class wire_cst_prepare_send_response extends ffi.Struct {
|
||||
external int fees_sat;
|
||||
}
|
||||
|
||||
final class wire_cst_connect_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> mnemonic;
|
||||
final class wire_cst_config extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> boltz_url;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> data_dir;
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> electrum_url;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> working_dir;
|
||||
|
||||
@ffi.Int32()
|
||||
external int network;
|
||||
|
||||
@ffi.Uint64()
|
||||
external int payment_timeout_sec;
|
||||
}
|
||||
|
||||
final class wire_cst_connect_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> mnemonic;
|
||||
|
||||
external wire_cst_config config;
|
||||
}
|
||||
|
||||
final class wire_cst_payment extends ffi.Struct {
|
||||
|
||||
@@ -28,19 +28,55 @@ class BackupRequest {
|
||||
other is BackupRequest && runtimeType == other.runtimeType && backupPath == other.backupPath;
|
||||
}
|
||||
|
||||
class ConnectRequest {
|
||||
final String mnemonic;
|
||||
final String? dataDir;
|
||||
final Network network;
|
||||
/// Configuration for the Liquid SDK
|
||||
class Config {
|
||||
final String boltzUrl;
|
||||
final String electrumUrl;
|
||||
|
||||
const ConnectRequest({
|
||||
required this.mnemonic,
|
||||
this.dataDir,
|
||||
/// Directory in which all SDK files (DB, log) are stored.
|
||||
final String workingDir;
|
||||
final Network network;
|
||||
final BigInt paymentTimeoutSec;
|
||||
|
||||
const Config({
|
||||
required this.boltzUrl,
|
||||
required this.electrumUrl,
|
||||
required this.workingDir,
|
||||
required this.network,
|
||||
required this.paymentTimeoutSec,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode => mnemonic.hashCode ^ dataDir.hashCode ^ network.hashCode;
|
||||
int get hashCode =>
|
||||
boltzUrl.hashCode ^
|
||||
electrumUrl.hashCode ^
|
||||
workingDir.hashCode ^
|
||||
network.hashCode ^
|
||||
paymentTimeoutSec.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is Config &&
|
||||
runtimeType == other.runtimeType &&
|
||||
boltzUrl == other.boltzUrl &&
|
||||
electrumUrl == other.electrumUrl &&
|
||||
workingDir == other.workingDir &&
|
||||
network == other.network &&
|
||||
paymentTimeoutSec == other.paymentTimeoutSec;
|
||||
}
|
||||
|
||||
class ConnectRequest {
|
||||
final String mnemonic;
|
||||
final Config config;
|
||||
|
||||
const ConnectRequest({
|
||||
required this.mnemonic,
|
||||
required this.config,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode => mnemonic.hashCode ^ config.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
@@ -48,8 +84,7 @@ class ConnectRequest {
|
||||
other is ConnectRequest &&
|
||||
runtimeType == other.runtimeType &&
|
||||
mnemonic == other.mnemonic &&
|
||||
dataDir == other.dataDir &&
|
||||
network == other.network;
|
||||
config == other.config;
|
||||
}
|
||||
|
||||
class GetInfoRequest {
|
||||
|
||||
@@ -294,6 +294,22 @@ class FlutterBreezLiquidBindings {
|
||||
_frbgen_breez_liquid_wire__crate__bindings__connectPtr
|
||||
.asFunction<void Function(int, ffi.Pointer<wire_cst_connect_request>)>();
|
||||
|
||||
void frbgen_breez_liquid_wire__crate__bindings__default_config(
|
||||
int port_,
|
||||
int network,
|
||||
) {
|
||||
return _frbgen_breez_liquid_wire__crate__bindings__default_config(
|
||||
port_,
|
||||
network,
|
||||
);
|
||||
}
|
||||
|
||||
late final _frbgen_breez_liquid_wire__crate__bindings__default_configPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64, ffi.Int32)>>(
|
||||
'frbgen_breez_liquid_wire__crate__bindings__default_config');
|
||||
late final _frbgen_breez_liquid_wire__crate__bindings__default_config =
|
||||
_frbgen_breez_liquid_wire__crate__bindings__default_configPtr.asFunction<void Function(int, int)>();
|
||||
|
||||
void frbgen_breez_liquid_wire__crate__bindings__parse_invoice(
|
||||
int port_,
|
||||
ffi.Pointer<wire_cst_list_prim_u_8_strict> input,
|
||||
@@ -589,13 +605,24 @@ final class wire_cst_prepare_send_response extends ffi.Struct {
|
||||
external int fees_sat;
|
||||
}
|
||||
|
||||
final class wire_cst_connect_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> mnemonic;
|
||||
final class wire_cst_config extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> boltz_url;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> data_dir;
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> electrum_url;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> working_dir;
|
||||
|
||||
@ffi.Int32()
|
||||
external int network;
|
||||
|
||||
@ffi.Uint64()
|
||||
external int payment_timeout_sec;
|
||||
}
|
||||
|
||||
final class wire_cst_connect_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> mnemonic;
|
||||
|
||||
external wire_cst_config config;
|
||||
}
|
||||
|
||||
final class wire_cst_payment extends ffi.Struct {
|
||||
|
||||
@@ -34,32 +34,78 @@ fun asBackupRequestList(arr: ReadableArray): List<BackupRequest> {
|
||||
return list
|
||||
}
|
||||
|
||||
fun asConnectRequest(connectRequest: ReadableMap): ConnectRequest? {
|
||||
fun asConfig(config: ReadableMap): Config? {
|
||||
if (!validateMandatoryFields(
|
||||
connectRequest,
|
||||
config,
|
||||
arrayOf(
|
||||
"mnemonic",
|
||||
"boltzUrl",
|
||||
"electrumUrl",
|
||||
"workingDir",
|
||||
"network",
|
||||
"paymentTimeoutSec",
|
||||
),
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val mnemonic = connectRequest.getString("mnemonic")!!
|
||||
val network = connectRequest.getString("network")?.let { asNetwork(it) }!!
|
||||
val dataDir = if (hasNonNullKey(connectRequest, "dataDir")) connectRequest.getString("dataDir") else null
|
||||
return ConnectRequest(
|
||||
mnemonic,
|
||||
val boltzUrl = config.getString("boltzUrl")!!
|
||||
val electrumUrl = config.getString("electrumUrl")!!
|
||||
val workingDir = config.getString("workingDir")!!
|
||||
val network = config.getString("network")?.let { asNetwork(it) }!!
|
||||
val paymentTimeoutSec = config.getDouble("paymentTimeoutSec").toULong()
|
||||
return Config(
|
||||
boltzUrl,
|
||||
electrumUrl,
|
||||
workingDir,
|
||||
network,
|
||||
dataDir,
|
||||
paymentTimeoutSec,
|
||||
)
|
||||
}
|
||||
|
||||
fun readableMapOf(config: Config): ReadableMap {
|
||||
return readableMapOf(
|
||||
"boltzUrl" to config.boltzUrl,
|
||||
"electrumUrl" to config.electrumUrl,
|
||||
"workingDir" to config.workingDir,
|
||||
"network" to config.network.name.lowercase(),
|
||||
"paymentTimeoutSec" to config.paymentTimeoutSec,
|
||||
)
|
||||
}
|
||||
|
||||
fun asConfigList(arr: ReadableArray): List<Config> {
|
||||
val list = ArrayList<Config>()
|
||||
for (value in arr.toArrayList()) {
|
||||
when (value) {
|
||||
is ReadableMap -> list.add(asConfig(value)!!)
|
||||
else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}"))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun asConnectRequest(connectRequest: ReadableMap): ConnectRequest? {
|
||||
if (!validateMandatoryFields(
|
||||
connectRequest,
|
||||
arrayOf(
|
||||
"config",
|
||||
"mnemonic",
|
||||
),
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val config = connectRequest.getMap("config")?.let { asConfig(it) }!!
|
||||
val mnemonic = connectRequest.getString("mnemonic")!!
|
||||
return ConnectRequest(
|
||||
config,
|
||||
mnemonic,
|
||||
)
|
||||
}
|
||||
|
||||
fun readableMapOf(connectRequest: ConnectRequest): ReadableMap {
|
||||
return readableMapOf(
|
||||
"config" to readableMapOf(connectRequest.config),
|
||||
"mnemonic" to connectRequest.mnemonic,
|
||||
"network" to connectRequest.network.name.lowercase(),
|
||||
"dataDir" to connectRequest.dataDir,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.breezliquidsdk
|
||||
import breez_liquid_sdk.*
|
||||
import com.facebook.react.bridge.*
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
@@ -34,12 +35,44 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext
|
||||
throw LiquidSdkException.Generic("Not initialized")
|
||||
}
|
||||
|
||||
@Throws(LiquidSdkException::class)
|
||||
private fun ensureWorkingDir(workingDir: String) {
|
||||
try {
|
||||
val workingDirFile = File(workingDir)
|
||||
|
||||
if (!workingDirFile.exists() && !workingDirFile.mkdirs()) {
|
||||
throw LiquidSdkException.Generic("Mandatory field workingDir must contain a writable directory")
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
throw LiquidSdkException.Generic("Mandatory field workingDir must contain a writable directory")
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun addListener(eventName: String) {}
|
||||
|
||||
@ReactMethod
|
||||
fun removeListeners(count: Int) {}
|
||||
|
||||
@ReactMethod
|
||||
fun defaultConfig(
|
||||
network: String,
|
||||
promise: Promise,
|
||||
) {
|
||||
executor.execute {
|
||||
try {
|
||||
val networkTmp = asNetwork(network)
|
||||
val res = defaultConfig(networkTmp)
|
||||
val workingDir = File(reactApplicationContext.filesDir.toString() + "/breezLiquidSdk")
|
||||
|
||||
res.workingDir = workingDir.absolutePath
|
||||
promise.resolve(readableMapOf(res))
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun parseInvoice(
|
||||
invoice: String,
|
||||
@@ -86,9 +119,9 @@ class BreezLiquidSDKModule(reactContext: ReactApplicationContext) : ReactContext
|
||||
asConnectRequest(
|
||||
req,
|
||||
) ?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "ConnectRequest")) }
|
||||
connectRequest.dataDir = connectRequest.dataDir?.takeUnless {
|
||||
it.isEmpty()
|
||||
} ?: run { reactApplicationContext.filesDir.toString() + "/breezLiquidSdk" }
|
||||
|
||||
ensureWorkingDir(connectRequest.config.workingDir)
|
||||
|
||||
bindingLiquidSdk = connect(connectRequest)
|
||||
promise.resolve(readableMapOf("status" to "ok"))
|
||||
} catch (e: Exception) {
|
||||
|
||||
7
packages/react-native/example/App.js
vendored
7
packages/react-native/example/App.js
vendored
@@ -8,7 +8,7 @@
|
||||
|
||||
import React, { useState } from "react"
|
||||
import { SafeAreaView, ScrollView, StatusBar, Text, TouchableOpacity, View } from "react-native"
|
||||
import { addEventListener, Network, getInfo, connect, removeEventListener } from "@breeztech/react-native-breez-liquid-sdk"
|
||||
import { addEventListener, connect, defaultConfig, getInfo, Network, removeEventListener } from "@breeztech/react-native-breez-liquid-sdk"
|
||||
import { generateMnemonic } from "@dreson4/react-native-quick-bip39"
|
||||
import { getSecureItem, setSecureItem } from "./utils/storage"
|
||||
|
||||
@@ -49,7 +49,10 @@ const App = () => {
|
||||
setSecureItem(MNEMONIC_STORE, mnemonic)
|
||||
}
|
||||
|
||||
await connect({ mnemonic, network: Network.LIQUID_TESTNET })
|
||||
const config = await defaultConfig(Network.TESTNET)
|
||||
addLine("defaultConfig", JSON.stringify(config))
|
||||
|
||||
await connect({ config, mnemonic })
|
||||
addLine("connect", null)
|
||||
|
||||
listenerId = await addEventListener(eventHandler)
|
||||
|
||||
@@ -38,35 +38,81 @@ enum BreezLiquidSDKMapper {
|
||||
return backupRequestList.map { v -> [String: Any?] in dictionaryOf(backupRequest: v) }
|
||||
}
|
||||
|
||||
static func asConnectRequest(connectRequest: [String: Any?]) throws -> ConnectRequest {
|
||||
guard let mnemonic = connectRequest["mnemonic"] as? String else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "mnemonic", typeName: "ConnectRequest"))
|
||||
static func asConfig(config: [String: Any?]) throws -> Config {
|
||||
guard let boltzUrl = config["boltzUrl"] as? String else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "boltzUrl", typeName: "Config"))
|
||||
}
|
||||
guard let networkTmp = connectRequest["network"] as? String else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "network", typeName: "ConnectRequest"))
|
||||
guard let electrumUrl = config["electrumUrl"] as? String else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "electrumUrl", typeName: "Config"))
|
||||
}
|
||||
guard let workingDir = config["workingDir"] as? String else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "workingDir", typeName: "Config"))
|
||||
}
|
||||
guard let networkTmp = config["network"] as? String else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "network", typeName: "Config"))
|
||||
}
|
||||
let network = try asNetwork(network: networkTmp)
|
||||
|
||||
var dataDir: String?
|
||||
if hasNonNilKey(data: connectRequest, key: "dataDir") {
|
||||
guard let dataDirTmp = connectRequest["dataDir"] as? String else {
|
||||
throw LiquidSdkError.Generic(message: errUnexpectedValue(fieldName: "dataDir"))
|
||||
guard let paymentTimeoutSec = config["paymentTimeoutSec"] as? UInt64 else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentTimeoutSec", typeName: "Config"))
|
||||
}
|
||||
dataDir = dataDirTmp
|
||||
|
||||
return Config(
|
||||
boltzUrl: boltzUrl,
|
||||
electrumUrl: electrumUrl,
|
||||
workingDir: workingDir,
|
||||
network: network,
|
||||
paymentTimeoutSec: paymentTimeoutSec
|
||||
)
|
||||
}
|
||||
|
||||
static func dictionaryOf(config: Config) -> [String: Any?] {
|
||||
return [
|
||||
"boltzUrl": config.boltzUrl,
|
||||
"electrumUrl": config.electrumUrl,
|
||||
"workingDir": config.workingDir,
|
||||
"network": valueOf(network: config.network),
|
||||
"paymentTimeoutSec": config.paymentTimeoutSec,
|
||||
]
|
||||
}
|
||||
|
||||
static func asConfigList(arr: [Any]) throws -> [Config] {
|
||||
var list = [Config]()
|
||||
for value in arr {
|
||||
if let val = value as? [String: Any?] {
|
||||
var config = try asConfig(config: val)
|
||||
list.append(config)
|
||||
} else {
|
||||
throw LiquidSdkError.Generic(message: errUnexpectedType(typeName: "Config"))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
static func arrayOf(configList: [Config]) -> [Any] {
|
||||
return configList.map { v -> [String: Any?] in dictionaryOf(config: v) }
|
||||
}
|
||||
|
||||
static func asConnectRequest(connectRequest: [String: Any?]) throws -> ConnectRequest {
|
||||
guard let configTmp = connectRequest["config"] as? [String: Any?] else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "config", typeName: "ConnectRequest"))
|
||||
}
|
||||
let config = try asConfig(config: configTmp)
|
||||
|
||||
guard let mnemonic = connectRequest["mnemonic"] as? String else {
|
||||
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "mnemonic", typeName: "ConnectRequest"))
|
||||
}
|
||||
|
||||
return ConnectRequest(
|
||||
mnemonic: mnemonic,
|
||||
network: network,
|
||||
dataDir: dataDir
|
||||
config: config,
|
||||
mnemonic: mnemonic
|
||||
)
|
||||
}
|
||||
|
||||
static func dictionaryOf(connectRequest: ConnectRequest) -> [String: Any?] {
|
||||
return [
|
||||
"config": dictionaryOf(config: connectRequest.config),
|
||||
"mnemonic": connectRequest.mnemonic,
|
||||
"network": valueOf(network: connectRequest.network),
|
||||
"dataDir": connectRequest.dataDir == nil ? nil : connectRequest.dataDir,
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
|
||||
@interface RCT_EXTERN_MODULE(RNBreezLiquidSDK, RCTEventEmitter)
|
||||
|
||||
RCT_EXTERN_METHOD(
|
||||
defaultConfig: (NSString*)network
|
||||
resolve: (RCTPromiseResolveBlock)resolve
|
||||
reject: (RCTPromiseRejectBlock)reject
|
||||
)
|
||||
|
||||
RCT_EXTERN_METHOD(
|
||||
parseInvoice: (NSString*)invoice
|
||||
resolve: (RCTPromiseResolveBlock)resolve
|
||||
|
||||
@@ -11,10 +11,15 @@ class RNBreezLiquidSDK: RCTEventEmitter {
|
||||
|
||||
private var bindingLiquidSdk: BindingLiquidSdk!
|
||||
|
||||
static var defaultDataDir: URL {
|
||||
static var breezLiquidSdkDirectory: URL {
|
||||
let applicationDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
let breezLiquidSdkDirectory = applicationDirectory.appendingPathComponent("breezLiquidSdk", isDirectory: true)
|
||||
|
||||
return applicationDirectory.appendingPathComponent("breezLiquidSdk", isDirectory: true)
|
||||
if !FileManager.default.fileExists(atPath: breezLiquidSdkDirectory.path) {
|
||||
try! FileManager.default.createDirectory(atPath: breezLiquidSdkDirectory.path, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
return breezLiquidSdkDirectory
|
||||
}
|
||||
|
||||
override init() {
|
||||
@@ -56,6 +61,28 @@ class RNBreezLiquidSDK: RCTEventEmitter {
|
||||
throw LiquidSdkError.Generic(message: "Not initialized")
|
||||
}
|
||||
|
||||
private func ensureWorkingDir(workingDir: String) throws {
|
||||
do {
|
||||
if !FileManager.default.fileExists(atPath: workingDir) {
|
||||
try FileManager.default.createDirectory(atPath: workingDir, withIntermediateDirectories: true)
|
||||
}
|
||||
} catch {
|
||||
throw LiquidSdkError.Generic(message: "Mandatory field workingDir must contain a writable directory")
|
||||
}
|
||||
}
|
||||
|
||||
@objc(defaultConfig:resolve:reject:)
|
||||
func defaultConfig(_ network: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
do {
|
||||
let networkTmp = try BreezLiquidSDKMapper.asNetwork(network: network)
|
||||
var res = BreezLiquidSDK.defaultConfig(network: networkTmp)
|
||||
res.workingDir = RNBreezLiquidSDK.breezLiquidSdkDirectory.path
|
||||
resolve(BreezLiquidSDKMapper.dictionaryOf(config: res))
|
||||
} catch let err {
|
||||
rejectErr(err: err, reject: reject)
|
||||
}
|
||||
}
|
||||
|
||||
@objc(parseInvoice:resolve:reject:)
|
||||
func parseInvoice(_ invoice: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
do {
|
||||
@@ -85,7 +112,8 @@ class RNBreezLiquidSDK: RCTEventEmitter {
|
||||
|
||||
do {
|
||||
var connectRequest = try BreezLiquidSDKMapper.asConnectRequest(connectRequest: req)
|
||||
connectRequest.dataDir = connectRequest.dataDir == nil || connectRequest.dataDir!.isEmpty ? RNBreezLiquidSDK.defaultDataDir.path : connectRequest.dataDir
|
||||
try ensureWorkingDir(workingDir: connectRequest.config.workingDir)
|
||||
|
||||
bindingLiquidSdk = try BreezLiquidSDK.connect(req: connectRequest)
|
||||
resolve(["status": "ok"])
|
||||
} catch let err {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NativeModules, Platform, NativeEventEmitter } from "react-native"
|
||||
import { NativeModules, Platform, EmitterSubscription, NativeEventEmitter } from "react-native"
|
||||
|
||||
const LINKING_ERROR =
|
||||
`The package 'react-native-breez-liquid-sdk' doesn't seem to be linked. Make sure: \n\n` +
|
||||
@@ -23,10 +23,17 @@ export interface BackupRequest {
|
||||
backupPath?: string
|
||||
}
|
||||
|
||||
export interface ConnectRequest {
|
||||
mnemonic: string
|
||||
export interface Config {
|
||||
boltzUrl: string
|
||||
electrumUrl: string
|
||||
workingDir: string
|
||||
network: Network
|
||||
dataDir?: string
|
||||
paymentTimeoutSec: number
|
||||
}
|
||||
|
||||
export interface ConnectRequest {
|
||||
config: Config
|
||||
mnemonic: string
|
||||
}
|
||||
|
||||
export interface GetInfoRequest {
|
||||
@@ -193,6 +200,12 @@ export const setLogger = async (logger: Logger): Promise<EmitterSubscription> =>
|
||||
|
||||
return subscription
|
||||
}
|
||||
|
||||
export const defaultConfig = async (network: Network): Promise<Config> => {
|
||||
const response = await BreezLiquidSDK.defaultConfig(network)
|
||||
return response
|
||||
}
|
||||
|
||||
export const parseInvoice = async (invoice: string): Promise<LnInvoice> => {
|
||||
const response = await BreezLiquidSDK.parseInvoice(invoice)
|
||||
return response
|
||||
|
||||
Reference in New Issue
Block a user