mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2026-01-18 05:24:25 +01:00
WASM: prevent filesystem access (#792)
* WASM: prevent filesystem access * Exclude logger module on wasm * Drop use of conditional compilation in `LiquidOnchainWallet` * Expose `LiquidSdkBuilder` and configure build for wasm * Move working_dir setup and log header to connect with signer * Add in memory persister constructor * Drop builder connect method * Remove `empty_wallet_cache` from WASM interface * Impose custom persister on wasm
This commit is contained in:
2
lib/Cargo.lock
generated
2
lib/Cargo.lock
generated
@@ -812,6 +812,7 @@ dependencies = [
|
||||
"env_logger 0.11.5",
|
||||
"flutter_rust_bridge",
|
||||
"futures-util",
|
||||
"getrandom 0.2.14",
|
||||
"glob",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
@@ -823,6 +824,7 @@ dependencies = [
|
||||
"mockall",
|
||||
"paste",
|
||||
"prost 0.13.4",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.13",
|
||||
"rusqlite",
|
||||
"rusqlite_migration",
|
||||
|
||||
@@ -85,11 +85,16 @@ uuid = { version = "1.8.0", features = ["v4", "js"] }
|
||||
[dev-dependencies]
|
||||
sdk-common = { workspace = true, features = ["test-utils"] }
|
||||
paste = "1.0.15"
|
||||
|
||||
# Non-WASM dev dependencies
|
||||
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dev-dependencies]
|
||||
tempdir = "0.3.7"
|
||||
|
||||
# WASM dev dependencies
|
||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.33"
|
||||
rand = "0.8"
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { version = "1.0.79", features = ["backtrace"] }
|
||||
|
||||
@@ -174,6 +174,7 @@ pub(crate) mod event;
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
pub(crate) mod frb_generated;
|
||||
pub(crate) mod lnurl;
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
pub mod logger;
|
||||
pub mod model;
|
||||
pub mod persist;
|
||||
@@ -186,7 +187,7 @@ pub(crate) mod swapper;
|
||||
pub(crate) mod sync;
|
||||
pub(crate) mod test_utils;
|
||||
pub(crate) mod utils;
|
||||
pub(crate) mod wallet;
|
||||
pub mod wallet;
|
||||
|
||||
pub use sdk_common::prelude::*;
|
||||
|
||||
|
||||
@@ -189,6 +189,10 @@ impl Config {
|
||||
LiquidNetwork::Regtest => BOLTZ_REGTEST,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_enabled(&self) -> bool {
|
||||
self.sync_service_url.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Network chosen for this Liquid SDK instance. Note that it represents both the Liquid and the
|
||||
@@ -821,7 +825,7 @@ pub(crate) trait BlockListener: Send + Sync {
|
||||
|
||||
// A swap enum variant
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Swap {
|
||||
pub enum Swap {
|
||||
Chain(ChainSwap),
|
||||
Send(SendSwap),
|
||||
Receive(ReceiveSwap),
|
||||
@@ -946,7 +950,7 @@ pub(crate) struct SwapMetadata {
|
||||
/// See <https://docs.boltz.exchange/v/api/lifecycle#chain-swaps>
|
||||
#[derive(Clone, Debug, Derivative)]
|
||||
#[derivative(PartialEq)]
|
||||
pub(crate) struct ChainSwap {
|
||||
pub struct ChainSwap {
|
||||
pub(crate) id: String,
|
||||
pub(crate) direction: Direction,
|
||||
/// The Bitcoin claim address is only set for Outgoing Chain Swaps
|
||||
@@ -1113,7 +1117,7 @@ pub(crate) struct ChainSwapUpdate {
|
||||
/// A submarine swap, used for Send
|
||||
#[derive(Clone, Debug, Derivative)]
|
||||
#[derivative(PartialEq)]
|
||||
pub(crate) struct SendSwap {
|
||||
pub struct SendSwap {
|
||||
pub(crate) id: String,
|
||||
/// Bolt11 or Bolt12 invoice. This is determined by whether `bolt12_offer` is set or not.
|
||||
pub(crate) invoice: String,
|
||||
@@ -1206,7 +1210,7 @@ impl SendSwap {
|
||||
/// A reverse swap, used for Receive
|
||||
#[derive(Clone, Debug, Derivative)]
|
||||
#[derivative(PartialEq)]
|
||||
pub(crate) struct ReceiveSwap {
|
||||
pub struct ReceiveSwap {
|
||||
pub(crate) id: String,
|
||||
pub(crate) preimage: String,
|
||||
/// JSON representation of [crate::persist::receive::InternalCreateReverseResponse]
|
||||
|
||||
@@ -44,10 +44,7 @@ mod tests {
|
||||
test_utils::persist::{create_persister, new_receive_swap, new_send_swap},
|
||||
};
|
||||
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[sdk_macros::test_all]
|
||||
#[sdk_macros::test_not_wasm]
|
||||
fn test_backup_and_restore() -> Result<()> {
|
||||
create_persister!(local);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ pub(crate) mod sync;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ops::Not;
|
||||
use std::{fs::create_dir_all, path::PathBuf, str::FromStr};
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use crate::lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription};
|
||||
use crate::model::*;
|
||||
@@ -32,7 +32,7 @@ use tokio::sync::broadcast::{self, Sender};
|
||||
|
||||
const DEFAULT_DB_FILENAME: &str = "storage.sql";
|
||||
|
||||
pub(crate) struct Persister {
|
||||
pub struct Persister {
|
||||
main_db_dir: PathBuf,
|
||||
network: LiquidNetwork,
|
||||
pub(crate) sync_trigger: Option<Sender<()>>,
|
||||
@@ -60,21 +60,58 @@ fn where_clauses_to_string(where_clauses: Vec<String>) -> String {
|
||||
}
|
||||
|
||||
impl Persister {
|
||||
pub fn new(working_dir: &str, network: LiquidNetwork, sync_enabled: bool) -> Result<Self> {
|
||||
/// Creates a new Persister that stores data on the provided `working_dir`.
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
pub fn new_using_fs(
|
||||
working_dir: &str,
|
||||
network: LiquidNetwork,
|
||||
sync_enabled: bool,
|
||||
asset_metadata: Option<Vec<AssetMetadata>>,
|
||||
) -> Result<Self> {
|
||||
let main_db_dir = PathBuf::from_str(working_dir)?;
|
||||
if !main_db_dir.exists() {
|
||||
create_dir_all(&main_db_dir)?;
|
||||
std::fs::create_dir_all(&main_db_dir)?;
|
||||
}
|
||||
Self::new_inner(main_db_dir, network, sync_enabled, asset_metadata)
|
||||
}
|
||||
|
||||
/// Creates a new Persister that only keeps data in memory.
|
||||
///
|
||||
/// Multiple persisters accessing the same in-memory data can be created by providing the
|
||||
/// same `database_id`.
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
pub fn new_in_memory(
|
||||
database_id: &str,
|
||||
network: LiquidNetwork,
|
||||
sync_enabled: bool,
|
||||
asset_metadata: Option<Vec<AssetMetadata>>,
|
||||
) -> Result<Self> {
|
||||
let main_db_dir = PathBuf::from_str(database_id)?;
|
||||
Self::new_inner(main_db_dir, network, sync_enabled, asset_metadata)
|
||||
}
|
||||
|
||||
fn new_inner(
|
||||
main_db_dir: PathBuf,
|
||||
network: LiquidNetwork,
|
||||
sync_enabled: bool,
|
||||
asset_metadata: Option<Vec<AssetMetadata>>,
|
||||
) -> Result<Self> {
|
||||
let mut sync_trigger = None;
|
||||
if sync_enabled {
|
||||
let (events_notifier, _) = broadcast::channel::<()>(1);
|
||||
sync_trigger = Some(events_notifier);
|
||||
}
|
||||
Ok(Persister {
|
||||
|
||||
let persister = Persister {
|
||||
main_db_dir,
|
||||
network,
|
||||
sync_trigger,
|
||||
})
|
||||
};
|
||||
|
||||
persister.init()?;
|
||||
persister.replace_asset_metadata(asset_metadata)?;
|
||||
|
||||
Ok(persister)
|
||||
}
|
||||
|
||||
pub(crate) fn get_connection(&self) -> Result<Connection> {
|
||||
|
||||
@@ -27,7 +27,7 @@ use crate::{
|
||||
|
||||
const LIQUID_TIP_LEEWAY: u32 = 3;
|
||||
|
||||
pub(crate) struct Recoverer {
|
||||
pub struct Recoverer {
|
||||
master_blinding_key: MasterBlindingKey,
|
||||
swapper: Arc<dyn Swapper>,
|
||||
onchain_wallet: Arc<dyn OnchainWallet>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::ops::Not as _;
|
||||
use std::{fs, path::PathBuf, str::FromStr, sync::Arc, time::Duration};
|
||||
use std::{path::PathBuf, str::FromStr, sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use boltz_client::{swaps::boltz::*, util::secrets::Preimage};
|
||||
@@ -72,7 +72,7 @@ pub const DEFAULT_EXTERNAL_INPUT_PARSERS: &[(&str, &str, &str)] = &[(
|
||||
|
||||
pub(crate) const NETWORK_PROPAGATION_GRACE_PERIOD: Duration = Duration::from_secs(30);
|
||||
|
||||
pub(crate) struct LiquidSdkBuilder {
|
||||
pub struct LiquidSdkBuilder {
|
||||
config: Config,
|
||||
signer: Arc<Box<dyn Signer>>,
|
||||
breez_server: Arc<BreezServer>,
|
||||
@@ -162,17 +162,20 @@ impl LiquidSdkBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
fn get_working_dir(&self) -> Result<String> {
|
||||
let fingerprint_hex: String =
|
||||
Xpub::decode(self.signer.xpub()?.as_slice())?.identifier()[0..4].to_hex();
|
||||
self.config
|
||||
.get_wallet_dir(&self.config.working_dir, &fingerprint_hex)
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Result<Arc<LiquidSdk>> {
|
||||
if let Some(breez_api_key) = &self.config.breez_api_key {
|
||||
LiquidSdk::validate_breez_api_key(breez_api_key)?
|
||||
}
|
||||
|
||||
fs::create_dir_all(&self.config.working_dir)?;
|
||||
let fingerprint_hex: String =
|
||||
Xpub::decode(self.signer.xpub()?.as_slice())?.identifier()[0..4].to_hex();
|
||||
let working_dir = self
|
||||
.config
|
||||
.get_wallet_dir(&self.config.working_dir, &fingerprint_hex)?;
|
||||
let cache_dir = self.config.get_wallet_dir(
|
||||
self.config
|
||||
.cache_dir
|
||||
@@ -181,24 +184,20 @@ impl LiquidSdkBuilder {
|
||||
&fingerprint_hex,
|
||||
)?;
|
||||
|
||||
let sync_enabled = self
|
||||
.config
|
||||
.sync_service_url
|
||||
.clone()
|
||||
.map(|_| true)
|
||||
.unwrap_or(false);
|
||||
|
||||
let persister = match self.persister.clone() {
|
||||
Some(persister) => persister,
|
||||
None => {
|
||||
let persister = Arc::new(Persister::new(
|
||||
&working_dir,
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
return Err(anyhow!(
|
||||
"Must provide a WASM-compatible persister on WASM builds"
|
||||
));
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
Arc::new(Persister::new_using_fs(
|
||||
&self.get_working_dir()?,
|
||||
self.config.network,
|
||||
sync_enabled,
|
||||
)?);
|
||||
persister.init()?;
|
||||
persister.replace_asset_metadata(self.config.asset_metadata.clone())?;
|
||||
persister
|
||||
self.config.sync_enabled(),
|
||||
self.config.asset_metadata.clone(),
|
||||
)?)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -226,7 +225,7 @@ impl LiquidSdkBuilder {
|
||||
Some(onchain_wallet) => onchain_wallet,
|
||||
None => Arc::new(LiquidOnchainWallet::new(
|
||||
self.config.clone(),
|
||||
&cache_dir,
|
||||
cache_dir,
|
||||
persister.clone(),
|
||||
self.signer.clone(),
|
||||
)?),
|
||||
@@ -383,43 +382,49 @@ impl LiquidSdk {
|
||||
/// * `passphrase` - the optional passphrase for the mnemonic
|
||||
/// * `seed` - the optional Liquid wallet seed
|
||||
pub async fn connect(req: ConnectRequest) -> Result<Arc<LiquidSdk>> {
|
||||
let start_ts = Instant::now();
|
||||
let is_mainnet = req.config.network == LiquidNetwork::Mainnet;
|
||||
let signer = Self::default_signer(&req)?;
|
||||
|
||||
let signer = match (req.mnemonic, req.seed) {
|
||||
(None, Some(seed)) => Box::new(SdkSigner::new_with_seed(seed, is_mainnet)?),
|
||||
(Some(mnemonic), None) => Box::new(SdkSigner::new(
|
||||
&mnemonic,
|
||||
req.passphrase.unwrap_or("".to_string()).as_ref(),
|
||||
Self::connect_with_signer(
|
||||
ConnectWithSignerRequest { config: req.config },
|
||||
Box::new(signer),
|
||||
)
|
||||
.inspect_err(|e| error!("Failed to connect: {:?}", e))
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn default_signer(req: &ConnectRequest) -> Result<SdkSigner> {
|
||||
let is_mainnet = req.config.network == LiquidNetwork::Mainnet;
|
||||
match (&req.mnemonic, &req.seed) {
|
||||
(None, Some(seed)) => Ok(SdkSigner::new_with_seed(seed.clone(), is_mainnet)?),
|
||||
(Some(mnemonic), None) => Ok(SdkSigner::new(
|
||||
mnemonic,
|
||||
req.passphrase.as_ref().unwrap_or(&"".to_string()).as_ref(),
|
||||
is_mainnet,
|
||||
)?),
|
||||
_ => return Err(anyhow!("Either `mnemonic` or `seed` must be set")),
|
||||
};
|
||||
|
||||
let sdk =
|
||||
Self::connect_with_signer(ConnectWithSignerRequest { config: req.config }, signer)
|
||||
.inspect_err(|e| error!("Failed to connect: {:?}", e))
|
||||
.await;
|
||||
|
||||
let init_time = Instant::now().duration_since(start_ts);
|
||||
utils::log_print_header(init_time);
|
||||
|
||||
sdk
|
||||
_ => Err(anyhow!("Either `mnemonic` or `seed` must be set")),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect_with_signer(
|
||||
req: ConnectWithSignerRequest,
|
||||
signer: Box<dyn Signer>,
|
||||
) -> Result<Arc<LiquidSdk>> {
|
||||
let start_ts = Instant::now();
|
||||
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
std::fs::create_dir_all(&req.config.working_dir)?;
|
||||
|
||||
let sdk = LiquidSdkBuilder::new(
|
||||
req.config,
|
||||
PRODUCTION_BREEZSERVER_URL.into(),
|
||||
Arc::new(signer),
|
||||
)?
|
||||
.build()?;
|
||||
sdk.start()
|
||||
.inspect_err(|e| error!("Failed to start an SDK instance: {:?}", e))
|
||||
.await?;
|
||||
sdk.start().await?;
|
||||
|
||||
let init_time = Instant::now().duration_since(start_ts);
|
||||
utils::log_print_header(init_time);
|
||||
|
||||
Ok(sdk)
|
||||
}
|
||||
|
||||
@@ -450,9 +455,8 @@ impl LiquidSdk {
|
||||
|
||||
/// Starts an SDK instance.
|
||||
///
|
||||
/// Internal method. Should only be called once per instance.
|
||||
/// Should only be called as part of [LiquidSdk::connect].
|
||||
async fn start(self: &Arc<LiquidSdk>) -> SdkResult<()> {
|
||||
/// Should only be called once per instance.
|
||||
pub async fn start(self: &Arc<LiquidSdk>) -> SdkResult<()> {
|
||||
let mut is_started = self.is_started.write().await;
|
||||
self.persister
|
||||
.update_send_swaps_by_state(Created, TimedOut, Some(true))
|
||||
@@ -3305,13 +3309,14 @@ impl LiquidSdk {
|
||||
}
|
||||
|
||||
/// Empties the Liquid Wallet cache for the [Config::network].
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
pub fn empty_wallet_cache(&self) -> Result<()> {
|
||||
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)?;
|
||||
fs::create_dir_all(path)?;
|
||||
std::fs::remove_dir_all(&path)?;
|
||||
std::fs::create_dir_all(path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3827,6 +3832,7 @@ impl LiquidSdk {
|
||||
/// An error is thrown if the log file cannot be created in the working directory.
|
||||
///
|
||||
/// An error is thrown if a global logger is already configured.
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
pub fn init_logging(log_dir: &str, app_logger: Option<Box<dyn log::Log>>) -> Result<()> {
|
||||
crate::logger::init_logging(log_dir, app_logger)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ pub(crate) struct SyncCompletedData {
|
||||
pub(crate) pushed_records_count: u32,
|
||||
}
|
||||
|
||||
pub(crate) struct SyncService {
|
||||
pub struct SyncService {
|
||||
remote_url: String,
|
||||
client_id: String,
|
||||
persister: Arc<Persister>,
|
||||
|
||||
@@ -123,16 +123,38 @@ pub(crate) fn new_receive_swap(
|
||||
|
||||
macro_rules! create_persister {
|
||||
($name:ident) => {
|
||||
let temp_dir = tempdir::TempDir::new("liquid-sdk")?;
|
||||
let $name = std::sync::Arc::new(crate::persist::Persister::new(
|
||||
temp_dir
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
let $name = {
|
||||
let db_id = {
|
||||
use rand::Rng;
|
||||
let res: String = rand::thread_rng()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(16)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
res
|
||||
};
|
||||
std::sync::Arc::new(crate::persist::Persister::new_in_memory(
|
||||
&db_id,
|
||||
crate::model::LiquidNetwork::Testnet,
|
||||
true,
|
||||
None,
|
||||
)?)
|
||||
};
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
let $name = {
|
||||
let temp_dir_path = tempdir::TempDir::new("liquid-sdk")?
|
||||
.path()
|
||||
.to_str()
|
||||
.ok_or(anyhow::anyhow!("Could not create temporary directory"))?,
|
||||
crate::model::LiquidNetwork::Testnet,
|
||||
true,
|
||||
)?);
|
||||
$name.init()?;
|
||||
.ok_or(anyhow::anyhow!("Could not create temporary directory"))?
|
||||
.to_string();
|
||||
std::sync::Arc::new(crate::persist::Persister::new_using_fs(
|
||||
&temp_dir_path,
|
||||
crate::model::LiquidNetwork::Testnet,
|
||||
true,
|
||||
None,
|
||||
)?)
|
||||
};
|
||||
};
|
||||
}
|
||||
pub(crate) use create_persister;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, create_dir_all};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{path::Path, str::FromStr, sync::Arc};
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use boltz_client::ElementsAddress;
|
||||
@@ -10,10 +8,10 @@ use log::{debug, info, warn};
|
||||
use lwk_common::Signer as LwkSigner;
|
||||
use lwk_common::{singlesig_desc, Singlesig};
|
||||
use lwk_wollet::elements::{AssetId, Txid};
|
||||
use lwk_wollet::ElectrumOptions;
|
||||
use lwk_wollet::{
|
||||
elements::{hex::ToHex, Address, Transaction},
|
||||
ElectrumClient, ElectrumUrl, ElementsNetwork, FsPersister, WalletTx, Wollet, WolletDescriptor,
|
||||
ElectrumClient, ElectrumOptions, ElectrumUrl, ElementsNetwork, FsPersister, NoPersist,
|
||||
WalletTx, Wollet, WolletDescriptor,
|
||||
};
|
||||
use sdk_common::bitcoin::hashes::{sha256, Hash};
|
||||
use sdk_common::bitcoin::secp256k1::PublicKey;
|
||||
@@ -99,28 +97,29 @@ pub trait OnchainWallet: Send + Sync {
|
||||
async fn full_scan(&self) -> Result<(), PaymentError>;
|
||||
}
|
||||
|
||||
pub(crate) struct LiquidOnchainWallet {
|
||||
pub struct LiquidOnchainWallet {
|
||||
config: Config,
|
||||
persister: Arc<Persister>,
|
||||
wallet: Arc<Mutex<Wollet>>,
|
||||
electrum_client: Mutex<Option<ElectrumClient>>,
|
||||
working_dir: String,
|
||||
working_dir: Option<String>,
|
||||
pub(crate) signer: SdkLwkSigner,
|
||||
}
|
||||
|
||||
impl LiquidOnchainWallet {
|
||||
/// Creates a new LiquidOnchainWallet that caches data on the provided `working_dir`.
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
working_dir: &String,
|
||||
working_dir: String,
|
||||
persister: Arc<Persister>,
|
||||
user_signer: Arc<Box<dyn Signer>>,
|
||||
) -> Result<Self> {
|
||||
let signer = crate::signer::SdkLwkSigner::new(user_signer.clone())?;
|
||||
let wollet = Self::create_wallet(&config, working_dir, &signer)?;
|
||||
let signer = SdkLwkSigner::new(user_signer.clone())?;
|
||||
let wollet = Self::create_wallet(&config, Some(&working_dir), &signer)?;
|
||||
|
||||
let working_dir_buf = PathBuf::from_str(working_dir)?;
|
||||
let working_dir_buf = std::path::PathBuf::from_str(&working_dir)?;
|
||||
if !working_dir_buf.exists() {
|
||||
create_dir_all(&working_dir_buf)?;
|
||||
std::fs::create_dir_all(&working_dir_buf)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
@@ -128,40 +127,70 @@ impl LiquidOnchainWallet {
|
||||
persister,
|
||||
wallet: Arc::new(Mutex::new(wollet)),
|
||||
electrum_client: Mutex::new(None),
|
||||
working_dir: working_dir.clone(),
|
||||
working_dir: Some(working_dir),
|
||||
signer,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_wallet<P: AsRef<Path>>(
|
||||
/// Creates a new LiquidOnchainWallet that caches data in memory
|
||||
pub fn new_in_memory(
|
||||
config: Config,
|
||||
persister: Arc<Persister>,
|
||||
user_signer: Arc<Box<dyn Signer>>,
|
||||
) -> Result<Self> {
|
||||
let signer = SdkLwkSigner::new(user_signer.clone())?;
|
||||
let wollet = Self::create_wallet(&config, None, &signer)?;
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
persister,
|
||||
wallet: Arc::new(Mutex::new(wollet)),
|
||||
electrum_client: Mutex::new(None),
|
||||
working_dir: None,
|
||||
signer,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_wallet(
|
||||
config: &Config,
|
||||
working_dir: P,
|
||||
working_dir: Option<&str>,
|
||||
signer: &SdkLwkSigner,
|
||||
) -> Result<Wollet> {
|
||||
let elements_network: ElementsNetwork = config.network.into();
|
||||
let descriptor = LiquidOnchainWallet::get_descriptor(signer, config.network)?;
|
||||
let mut lwk_persister =
|
||||
FsPersister::new(working_dir.as_ref(), elements_network, &descriptor)?;
|
||||
let wollet_res = Wollet::new(elements_network, lwk_persister, descriptor.clone());
|
||||
let wollet_res = match &working_dir {
|
||||
Some(working_dir) => Wollet::new(
|
||||
elements_network,
|
||||
FsPersister::new(working_dir, elements_network, &descriptor)?,
|
||||
descriptor.clone(),
|
||||
),
|
||||
None => Wollet::new(elements_network, NoPersist::new(), descriptor.clone()),
|
||||
};
|
||||
match wollet_res {
|
||||
Ok(wollet) => Ok(wollet),
|
||||
Err(
|
||||
res @ Err(
|
||||
lwk_wollet::Error::PersistError(_)
|
||||
| lwk_wollet::Error::UpdateHeightTooOld { .. }
|
||||
| lwk_wollet::Error::UpdateOnDifferentStatus { .. },
|
||||
) => {
|
||||
warn!("Update error initialising wollet, wipping storage and retrying: {wollet_res:?}");
|
||||
let mut path = working_dir.as_ref().to_path_buf();
|
||||
path.push(elements_network.as_str());
|
||||
fs::remove_dir_all(&path)?;
|
||||
warn!("Wiping wallet in path: {:?}", path);
|
||||
lwk_persister = FsPersister::new(working_dir, elements_network, &descriptor)?;
|
||||
Ok(Wollet::new(
|
||||
elements_network,
|
||||
lwk_persister,
|
||||
descriptor.clone(),
|
||||
)?)
|
||||
}
|
||||
) => match working_dir {
|
||||
Some(working_dir) => {
|
||||
warn!(
|
||||
"Update error initialising wollet, wipping storage and retrying: {res:?}"
|
||||
);
|
||||
let mut path = std::path::PathBuf::from(working_dir);
|
||||
path.push(elements_network.as_str());
|
||||
std::fs::remove_dir_all(&path)?;
|
||||
warn!("Wiping wallet in path: {:?}", path);
|
||||
let lwk_persister =
|
||||
FsPersister::new(working_dir, elements_network, &descriptor)?;
|
||||
Ok(Wollet::new(
|
||||
elements_network,
|
||||
lwk_persister,
|
||||
descriptor.clone(),
|
||||
)?)
|
||||
}
|
||||
None => res.map_err(Into::into),
|
||||
},
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
@@ -397,7 +426,7 @@ impl OnchainWallet for LiquidOnchainWallet {
|
||||
Err(lwk_wollet::Error::UpdateHeightTooOld { .. }) => {
|
||||
warn!("Full scan failed with update height too old, wiping storage and retrying");
|
||||
let mut new_wallet =
|
||||
Self::create_wallet(&self.config, &self.working_dir, &self.signer)?;
|
||||
Self::create_wallet(&self.config, self.working_dir.as_deref(), &self.signer)?;
|
||||
lwk_wollet::full_scan_to_index_with_electrum_client(
|
||||
&mut new_wallet,
|
||||
index_with_buffer,
|
||||
@@ -443,7 +472,6 @@ mod tests {
|
||||
use crate::test_utils::persist::create_persister;
|
||||
use crate::wallet::LiquidOnchainWallet;
|
||||
use anyhow::Result;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
@@ -456,15 +484,27 @@ mod tests {
|
||||
|
||||
let config = Config::testnet(None);
|
||||
|
||||
// Create a temporary directory for working_dir
|
||||
let temp_dir = TempDir::new("").unwrap();
|
||||
let working_dir = temp_dir.path().to_str().unwrap().to_string();
|
||||
|
||||
create_persister!(storage);
|
||||
|
||||
let wallet: Arc<dyn OnchainWallet> = Arc::new(
|
||||
LiquidOnchainWallet::new(config, &working_dir, storage, sdk_signer.clone()).unwrap(),
|
||||
);
|
||||
let wallet: Arc<dyn OnchainWallet> =
|
||||
if cfg!(not(all(target_family = "wasm", target_os = "unknown"))) {
|
||||
// Create a temporary directory for working_dir
|
||||
let working_dir = tempdir::TempDir::new("")
|
||||
.unwrap()
|
||||
.path()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
Arc::new(
|
||||
LiquidOnchainWallet::new(config, working_dir, storage, sdk_signer.clone())
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
Arc::new(
|
||||
LiquidOnchainWallet::new_in_memory(config, storage, sdk_signer.clone())
|
||||
.unwrap(),
|
||||
)
|
||||
};
|
||||
|
||||
// Test message
|
||||
let message = "Hello, Liquid!";
|
||||
|
||||
@@ -6,15 +6,17 @@ mod signer;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::event::{EventListener, WasmEventListener};
|
||||
use crate::model::*;
|
||||
use anyhow::anyhow;
|
||||
use breez_sdk_liquid::sdk::LiquidSdk;
|
||||
use breez_sdk_liquid::persist::Persister;
|
||||
use breez_sdk_liquid::sdk::{LiquidSdk, LiquidSdkBuilder};
|
||||
use breez_sdk_liquid::wallet::LiquidOnchainWallet;
|
||||
use breez_sdk_liquid::PRODUCTION_BREEZSERVER_URL;
|
||||
use log::Level;
|
||||
use signer::{Signer, WasmSigner};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::event::{EventListener, WasmEventListener};
|
||||
use crate::model::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct BindingLiquidSdk {
|
||||
sdk: Arc<LiquidSdk>,
|
||||
@@ -22,8 +24,8 @@ pub struct BindingLiquidSdk {
|
||||
|
||||
#[wasm_bindgen(js_name = "connect")]
|
||||
pub async fn connect(req: ConnectRequest) -> WasmResult<BindingLiquidSdk> {
|
||||
let sdk = LiquidSdk::connect(req.into()).await?;
|
||||
Ok(BindingLiquidSdk { sdk })
|
||||
let signer = Box::new(LiquidSdk::default_signer(&req.clone().into())?);
|
||||
connect_inner(req.config, signer).await
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "connectWithSigner")]
|
||||
@@ -31,8 +33,42 @@ pub async fn connect_with_signer(
|
||||
req: ConnectWithSignerRequest,
|
||||
signer: Signer,
|
||||
) -> WasmResult<BindingLiquidSdk> {
|
||||
let wasm_signer = Box::new(WasmSigner { signer });
|
||||
let sdk = LiquidSdk::connect_with_signer(req.into(), wasm_signer).await?;
|
||||
let signer: Box<dyn breez_sdk_liquid::model::Signer> = Box::new(WasmSigner { signer });
|
||||
connect_inner(req.config, signer).await
|
||||
}
|
||||
|
||||
async fn connect_inner(
|
||||
config: Config,
|
||||
signer: Box<dyn breez_sdk_liquid::model::Signer>,
|
||||
) -> WasmResult<BindingLiquidSdk> {
|
||||
let config: breez_sdk_liquid::model::Config = config.into();
|
||||
let signer = Arc::new(signer);
|
||||
|
||||
let mut sdk_builder = LiquidSdkBuilder::new(
|
||||
config.clone(),
|
||||
PRODUCTION_BREEZSERVER_URL.to_string(),
|
||||
Arc::clone(&signer),
|
||||
)?;
|
||||
|
||||
let persister = Arc::new(Persister::new_in_memory(
|
||||
&config.working_dir,
|
||||
config.network,
|
||||
config.sync_enabled(),
|
||||
config.asset_metadata.clone(),
|
||||
)?);
|
||||
|
||||
let onchain_wallet = Arc::new(LiquidOnchainWallet::new_in_memory(
|
||||
config,
|
||||
Arc::clone(&persister),
|
||||
signer,
|
||||
)?);
|
||||
|
||||
sdk_builder.persister(persister);
|
||||
sdk_builder.onchain_wallet(onchain_wallet);
|
||||
|
||||
let sdk = sdk_builder.build()?;
|
||||
sdk.start().await?;
|
||||
|
||||
Ok(BindingLiquidSdk { sdk })
|
||||
}
|
||||
|
||||
@@ -294,12 +330,6 @@ impl BindingLiquidSdk {
|
||||
Ok(self.sdk.recommended_fees().await?.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "emptyWalletCache")]
|
||||
pub fn empty_wallet_cache(&self) -> WasmResult<()> {
|
||||
self.sdk.empty_wallet_cache()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "backup")]
|
||||
pub fn backup(&self, req: BackupRequest) -> WasmResult<()> {
|
||||
self.sdk.backup(req.into())?;
|
||||
|
||||
@@ -10,6 +10,7 @@ pub enum Network {
|
||||
Regtest,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::ExternalInputParser)]
|
||||
pub struct ExternalInputParser {
|
||||
pub provider_id: String,
|
||||
@@ -292,6 +293,7 @@ pub struct Symbol {
|
||||
pub position: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::Config)]
|
||||
pub struct Config {
|
||||
pub liquid_electrum_url: String,
|
||||
@@ -310,6 +312,7 @@ pub struct Config {
|
||||
pub asset_metadata: Option<Vec<AssetMetadata>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::LiquidNetwork)]
|
||||
pub enum LiquidNetwork {
|
||||
Mainnet,
|
||||
@@ -330,6 +333,7 @@ pub enum SdkEvent {
|
||||
Synced,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::ConnectRequest)]
|
||||
pub struct ConnectRequest {
|
||||
pub config: Config,
|
||||
@@ -639,6 +643,7 @@ pub struct LnUrlInfo {
|
||||
pub lnurl_withdraw_endpoint: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::AssetMetadata)]
|
||||
pub struct AssetMetadata {
|
||||
pub asset_id: String,
|
||||
|
||||
Reference in New Issue
Block a user