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/packages/demo-wallet/.proxyrc.js b/.proxyrc.js
similarity index 100%
rename from packages/demo-wallet/.proxyrc.js
rename to .proxyrc.js
diff --git a/Cargo.lock b/Cargo.lock
index 9d23467..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=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a"
+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=d1fa3e846c5e61de3f1df23dd9f4d5416915631a#d1fa3e846c5e61de3f1df23dd9f4d5416915631a"
+source = "git+https://github.com/ChainSafe/librustzcash?rev=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
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=9673cc2859e8a2528d1efd3c74795363f87ddf8f#9673cc2859e8a2528d1efd3c74795363f87ddf8f"
dependencies = [
"base64",
"nom",
"percent-encoding",
+ "serde",
+ "serde_with",
"zcash_address",
"zcash_protocol",
]
diff --git a/Cargo.toml b/Cargo.toml
index 69da9c9..1f57d91 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
@@ -61,12 +60,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 = "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 +76,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 = "9673cc2859e8a2528d1efd3c74795363f87ddf8f", default-features = false, features = ["unstable", "orchard"], optional = true }
getrandom = { version = "0.2", features = ["js"] }
thiserror = "1.0.63"
diff --git a/README.md b/README.md
index bb4888b..f24161f 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,43 @@ 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
+pnpm serve
```
-## Development
+> [!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
-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.
+## Development
### 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/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/package.json b/package.json
index c01bdc9..a4310ba 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",
+ "serve": "pnpm --filter ./packages/demo-wallet run serve",
+ "start:dev": "parcel --no-autoinstall packages/demo-wallet",
"test": "pnpm -r test"
},
"dependencies": {
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/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 8d82e8b..97cdc48 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);
}
@@ -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, 0);
+ 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/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
-
+
diff --git a/packages/e2e-tests/dist/serve.json b/packages/e2e-tests/dist/serve.json
new file mode 100644
index 0000000..117f86c
--- /dev/null
+++ b/packages/e2e-tests/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/pnpm-lock.yaml b/pnpm-lock.yaml
index ea51fe9..5552d89 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,7 +20,7 @@ importers:
version: 2.12.0(@swc/helpers@0.5.13)
parcel:
specifier: ^2.12.0
- version: 2.12.0(@swc/helpers@0.5.13)(typescript@5.6.2)
+ version: 2.12.0(@swc/helpers@0.5.13)(postcss@8.4.47)(relateurl@0.2.7)(terser@5.34.1)(typescript@5.6.2)
process:
specifier: ^0.11.10
version: 0.11.10
@@ -54,15 +54,19 @@ importers:
typescript:
specifier: ^5.6.2
version: 5.6.2
+ devDependencies:
+ serve:
+ specifier: ^14.2.3
+ version: 14.2.3
packages/e2e-tests:
devDependencies:
'@playwright/test':
specifier: ^1.47.2
- version: 1.47.2
+ version: 1.48.0
'@types/node':
specifier: ^22.7.4
- version: 22.7.4
+ version: 22.7.5
'@webzjs/webz-core':
specifier: workspace:^
version: link:../webz-core
@@ -90,6 +94,27 @@ packages:
resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==}
engines: {node: '>=6.9.0'}
+ '@jridgewell/gen-mapping@0.3.5':
+ resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/set-array@1.2.1':
+ resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/source-map@0.3.6':
+ resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
+
+ '@jridgewell/sourcemap-codec@1.5.0':
+ resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
'@lezer/common@1.2.1':
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
@@ -458,8 +483,8 @@ packages:
peerDependencies:
'@parcel/core': ^2.12.0
- '@playwright/test@1.47.2':
- resolution: {integrity: sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==}
+ '@playwright/test@1.48.0':
+ resolution: {integrity: sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==}
engines: {node: '>=18'}
hasBin: true
@@ -565,8 +590,8 @@ packages:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
- '@types/node@22.7.4':
- resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==}
+ '@types/node@22.7.5':
+ resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==}
'@types/prop-types@15.7.13':
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
@@ -593,6 +618,11 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
+ acorn@8.12.1:
+ resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
ajv@8.12.0:
resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
@@ -658,6 +688,9 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
bytes@3.0.0:
resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
engines: {node: '>= 0.8'}
@@ -725,6 +758,9 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ commander@2.20.3:
+ resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
@@ -1127,6 +1163,11 @@ packages:
msgpackr@1.11.0:
resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==}
+ nanoid@3.3.7:
+ resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
@@ -1203,19 +1244,23 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
- playwright-core@1.47.2:
- resolution: {integrity: sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==}
+ playwright-core@1.48.0:
+ resolution: {integrity: sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==}
engines: {node: '>=18'}
hasBin: true
- playwright@1.47.2:
- resolution: {integrity: sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==}
+ playwright@1.48.0:
+ resolution: {integrity: sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==}
engines: {node: '>=18'}
hasBin: true
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ postcss@8.4.47:
+ resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
posthtml-parser@0.10.2:
resolution: {integrity: sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==}
engines: {node: '>=12'}
@@ -1316,6 +1361,10 @@ packages:
resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
engines: {node: '>=0.10.0'}
+ relateurl@0.2.7:
+ resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
+ engines: {node: '>= 0.10'}
+
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -1357,6 +1406,13 @@ packages:
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map-support@0.5.21:
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
@@ -1410,6 +1466,11 @@ packages:
resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==}
engines: {node: '>=8'}
+ terser@5.34.1:
+ resolution: {integrity: sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
timsort@0.3.0:
resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==}
@@ -1505,6 +1566,34 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
+ '@jridgewell/gen-mapping@0.3.5':
+ dependencies:
+ '@jridgewell/set-array': 1.2.1
+ '@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/trace-mapping': 0.3.25
+ optional: true
+
+ '@jridgewell/resolve-uri@3.1.2':
+ optional: true
+
+ '@jridgewell/set-array@1.2.1':
+ optional: true
+
+ '@jridgewell/source-map@0.3.6':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
+ optional: true
+
+ '@jridgewell/sourcemap-codec@1.5.0':
+ optional: true
+
+ '@jridgewell/trace-mapping@0.3.25':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+ optional: true
+
'@lezer/common@1.2.1': {}
'@lezer/lr@1.4.2':
@@ -1584,14 +1673,14 @@ snapshots:
transitivePeerDependencies:
- '@parcel/core'
- '@parcel/config-default@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)(typescript@5.6.2)':
+ '@parcel/config-default@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)(postcss@8.4.47)(relateurl@0.2.7)(terser@5.34.1)(typescript@5.6.2)':
dependencies:
'@parcel/bundler-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))
'@parcel/compressor-raw': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))
'@parcel/core': 2.12.0(@swc/helpers@0.5.13)
'@parcel/namer-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))
'@parcel/optimizer-css': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))
- '@parcel/optimizer-htmlnano': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(typescript@5.6.2)
+ '@parcel/optimizer-htmlnano': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(postcss@8.4.47)(relateurl@0.2.7)(terser@5.34.1)(typescript@5.6.2)
'@parcel/optimizer-image': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))
'@parcel/optimizer-svgo': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))
'@parcel/optimizer-swc': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)
@@ -1722,10 +1811,10 @@ snapshots:
transitivePeerDependencies:
- '@parcel/core'
- '@parcel/optimizer-htmlnano@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(typescript@5.6.2)':
+ '@parcel/optimizer-htmlnano@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(postcss@8.4.47)(relateurl@0.2.7)(terser@5.34.1)(typescript@5.6.2)':
dependencies:
'@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))
- htmlnano: 2.1.1(svgo@2.8.0)(typescript@5.6.2)
+ htmlnano: 2.1.1(postcss@8.4.47)(relateurl@0.2.7)(svgo@2.8.0)(terser@5.34.1)(typescript@5.6.2)
nullthrows: 1.1.1
posthtml: 0.16.6
svgo: 2.8.0
@@ -2137,9 +2226,9 @@ snapshots:
transitivePeerDependencies:
- '@swc/helpers'
- '@playwright/test@1.47.2':
+ '@playwright/test@1.48.0':
dependencies:
- playwright: 1.47.2
+ playwright: 1.48.0
'@popperjs/core@2.11.8': {}
@@ -2226,7 +2315,7 @@ snapshots:
'@trysound/sax@0.2.0': {}
- '@types/node@22.7.4':
+ '@types/node@22.7.5':
dependencies:
undici-types: 6.19.8
@@ -2256,6 +2345,9 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
+ acorn@8.12.1:
+ optional: true
+
ajv@8.12.0:
dependencies:
fast-deep-equal: 3.1.3
@@ -2326,6 +2418,9 @@ snapshots:
node-releases: 2.0.18
update-browserslist-db: 1.1.0(browserslist@4.23.3)
+ buffer-from@1.1.2:
+ optional: true
+
bytes@3.0.0: {}
callsites@3.1.0: {}
@@ -2379,6 +2474,9 @@ snapshots:
color-name@1.1.4: {}
+ commander@2.20.3:
+ optional: true
+
commander@7.2.0: {}
compressible@2.0.18:
@@ -2535,13 +2633,16 @@ snapshots:
has-flag@4.0.0: {}
- htmlnano@2.1.1(svgo@2.8.0)(typescript@5.6.2):
+ htmlnano@2.1.1(postcss@8.4.47)(relateurl@0.2.7)(svgo@2.8.0)(terser@5.34.1)(typescript@5.6.2):
dependencies:
cosmiconfig: 9.0.0(typescript@5.6.2)
posthtml: 0.16.6
timsort: 0.3.0
optionalDependencies:
+ postcss: 8.4.47
+ relateurl: 0.2.7
svgo: 2.8.0
+ terser: 5.34.1
transitivePeerDependencies:
- typescript
@@ -2718,6 +2819,9 @@ snapshots:
optionalDependencies:
msgpackr-extract: 3.0.3
+ nanoid@3.3.7:
+ optional: true
+
negotiator@0.6.3: {}
node-addon-api@6.1.0: {}
@@ -2755,9 +2859,9 @@ snapshots:
ordered-binary@1.5.1: {}
- parcel@2.12.0(@swc/helpers@0.5.13)(typescript@5.6.2):
+ parcel@2.12.0(@swc/helpers@0.5.13)(postcss@8.4.47)(relateurl@0.2.7)(terser@5.34.1)(typescript@5.6.2):
dependencies:
- '@parcel/config-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)(typescript@5.6.2)
+ '@parcel/config-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)(postcss@8.4.47)(relateurl@0.2.7)(terser@5.34.1)(typescript@5.6.2)
'@parcel/core': 2.12.0(@swc/helpers@0.5.13)
'@parcel/diagnostic': 2.12.0
'@parcel/events': 2.12.0
@@ -2803,16 +2907,23 @@ snapshots:
picomatch@2.3.1: {}
- playwright-core@1.47.2: {}
+ playwright-core@1.48.0: {}
- playwright@1.47.2:
+ playwright@1.48.0:
dependencies:
- playwright-core: 1.47.2
+ playwright-core: 1.48.0
optionalDependencies:
fsevents: 2.3.2
postcss-value-parser@4.2.0: {}
+ postcss@8.4.47:
+ dependencies:
+ nanoid: 3.3.7
+ picocolors: 1.1.0
+ source-map-js: 1.2.1
+ optional: true
+
posthtml-parser@0.10.2:
dependencies:
htmlparser2: 7.2.0
@@ -2922,6 +3033,9 @@ snapshots:
dependencies:
rc: 1.2.8
+ relateurl@0.2.7:
+ optional: true
+
require-from-string@2.0.2: {}
resolve-from@4.0.0: {}
@@ -2971,6 +3085,15 @@ snapshots:
signal-exit@3.0.7: {}
+ source-map-js@1.2.1:
+ optional: true
+
+ source-map-support@0.5.21:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+ optional: true
+
source-map@0.6.1: {}
srcset@4.0.0: {}
@@ -3021,6 +3144,14 @@ snapshots:
term-size@2.2.1: {}
+ terser@5.34.1:
+ dependencies:
+ '@jridgewell/source-map': 0.3.6
+ acorn: 8.12.1
+ commander: 2.20.3
+ source-map-support: 0.5.21
+ optional: true
+
timsort@0.3.0: {}
to-regex-range@5.0.1:
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..761adb2
--- /dev/null
+++ b/src/bindgen/proposal.rs
@@ -0,0 +1,33 @@
+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 {
+ /// Returns a JSON object with the details of the 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 f974f97..4099a41 100644
--- a/src/bindgen/wallet.rs
+++ b/src/bindgen/wallet.rs
@@ -1,44 +1,90 @@
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;
+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::consensus;
+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
///
+/// 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 these can be used to build transactions that spend notes
+/// 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.
+///
+/// ```javascript
+/// await wallet.sync();
+/// ```
+///
+/// ## Transacting
///
-/// ## Building Transactions
+/// Sending a transaction is a three step process: proposing, authorizing, and sending.
///
-/// TODO
+/// 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)]
@@ -66,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,
@@ -88,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,
@@ -108,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)?;
@@ -118,35 +196,19 @@ 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
- /// 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> {
+ ///
+ /// 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());
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!(
@@ -155,7 +217,7 @@ impl WebWallet {
);
let db = db;
- db.sync2().await.unwrap_throw();
+ db.sync().await.unwrap_throw();
})
.unwrap_throw()
.join_async();
@@ -167,33 +229,99 @@ impl WebWallet {
Ok(self.inner.get_wallet_summary().await?.map(Into::into))
}
+ /// Create a new transaction proposal to send funds to a given address
///
- /// 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.
+ /// 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
///
- pub async fn transfer(
+ /// * `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,
- 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())
+ }
+
+ /// 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
+ /// 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, account_hd_index, &self.inner.network)?;
+ let txids = self
+ .inner
+ .create_proposed_transactions(proposal.into(), &usk)
+ .await?;
+ 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
+ ///
+ /// * `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
}
- /// Forwards a call to lightwalletd to retrieve the height of the latest block in the chain
+ ///////////////////////////////////////////////////////////////////////////////////////
+ // lightwalletd gRPC methods
+ ///////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// 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/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/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();
}
diff --git a/src/wallet.rs b/src/wallet.rs
index cccdc0d..22bb521 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::{
- 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;
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;
@@ -227,7 +224,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();
@@ -244,113 +241,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