diff --git a/.gitignore b/.gitignore index a981609..5ea1458 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target node_modules .DS_STORE +.idea diff --git a/Cargo.lock b/Cargo.lock index c918736..1efa0cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,18 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -59,6 +71,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "anyhow" version = "1.0.88" @@ -423,6 +441,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "daggy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a9304e55e9d601a39ae4deaba85406d5c0980e106f65afcf0460e9af1e7602" +dependencies = [ + "petgraph", +] + [[package]] name = "delegate-display" version = "2.1.1" @@ -473,7 +500,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equihash" version = "0.2.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "blake2b_simd", "byteorder", @@ -498,11 +525,23 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "blake2b_simd", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fancy_constructor" version = "1.3.0" @@ -746,6 +785,19 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] [[package]] name = "heck" @@ -990,6 +1042,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1353,6 +1416,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "poly1305" version = "0.8.0" @@ -1626,6 +1695,21 @@ dependencies = [ "digest", ] +[[package]] +name = "rusqlite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "time", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1728,6 +1812,29 @@ dependencies = [ "zip32", ] +[[package]] +name = "schemer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835d8f9478fd2936195fc941a8666b0d0894d5bf3631cbb884a8ce8ba631f339" +dependencies = [ + "daggy", + "log", + "thiserror", + "uuid", +] + +[[package]] +name = "schemer-rusqlite" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fb5ac1fa52c58e2c6a618e3149d464e7ad8d0effca74990ea29c1fe2338b3b1" +dependencies = [ + "rusqlite", + "schemer", + "uuid", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1929,11 +2036,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "itoa", "js-sys", "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -1942,6 +2051,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2288,6 +2407,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -2544,6 +2669,8 @@ dependencies = [ "ripemd", "secrecy", "sha2", + "subtle", + "tempfile", "thiserror", "tokio", "tokio_with_wasm", @@ -2560,6 +2687,7 @@ dependencies = [ "zcash_address", "zcash_client_backend", "zcash_client_memory", + "zcash_client_sqlite", "zcash_keys", "zcash_primitives", "zcash_proofs", @@ -2702,7 +2830,7 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.5.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "bech32", "bs58", @@ -2714,7 +2842,7 @@ dependencies = [ [[package]] name = "zcash_client_backend" version = "0.13.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "base64 0.21.7", "bech32", @@ -2758,7 +2886,7 @@ dependencies = [ [[package]] name = "zcash_client_memory" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "bs58", "byteorder", @@ -2785,10 +2913,46 @@ dependencies = [ "zip32", ] +[[package]] +name = "zcash_client_sqlite" +version = "0.11.2" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" +dependencies = [ + "bs58", + "byteorder", + "document-features", + "group", + "incrementalmerkletree", + "jubjub", + "maybe-rayon", + "nonempty", + "orchard", + "prost 0.13.2", + "regex", + "rusqlite", + "sapling-crypto", + "schemer", + "schemer-rusqlite", + "secrecy", + "shardtree", + "static_assertions", + "subtle", + "time", + "tracing", + "uuid", + "zcash_address", + "zcash_client_backend", + "zcash_encoding", + "zcash_keys", + "zcash_primitives", + "zcash_protocol", + "zip32", +] + [[package]] name = "zcash_encoding" version = "0.2.1" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "byteorder", "nonempty", @@ -2797,7 +2961,7 @@ dependencies = [ [[package]] name = "zcash_keys" version = "0.3.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "bech32", "bip32", @@ -2838,7 +3002,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.17.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "aes", "bip32", @@ -2876,7 +3040,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.17.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "bellman", "blake2b_simd", @@ -2896,7 +3060,7 @@ dependencies = [ [[package]] name = "zcash_protocol" version = "0.3.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "document-features", "memuse", @@ -2965,7 +3129,7 @@ dependencies = [ [[package]] name = "zip321" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=a77e8a0204dab421fdbf5822e585716339567b96#a77e8a0204dab421fdbf5822e585716339567b96" +source = "git+https://github.com/ChainSafe/librustzcash?rev=c97a3f6e60446523fafa63cdf77b6c2584ac9f3b#c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" dependencies = [ "base64 0.21.7", "nom", diff --git a/Cargo.toml b/Cargo.toml index 12632a1..b19a435 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,14 @@ resolver = "2" [lib] crate-type = ["cdylib", "rlib"] +[[example]] +name = "simple-sync" +path = "examples/simple-sync.rs" + +[[example]] +name = "message-board-sync" +path = "examples/message-board-sync.rs" + [profile.release] # Tell `rustc` to optimize for small code size. opt-level = 3 @@ -24,12 +32,13 @@ wasm-opt = ["-O4", "-O4"] [features] default = ["native", "multicore"] -native = ["tonic/channel", "tonic/gzip", "tonic/tls-webpki-roots"] multicore = ["zcash_proofs/multicore", "zcash_primitives/multicore", "zcash_client_memory/multicore"] # WASM specific features wasm = ["console_error_panic_hook", "dep:tracing-web", "zcash_client_backend/wasm-bindgen"] wasm-parallel = ["wasm", "wasm-bindgen-rayon", "multicore"] +native = ["dep:tokio", "tonic/channel", "tonic/gzip", "tonic/tls-webpki-roots"] +sqlite-db = ["dep:zcash_client_sqlite"] console_error_panic_hook = ["dep:console_error_panic_hook"] no-bundler = ["wasm-bindgen-rayon?/no-bundler"] @@ -38,7 +47,7 @@ no-bundler = ["wasm-bindgen-rayon?/no-bundler"] wasm-bindgen = "0.2.93" js-sys = "0.3.70" wasm-bindgen-futures = "0.4.43" -web-sys = { version = "0.3.70", features = ["console"] } +web-sys = { version = "0.3.70", features = ["console"] } wasm-bindgen-rayon = { version = "1.2.1", optional = true } # WASM specific dependencies @@ -48,13 +57,12 @@ tonic-web-wasm-client = "0.6.0" tokio_with_wasm = { version = "0.7.1", features = ["rt", "rt-multi-thread", "sync", "macros"] } ## Zcash dependencies -zcash_keys = { git = "https://github.com/ChainSafe/librustzcash", rev = "a77e8a0204dab421fdbf5822e585716339567b96", features = ["transparent-inputs", "orchard", "sapling", "unstable"] } -zcash_client_backend = { git = "https://github.com/ChainSafe/librustzcash", rev = "a77e8a0204dab421fdbf5822e585716339567b96", default-features = false, features = ["lightwalletd-tonic"] } -zcash_client_memory = { git = "https://github.com/ChainSafe/librustzcash", rev = "a77e8a0204dab421fdbf5822e585716339567b96", features = ["orchard"] } -zcash_primitives = { git = "https://github.com/ChainSafe/librustzcash", rev = "a77e8a0204dab421fdbf5822e585716339567b96" } -zcash_address = { git = "https://github.com/ChainSafe/librustzcash", rev = "a77e8a0204dab421fdbf5822e585716339567b96" } -zcash_proofs = { git = "https://github.com/ChainSafe/librustzcash", rev = "a77e8a0204dab421fdbf5822e585716339567b96", default-features = false, features = ["bundled-prover"] } - +zcash_keys = { git = "https://github.com/ChainSafe/librustzcash", rev = "c97a3f6e60446523fafa63cdf77b6c2584ac9f3b", features = ["transparent-inputs", "orchard", "sapling", "unstable"] } +zcash_client_backend = { git = "https://github.com/ChainSafe/librustzcash", rev = "c97a3f6e60446523fafa63cdf77b6c2584ac9f3b", default-features = false, features = ["lightwalletd-tonic"] } +zcash_client_memory = { git = "https://github.com/ChainSafe/librustzcash", rev = "c97a3f6e60446523fafa63cdf77b6c2584ac9f3b", features = ["orchard"] } +zcash_primitives = { git = "https://github.com/ChainSafe/librustzcash", rev = "c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" } +zcash_address = { git = "https://github.com/ChainSafe/librustzcash", rev = "c97a3f6e60446523fafa63cdf77b6c2584ac9f3b" } +zcash_proofs = { git = "https://github.com/ChainSafe/librustzcash", rev = "c97a3f6e60446523fafa63cdf77b6c2584ac9f3b", default-features = false, features = ["bundled-prover"] } ## gRPC Web dependencies prost = { version = "0.12", default-features = false } @@ -62,6 +70,11 @@ tonic = { version = "0.12", default-features = false, features = [ "prost", ] } + +# Used in Native tests +tokio = { version = "1.0", features = ["rt", "macros", "rt-multi-thread"], optional = true } +zcash_client_sqlite = { git = "https://github.com/ChainSafe/librustzcash", rev = "c97a3f6e60446523fafa63cdf77b6c2584ac9f3b", default-features = false, features = ["unstable", "orchard"], optional = true } + getrandom = { version = "0.2", features = ["js"] } thiserror = "1.0.63" indexed_db_futures = "0.5.0" @@ -75,9 +88,11 @@ hex = "0.4.3" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing = "0.1.40" rayon = "1.8" +subtle = "2.6.1" [dev-dependencies] wasm-bindgen-test = "0.3.43" +tempfile = "3.12" # Used in Native tests tokio = { version = "1.0", features = ["rt", "macros"] } @@ -98,3 +113,5 @@ wasm-bindgen-rayon = { git = "https://github.com/9SMTM6/wasm-bindgen-rayon", rev # zcash_proofs = {path = "../librustzcash/zcash_proofs" } # zcash_protocol = { path = "../librustzcash/components/zcash_protocol"} # zip32 = { git = "https://github.com/zcash/zip32.git", branch = "diversifier_index_ord"} + + diff --git a/examples/message-board-sync.rs b/examples/message-board-sync.rs new file mode 100644 index 0000000..dcb4496 --- /dev/null +++ b/examples/message-board-sync.rs @@ -0,0 +1,77 @@ +use std::sync::Once; + +use std::num::NonZeroU32; +use webz_core::Wallet; +use zcash_primitives::consensus::Network; +static INIT: Once = Once::new(); +const SAPLING_EFVK: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz"; +pub fn initialize() { + INIT.call_once(|| { + webz_core::init::start(); + }); +} +#[tokio::main] +async fn main() { + use zcash_keys::keys::UnifiedFullViewingKey; + use zcash_primitives::{consensus, constants}; + let db_cache = tempfile::tempdir().unwrap(); + let _db_data = tempfile::NamedTempFile::new_in(db_cache.path()).unwrap(); + + initialize(); + let url = "https://zec.rocks:443"; + let c = tonic::transport::Channel::from_shared(url).unwrap(); + + let tls = tonic::transport::ClientTlsConfig::new() + .domain_name("zec.rocks") + .with_webpki_roots(); + let channel = c.tls_config(tls).unwrap(); + + #[cfg(feature = "sqlite-db")] + let wallet_db = { + use zcash_client_sqlite::{ + chain::init::init_blockmeta_db, wallet::init::init_wallet_db, FsBlockDb, WalletDb, + }; + + let mut db_cache = FsBlockDb::for_path(&db_cache).unwrap(); + let mut wallet_db = WalletDb::for_path(&_db_data, consensus::Network::MainNetwork).unwrap(); + init_blockmeta_db(&mut db_cache).unwrap(); + init_wallet_db(&mut wallet_db, None).unwrap(); + wallet_db + }; + + #[cfg(not(feature = "sqlite-db"))] + let wallet_db = zcash_client_memory::MemoryWalletDb::new( + consensus::Network::MainNetwork, + webz_core::PRUNING_DEPTH, + ); + + let mut w = Wallet::new( + wallet_db, + channel.connect().await.unwrap(), + Network::MainNetwork, + NonZeroU32::try_from(1).unwrap(), + ) + .unwrap(); + + let s = zcash_keys::encoding::decode_extended_full_viewing_key( + constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + SAPLING_EFVK.trim(), + ) + .unwrap(); + + let ufvk = UnifiedFullViewingKey::from_sapling_extended_full_viewing_key(s).unwrap(); + let id = w.import_ufvk(&ufvk, Some(2477329)).await.unwrap(); + tracing::info!("Created account with id: {}", id); + + tracing::info!("Syncing wallet"); + w.sync(|scanned_to, tip| { + println!("Scanned: {}/{}", scanned_to, tip); + }) + .await + .unwrap(); + + tracing::info!("Syncing complete :)"); + + let summary = w.get_wallet_summary().unwrap(); + tracing::info!("Wallet summary: {:?}", summary); +} diff --git a/examples/simple-sync.rs b/examples/simple-sync.rs new file mode 100644 index 0000000..f13bf78 --- /dev/null +++ b/examples/simple-sync.rs @@ -0,0 +1,92 @@ +use std::sync::Once; + +use std::num::NonZeroU32; +use webz_core::Wallet; +use zcash_address::ZcashAddress; +use zcash_primitives::consensus::Network; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +macro_rules! console_log { + ($($t:tt)*) => (web_sys::console::log_1(&format!($($t)*).into())) +} +const SEED: &str = "visit armed kite pen cradle toward reward clay marble oil write dove blind oyster silk oyster original message skate bench tone enable stadium element"; +const HD_INDEX: u32 = 0; +const BIRTHDAY: Option = Some(2577329); + +static INIT: Once = Once::new(); + +pub fn initialize() { + INIT.call_once(|| { + webz_core::init::start(); + }); +} + +#[cfg(feature = "native")] +#[tokio::main] +async fn main() { + let db_cache = tempfile::tempdir().unwrap(); + let _db_data = tempfile::NamedTempFile::new_in(db_cache.path()).unwrap(); + + initialize(); + rayon::spawn(|| { + let num_parallel = rayon::current_num_threads(); + tracing::info!("Native rayon has {} threads", num_parallel); + }); + + let url = "https://testnet.zec.rocks:443"; + let c = tonic::transport::Channel::from_shared(url).unwrap(); + + let tls = tonic::transport::ClientTlsConfig::new() + .domain_name("testnet.zec.rocks") + .with_webpki_roots(); + let channel = c.tls_config(tls).unwrap(); + + #[cfg(feature = "sqlite-db")] + let wallet_db = { + use zcash_client_sqlite::{ + chain::init::init_blockmeta_db, wallet::init::init_wallet_db, FsBlockDb, WalletDb, + }; + + let mut db_cache = FsBlockDb::for_path(&db_cache).unwrap(); + let mut wallet_db = WalletDb::for_path(&_db_data, Network::TestNetwork).unwrap(); + init_blockmeta_db(&mut db_cache).unwrap(); + init_wallet_db(&mut wallet_db, None).unwrap(); + wallet_db + }; + + #[cfg(not(feature = "sqlite-db"))] + let wallet_db = + zcash_client_memory::MemoryWalletDb::new(Network::TestNetwork, webz_core::PRUNING_DEPTH); + + let mut w = Wallet::new( + wallet_db, + channel.connect().await.unwrap(), + Network::TestNetwork, + NonZeroU32::try_from(1).unwrap(), + ) + .unwrap(); + + let id = w.create_account(SEED, HD_INDEX, BIRTHDAY).await.unwrap(); + tracing::info!("Created account with id: {}", id); + + tracing::info!("Syncing wallet"); + w.sync(|scanned_to, tip| { + println!("Scanned: {}/{}", scanned_to, tip); + }) + .await + .unwrap(); + + tracing::info!("Syncing complete :)"); + + let summary = w.get_wallet_summary().unwrap(); + tracing::info!("Wallet summary: {:?}", summary); + + tracing::info!("Proposing a transaction"); + let addr = ZcashAddress::try_from_encoded("utest1z00xn09t4eyeqw9zmjss75sf460423dymgyfjn8rtlj26cffy0yad3eea82xekk24s00wnm38cvyrm2c6x7fxlc0ns4a5j7utgl6lchvglfvl9g9p56fqwzvzvj9d3z6r6ft88j654d7dj0ep6myq5duz9s8x78fdzmtx04d2qn8ydkxr4lfdhlkx9ktrw98gd97dateegrr68vl8xu"); + + w.transfer(SEED, 0, addr.unwrap(), 1000).await.unwrap(); + tracing::info!("Transaction proposed"); + + let summary = w.get_wallet_summary().unwrap(); + tracing::info!("Wallet summary: {:?}", summary); +} diff --git a/justfile b/justfile index 6b039da..6b1b41e 100644 --- a/justfile +++ b/justfile @@ -7,11 +7,30 @@ default: build: wasm-pack build -t web --release --out-dir ./packages/webz-core -Z --no-default-features --features="wasm-parallel,no-bundler" build-std="panic_abort,std" +## Wasm Tests test-web: WASM_BINDGEN_TEST_TIMEOUT=99999 wasm-pack test --release --firefox --no-default-features --features="wasm-parallel,no-bundler" -Z build-std="panic_abort,std" -test-native: - cargo test -r -- --nocapture + +test-message-board-web: + WASM_BINDGEN_TEST_TIMEOUT=99999 wasm-pack test --release --firefox --no-default-features --features="wasm-parallel,no-bundler" -Z build-std="panic_abort,std" --test message-board-sync + +test-simple-web: + WASM_BINDGEN_TEST_TIMEOUT=99999 wasm-pack test --release --firefox --no-default-features --features="wasm-parallel,no-bundler" -Z build-std="panic_abort,std" --test simple-sync-and-send + + +## Native Examples +example-simple: + cargo run -r --example simple-sync + +example-simple-sqlite: + cargo run -r --example simple-sync --features="sqlite-db" + +example-message-board: + cargo run -r --example message-board-sync + +example-message-board-sqlite: + cargo run -r --example message-board-sync --features="sqlite-db" alias c := check diff --git a/src/bindgen/wallet.rs b/src/bindgen/wallet.rs index 9c1530c..542f48c 100644 --- a/src/bindgen/wallet.rs +++ b/src/bindgen/wallet.rs @@ -6,10 +6,12 @@ use wasm_bindgen::prelude::*; use tonic_web_wasm_client::Client; use zcash_address::ZcashAddress; +use zcash_client_memory::MemoryWalletDb; +use zcash_keys::keys::UnifiedFullViewingKey; use zcash_primitives::consensus::{self, BlockHeight}; use crate::error::Error; -use crate::{BlockRange, Wallet}; +use crate::{BlockRange, MemoryWallet, Wallet, PRUNING_DEPTH}; /// # A Zcash wallet /// @@ -32,7 +34,17 @@ use crate::{BlockRange, Wallet}; /// #[wasm_bindgen] pub struct WebWallet { - inner: Wallet, + inner: MemoryWallet, +} + +impl WebWallet { + fn network_from_str(network: &str) -> Result { + match network { + "main" => Ok(consensus::Network::MainNetwork), + "test" => Ok(consensus::Network::TestNetwork), + _ => Err(Error::InvalidNetwork(network.to_string())), + } + } } #[wasm_bindgen] @@ -44,19 +56,18 @@ impl WebWallet { lightwalletd_url: &str, min_confirmations: u32, ) -> Result { - let network = match network { - "main" => consensus::Network::MainNetwork, - "test" => consensus::Network::TestNetwork, - _ => { - return Err(Error::InvalidNetwork(network.to_string())); - } - }; + let network = Self::network_from_str(network)?; let min_confirmations = NonZeroU32::try_from(min_confirmations) .map_err(|_| Error::InvalidMinConformations(min_confirmations))?; let client = Client::new(lightwalletd_url.to_string()); Ok(Self { - inner: Wallet::new(client, network, min_confirmations)?, + inner: Wallet::new( + MemoryWalletDb::new(network, PRUNING_DEPTH), + client, + network, + min_confirmations, + )?, }) } @@ -78,6 +89,17 @@ impl WebWallet { .await } + pub async fn import_ufvk( + &mut self, + key: &str, + birthday_height: Option, + ) -> Result { + let ufvk = UnifiedFullViewingKey::decode(&self.inner.network, key) + .map_err(Error::KeyParseError)?; + + self.inner.import_ufvk(&ufvk, birthday_height).await + } + pub fn suggest_scan_ranges(&self) -> Result, Error> { self.inner.suggest_scan_ranges() } diff --git a/src/error.rs b/src/error.rs index 283a6ef..0987210 100644 --- a/src/error.rs +++ b/src/error.rs @@ -44,6 +44,12 @@ pub enum Error { InvalidAmount(#[from] zcash_primitives::transaction::components::amount::BalanceError), #[error("Failed to send transaction")] SendFailed { code: i32, reason: String }, + #[error("Failed to parse key: {0}")] + KeyParseError(String), + + #[cfg(feature = "sqlite-db")] + #[error("Sqlite error: {0}")] + SqliteError(#[from] zcash_client_sqlite::error::SqliteClientError), } impl From for JsValue { diff --git a/src/init.rs b/src/init.rs index 61b8190..32c1956 100644 --- a/src/init.rs +++ b/src/init.rs @@ -26,7 +26,7 @@ fn setup_tracing() { .with_default_directive(LevelFilter::INFO.into()) .from_env() .unwrap(); - let fmt_layer = tracing_subscriber::fmt::layer().with_ansi(true).pretty(); + let fmt_layer = tracing_subscriber::fmt::layer().with_ansi(true); tracing_subscriber::registry() .with(filter_layer) .with(fmt_layer) diff --git a/src/lib.rs b/src/lib.rs index b731246..7dcc67a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,9 +13,16 @@ pub mod wallet; pub use wallet::Wallet; use wasm_bindgen::prelude::*; +use zcash_client_memory::MemoryWalletDb; +use zcash_primitives::consensus; + +/// The maximum number of checkpoints to store in each shard-tree +pub const PRUNING_DEPTH: usize = 100; #[cfg(all(feature = "wasm-parallel"))] pub use wasm_bindgen_rayon::init_thread_pool; #[wasm_bindgen] pub struct BlockRange(pub u32, pub u32); + +pub type MemoryWallet = Wallet, T>; diff --git a/src/wallet.rs b/src/wallet.rs index a04c7f8..9d79d50 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -15,15 +15,21 @@ use crate::BlockRange; use rayon::iter::IntoParallelIterator; use rayon::iter::IntoParallelRefIterator; use rayon::iter::ParallelIterator; +use std::fmt::Debug; +use std::hash::Hash; +use subtle::ConditionallySelectable; use zcash_address::ZcashAddress; use zcash_client_backend::data_api::wallet::{ create_proposed_transactions, input_selection::GreedyInputSelector, propose_transfer, }; +use zcash_client_backend::data_api::{scanning::ScanRange, WalletCommitmentTrees}; use zcash_client_backend::data_api::{ AccountBirthday, AccountPurpose, InputSource, NullifierQuery, WalletRead, WalletSummary, WalletWrite, }; use zcash_client_backend::fees::zip317::SingleOutputChangeStrategy; +use zcash_client_backend::proposal::Proposal; +use zcash_client_backend::proto::compact_formats::CompactBlock; use zcash_client_backend::proto::service::{ self, compact_tx_streamer_client::CompactTxStreamerClient, }; @@ -31,24 +37,21 @@ use zcash_client_backend::scanning::{scan_block, Nullifiers, ScanningKeys}; use zcash_client_backend::wallet::OvkPolicy; use zcash_client_backend::zip321::{Payment, TransactionRequest}; use zcash_client_backend::ShieldedProtocol; -use zcash_client_backend::{data_api::scanning::ScanRange, proto::compact_formats::CompactBlock}; use zcash_client_memory::MemoryWalletDb; -use zcash_keys::keys::UnifiedSpendingKey; +use zcash_keys::keys::{UnifiedFullViewingKey, UnifiedSpendingKey}; use zcash_primitives::consensus::{self, BlockHeight, Network}; use zcash_primitives::transaction::components::amount::NonNegativeAmount; use zcash_primitives::transaction::fees::zip317::FeeRule; use zcash_primitives::transaction::TxId; use zcash_proofs::prover::LocalTxProver; -const BATCH_SIZE: u32 = 10000; /// The maximum number of checkpoints to store in each shard-tree const PRUNING_DEPTH: usize = 100; -type Proposal = - zcash_client_backend::proposal::Proposal; - fn is_sync() {} fn is_send() {} +const BATCH_SIZE: u32 = 10000; + /// # A Zcash wallet /// /// A wallet is a set of accounts that can be synchronized together with the blockchain. @@ -68,18 +71,30 @@ fn is_send() {} /// /// TODO /// - -pub struct Wallet { +pub struct Wallet { /// Internal database used to maintain wallet data (e.g. accounts, transactions, cached blocks) - pub(crate) db: MemoryWalletDb, + pub(crate) db: W, // gRPC client used to connect to a lightwalletd instance for network data pub(crate) client: CompactTxStreamerClient, pub(crate) network: consensus::Network, pub(crate) min_confirmations: NonZeroU32, } -impl Wallet +impl Wallet where + W: WalletRead + + WalletWrite + + InputSource< + AccountId = ::AccountId, + Error = ::Error, + NoteRef = NoteRef, + > + WalletCommitmentTrees, + + AccountId: Copy + Debug + Eq + Hash + Default + Send + ConditionallySelectable + 'static, + NoteRef: Copy + Eq + Ord + Debug, + Error: From<::Error>, + + // GRPC connection Trait Bounds T: GrpcService, T::Error: Into, T::ResponseBody: Body + std::marker::Send + 'static, @@ -87,12 +102,13 @@ where { /// Create a new instance of a Zcash wallet for a given network pub fn new( + db: W, client: T, network: Network, min_confirmations: NonZeroU32, - ) -> Result, Error> { + ) -> Result { Ok(Wallet { - db: MemoryWalletDb::new(network, PRUNING_DEPTH), + db, client: CompactTxStreamerClient::new(client), network, min_confirmations, @@ -116,6 +132,26 @@ where let usk = usk_from_seed_str(seed_phrase, account_index, &self.network)?; let ufvk = usk.to_unified_full_viewing_key(); + self.import_account_ufvk(&ufvk, birthday_height, AccountPurpose::Spending) + .await + } + + pub async fn import_ufvk( + &mut self, + ufvk: &UnifiedFullViewingKey, + birthday_height: Option, + ) -> Result { + self.import_account_ufvk(ufvk, birthday_height, AccountPurpose::ViewOnly) + .await + } + + /// Helper method for importing an account directly from a Ufvk or from seed. + async fn import_account_ufvk( + &mut self, + ufvk: &UnifiedFullViewingKey, + birthday_height: Option, + purpose: AccountPurpose, + ) -> Result { let birthday = match birthday_height { Some(height) => height, None => { @@ -142,10 +178,8 @@ where AccountBirthday::from_treestate(treestate, None).map_err(|_| Error::BirthdayError)? }; - let _account = self - .db - .import_account_ufvk(&ufvk, &birthday, AccountPurpose::Spending)?; - // TOOD: Make this public on account Ok(account.account_id().to_string()) + let _account = self.db.import_account_ufvk(ufvk, &birthday, purpose)?; + Ok("0".to_string()) } @@ -243,7 +277,7 @@ where self.db.put_blocks( &chainstate, blocks - .into_par_iter() + .into_iter() .map(|compact_block| { scan_block( &self.network, @@ -260,16 +294,7 @@ where Ok(()) } - pub fn get_wallet_summary( - &self, - ) -> Result< - Option< - WalletSummary< - as WalletRead>::AccountId, - >, - >, - Error, - > { + pub fn get_wallet_summary(&self) -> Result>, Error> { Ok(self.db.get_wallet_summary(self.min_confirmations.into())?) } @@ -299,7 +324,7 @@ where account_index: usize, to_address: ZcashAddress, value: u64, - ) -> Result { + ) -> Result, Error> { let account_id = self.db.get_account_ids()?[account_index]; let input_selector = GreedyInputSelector::new( @@ -320,12 +345,7 @@ where .get_target_and_anchor_heights(self.min_confirmations)? ); - let proposal = propose_transfer::< - _, - _, - _, - as InputSource>::Error, - >( + let proposal = propose_transfer::<_, _, _, ::Error>( &mut self.db, &self.network, account_id, @@ -346,7 +366,7 @@ where /// pub(crate) fn create_proposed_transactions( &mut self, - proposal: Proposal, + proposal: Proposal, usk: &UnifiedSpendingKey, ) -> Result, Error> { let prover = LocalTxProver::bundled(); diff --git a/tests/message-board-sync.rs b/tests/message-board-sync.rs new file mode 100644 index 0000000..41ffac2 --- /dev/null +++ b/tests/message-board-sync.rs @@ -0,0 +1,47 @@ +use wasm_bindgen_test::*; +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +use std::sync::Once; +use webz_core::bindgen::wallet::WebWallet; +use zcash_keys::keys::UnifiedFullViewingKey; +use zcash_primitives::consensus::Network; +use zcash_primitives::constants; + +// Required to initialize the logger and panic hooks only once +static INIT: Once = Once::new(); +pub fn initialize() { + INIT.call_once(|| { + webz_core::init::start(); + }); +} +const SAPLING_EFVK: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz"; + +#[wasm_bindgen_test] +async fn test_message_board() { + initialize(); + + let mut w = WebWallet::new("main", "https://zcash-mainnet.chainsafe.dev", 1).unwrap(); + + let s = zcash_keys::encoding::decode_extended_full_viewing_key( + constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + SAPLING_EFVK.trim(), + ) + .unwrap(); + + let ufvk = UnifiedFullViewingKey::from_sapling_extended_full_viewing_key(s).unwrap(); + let ufvk_str = ufvk.encode(&Network::MainNetwork); + let id = w.import_ufvk(&ufvk_str, Some(2477329)).await.unwrap(); + tracing::info!("Created account with id: {}", id); + + tracing::info!("Syncing wallet"); + w.sync(&js_sys::Function::new_with_args( + "scanned_to, tip", + "console.log('Scanned: ', scanned_to, '/', tip)", + )) + .await + .unwrap(); + tracing::info!("Syncing complete :)"); + + let summary = w.get_wallet_summary().unwrap(); + tracing::info!("Wallet summary: {:?}", summary); +} diff --git a/tests/simple-sync-and-send.rs b/tests/simple-sync-and-send.rs new file mode 100644 index 0000000..610cd67 --- /dev/null +++ b/tests/simple-sync-and-send.rs @@ -0,0 +1,46 @@ +use wasm_bindgen_test::*; +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +use std::sync::Once; +use webz_core::bindgen::wallet::WebWallet; + +const SEED: &str = "visit armed kite pen cradle toward reward clay marble oil write dove blind oyster silk oyster original message skate bench tone enable stadium element"; +const HD_INDEX: u32 = 0; +const BIRTHDAY: Option = Some(2577329); + +// Required to initialize the logger and panic hooks only once +static INIT: Once = Once::new(); +pub fn initialize() { + INIT.call_once(|| { + webz_core::init::start(); + }); +} + +#[wasm_bindgen_test] +async fn test_get_and_scan_range() { + initialize(); + + let mut w = WebWallet::new("test", "https://zcash-testnet.chainsafe.dev", 1).unwrap(); + + let id = w.create_account(SEED, HD_INDEX, BIRTHDAY).await.unwrap(); + tracing::info!("Created account with id: {}", id); + + tracing::info!("Syncing wallet"); + w.sync(&js_sys::Function::new_with_args( + "scanned_to, tip", + "console.log('Scanned: ', scanned_to, '/', tip)", + )) + .await + .unwrap(); + tracing::info!("Syncing complete :)"); + + let summary = w.get_wallet_summary().unwrap(); + tracing::info!("Wallet summary: {:?}", summary); + + tracing::info!("Proposing a transaction"); + w.transfer(SEED, 0, "utest1z00xn09t4eyeqw9zmjss75sf460423dymgyfjn8rtlj26cffy0yad3eea82xekk24s00wnm38cvyrm2c6x7fxlc0ns4a5j7utgl6lchvglfvl9g9p56fqwzvzvj9d3z6r6ft88j654d7dj0ep6myq5duz9s8x78fdzmtx04d2qn8ydkxr4lfdhlkx9ktrw98gd97dateegrr68vl8xu".to_string(), 1000).await.unwrap(); + tracing::info!("Transaction proposed"); + + let summary = w.get_wallet_summary().unwrap(); + tracing::info!("Wallet summary: {:?}", summary); +} diff --git a/tests/tests.rs b/tests/tests.rs deleted file mode 100644 index e61c112..0000000 --- a/tests/tests.rs +++ /dev/null @@ -1,124 +0,0 @@ -use wasm_bindgen_test::*; - -use webz_core::{bindgen::wallet::WebWallet, Wallet}; -use zcash_address::ZcashAddress; -use zcash_primitives::consensus::Network; - -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); -macro_rules! console_log { - ($($t:tt)*) => (web_sys::console::log_1(&format!($($t)*).into())) -} -const SEED: &str = "visit armed kite pen cradle toward reward clay marble oil write dove blind oyster silk oyster original message skate bench tone enable stadium element"; -const HD_INDEX: u32 = 0; -const BIRTHDAY: Option = Some(2577329); - -const THREADS: usize = 5; -use tokio_with_wasm::alias as tokio; - -static INIT: Once = Once::new(); -// Required to initialize the logger and panic hooks only once -use std::{num::NonZeroU32, sync::Once}; -pub fn initialize() { - INIT.call_once(|| { - webz_core::init::start(); - }); -} -#[cfg(all(feature = "wasm-parallel"))] -async fn init_threadpool(threads: usize) -> wasm_bindgen_futures::JsFuture { - console_log!("Initializing thread pool with {} threads", threads); - wasm_bindgen_futures::JsFuture::from(wasm_bindgen_rayon::init_thread_pool(threads)) -} -fn is_sync() {} -fn is_send() {} -#[wasm_bindgen_test] -async fn test_get_and_scan_range() { - initialize(); - - #[cfg(all(feature = "wasm-parallel"))] - init_threadpool(THREADS).await; - - let mut w = WebWallet::new("test", "http://localhost:443", 1).unwrap(); - is_sync::(); - is_send::(); - let id = w.create_account(SEED, HD_INDEX, BIRTHDAY).await.unwrap(); - tracing::info!("Created account with id: {}", id); - - tracing::info!("Syncing wallet"); - // Note to self: js_sys::Function is NOT send or sync - // TODO: Find a better way to do this... Perhaps passing a Stream or something instead? - w.sync(&js_sys::Function::new_with_args( - "scanned_to, tip", - "console.log('Scanned: ', scanned_to, '/', tip)", - )) - .await - .unwrap(); - tracing::info!("Syncing complete :)"); - - let summary = w.get_wallet_summary().unwrap(); - tracing::info!("Wallet summary: {:?}", summary); - - tracing::info!("Proposing a transaction"); - let handle = tokio::task::spawn_blocking(|| { - rayon::scope(|s| { - s.spawn(|_| { - let num_parallel = rayon::current_num_threads(); - tracing::info!("WASM rayon has {} threads", num_parallel); - }) - }); - }); - w.transfer(SEED, 0, "utest1z00xn09t4eyeqw9zmjss75sf460423dymgyfjn8rtlj26cffy0yad3eea82xekk24s00wnm38cvyrm2c6x7fxlc0ns4a5j7utgl6lchvglfvl9g9p56fqwzvzvj9d3z6r6ft88j654d7dj0ep6myq5duz9s8x78fdzmtx04d2qn8ydkxr4lfdhlkx9ktrw98gd97dateegrr68vl8xu".to_string(), 1000).await.unwrap(); - tracing::info!("Transaction proposed"); - - let summary = w.get_wallet_summary().unwrap(); - tracing::info!("Wallet summary: {:?}", summary); - - handle.await.unwrap(); -} - -#[cfg(feature = "native")] -#[tokio::test] -async fn test_get_and_scan_range_native() { - initialize(); - rayon::spawn(|| { - let num_parallel = rayon::current_num_threads(); - tracing::info!("Native rayon has {} threads", num_parallel); - }); - - let url = "https://testnet.zec.rocks:443"; - let c = tonic::transport::Channel::from_shared(url).unwrap(); - - let tls = tonic::transport::ClientTlsConfig::new() - .domain_name("testnet.zec.rocks") - .with_webpki_roots(); - let channel = c.tls_config(tls).unwrap(); - let mut w = Wallet::new( - channel.connect().await.unwrap(), - Network::TestNetwork, - NonZeroU32::try_from(1).unwrap(), - ) - .unwrap(); - - let id = w.create_account(SEED, HD_INDEX, BIRTHDAY).await.unwrap(); - tracing::info!("Created account with id: {}", id); - - tracing::info!("Syncing wallet"); - w.sync(|scanned_to, tip| { - println!("Scanned: {}/{}", scanned_to, tip); - }) - .await - .unwrap(); - - tracing::info!("Syncing complete :)"); - - let summary = w.get_wallet_summary().unwrap(); - tracing::info!("Wallet summary: {:?}", summary); - - tracing::info!("Proposing a transaction"); - let addr = ZcashAddress::try_from_encoded("utest1z00xn09t4eyeqw9zmjss75sf460423dymgyfjn8rtlj26cffy0yad3eea82xekk24s00wnm38cvyrm2c6x7fxlc0ns4a5j7utgl6lchvglfvl9g9p56fqwzvzvj9d3z6r6ft88j654d7dj0ep6myq5duz9s8x78fdzmtx04d2qn8ydkxr4lfdhlkx9ktrw98gd97dateegrr68vl8xu"); - - w.transfer(SEED, 0, addr.unwrap(), 1000).await.unwrap(); - tracing::info!("Transaction proposed"); - - let summary = w.get_wallet_summary().unwrap(); - tracing::info!("Wallet summary: {:?}", summary); -}