From 443ad173dd770eafff632ae9ad03f6c002e4f310 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Mon, 30 Sep 2024 13:39:00 -0400 Subject: [PATCH 01/11] remove the legacy sync algo --- Cargo.toml | 1 - justfile | 10 +-- src/bindgen/wallet.rs | 23 +----- src/wallet.rs | 137 +--------------------------------- tests/message-board-sync.rs | 18 +---- tests/simple-sync-and-send.rs | 16 +--- 6 files changed, 17 insertions(+), 188 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3881961..ac92dd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,6 @@ native = ["tonic/channel", "tonic/gzip", "tonic/tls-webpki-roots", "tokio/macros sqlite-db = ["dep:zcash_client_sqlite"] console_error_panic_hook = ["dep:console_error_panic_hook"] no-bundler = ["wasm-bindgen-rayon?/no-bundler", "wasm_thread/no-bundler"] -sync2 = [] [dependencies] ## Web dependencies diff --git a/justfile b/justfile index 1c62999..46ea915 100644 --- a/justfile +++ b/justfile @@ -2,26 +2,26 @@ default: just --list build *features: - wasm-pack build --no-opt -t web --scope webzjs --release --out-dir ./packages/webz-core --no-default-features --features="wasm wasm-parallel sync2 {{features}}" -Z build-std="panic_abort,std" + wasm-pack build --no-opt -t web --scope webzjs --release --out-dir ./packages/webz-core --no-default-features --features="wasm wasm-parallel {{features}}" -Z build-std="panic_abort,std" # All Wasm Tests test-web *features: WASM_BINDGEN_TEST_TIMEOUT=99999 wasm-pack test --release --firefox --no-default-features --features "wasm no-bundler {{features}}" -Z build-std="panic_abort,std" -# sync message board in the web: addigional args: sync2 +# sync message board in the web: addigional args: test-message-board-web *features: WASM_BINDGEN_TEST_TIMEOUT=99999 wasm-pack test --release --chrome --no-default-features --features "wasm no-bundler {{features}}" -Z build-std="panic_abort,std" --test message-board-sync -# simple example in the web: additional args: sync2 +# simple example in the web: additional args: test-simple-web *features: WASM_BINDGEN_TEST_TIMEOUT=99999 wasm-pack test --release --chrome --no-default-features --features "wasm no-bundler {{features}}" -Z build-std="panic_abort,std" --test simple-sync-and-send -# simple example: additional args: sync2, sqlite-db +# simple example: additional args:, sqlite-db example-simple *features: RUST_LOG="info,zcash_client_backend::sync=debug" cargo run -r --example simple-sync --features "native {{features}}" -# sync the message board: additional args: sync2, sqlite-db +# sync the message board: additional args:, sqlite-db example-message-board *features: RUST_LOG=info,zcash_client_backend::sync=debug cargo run -r --example message-board-sync --features "native {{features}}" diff --git a/src/bindgen/wallet.rs b/src/bindgen/wallet.rs index 0d0b1e7..400175b 100644 --- a/src/bindgen/wallet.rs +++ b/src/bindgen/wallet.rs @@ -117,31 +117,14 @@ impl WebWallet { self.inner.suggest_scan_ranges().await } - /// Synchronize the wallet with the blockchain up to the tip - /// The passed callback will be called for every batch of blocks processed with the current progress - pub async fn sync(&self, callback: &js_sys::Function) -> Result<(), Error> { - let callback = move |scanned_to: BlockHeight, tip: BlockHeight| { - let this = JsValue::null(); - let _ = callback.call2( - &this, - &JsValue::from(Into::::into(scanned_to)), - &JsValue::from(Into::::into(tip)), - ); - }; - - self.inner.sync(callback).await?; - - Ok(()) - } - /// Synchronize the wallet with the blockchain up to the tip using zcash_client_backend's algo - pub async fn sync2(&self) -> Result<(), Error> { + pub async fn sync(&self) -> Result<(), Error> { assert!(!thread::is_web_worker_thread()); let db = self.inner.clone(); let sync_handler = thread::Builder::new() - .name("sync2".to_string()) + .name("sync".to_string()) .spawn_async(|| async { assert!(thread::is_web_worker_thread()); tracing::debug!( @@ -150,7 +133,7 @@ impl WebWallet { ); let db = db; - db.sync2().await.unwrap_throw(); + db.sync().await.unwrap_throw(); }) .unwrap_throw() .join_async(); diff --git a/src/wallet.rs b/src/wallet.rs index 71cc1cc..63d385f 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,7 +1,6 @@ use std::num::NonZeroU32; use bip0039::{English, Mnemonic}; -use futures_util::{StreamExt, TryStreamExt}; use nonempty::NonEmpty; use secrecy::{ExposeSecret, SecretVec, Zeroize}; use tonic::{ @@ -22,23 +21,21 @@ 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::WalletCommitmentTrees; use zcash_client_backend::data_api::{ - AccountBirthday, AccountPurpose, InputSource, NullifierQuery, WalletRead, WalletSummary, - WalletWrite, + AccountBirthday, AccountPurpose, InputSource, WalletRead, WalletSummary, WalletWrite, }; use zcash_client_backend::fees::zip317::SingleOutputChangeStrategy; use zcash_client_backend::proposal::Proposal; use zcash_client_backend::proto::service::{ self, compact_tx_streamer_client::CompactTxStreamerClient, }; -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_memory::{MemBlockCache, MemoryWalletDb}; use zcash_keys::keys::{UnifiedFullViewingKey, UnifiedSpendingKey}; -use zcash_primitives::consensus::{self, BlockHeight, Network}; +use zcash_primitives::consensus::{self, Network}; use zcash_primitives::transaction::components::amount::NonNegativeAmount; use zcash_primitives::transaction::fees::zip317::FeeRule; use zcash_primitives::transaction::TxId; @@ -228,7 +225,7 @@ where })?) } - pub async fn sync2(&self) -> Result<(), Error> { + pub async fn sync(&self) -> Result<(), Error> { let mut client = self.client.clone(); // TODO: This should be held in the Wallet struct so we can download in parallel let db_cache = MemBlockCache::new(); @@ -245,113 +242,6 @@ where .map_err(Into::into) } - /// Synchronize the wallet with the blockchain up to the tip - /// The passed callback will be called for every batch of blocks processed with the current progress - pub async fn sync(&self, callback: impl Fn(BlockHeight, BlockHeight)) -> Result<(), Error> { - let tip = self.update_chain_tip().await?; - - tracing::info!("Retrieving suggested scan ranges from wallet"); - let scan_ranges = self.db.read().await.suggest_scan_ranges()?; - tracing::info!("Suggested scan ranges: {:?}", scan_ranges); - - // TODO: Ensure wallet's view of the chain tip as of the previous wallet session is valid. - // See https://github.com/Electric-Coin-Company/zec-sqlite-cli/blob/8c2e49f6d3067ec6cc85248488915278c3cb1c5a/src/commands/sync.rs#L157 - - // Download and process all blocks in the requested ranges - // Split each range into BATCH_SIZE chunks to avoid requesting too many blocks at once - for scan_range in scan_ranges.into_iter().flat_map(|r| { - // Limit the number of blocks we download and scan at any one time. - (0..).scan(r, |acc, _| { - if acc.is_empty() { - None - } else if let Some((cur, next)) = acc.split_at(acc.block_range().start + BATCH_SIZE) - { - *acc = next; - Some(cur) - } else { - let cur = acc.clone(); - let end = acc.block_range().end; - *acc = ScanRange::from_parts(end..end, acc.priority()); - Some(cur) - } - }) - }) { - self.fetch_and_scan_range( - scan_range.block_range().start.into(), - scan_range.block_range().end.into(), - ) - .await?; - callback(scan_range.block_range().end, tip); - } - - Ok(()) - } - - /// Download and process all blocks in the given range - async fn fetch_and_scan_range(&self, start: u32, end: u32) -> Result<(), Error> { - let mut client = self.client.clone(); - // get the chainstate prior to the range - let tree_state = client - .get_tree_state(service::BlockId { - height: (start - 1).into(), - ..Default::default() - }) - .await?; - let chainstate = tree_state.into_inner().to_chain_state()?; - - // Get the scanning keys from the DB - let account_ufvks = self.db.read().await.get_unified_full_viewing_keys()?; - let scanning_keys = ScanningKeys::from_account_ufvks(account_ufvks); - - // Get the nullifiers for the unspent notes we are tracking - let nullifiers = Nullifiers::new( - self.db - .read() - .await - .get_sapling_nullifiers(NullifierQuery::Unspent)?, - self.db - .read() - .await - .get_orchard_nullifiers(NullifierQuery::Unspent)?, - ); - - let range = service::BlockRange { - start: Some(service::BlockId { - height: start.into(), - ..Default::default() - }), - end: Some(service::BlockId { - height: (end - 1).into(), - ..Default::default() - }), - }; - - tracing::info!("Scanning block range: {:?} to {:?}", start, end); - - let scanned_blocks = client - .get_block_range(range) - .await? - .into_inner() - .map(|compact_block| { - scan_block( - &self.network, - compact_block.unwrap(), - &scanning_keys, - &nullifiers, - None, - ) - }) - .try_collect() - .await?; - - self.db - .write() - .await - .put_blocks(&chainstate, scanned_blocks)?; - - Ok(()) - } - pub async fn get_wallet_summary(&self) -> Result>, Error> { Ok(self .db @@ -360,25 +250,6 @@ where .get_wallet_summary(self.min_confirmations.into())?) } - pub(crate) async fn update_chain_tip(&self) -> Result { - tracing::info!("Retrieving chain tip from lightwalletd"); - - let tip_height = self - .client - .clone() - .get_latest_block(service::ChainSpec::default()) - .await? - .get_ref() - .height - .try_into() - .unwrap(); - - tracing::info!("Latest block height is {}", tip_height); - self.db.write().await.update_chain_tip(tip_height)?; - - Ok(tip_height) - } - /// /// Create a transaction proposal to send funds from the wallet to a given address /// diff --git a/tests/message-board-sync.rs b/tests/message-board-sync.rs index 829bb7a..8163e54 100644 --- a/tests/message-board-sync.rs +++ b/tests/message-board-sync.rs @@ -39,21 +39,9 @@ async fn test_message_board() { let id = w.import_ufvk(&ufvk_str, Some(2477329)).await.unwrap(); tracing::info!("Created account with id: {}", id); - #[cfg(not(feature = "sync2"))] - { - tracing::info!("Syncing wallet with our sync impl"); - w.sync(&js_sys::Function::new_with_args( - "scanned_to, tip", - "console.log('Scanned: ', scanned_to, '/', tip)", - )) - .await - .unwrap(); - } - #[cfg(feature = "sync2")] - { - tracing::info!("Syncing wallet with sync2"); - w.sync2().await.unwrap(); - } + tracing::info!("Syncing wallet with our sync impl"); + w.sync().await.unwrap(); + tracing::info!("Syncing complete :)"); let summary = w.get_wallet_summary().await.unwrap(); diff --git a/tests/simple-sync-and-send.rs b/tests/simple-sync-and-send.rs index 68b48c7..b5f2e1f 100644 --- a/tests/simple-sync-and-send.rs +++ b/tests/simple-sync-and-send.rs @@ -33,20 +33,8 @@ async fn test_get_and_scan_range() { let w_clone = w.clone(); let w = w_clone; - #[cfg(not(feature = "sync2"))] - { - w.sync(&js_sys::Function::new_with_args( - "scanned_to, tip", - "console.log('Scanned: ', scanned_to, '/', tip)", - )) - .await - .unwrap(); - } - #[cfg(feature = "sync2")] - { - tracing::info!("Syncing wallet with sync2"); - w.sync2().await.unwrap(); - } + w.sync().await.unwrap(); + tracing::info!("Syncing complete :)"); let summary = w.get_wallet_summary().await.unwrap(); From 442ab3e4ab898f41cdcbe8c186619e36f61dda0e Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Mon, 30 Sep 2024 14:01:53 -0400 Subject: [PATCH 02/11] add new fn to wallet to just send an existing transaction --- src/error.rs | 2 ++ src/wallet.rs | 72 +++++++++++++++++++++++++-------------------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/error.rs b/src/error.rs index 5dfb8c3..b097f3e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,6 +59,8 @@ pub enum Error { SqliteError(#[from] zcash_client_sqlite::error::SqliteClientError), #[error("Invalid seed phrase")] InvalidSeedPhrase, + #[error("Failed when creating transaction")] + FailedToCreateTransaction, } impl From for JsValue { diff --git a/src/wallet.rs b/src/wallet.rs index 71cc1cc..e4c0dfc 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -451,27 +451,52 @@ where OvkPolicy::Sender, &proposal, ) - .unwrap(); + .map_err(|_| Error::FailedToCreateTransaction)?; Ok(transactions) } + pub async fn send_authorized_transactions( + &mut self, + txids: &NonEmpty, + ) -> Result<(), Error> { + for txid in txids.iter() { + let (txid, raw_tx) = self + .db + .read() + .await + .get_transaction(*txid)? + .map(|tx| { + let mut raw_tx = service::RawTransaction::default(); + tx.write(&mut raw_tx.data).unwrap(); + (tx.txid(), raw_tx) + }) + .unwrap(); + + let response = self.client.send_transaction(raw_tx).await?.into_inner(); + + if response.error_code != 0 { + return Err(Error::SendFailed { + code: response.error_code, + reason: response.error_message, + }); + } else { + tracing::info!("Transaction {} send successfully :)", txid); + } + } + Ok(()) + } + /// - /// Create a transaction proposal to send funds from the wallet to a given address and if approved will sign it and send the proposed transaction(s) to the network - /// - /// First a proposal is created by selecting inputs and outputs to cover the requested amount. This proposal is then sent to the approval callback. - /// This allows wallet developers to display a confirmation dialog to the user before continuing. - /// - /// # Arguments + /// A helper function that creates a proposal, creates a transation from the proposal and then submits it /// pub async fn transfer( - &self, + &mut self, seed_phrase: &str, from_account_index: usize, to_address: ZcashAddress, value: u64, ) -> Result<(), Error> { - let mut client = self.client.clone(); let usk = usk_from_seed_str(seed_phrase, 0, &self.network)?; let proposal = self .propose_transfer(from_account_index, to_address, value) @@ -480,33 +505,8 @@ where let txids = self.create_proposed_transactions(proposal, &usk).await?; // send the transactions to the network!! - tracing::info!("Sending transaction..."); - let txid = *txids.first(); - let (txid, raw_tx) = self - .db - .read() - .await - .get_transaction(txid)? - .map(|tx| { - let mut raw_tx = service::RawTransaction::default(); - tx.write(&mut raw_tx.data).unwrap(); - (tx.txid(), raw_tx) - }) - .unwrap(); - - // tracing::info!("Transaction hex: 0x{}", hex::encode(&raw_tx.data)); - - let response = client.send_transaction(raw_tx).await?.into_inner(); - - if response.error_code != 0 { - Err(Error::SendFailed { - code: response.error_code, - reason: response.error_message, - }) - } else { - tracing::info!("Transaction {} send successfully :)", txid); - Ok(()) - } + tracing::info!("Sending transactions"); + self.send_authorized_transactions(&txids).await } } From bacb41a9067e08ed8988ff52b1e3bd76941dd4a5 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Mon, 30 Sep 2024 16:17:28 -0400 Subject: [PATCH 03/11] draft propose_transfer that returns serialized proposal --- Cargo.lock | 36 ++++++++++++++++++++++----------- Cargo.toml | 14 ++++++------- src/bindgen/wallet.rs | 47 ++++++++++++++++++++++++++++++++++++++++++- src/wallet.rs | 11 +++++----- 4 files changed, 83 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d23467..759f765 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -806,7 +806,7 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "equihash" version = "0.2.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "blake2b_simd", "byteorder", @@ -858,7 +858,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "blake2b_simd", ] @@ -1680,6 +1680,9 @@ name = "nonempty" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +dependencies = [ + "serde", +] [[package]] name = "nu-ansi-term" @@ -3648,11 +3651,13 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.5.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "bech32", "bs58", "f4jumble", + "serde", + "serde_with", "zcash_encoding", "zcash_protocol", ] @@ -3660,7 +3665,7 @@ dependencies = [ [[package]] name = "zcash_client_backend" version = "0.13.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "async-trait", "base64", @@ -3686,6 +3691,8 @@ dependencies = [ "rayon", "sapling-crypto", "secrecy", + "serde", + "serde_with", "shardtree", "subtle", "time", @@ -3706,7 +3713,7 @@ dependencies = [ [[package]] name = "zcash_client_memory" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "async-trait", "bs58", @@ -3741,7 +3748,7 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" version = "0.11.2" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "bs58", "byteorder", @@ -3777,7 +3784,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.2.1" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "byteorder", "nonempty", @@ -3786,7 +3793,7 @@ dependencies = [ [[package]] name = "zcash_keys" version = "0.3.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "bech32", "bip32", @@ -3827,7 +3834,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.17.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "aes", "bip32", @@ -3851,6 +3858,7 @@ dependencies = [ "ripemd", "sapling-crypto", "secp256k1", + "serde", "sha2", "subtle", "tracing", @@ -3865,7 +3873,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.17.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "bellman", "blake2b_simd", @@ -3885,10 +3893,12 @@ dependencies = [ [[package]] name = "zcash_protocol" version = "0.3.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "document-features", "memuse", + "serde", + "serde_with", ] [[package]] @@ -3954,11 +3964,13 @@ dependencies = [ [[package]] name = "zip321" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a" +source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" dependencies = [ "base64", "nom", "percent-encoding", + "serde", + "serde_with", "zcash_address", "zcash_protocol", ] diff --git a/Cargo.toml b/Cargo.toml index 69da9c9..a94006d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,12 +61,12 @@ tokio_with_wasm = { version = "0.7.1", features = ["rt", "rt-multi-thread", "syn ## Zcash dependencies -zcash_keys = { git = "https://github.com/ChainSafe/librustzcash", rev = "d1fa3e846c5e61de3f1df23dd9f4d5416915631a", features = ["transparent-inputs", "orchard", "sapling", "unstable"] } -zcash_client_backend = { git = "https://github.com/ChainSafe/librustzcash", rev = "d1fa3e846c5e61de3f1df23dd9f4d5416915631a", default-features = false, features = ["sync", "lightwalletd-tonic", "wasm-bindgen", "orchard"] } -zcash_client_memory = { git = "https://github.com/ChainSafe/librustzcash", rev = "d1fa3e846c5e61de3f1df23dd9f4d5416915631a", features = ["orchard"] } -zcash_primitives = { git = "https://github.com/ChainSafe/librustzcash", rev = "d1fa3e846c5e61de3f1df23dd9f4d5416915631a" } -zcash_address = { git = "https://github.com/ChainSafe/librustzcash", rev = "d1fa3e846c5e61de3f1df23dd9f4d5416915631a" } -zcash_proofs = { git = "https://github.com/ChainSafe/librustzcash", rev = "d1fa3e846c5e61de3f1df23dd9f4d5416915631a", default-features = false, features = ["bundled-prover"] } +zcash_keys = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", features = ["transparent-inputs", "orchard", "sapling", "unstable"] } +zcash_client_backend = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", default-features = false, features = ["sync", "lightwalletd-tonic", "wasm-bindgen", "orchard"] } +zcash_client_memory = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", features = ["orchard"] } +zcash_primitives = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" } +zcash_address = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" } +zcash_proofs = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", default-features = false, features = ["bundled-prover"] } ## gRPC Web dependencies prost = { version = "0.12", default-features = false } @@ -77,7 +77,7 @@ tonic = { version = "0.12", default-features = false, features = [ # Used in Native tests tokio = { version = "1.0" } -zcash_client_sqlite = { git = "https://github.com/ChainSafe/librustzcash", rev = "d1fa3e846c5e61de3f1df23dd9f4d5416915631a", default-features = false, features = ["unstable", "orchard"], optional = true } +zcash_client_sqlite = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", default-features = false, features = ["unstable", "orchard"], optional = true } getrandom = { version = "0.2", features = ["js"] } thiserror = "1.0.63" diff --git a/src/bindgen/wallet.rs b/src/bindgen/wallet.rs index f974f97..eeff254 100644 --- a/src/bindgen/wallet.rs +++ b/src/bindgen/wallet.rs @@ -9,17 +9,22 @@ use crate::error::Error; use crate::{BlockRange, Wallet, PRUNING_DEPTH}; use wasm_thread as thread; use zcash_address::ZcashAddress; -use zcash_client_backend::data_api::WalletRead; +use zcash_client_backend::data_api::{WalletRead, InputSource}; +use zcash_client_backend::proposal::Proposal; use zcash_client_backend::proto::service::{ compact_tx_streamer_client::CompactTxStreamerClient, ChainSpec, }; use zcash_client_memory::MemoryWalletDb; use zcash_keys::keys::UnifiedFullViewingKey; use zcash_primitives::consensus::{self, BlockHeight}; +use zcash_primitives::transaction::fees::zip317::FeeRule; +use zcash_primitives::transaction::TxId; + pub type MemoryWallet = Wallet, T>; pub type AccountId = as WalletRead>::AccountId; +pub type NoteRef = as InputSource>::NoteRef; /// # A Zcash wallet /// @@ -193,6 +198,46 @@ impl WebWallet { .await } + /// + /// Create a transaction proposal to send funds from the wallet to a given address. + /// + pub async fn propose_transfer( + &self, + account_id: u32, + to_address: String, + value: u64, + ) -> Result { + let to_address = ZcashAddress::try_from_encoded(&to_address)?; + let proposal = self.inner.propose_transfer(AccountId::from(account_id), to_address, value).await?; + Ok(serde_wasm_bindgen::to_value(&proposal).unwrap()) + } + + /// + /// Perform the proving and signing required to create one or more transaction from the proposal. Created transactions are stored in the wallet database. + /// + /// Note: At the moment this requires a USK but ideally we want to be able to hand the signing off to a separate service + /// e.g. browser plugin, hardware wallet, etc. Will need to look into refactoring librustzcash create_proposed_transactions to allow for this + /// + // pub async fn create_proposed_transactions( + // &self, + // proposal: Proposal, + // usk: &UnifiedSpendingKey, + // ) -> Result, Error> { + // self.inner + // .create_proposed_transactions(proposal, usk) + // .await + // } + + /// + /// Send a list of transactions to the network via the lightwalletd instance this wallet is connected to + /// + // pub async fn send_authorized_transactions( + // &self, + // txids: &[TxId], + // ) -> Result<(), Error> { + // self.inner.send_authorized_transactions(txids).await + // } + /// Forwards a call to lightwalletd to retrieve the height of the latest block in the chain pub async fn get_latest_block(&self) -> Result { self.client() diff --git a/src/wallet.rs b/src/wallet.rs index b69f153..aaffa46 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -381,7 +381,7 @@ where /// /// Create a transaction proposal to send funds from the wallet to a given address /// - async fn propose_transfer( + pub async fn propose_transfer( &self, account_id: AccountId, to_address: ZcashAddress, @@ -426,7 +426,7 @@ where /// Note: At the moment this requires a USK but ideally we want to be able to hand the signing off to a separate service /// e.g. browser plugin, hardware wallet, etc. Will need to look into refactoring librustzcash create_proposed_transactions to allow for this /// - pub(crate) async fn create_proposed_transactions( + pub async fn create_proposed_transactions( &self, proposal: Proposal, usk: &UnifiedSpendingKey, @@ -454,9 +454,10 @@ where } pub async fn send_authorized_transactions( - &mut self, + &self, txids: &NonEmpty, ) -> Result<(), Error> { + let mut client = self.client.clone(); for txid in txids.iter() { let (txid, raw_tx) = self .db @@ -470,7 +471,7 @@ where }) .unwrap(); - let response = self.client.send_transaction(raw_tx).await?.into_inner(); + let response = client.send_transaction(raw_tx).await?.into_inner(); if response.error_code != 0 { return Err(Error::SendFailed { @@ -488,7 +489,7 @@ where /// A helper function that creates a proposal, creates a transation from the proposal and then submits it /// pub async fn transfer( - &mut self, + &self, seed_phrase: &str, from_account_id: AccountId, to_address: ZcashAddress, From 874d78d3705d23fa1362185769fe57ba7d282460 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 1 Oct 2024 14:28:22 -0400 Subject: [PATCH 04/11] restructured send process --- Cargo.lock | 24 ++--- Cargo.toml | 14 +-- packages/demo-wallet/src/App/Actions.tsx | 10 ++- .../src/App/components/ImportAccount.tsx | 1 + src/bindgen/mod.rs | 1 + src/bindgen/proposal.rs | 32 +++++++ src/bindgen/wallet.rs | 89 +++++++------------ src/wallet.rs | 7 +- 8 files changed, 97 insertions(+), 81 deletions(-) create mode 100644 src/bindgen/proposal.rs diff --git a/Cargo.lock b/Cargo.lock index 759f765..3429cde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -806,7 +806,7 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "equihash" version = "0.2.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "blake2b_simd", "byteorder", @@ -858,7 +858,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "blake2b_simd", ] @@ -3651,7 +3651,7 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.5.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "bech32", "bs58", @@ -3665,7 +3665,7 @@ dependencies = [ [[package]] name = "zcash_client_backend" version = "0.13.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "async-trait", "base64", @@ -3713,7 +3713,7 @@ dependencies = [ [[package]] name = "zcash_client_memory" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "async-trait", "bs58", @@ -3748,7 +3748,7 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" version = "0.11.2" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "bs58", "byteorder", @@ -3784,7 +3784,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.2.1" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "byteorder", "nonempty", @@ -3793,7 +3793,7 @@ dependencies = [ [[package]] name = "zcash_keys" version = "0.3.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "bech32", "bip32", @@ -3834,7 +3834,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.17.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "aes", "bip32", @@ -3873,7 +3873,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.17.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "bellman", "blake2b_simd", @@ -3893,7 +3893,7 @@ dependencies = [ [[package]] name = "zcash_protocol" version = "0.3.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "document-features", "memuse", @@ -3964,7 +3964,7 @@ dependencies = [ [[package]] name = "zip321" version = "0.1.0" -source = "git+https://github.com/ChainSafe/librustzcash?rev=efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0#efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" +source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f" dependencies = [ "base64", "nom", diff --git a/Cargo.toml b/Cargo.toml index a94006d..5af0dd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,12 +61,12 @@ tokio_with_wasm = { version = "0.7.1", features = ["rt", "rt-multi-thread", "syn ## Zcash dependencies -zcash_keys = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", features = ["transparent-inputs", "orchard", "sapling", "unstable"] } -zcash_client_backend = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", default-features = false, features = ["sync", "lightwalletd-tonic", "wasm-bindgen", "orchard"] } -zcash_client_memory = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", features = ["orchard"] } -zcash_primitives = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" } -zcash_address = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0" } -zcash_proofs = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", default-features = false, features = ["bundled-prover"] } +zcash_keys = { git = "https://github.com/ChainSafe/librustzcash", rev = "9673cc2859e8a2528d1efd3c74795363f87ddf8f", features = ["transparent-inputs", "orchard", "sapling", "unstable"] } +zcash_client_backend = { git = "https://github.com/ChainSafe/librustzcash", rev = "9673cc2859e8a2528d1efd3c74795363f87ddf8f", default-features = false, features = ["sync", "lightwalletd-tonic", "wasm-bindgen", "orchard"] } +zcash_client_memory = { git = "https://github.com/ChainSafe/librustzcash", rev = "9673cc2859e8a2528d1efd3c74795363f87ddf8f", features = ["orchard"] } +zcash_primitives = { git = "https://github.com/ChainSafe/librustzcash", rev = "9673cc2859e8a2528d1efd3c74795363f87ddf8f" } +zcash_address = { git = "https://github.com/ChainSafe/librustzcash", rev = "9673cc2859e8a2528d1efd3c74795363f87ddf8f" } +zcash_proofs = { git = "https://github.com/ChainSafe/librustzcash", rev = "9673cc2859e8a2528d1efd3c74795363f87ddf8f", default-features = false, features = ["bundled-prover"] } ## gRPC Web dependencies prost = { version = "0.12", default-features = false } @@ -77,7 +77,7 @@ tonic = { version = "0.12", default-features = false, features = [ # Used in Native tests tokio = { version = "1.0" } -zcash_client_sqlite = { git = "https://github.com/ChainSafe/librustzcash", rev = "efbaeec65dbf3b5126454fcb7a8f9be83cb9a9e0", default-features = false, features = ["unstable", "orchard"], optional = true } +zcash_client_sqlite = { git = "https://github.com/ChainSafe/librustzcash", rev = "9673cc2859e8a2528d1efd3c74795363f87ddf8f", default-features = false, features = ["unstable", "orchard"], optional = true } getrandom = { version = "0.2", features = ["js"] } thiserror = "1.0.63" diff --git a/packages/demo-wallet/src/App/Actions.tsx b/packages/demo-wallet/src/App/Actions.tsx index 8d82e8b..afc0c74 100644 --- a/packages/demo-wallet/src/App/Actions.tsx +++ b/packages/demo-wallet/src/App/Actions.tsx @@ -61,6 +61,12 @@ export async function triggerTransfer( } let activeAccountSeedPhrase = state.accountSeeds.get(state.activeAccount) || ""; - await state.webWallet?.transfer(activeAccountSeedPhrase, state.activeAccount, toAddress, amount); - await syncStateWithWallet(state, dispatch); + + let proposal = await state.webWallet?.propose_transfer(state.activeAccount, toAddress, amount); + console.log(JSON.stringify(proposal.describe(), null, 2)); + + let txids = await state.webWallet.create_proposed_transactions(proposal, activeAccountSeedPhrase); + console.log(JSON.stringify(txids, null, 2)); + + await state.webWallet.send_authorized_transactions(txids); } diff --git a/packages/demo-wallet/src/App/components/ImportAccount.tsx b/packages/demo-wallet/src/App/components/ImportAccount.tsx index 8588efc..aec337c 100644 --- a/packages/demo-wallet/src/App/components/ImportAccount.tsx +++ b/packages/demo-wallet/src/App/components/ImportAccount.tsx @@ -18,6 +18,7 @@ export function ImportAccount() { await addNewAccount(state, dispatch, seedPhrase, birthdayHeight); toast.success("Account imported successfully", { position: "top-center", + autoClose: 2000, }); setBirthdayHeight(0); setSeedPhrase(""); diff --git a/src/bindgen/mod.rs b/src/bindgen/mod.rs index 2fff25c..8aa035b 100644 --- a/src/bindgen/mod.rs +++ b/src/bindgen/mod.rs @@ -1 +1,2 @@ +pub mod proposal; pub mod wallet; diff --git a/src/bindgen/proposal.rs b/src/bindgen/proposal.rs new file mode 100644 index 0000000..8ae6a57 --- /dev/null +++ b/src/bindgen/proposal.rs @@ -0,0 +1,32 @@ +use wasm_bindgen::prelude::*; + +use super::wallet::NoteRef; +use zcash_primitives::transaction::fees::zip317::FeeRule; + +/// A handler to an immutable proposal. This can be passed to `create_proposed_transactions` to prove/authorize the transactions +/// before they are sent to the network. +/// +/// The proposal can be reviewed by calling `describe` which will return a JSON object with the details of the proposal. +#[wasm_bindgen] +pub struct Proposal { + inner: zcash_client_backend::proposal::Proposal, +} + +impl From> for Proposal { + fn from(inner: zcash_client_backend::proposal::Proposal) -> Self { + Self { inner } + } +} + +impl From for zcash_client_backend::proposal::Proposal { + fn from(proposal: Proposal) -> Self { + proposal.inner + } +} + +#[wasm_bindgen] +impl Proposal { + pub fn describe(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.inner).unwrap() + } +} diff --git a/src/bindgen/wallet.rs b/src/bindgen/wallet.rs index eeff254..0750884 100644 --- a/src/bindgen/wallet.rs +++ b/src/bindgen/wallet.rs @@ -1,26 +1,25 @@ use std::num::NonZeroU32; +use nonempty::NonEmpty; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; use tonic_web_wasm_client::Client; use crate::error::Error; -use crate::{BlockRange, Wallet, PRUNING_DEPTH}; +use crate::wallet::usk_from_seed_str; +use crate::{bindgen::proposal::Proposal, BlockRange, Wallet, PRUNING_DEPTH}; use wasm_thread as thread; use zcash_address::ZcashAddress; -use zcash_client_backend::data_api::{WalletRead, InputSource}; -use zcash_client_backend::proposal::Proposal; +use zcash_client_backend::data_api::{InputSource, WalletRead}; use zcash_client_backend::proto::service::{ compact_tx_streamer_client::CompactTxStreamerClient, ChainSpec, }; use zcash_client_memory::MemoryWalletDb; use zcash_keys::keys::UnifiedFullViewingKey; use zcash_primitives::consensus::{self, BlockHeight}; -use zcash_primitives::transaction::fees::zip317::FeeRule; use zcash_primitives::transaction::TxId; - pub type MemoryWallet = Wallet, T>; pub type AccountId = as WalletRead>::AccountId; @@ -173,70 +172,50 @@ impl WebWallet { } /// - /// Create a transaction proposal to send funds from the wallet to a given address and if approved will sign it and send the proposed transaction(s) to the network - /// - /// First a proposal is created by selecting inputs and outputs to cover the requested amount. This proposal is then sent to the approval callback. - /// This allows wallet developers to display a confirmation dialog to the user before continuing. - /// - /// # Arguments + /// Create a transaction proposal to send funds from the wallet to a given address. /// - pub async fn transfer( + pub async fn propose_transfer( &self, - seed_phrase: &str, - from_account_id: u32, + account_id: u32, to_address: String, value: u64, - ) -> Result<(), Error> { + ) -> Result { let to_address = ZcashAddress::try_from_encoded(&to_address)?; - self.inner - .transfer( - seed_phrase, - AccountId::from(from_account_id), - to_address, - value, - ) - .await + let proposal = self + .inner + .propose_transfer(AccountId::from(account_id), to_address, value) + .await?; + Ok(proposal.into()) } /// - /// Create a transaction proposal to send funds from the wallet to a given address. + /// Perform the proving and signing required to create one or more transaction from the proposal. + /// Created transactions are stored in the wallet database and a list of the IDs is returned /// - pub async fn propose_transfer( + pub async fn create_proposed_transactions( &self, - account_id: u32, - to_address: String, - value: u64, + proposal: Proposal, + seed_phrase: &str, ) -> Result { - let to_address = ZcashAddress::try_from_encoded(&to_address)?; - let proposal = self.inner.propose_transfer(AccountId::from(account_id), to_address, value).await?; - Ok(serde_wasm_bindgen::to_value(&proposal).unwrap()) + let usk = usk_from_seed_str(seed_phrase, 0, &self.inner.network)?; + let txids = self + .inner + .create_proposed_transactions(proposal.into(), &usk) + .await?; + Ok(serde_wasm_bindgen::to_value(&txids).unwrap()) } - - /// - /// Perform the proving and signing required to create one or more transaction from the proposal. Created transactions are stored in the wallet database. - /// - /// Note: At the moment this requires a USK but ideally we want to be able to hand the signing off to a separate service - /// e.g. browser plugin, hardware wallet, etc. Will need to look into refactoring librustzcash create_proposed_transactions to allow for this + /// - // pub async fn create_proposed_transactions( - // &self, - // proposal: Proposal, - // usk: &UnifiedSpendingKey, - // ) -> Result, Error> { - // self.inner - // .create_proposed_transactions(proposal, usk) - // .await - // } - - /// /// Send a list of transactions to the network via the lightwalletd instance this wallet is connected to - /// - // pub async fn send_authorized_transactions( - // &self, - // txids: &[TxId], - // ) -> Result<(), Error> { - // self.inner.send_authorized_transactions(txids).await - // } + /// + pub async fn send_authorized_transactions(&self, txids: JsValue) -> Result<(), Error> { + let txids: NonEmpty = serde_wasm_bindgen::from_value(txids).unwrap(); + self.inner.send_authorized_transactions(&txids).await + } + + /////////////////////////////////////////////////////////////////////////////////////// + // lightwalletd gRPC methods + /////////////////////////////////////////////////////////////////////////////////////// /// Forwards a call to lightwalletd to retrieve the height of the latest block in the chain pub async fn get_latest_block(&self) -> Result { diff --git a/src/wallet.rs b/src/wallet.rs index aaffa46..548dde6 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -453,10 +453,7 @@ where Ok(transactions) } - pub async fn send_authorized_transactions( - &self, - txids: &NonEmpty, - ) -> Result<(), Error> { + pub async fn send_authorized_transactions(&self, txids: &NonEmpty) -> Result<(), Error> { let mut client = self.client.clone(); for txid in txids.iter() { let (txid, raw_tx) = self @@ -508,7 +505,7 @@ where } } -fn usk_from_seed_str( +pub(crate) fn usk_from_seed_str( seed: &str, account_id: u32, network: &consensus::Network, From 38aa204601c5bd87ccef533eeae7b10d938c25c5 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Tue, 1 Oct 2024 14:28:55 -0400 Subject: [PATCH 05/11] fmt --- src/bindgen/proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindgen/proposal.rs b/src/bindgen/proposal.rs index 8ae6a57..db6bdae 100644 --- a/src/bindgen/proposal.rs +++ b/src/bindgen/proposal.rs @@ -5,7 +5,7 @@ use zcash_primitives::transaction::fees::zip317::FeeRule; /// A handler to an immutable proposal. This can be passed to `create_proposed_transactions` to prove/authorize the transactions /// before they are sent to the network. -/// +/// /// The proposal can be reviewed by calling `describe` which will return a JSON object with the details of the proposal. #[wasm_bindgen] pub struct Proposal { From 6a1dee7f90f81f50369ec0dba4ad5913ab6a41b8 Mon Sep 17 00:00:00 2001 From: Eric Tu Date: Tue, 1 Oct 2024 16:03:41 -0400 Subject: [PATCH 06/11] clippy --- src/bindgen/wallet.rs | 2 +- src/wallet.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/bindgen/wallet.rs b/src/bindgen/wallet.rs index 38e6398..3917963 100644 --- a/src/bindgen/wallet.rs +++ b/src/bindgen/wallet.rs @@ -17,7 +17,7 @@ use zcash_client_backend::proto::service::{ }; use zcash_client_memory::MemoryWalletDb; use zcash_keys::keys::UnifiedFullViewingKey; -use zcash_primitives::consensus::{self, BlockHeight}; +use zcash_primitives::consensus; use zcash_primitives::transaction::TxId; pub type MemoryWallet = Wallet, T>; diff --git a/src/wallet.rs b/src/wallet.rs index 920160f..22bb521 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -23,8 +23,7 @@ use zcash_client_backend::data_api::wallet::{ }; use zcash_client_backend::data_api::WalletCommitmentTrees; use zcash_client_backend::data_api::{ - Account, AccountBirthday, AccountPurpose, InputSource, NullifierQuery, WalletRead, - WalletSummary, WalletWrite, + Account, AccountBirthday, AccountPurpose, InputSource, WalletRead, WalletSummary, WalletWrite, }; use zcash_client_backend::fees::zip317::SingleOutputChangeStrategy; use zcash_client_backend::proposal::Proposal; From 904500d80310c920ce05c60a14bdd1ea6ebe66b9 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Thu, 10 Oct 2024 10:11:42 +1100 Subject: [PATCH 07/11] add .proxyrc to root so we can use the dev server again --- .proxyrc.js | 7 +++++++ packages/demo-wallet/src/index.html | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .proxyrc.js diff --git a/.proxyrc.js b/.proxyrc.js new file mode 100644 index 0000000..fdb1831 --- /dev/null +++ b/.proxyrc.js @@ -0,0 +1,7 @@ +module.exports = function (app) { + app.use((req, res, next) => { + res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + next(); + }); + }; diff --git a/packages/demo-wallet/src/index.html b/packages/demo-wallet/src/index.html index f0bc334..60c5356 100644 --- a/packages/demo-wallet/src/index.html +++ b/packages/demo-wallet/src/index.html @@ -3,7 +3,7 @@ WebZjs Wallet Demo - +
From 0b02bc31c42bff388201904736e2c3f96074305b Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Fri, 11 Oct 2024 12:31:50 +1100 Subject: [PATCH 08/11] persist serve.json files --- .gitignore | 18 +- README.md | 41 +- package.json | 5 +- packages/demo-wallet/.gitignore | 4 +- packages/demo-wallet/.proxyrc.js | 7 - packages/demo-wallet/README.md | 2 + packages/demo-wallet/dist/serve.json | 18 + packages/demo-wallet/package.json | 6 + packages/demo-wallet/src/App/Actions.tsx | 2 +- pnpm-lock.yaml | 641 ++++++++++++++++++++++- 10 files changed, 696 insertions(+), 48 deletions(-) delete mode 100644 packages/demo-wallet/.proxyrc.js create mode 100644 packages/demo-wallet/dist/serve.json diff --git a/.gitignore b/.gitignore index 1b19cd7..da71061 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,20 @@ node_modules .DS_STORE .idea .parcel-cache -dist + +.pnp.* +.yarn/* + +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +.parcel-cache + +packages/demo-wallet/dist/* +packages/e2e-tests/dist/* + +!packages/demo-wallet/dist/serve.json +!packages/e2e-tests/dist/serve.json diff --git a/README.md b/README.md index bb4888b..37ba4e3 100644 --- a/README.md +++ b/README.md @@ -10,33 +10,26 @@ WebZjs aims to make it simple to securely interact with Zcash from within the br Being a private blockchain Zcash places a lot more demands on the wallet than a public blockchain like Ethereum. WebZjs uses everything at its disposal to give efficient sync times and a good user experience. -## Usage - -```typescript -// TODO -``` - ## Building ### Prerequisites +- [Rust and Cargo](https://www.rust-lang.org/tools/install) - This repo uses [just](https://github.com/casey/just) as a command runner. Please install this first. -- Install [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) +- [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) - Requires clang 17 or later - On Mac this can be installed by updating LLVM using your preferred package manager (e.g. macports, brew) - Tested with Rust nightly-2024-08-07 -### Building for Browser +### Building WebZjs -This just script uses wasm-pack to build a web-ready javascript library. +This just script uses wasm-pack to build a web-ready copy of `webz-core` into the `packages` directory ```shell -just build-web +just build ``` -After building the resulting lib can be found in `packages/webz-core`. - -### Building the example web-wallet +### Building and running the demo-wallet #### Prerequisites @@ -44,38 +37,40 @@ After building the resulting lib can be found in `packages/webz-core`. ### Building -Install dependencies with +First build WebZjs with + +```shell +just build +``` + +Install js dependencies with ```shell pnpm i ``` -Build WebZjs with +Build the demo wallet with ```shell -just build +pnpm build ``` -Start a dev server with the page +Serve it with ```shell -pnpm start:dev + ``` ## Development -The [`.cargo/config.toml`](./.cargo/config.toml) file sets the build target to `wasm32-unknown-unknown` so the regular cargo commands (e.g. `check`, `build`) will run against this target. - ### Testing -Tests are run in a headless browser environment and can be run with +Browser tests are run in a headless browser environment and can be run with ```shell just test-web ``` - - ## Security Warnings These libraries are currently under development, have received no reviews or audit, and come with no guarantees whatsoever. diff --git a/package.json b/package.json index 229e8cf..02a60b2 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "scripts": { "preinstall": "npx only-allow pnpm", - "start:dev": "parcel --no-autoinstall packages/demo-wallet", - "build": "parcel build --no-cache --no-autoinstall packages/demo-wallet" + "build": "parcel build --no-cache --no-autoinstall packages/demo-wallet", + "serve": "pnpm --filter ./packages/demo-wallet run serve", + "start:dev": "parcel --no-autoinstall packages/demo-wallet" }, "dependencies": { "@webzjs/webz-core": "workspace:^", diff --git a/packages/demo-wallet/.gitignore b/packages/demo-wallet/.gitignore index 863381c..44a92b6 100644 --- a/packages/demo-wallet/.gitignore +++ b/packages/demo-wallet/.gitignore @@ -1,11 +1,13 @@ .pnp.* .yarn/* +dist/* + !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions +!dist/serve.json -dist .parcel-cache wasm-pkg diff --git a/packages/demo-wallet/.proxyrc.js b/packages/demo-wallet/.proxyrc.js deleted file mode 100644 index fdb1831..0000000 --- a/packages/demo-wallet/.proxyrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = function (app) { - app.use((req, res, next) => { - res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); - res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); - next(); - }); - }; diff --git a/packages/demo-wallet/README.md b/packages/demo-wallet/README.md index 9051997..0f1d5ef 100644 --- a/packages/demo-wallet/README.md +++ b/packages/demo-wallet/README.md @@ -1 +1,3 @@ # WebZjs Demo Web Wallet + +Please see the root README for instructions diff --git a/packages/demo-wallet/dist/serve.json b/packages/demo-wallet/dist/serve.json new file mode 100644 index 0000000..117f86c --- /dev/null +++ b/packages/demo-wallet/dist/serve.json @@ -0,0 +1,18 @@ +{ + "headers": [ + { + "source": "**/*", + "headers": [ + { + "key": "Cross-Origin-Opener-Policy", + "value": "same-origin" + }, + { + "key": "Cross-Origin-Embedder-Policy", + "value": "require-corp" + } + ] + } + ] + } + \ No newline at end of file diff --git a/packages/demo-wallet/package.json b/packages/demo-wallet/package.json index e7b60f5..cfc6598 100644 --- a/packages/demo-wallet/package.json +++ b/packages/demo-wallet/package.json @@ -1,6 +1,9 @@ { "name": "@webzjs/demo-wallet", "source": "src/index.html", + "scripts": { + "serve": "serve dist" + }, "dependencies": { "@types/react": "^18.3.9", "@types/react-dom": "^18.3.0", @@ -11,5 +14,8 @@ "react-dom": "^18.2.0", "react-toastify": "^10.0.5", "typescript": "^5.6.2" + }, + "devDependencies": { + "serve": "^14.2.3" } } diff --git a/packages/demo-wallet/src/App/Actions.tsx b/packages/demo-wallet/src/App/Actions.tsx index afc0c74..489b0ca 100644 --- a/packages/demo-wallet/src/App/Actions.tsx +++ b/packages/demo-wallet/src/App/Actions.tsx @@ -43,7 +43,7 @@ export async function triggerRescan( if (!state.webWallet) { throw new Error("Wallet not initialized"); } - await state.webWallet?.sync2(); + await state.webWallet?.sync(); await syncStateWithWallet(state, dispatch); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99495ac..85f4531 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,9 @@ importers: react-toastify: specifier: ^10.0.5 version: 10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + serve: + specifier: ^14.2.3 + version: 14.2.3 typescript: specifier: ^5.6.2 version: 5.6.2 @@ -560,9 +563,30 @@ packages: '@types/warning@3.0.3': resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==} + '@zeit/schemas@2.36.0': + resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==} + abortcontroller-polyfill@1.7.5: resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -571,9 +595,22 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + arch@2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base-x@3.0.10: resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} @@ -585,6 +622,13 @@ packages: peerDependencies: '@popperjs/core': ^2.11.8 + boxen@7.0.0: + resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} + engines: {node: '>=14.16'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -594,13 +638,25 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + caniuse-lite@1.0.30001663: resolution: {integrity: sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==} + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -609,6 +665,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.0.1: + resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -616,6 +676,14 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + clipboardy@3.0.0: + resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + clone@2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} @@ -641,6 +709,21 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.7.4: + resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-disposition@0.5.2: + resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} + engines: {node: '>= 0.6'} + cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -650,6 +733,10 @@ packages: typescript: optional: true + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -668,6 +755,18 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -704,9 +803,18 @@ packages: resolution: {integrity: sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==} engines: {node: '>=6'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.28: resolution: {integrity: sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} @@ -729,6 +837,16 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-url-parser@1.1.3: + resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -737,6 +855,10 @@ packages: resolution: {integrity: sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==} engines: {node: '>=6'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -781,20 +903,36 @@ packages: htmlparser2@7.2.0: resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -806,6 +944,21 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-port-reachable@4.0.0: + resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -816,6 +969,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -899,10 +1055,46 @@ packages: mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.33.0: + resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.53.0: + resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.18: + resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + msgpackr-extract@3.0.3: resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true @@ -910,6 +1102,10 @@ packages: msgpackr@1.11.0: resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==} + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + node-addon-api@6.1.0: resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} @@ -927,6 +1123,10 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -937,6 +1137,14 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + ordered-binary@1.5.1: resolution: {integrity: sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==} @@ -953,6 +1161,16 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + path-is-inside@1.0.2: + resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@2.2.1: + resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==} + picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} @@ -991,6 +1209,21 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + range-parser@1.2.0: + resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} + engines: {node: '>= 0.6'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-bootstrap@2.10.4: resolution: {integrity: sha512-W3398nBM2CBfmGP2evneEO3ZZwEMPtHs72q++eNw60uDGDAdiGn0f9yNys91eo7/y8CTF5Ke1C0QO8JFVPU40Q==} peerDependencies: @@ -1041,10 +1274,24 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + registry-auth-token@3.3.2: + resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} + + registry-url@3.1.0: + resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -1056,6 +1303,25 @@ packages: engines: {node: '>=10'} hasBin: true + serve-handler@6.1.5: + resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==} + + serve@14.2.3: + resolution: {integrity: sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==} + engines: {node: '>= 14'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -1068,6 +1334,30 @@ packages: resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -1099,6 +1389,10 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + typescript@5.6.2: resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} engines: {node: '>=14.17'} @@ -1120,16 +1414,39 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-check@1.5.4: + resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + utility-types@3.11.0: resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} engines: {node: '>= 4'} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} weak-lru-cache@1.2.2: resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + snapshots: '@babel/code-frame@7.24.7': @@ -1209,15 +1526,13 @@ snapshots: transitivePeerDependencies: - '@parcel/core' - '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)': + '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))': dependencies: '@parcel/core': 2.12.0(@swc/helpers@0.5.13) '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/logger': 2.12.0 '@parcel/utils': 2.12.0 lmdb: 2.8.5 - transitivePeerDependencies: - - '@swc/helpers' '@parcel/codeframe@2.12.0': dependencies: @@ -1277,7 +1592,7 @@ snapshots: '@parcel/core@2.12.0(@swc/helpers@0.5.13)': dependencies: '@mischnic/json-sourcemap': 0.1.1 - '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) '@parcel/diagnostic': 2.12.0 '@parcel/events': 2.12.0 '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) @@ -1290,7 +1605,7 @@ snapshots: '@parcel/source-map': 2.1.1 '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) abortcontroller-polyfill: 1.7.5 base-x: 3.0.10 browserslist: 4.23.3 @@ -1318,7 +1633,7 @@ snapshots: '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/utils': 2.12.0 '@parcel/watcher': 2.4.1 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) transitivePeerDependencies: - '@swc/helpers' @@ -1392,7 +1707,7 @@ snapshots: '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) '@parcel/rust': 2.12.0 '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/optimizer-svgo@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))': dependencies: @@ -1424,7 +1739,7 @@ snapshots: '@parcel/node-resolver-core': 3.3.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@swc/core': 1.7.28(@swc/helpers@0.5.13) semver: 7.6.3 transitivePeerDependencies: @@ -1613,7 +1928,7 @@ snapshots: '@parcel/core': 2.12.0(@swc/helpers@0.5.13) '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) nullthrows: 1.1.1 '@parcel/transformer-js@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))': @@ -1624,7 +1939,7 @@ snapshots: '@parcel/rust': 2.12.0 '@parcel/source-map': 2.1.1 '@parcel/utils': 2.12.0 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@swc/helpers': 0.5.13 browserslist: 4.23.3 nullthrows: 1.1.1 @@ -1692,12 +2007,12 @@ snapshots: '@parcel/types@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)': dependencies: - '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) '@parcel/diagnostic': 2.12.0 '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/package-manager': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/source-map': 2.1.1 - '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) utility-types: 3.11.0 transitivePeerDependencies: - '@parcel/core' @@ -1770,7 +2085,7 @@ snapshots: '@parcel/watcher-win32-ia32': 2.4.1 '@parcel/watcher-win32-x64': 2.4.1 - '@parcel/workers@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))': + '@parcel/workers@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)': dependencies: '@parcel/core': 2.12.0(@swc/helpers@0.5.13) '@parcel/diagnostic': 2.12.0 @@ -1779,6 +2094,8 @@ snapshots: '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/utils': 2.12.0 nullthrows: 1.1.1 + transitivePeerDependencies: + - '@swc/helpers' '@popperjs/core@2.11.8': {} @@ -1882,8 +2199,30 @@ snapshots: '@types/warning@3.0.3': {} + '@zeit/schemas@2.36.0': {} + abortcontroller-polyfill@1.7.5: {} + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -1892,8 +2231,16 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.1: {} + + arch@2.2.0: {} + + arg@5.0.2: {} + argparse@2.0.1: {} + balanced-match@1.0.2: {} + base-x@3.0.10: dependencies: safe-buffer: 5.2.1 @@ -1904,6 +2251,22 @@ snapshots: dependencies: '@popperjs/core': 2.11.8 + boxen@7.0.0: + dependencies: + ansi-align: 3.0.1 + camelcase: 7.0.1 + chalk: 5.0.1 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.19.0 + widest-line: 4.0.1 + wrap-ansi: 8.1.0 + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -1915,10 +2278,18 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) + bytes@3.0.0: {} + callsites@3.1.0: {} + camelcase@7.0.1: {} + caniuse-lite@1.0.30001663: {} + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -1930,10 +2301,20 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.0.1: {} + chrome-trace-event@1.0.4: {} classnames@2.5.1: {} + cli-boxes@3.0.0: {} + + clipboardy@3.0.0: + dependencies: + arch: 2.2.0 + execa: 5.1.1 + is-wsl: 2.2.0 + clone@2.1.2: {} clsx@2.1.1: {} @@ -1952,6 +2333,26 @@ snapshots: commander@7.2.0: {} + compressible@2.0.18: + dependencies: + mime-db: 1.53.0 + + compression@1.7.4: + dependencies: + accepts: 1.3.8 + bytes: 3.0.0 + compressible: 2.0.18 + debug: 2.6.9 + on-headers: 1.0.2 + safe-buffer: 5.1.2 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + content-disposition@0.5.2: {} + cosmiconfig@9.0.0(typescript@5.6.2): dependencies: env-paths: 2.2.1 @@ -1961,6 +2362,12 @@ snapshots: optionalDependencies: typescript: 5.6.2 + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -1982,6 +2389,12 @@ snapshots: csstype@3.1.3: {} + debug@2.6.9: + dependencies: + ms: 2.0.0 + + deep-extend@0.6.0: {} + dequal@2.0.3: {} detect-libc@1.0.3: {} @@ -2015,8 +2428,14 @@ snapshots: dotenv@7.0.0: {} + eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.28: {} + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + entities@2.2.0: {} entities@3.0.1: {} @@ -2031,12 +2450,32 @@ snapshots: escape-string-regexp@1.0.5: {} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + fast-deep-equal@3.1.3: {} + + fast-url-parser@1.1.3: + dependencies: + punycode: 1.4.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 get-port@4.2.0: {} + get-stream@6.0.1: {} + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -2062,19 +2501,27 @@ snapshots: domutils: 2.8.0 entities: 3.0.1 + human-signals@2.1.0: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 + ini@1.3.8: {} + invariant@2.2.4: dependencies: loose-envify: 1.4.0 is-arrayish@0.2.1: {} + is-docker@2.2.1: {} + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -2083,6 +2530,16 @@ snapshots: is-number@7.0.0: {} + is-port-reachable@4.0.0: {} + + is-stream@2.0.1: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -2091,6 +2548,8 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@1.0.0: {} + json5@2.2.3: {} lightningcss-darwin-arm64@1.27.0: @@ -2161,11 +2620,37 @@ snapshots: mdn-data@2.0.14: {} + merge-stream@2.0.0: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.33.0: {} + + mime-db@1.52.0: {} + + mime-db@1.53.0: {} + + mime-types@2.1.18: + dependencies: + mime-db: 1.33.0 + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimist@1.2.8: {} + + ms@2.0.0: {} + msgpackr-extract@3.0.3: dependencies: node-gyp-build-optional-packages: 5.2.2 @@ -2182,6 +2667,8 @@ snapshots: optionalDependencies: msgpackr-extract: 3.0.3 + negotiator@0.6.3: {} + node-addon-api@6.1.0: {} node-addon-api@7.1.1: {} @@ -2197,6 +2684,10 @@ snapshots: node-releases@2.0.18: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -2205,6 +2696,12 @@ snapshots: object-assign@4.1.1: {} + on-headers@1.0.2: {} + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + ordered-binary@1.5.1: {} parcel@2.12.0(@swc/helpers@0.5.13)(typescript@5.6.2): @@ -2245,6 +2742,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + path-is-inside@1.0.2: {} + + path-key@3.1.1: {} + + path-to-regexp@2.2.1: {} + picocolors@1.1.0: {} picomatch@2.3.1: {} @@ -2282,6 +2785,19 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + punycode@1.4.1: {} + + punycode@2.3.1: {} + + range-parser@1.2.0: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-bootstrap@2.10.4(@types/react@18.3.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.25.6 @@ -2338,8 +2854,21 @@ snapshots: regenerator-runtime@0.14.1: {} + registry-auth-token@3.3.2: + dependencies: + rc: 1.2.8 + safe-buffer: 5.2.1 + + registry-url@3.1.0: + dependencies: + rc: 1.2.8 + + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} scheduler@0.23.2: @@ -2348,12 +2877,71 @@ snapshots: semver@7.6.3: {} + serve-handler@6.1.5: + dependencies: + bytes: 3.0.0 + content-disposition: 0.5.2 + fast-url-parser: 1.1.3 + mime-types: 2.1.18 + minimatch: 3.1.2 + path-is-inside: 1.0.2 + path-to-regexp: 2.2.1 + range-parser: 1.2.0 + + serve@14.2.3: + dependencies: + '@zeit/schemas': 2.36.0 + ajv: 8.12.0 + arg: 5.0.2 + boxen: 7.0.0 + chalk: 5.0.1 + chalk-template: 0.4.0 + clipboardy: 3.0.0 + compression: 1.7.4 + is-port-reachable: 4.0.0 + serve-handler: 6.1.5 + update-check: 1.5.4 + transitivePeerDependencies: + - supports-color + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + source-map@0.6.1: {} srcset@4.0.0: {} stable@0.1.8: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-final-newline@2.0.0: {} + + strip-json-comments@2.0.1: {} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -2384,6 +2972,8 @@ snapshots: type-fest@0.20.2: {} + type-fest@2.19.0: {} + typescript@5.6.2: {} uncontrollable@7.2.1(react@18.3.1): @@ -2404,10 +2994,35 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.0 + update-check@1.5.4: + dependencies: + registry-auth-token: 3.3.2 + registry-url: 3.1.0 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + utility-types@3.11.0: {} + vary@1.1.2: {} + warning@4.0.3: dependencies: loose-envify: 1.4.0 weak-lru-cache@1.2.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + widest-line@4.0.1: + dependencies: + string-width: 5.1.2 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 From 9a1dc8f25c00f6fffee649631bcb3acae7d9f78b Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Fri, 11 Oct 2024 12:38:00 +1100 Subject: [PATCH 09/11] adds instructions to readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 37ba4e3..f24161f 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,12 @@ pnpm build Serve it with ```shell - +pnpm serve ``` +> [!IMPORTANT] +> For unknown reasons it is currently not possible to use the parcel dev server to serve the demo-wallet hence the build and then serve steps + ## Development ### Testing From 16cfa0b5c217e1b60ebb4ef4ad2018e553271821 Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Fri, 11 Oct 2024 15:28:05 +1100 Subject: [PATCH 10/11] add details docs with examples for current API --- packages/demo-wallet/src/App/Actions.tsx | 2 +- src/bindgen/proposal.rs | 1 + src/bindgen/wallet.rs | 172 +++++++++++++++++++---- src/init.rs | 2 +- 4 files changed, 150 insertions(+), 27 deletions(-) diff --git a/packages/demo-wallet/src/App/Actions.tsx b/packages/demo-wallet/src/App/Actions.tsx index 489b0ca..97cdc48 100644 --- a/packages/demo-wallet/src/App/Actions.tsx +++ b/packages/demo-wallet/src/App/Actions.tsx @@ -65,7 +65,7 @@ export async function triggerTransfer( let proposal = await state.webWallet?.propose_transfer(state.activeAccount, toAddress, amount); console.log(JSON.stringify(proposal.describe(), null, 2)); - let txids = await state.webWallet.create_proposed_transactions(proposal, activeAccountSeedPhrase); + let txids = await state.webWallet.create_proposed_transactions(proposal, activeAccountSeedPhrase, 0); console.log(JSON.stringify(txids, null, 2)); await state.webWallet.send_authorized_transactions(txids); diff --git a/src/bindgen/proposal.rs b/src/bindgen/proposal.rs index db6bdae..761adb2 100644 --- a/src/bindgen/proposal.rs +++ b/src/bindgen/proposal.rs @@ -26,6 +26,7 @@ impl From for zcash_client_backend::proposal::Proposal JsValue { serde_wasm_bindgen::to_value(&self.inner).unwrap() } diff --git a/src/bindgen/wallet.rs b/src/bindgen/wallet.rs index 3917963..eb1aef8 100644 --- a/src/bindgen/wallet.rs +++ b/src/bindgen/wallet.rs @@ -27,23 +27,65 @@ pub type NoteRef = as Inpu /// # A Zcash wallet /// -/// A wallet is a set of accounts that can be synchronized together with the blockchain. -/// Once synchronized these can be used to build transactions that spend notes +/// This is the main entry point for interacting with this library. +/// For the most part you will only need to create and interact with a Wallet instance. /// +/// A wallet is a set of accounts that can be synchronized together with the blockchain. +/// Once synchronized, the wallet can be used to propose, build and send transactions. +/// +/// Create a new WebWallet with +/// ```javascript +/// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10); +/// ``` +/// /// ## Adding Accounts /// -/// TODO +/// Accounts can be added by either importing a seed phrase or a Unified Full Viewing Key (UFVK). +/// If you do import via a UFVK it is important that you also have access to the Unified Spending Key (USK) for that account otherwise the wallet will not be able to create transactions. +/// +/// When importing an account you can also specify the block height at which the account was created. This can significantly reduce the time it takes to sync the account as the wallet will only scan for transactions after this height. +/// Failing to provide a birthday height will result in extremely slow sync times as the wallet will need to scan the entire blockchain. /// +/// e.g. +/// ```javascript +/// const account_id = await wallet.create_account("...", 1, 2657762) +/// +/// // OR +/// +/// const account_id = await wallet.import_ufvk("...", 2657762) +/// `` +/// /// ## Synchronizing /// -/// A wallet can be syncced with the blockchain by feeding it blocks. The accounts currently managed by the wallet will be used to -/// scan the blocks and retrieve relevant transactions. The wallet itself keeps track of blocks it has seen and can be queried for -/// the suggest range of blocks that should be retrieved for it to process next. +/// The wallet can be synchronized with the blockchain by calling the `sync` method. This will fetch compact blocks from the connected lightwalletd instance and scan them for transactions. +/// The sync method uses a built-in strategy to determine which blocks is needs to download and scan in order to gain full knowledge of the balances for all accounts that are managed. +/// +/// Syncing is a long running process and so is delegated to a WebWorker to prevent from blocking the main thread. It is safe to call other methods on the wallet during syncing although they may take +/// longer than usual while they wait for a write-lock to be released. /// -/// ## Building Transactions +/// ```javascript +/// await wallet.sync(); +/// ``` +/// +/// ## Transacting /// -/// TODO +/// Sending a transaction is a three step process: proposing, authorizing, and sending. +/// +/// A transaction proposal is created by calling `propose_transfer` with the intended recipient and amount. This will create a proposal object that describes which notes will be spent in order to fulfil this request. +/// The proposal should be presented to the user for review before being authorized. +/// +/// To authorize the transaction the caller must currently provide the seed phrase and account index of the account that will be used to sign the transaction. This method also perform the SNARK proving which is an expensive operation and performed in parallel by a series of WebWorkers. +/// Note: Handing the sensitive key material this way is not recommended for production applications. Upcoming changes to how proposals are authorized will allow separation of proof generation and signing but currently these are coupled. /// +/// Finally, A transaction can be sent to the network by calling `send_authorized_transactions` with the list of transaction IDs that were generated by the authorization step. +/// +/// The full flow looks like +/// ```javascript +/// const proposal = wallet.propose_transfer(1, "...", 100000000); +/// const authorized_txns = wallet.create_proposed_transactions(proposal, "...", 1); +/// await wallet.send_authorized_transactions(authorized_txns); +/// ``` +/// #[wasm_bindgen] #[derive(Clone)] pub struct WebWallet { @@ -70,7 +112,19 @@ impl WebWallet { #[wasm_bindgen] impl WebWallet { - /// Create a new instance of a Zcash wallet for a given network + /// Create a new instance of a Zcash wallet for a given network. Only one instance should be created per page. + /// + /// # Arguments + /// + /// * `network` - Must be one of "main" or "test" + /// * `lightwalletd_url` - Url of the lightwalletd instance to connect to (e.g. https://zcash-mainnet.chainsafe.dev) + /// * `min_confirmations` - Number of confirmations required before a transaction is considered final + /// + /// # Examples + /// + /// ```javascript + /// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10); + /// ``` #[wasm_bindgen(constructor)] pub fn new( network: &str, @@ -92,13 +146,20 @@ impl WebWallet { }) } - /// Add a new account to the wallet - /// + /// Add a new account to the wallet using a given seed phrase + /// /// # Arguments - /// seed_phrase - mnemonic phrase to initialise the wallet - /// account_hd_index - The HD derivation index to use. Can be any integer - /// birthday_height - The block height at which the account was created, optionally None and the current height is used /// + /// * `seed_phrase` - 24 word mnemonic seed phrase + /// * `account_hd_index` - [ZIP32](https://zips.z.cash/zip-0032) hierarchical deterministic index of the account + /// * `birthday_height` - Block height at which the account was created. The sync logic will assume no funds are send or received prior to this height which can VERY significantly reduce sync time + /// + /// # Examples + /// + /// ```javascript + /// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10); + /// const account_id = await wallet.create_account("...", 1, 2657762) + /// ``` pub async fn create_account( &self, seed_phrase: &str, @@ -112,6 +173,19 @@ impl WebWallet { .map(|id| *id) } + /// Add a new account to the wallet by directly importing a Unified Full Viewing Key (UFVK) + /// + /// # Arguments + /// + /// * `key` - [ZIP316](https://zips.z.cash/zip-0316) encoded UFVK + /// * `birthday_height` - Block height at which the account was created. The sync logic will assume no funds are send or received prior to this height which can VERY significantly reduce sync time + /// + /// # Examples + /// + /// ```javascript + /// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10); + /// const account_id = await wallet.import_ufvk("...", 2657762) + /// ``` pub async fn import_ufvk(&self, key: &str, birthday_height: Option) -> Result { let ufvk = UnifiedFullViewingKey::decode(&self.inner.network, key) .map_err(Error::KeyParseError)?; @@ -122,11 +196,12 @@ impl WebWallet { .map(|id| *id) } - pub async fn suggest_scan_ranges(&self) -> Result, Error> { - self.inner.suggest_scan_ranges().await - } - - /// Synchronize the wallet with the blockchain up to the tip using zcash_client_backend's algo + /// + /// Start a background sync task which will fetch and scan blocks from the connected lighwalletd server + /// + /// IMPORTANT: This will spawn a new webworker which will handle the sync task. The sync task will continue to run in the background until the sync process is complete. + /// During this time the main thread will not block but certain wallet methods may temporarily block while the wallet is being written to during the sync. + /// pub async fn sync(&self) -> Result<(), Error> { assert!(!thread::is_web_worker_thread()); @@ -154,9 +229,26 @@ impl WebWallet { Ok(self.inner.get_wallet_summary().await?.map(Into::into)) } + + /// Create a new transaction proposal to send funds to a given address + /// + /// Not this does NOT sign, generate a proof, or send the transaction. It will only craft the proposal which designates how notes from this account can be spent to realize the requested transfer. + /// + /// # Arguments /// - /// Create a transaction proposal to send funds from the wallet to a given address. + /// * `account_id` - The ID of the account in this wallet to send funds from + /// * `to_address` - [ZIP316](https://zips.z.cash/zip-0316) encoded address to send funds to + /// * `value` - Amount to send in Zatoshis (1 ZEC = 100_000_000 Zatoshis) + /// + /// # Returns + /// + /// A proposal object which can be inspected and later used to generate a valid transaction + /// + /// # Examples /// + /// ```javascript + /// const proposal = await wallet.propose_transfer(1, "u18rakpts0de589sx9dkamcjms3apruqqax9k2s6e7zjxx9vv5kc67pks2trg9d3nrgd5acu8w8arzjjuepakjx38dyxl6ahd948w0mhdt9jxqsntan6px3ysz80s04a87pheg2mqvlzpehrgup7568nfd6ez23xd69ley7802dfvplnfn7c07vlyumcnfjul4pvv630ac336rjhjyak5", 100000000); + /// ``` pub async fn propose_transfer( &self, account_id: u32, @@ -171,16 +263,32 @@ impl WebWallet { Ok(proposal.into()) } + /// Generate a valid Zcash transaction from a given proposal + /// + /// # Arguments /// - /// Perform the proving and signing required to create one or more transaction from the proposal. - /// Created transactions are stored in the wallet database and a list of the IDs is returned + /// * `proposal` - A proposal object generated by `propose_transfer` + /// * `seed_phrase` - 24 word mnemonic seed phrase. This MUST correspond to the accountID used when creating the proposal. + /// * `account_hd_index` - [ZIP32](https://zips.z.cash/zip-0032) hierarchical deterministic index of the account. This MUST correspond to the accountID used when creating the proposal. + /// + /// # Returns + /// + /// A list of transaction IDs which can be used to track the status of the transaction on the network. + /// The transactions themselves are stored within the wallet + /// + /// # Examples /// + /// ```javascript + /// const proposal = await wallet.propose_transfer(1, "u18rakpts0de589sx9dkamcjms3apruqqax9k2s6e7zjxx9vv5kc67pks2trg9d3nrgd5acu8w8arzjjuepakjx38dyxl6ahd948w0mhdt9jxqsntan6px3ysz80s04a87pheg2mqvlzpehrgup7568nfd6ez23xd69ley7802dfvplnfn7c07vlyumcnfjul4pvv630ac336rjhjyak5", 100000000); + /// const authorized_txns = await wallet.create_proposed_transactions(proposal, "...", 1); + /// ``` pub async fn create_proposed_transactions( &self, proposal: Proposal, seed_phrase: &str, + account_hd_index: u32, ) -> Result { - let usk = usk_from_seed_str(seed_phrase, 0, &self.inner.network)?; + let usk = usk_from_seed_str(seed_phrase, account_hd_index, &self.inner.network)?; let txids = self .inner .create_proposed_transactions(proposal.into(), &usk) @@ -188,9 +296,21 @@ impl WebWallet { Ok(serde_wasm_bindgen::to_value(&txids).unwrap()) } + /// Send a list of authorized transactions to the network to be included in the blockchain + /// + /// These will be sent via the connected lightwalletd instance + /// + /// # Arguments /// - /// Send a list of transactions to the network via the lightwalletd instance this wallet is connected to + /// * `txids` - A list of transaction IDs (typically generated by `create_proposed_transactions`) + /// + /// # Examples /// + /// ```javascript + /// const proposal = wallet.propose_transfer(1, "u18rakpts0de589sx9dkamcjms3apruqqax9k2s6e7zjxx9vv5kc67pks2trg9d3nrgd5acu8w8arzjjuepakjx38dyxl6ahd948w0mhdt9jxqsntan6px3ysz80s04a87pheg2mqvlzpehrgup7568nfd6ez23xd69ley7802dfvplnfn7c07vlyumcnfjul4pvv630ac336rjhjyak5", 100000000); + /// const authorized_txns = wallet.create_proposed_transactions(proposal, "...", 1); + /// await wallet.send_authorized_transactions(authorized_txns); + /// ``` pub async fn send_authorized_transactions(&self, txids: JsValue) -> Result<(), Error> { let txids: NonEmpty = serde_wasm_bindgen::from_value(txids).unwrap(); self.inner.send_authorized_transactions(&txids).await @@ -200,7 +320,9 @@ impl WebWallet { // lightwalletd gRPC methods /////////////////////////////////////////////////////////////////////////////////////// - /// Forwards a call to lightwalletd to retrieve the height of the latest block in the chain + /// + /// Get the hightest known block height from the connected lightwalletd instance + /// pub async fn get_latest_block(&self) -> Result { self.client() .get_latest_block(ChainSpec {}) diff --git a/src/init.rs b/src/init.rs index 4896720..917a59e 100644 --- a/src/init.rs +++ b/src/init.rs @@ -61,7 +61,7 @@ fn setup_tracing() { } #[wasm_bindgen(start)] -pub fn start() { +fn start() { set_panic_hook(); setup_tracing(); } From dd447c0a96d8f027d73b16ae079f7dc1be2f0cfe Mon Sep 17 00:00:00 2001 From: Willem Olding Date: Fri, 11 Oct 2024 15:29:47 +1100 Subject: [PATCH 11/11] rust fmt --- src/bindgen/wallet.rs | 63 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/bindgen/wallet.rs b/src/bindgen/wallet.rs index eb1aef8..4099a41 100644 --- a/src/bindgen/wallet.rs +++ b/src/bindgen/wallet.rs @@ -30,62 +30,62 @@ pub type NoteRef = as Inpu /// This is the main entry point for interacting with this library. /// For the most part you will only need to create and interact with a Wallet instance. /// -/// A wallet is a set of accounts that can be synchronized together with the blockchain. +/// A wallet is a set of accounts that can be synchronized together with the blockchain. /// Once synchronized, the wallet can be used to propose, build and send transactions. /// /// Create a new WebWallet with /// ```javascript /// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10); /// ``` -/// +/// /// ## Adding Accounts /// /// Accounts can be added by either importing a seed phrase or a Unified Full Viewing Key (UFVK). /// If you do import via a UFVK it is important that you also have access to the Unified Spending Key (USK) for that account otherwise the wallet will not be able to create transactions. -/// +/// /// When importing an account you can also specify the block height at which the account was created. This can significantly reduce the time it takes to sync the account as the wallet will only scan for transactions after this height. /// Failing to provide a birthday height will result in extremely slow sync times as the wallet will need to scan the entire blockchain. /// /// e.g. /// ```javascript /// const account_id = await wallet.create_account("...", 1, 2657762) -/// +/// /// // OR -/// +/// /// const account_id = await wallet.import_ufvk("...", 2657762) /// `` -/// +/// /// ## Synchronizing /// /// The wallet can be synchronized with the blockchain by calling the `sync` method. This will fetch compact blocks from the connected lightwalletd instance and scan them for transactions. /// The sync method uses a built-in strategy to determine which blocks is needs to download and scan in order to gain full knowledge of the balances for all accounts that are managed. -/// +/// /// Syncing is a long running process and so is delegated to a WebWorker to prevent from blocking the main thread. It is safe to call other methods on the wallet during syncing although they may take /// longer than usual while they wait for a write-lock to be released. /// /// ```javascript /// await wallet.sync(); /// ``` -/// +/// /// ## Transacting /// /// Sending a transaction is a three step process: proposing, authorizing, and sending. -/// +/// /// A transaction proposal is created by calling `propose_transfer` with the intended recipient and amount. This will create a proposal object that describes which notes will be spent in order to fulfil this request. /// The proposal should be presented to the user for review before being authorized. -/// +/// /// To authorize the transaction the caller must currently provide the seed phrase and account index of the account that will be used to sign the transaction. This method also perform the SNARK proving which is an expensive operation and performed in parallel by a series of WebWorkers. /// Note: Handing the sensitive key material this way is not recommended for production applications. Upcoming changes to how proposals are authorized will allow separation of proof generation and signing but currently these are coupled. /// /// Finally, A transaction can be sent to the network by calling `send_authorized_transactions` with the list of transaction IDs that were generated by the authorization step. -/// +/// /// The full flow looks like /// ```javascript /// const proposal = wallet.propose_transfer(1, "...", 100000000); /// const authorized_txns = wallet.create_proposed_transactions(proposal, "...", 1); /// await wallet.send_authorized_transactions(authorized_txns); /// ``` -/// +/// #[wasm_bindgen] #[derive(Clone)] pub struct WebWallet { @@ -113,7 +113,7 @@ impl WebWallet { #[wasm_bindgen] impl WebWallet { /// Create a new instance of a Zcash wallet for a given network. Only one instance should be created per page. - /// + /// /// # Arguments /// /// * `network` - Must be one of "main" or "test" @@ -147,7 +147,7 @@ impl WebWallet { } /// Add a new account to the wallet using a given seed phrase - /// + /// /// # Arguments /// /// * `seed_phrase` - 24 word mnemonic seed phrase @@ -174,7 +174,7 @@ impl WebWallet { } /// Add a new account to the wallet by directly importing a Unified Full Viewing Key (UFVK) - /// + /// /// # Arguments /// /// * `key` - [ZIP316](https://zips.z.cash/zip-0316) encoded UFVK @@ -196,12 +196,12 @@ impl WebWallet { .map(|id| *id) } - /// + /// /// Start a background sync task which will fetch and scan blocks from the connected lighwalletd server - /// + /// /// IMPORTANT: This will spawn a new webworker which will handle the sync task. The sync task will continue to run in the background until the sync process is complete. /// During this time the main thread will not block but certain wallet methods may temporarily block while the wallet is being written to during the sync. - /// + /// pub async fn sync(&self) -> Result<(), Error> { assert!(!thread::is_web_worker_thread()); @@ -229,21 +229,20 @@ impl WebWallet { Ok(self.inner.get_wallet_summary().await?.map(Into::into)) } - /// Create a new transaction proposal to send funds to a given address - /// + /// /// Not this does NOT sign, generate a proof, or send the transaction. It will only craft the proposal which designates how notes from this account can be spent to realize the requested transfer. - /// + /// /// # Arguments /// /// * `account_id` - The ID of the account in this wallet to send funds from /// * `to_address` - [ZIP316](https://zips.z.cash/zip-0316) encoded address to send funds to /// * `value` - Amount to send in Zatoshis (1 ZEC = 100_000_000 Zatoshis) - /// + /// /// # Returns - /// + /// /// A proposal object which can be inspected and later used to generate a valid transaction - /// + /// /// # Examples /// /// ```javascript @@ -264,18 +263,18 @@ impl WebWallet { } /// Generate a valid Zcash transaction from a given proposal - /// + /// /// # Arguments /// /// * `proposal` - A proposal object generated by `propose_transfer` /// * `seed_phrase` - 24 word mnemonic seed phrase. This MUST correspond to the accountID used when creating the proposal. /// * `account_hd_index` - [ZIP32](https://zips.z.cash/zip-0032) hierarchical deterministic index of the account. This MUST correspond to the accountID used when creating the proposal. - /// + /// /// # Returns - /// + /// /// A list of transaction IDs which can be used to track the status of the transaction on the network. /// The transactions themselves are stored within the wallet - /// + /// /// # Examples /// /// ```javascript @@ -297,13 +296,13 @@ impl WebWallet { } /// Send a list of authorized transactions to the network to be included in the blockchain - /// + /// /// These will be sent via the connected lightwalletd instance - /// + /// /// # Arguments /// /// * `txids` - A list of transaction IDs (typically generated by `create_proposed_transactions`) - /// + /// /// # Examples /// /// ```javascript @@ -322,7 +321,7 @@ impl WebWallet { /// /// Get the hightest known block height from the connected lightwalletd instance - /// + /// pub async fn get_latest_block(&self) -> Result { self.client() .get_latest_block(ChainSpec {})