Skip to content

Commit

Permalink
Add wasm support
Browse files Browse the repository at this point in the history
Add a module "wasm" with structures to be called from wasm to use
bdk-cli in repl mode
  • Loading branch information
danielabrozzoni committed Sep 5, 2022
1 parent 51c42d1 commit 54d4804
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update `bdk` and `bdk-reserves` to v0.19.0.
- Change default database to `sqlite`.
- Change the `esplora-reqwest` feature to always use async mode
- Add a module `wasm` containing objects to use bdk-cli from web assembly

## [0.5.0]

Expand Down
15 changes: 13 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,18 @@ fd-lock = { version = "=3.0.2", optional = true }
regex = { version = "1", optional = true }
bdk-reserves = { version = "0.19", optional = true}
electrsd = { version= "0.12", features = ["trigger", "bitcoind_22_0"], optional = true}
tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread"], optional = true }

# Platform-specific dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "=0.2.79", features = ["serde-serialize"] }
wasm-bindgen-futures = { version = "0.4" }
js-sys = "=0.3.56"
wasm-logger = "0.2.0"
secp256k1 = { version = "0.22.0", default-features = false }
rand = { version = "^0.6", features = ["wasm-bindgen"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread"] }

[features]
default = ["repl", "sqlite-db"]
Expand All @@ -45,7 +56,7 @@ electrum = ["bdk/electrum"]
compact_filters = ["bdk/compact_filters"]
esplora = []
esplora-ureq = ["esplora", "bdk/use-esplora-ureq"]
async-interface = ["bdk/async-interface", "tokio"]
async-interface = ["bdk/async-interface"]
esplora-reqwest = ["esplora", "bdk/use-esplora-reqwest", "bdk/reqwest-default-tls", "async-interface"]

# Use this to consensus verify transactions at sync time
Expand Down
2 changes: 1 addition & 1 deletion src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ pub enum KeySubCommand {
},
}

#[cfg(feature = "repl")]
#[cfg(any(feature = "repl", target_arch = "wasm32"))]
#[derive(Debug, StructOpt, Clone, PartialEq)]
#[structopt(global_settings =&[AppSettings::NoBinaryName])]
pub enum ReplSubCommand {
Expand Down
7 changes: 7 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ mod commands;
mod handlers;
mod nodes;
mod utils;
#[cfg(target_arch = "wasm32")]
mod wasm;

use nodes::Nodes;

Expand All @@ -31,6 +33,7 @@ use structopt::StructOpt;
const REPL_LINE_SPLIT_REGEX: &str = r#""([^"]*)"|'([^']*)'|([\w\-]+)"#;

#[maybe_async]
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(feature = "async-interface", tokio::main)]
fn main() {
env_logger::init();
Expand Down Expand Up @@ -109,3 +112,7 @@ fn main() {
},
}
}

// wasm32 requires a non-async main
#[cfg(target_arch = "wasm32")]
fn main() {}
92 changes: 92 additions & 0 deletions src/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use crate::commands::OfflineWalletSubCommand::*;
#[cfg(any(
feature = "electrum",
feature = "esplora",
feature = "compact_filters",
feature = "rpc"
))]
use crate::commands::OnlineWalletSubCommand::*;
use crate::commands::*;
use crate::handlers::*;
use crate::utils::*;
use crate::Nodes;
use bdk::database::AnyDatabase;
use bdk::{bitcoin::Network, Error, Wallet};
use js_sys::Promise;
use std::rc::Rc;
use std::str::FromStr;
use structopt::StructOpt;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;

#[wasm_bindgen]
pub struct WasmWallet {
wallet: Rc<Wallet<AnyDatabase>>,
wallet_opts: Rc<WalletOpts>,
network: Network,
}

#[wasm_bindgen]
pub fn log_init() {
wasm_logger::init(wasm_logger::Config::default());
}

#[wasm_bindgen]
impl WasmWallet {
#[wasm_bindgen(constructor)]
pub fn new(network: JsValue, wallet_opts: Vec<JsValue>) -> Result<WasmWallet, String> {
let wallet_opts = wallet_opts
.into_iter()
.map(|a| a.as_string().expect("Invalid type"));
let wallet_opts: WalletOpts =
WalletOpts::from_iter_safe(wallet_opts).map_err(|e| e.to_string())?;
let network = network
.as_string()
.ok_or_else(|| "Invalid network :)".to_string())?;
let network = Network::from_str(&network).map_err(|e| e.to_string())?;
let wallet_opts =
maybe_descriptor_wallet_name(wallet_opts, network).map_err(|e| e.to_string())?;
let database = open_database(&wallet_opts).map_err(|e| e.to_string())?;
let wallet = new_wallet(network, &wallet_opts, database).map_err(|e| e.to_string())?;
Ok(WasmWallet {
wallet: Rc::new(wallet),
wallet_opts: Rc::new(wallet_opts),
network,
})
}

pub fn run_command(&self, command: Vec<JsValue>) -> Promise {
let wallet = Rc::clone(&self.wallet);
let wallet_opts = Rc::clone(&self.wallet_opts);
let network = self.network;

// The promise returned contains a Result<JsValue, String>
future_to_promise(async move {
let command = command
.into_iter()
.map(|a| a.as_string().expect("Invalid type"));
let repl_subcommand =
ReplSubCommand::from_iter_safe(command).map_err(|e| e.to_string())?;
log::debug!("repl_subcommand = {:?}", repl_subcommand);

let result = match repl_subcommand {
ReplSubCommand::OnlineWalletSubCommand(online_subcommand) => {
let blockchain = new_blockchain(network, &wallet_opts, &Nodes::None)
.map_err(|e| e.to_string())?;
handle_online_wallet_subcommand(&wallet, &blockchain, online_subcommand).await
}
ReplSubCommand::OfflineWalletSubCommand(offline_subcommand) => {
handle_offline_wallet_subcommand(&wallet, &wallet_opts, offline_subcommand)
}
ReplSubCommand::KeySubCommand(key_subcommand) => {
handle_key_subcommand(network, key_subcommand)
}
ReplSubCommand::Exit => return Ok(JsValue::NULL),
};

result
.map(|v| JsValue::from_serde(&v).expect("Serde serialization failed"))
.map_err(|e| e.to_string().into())
})
}
}

0 comments on commit 54d4804

Please sign in to comment.