@ledgerhq/zcash-utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @ledgerhq/zcash-utils
2
+
3
+ Rust workspace for Zcash cryptographic operations. Provides key derivation,
4
+ shielded transaction decryption, and compact block scanning across multiple
5
+ runtime targets.
6
+
7
+ ## Build targets
8
+
9
+ | Target | Script | Output |
10
+ | --------------------- | ------------------------------ | --------------------------------------- |
11
+ | Node.js / Electron | `./scripts/build-napi.sh` | `index.*.node` |
12
+ | macOS CLI (universal) | `./scripts/build-cli-macos.sh` | `dist/ledger-zcash-cli-macos-universal` |
13
+ | Linux CLI (static) | `./scripts/build-cli-linux.sh` | `dist/ledger-zcash-cli-linux-x86_64` |
14
+
15
+ See [`docs/build-targets.md`](docs/build-targets.md) for prerequisites and details.
16
+
17
+ ## Crate structure
18
+
19
+ ```
20
+ zcash-crypto Pure cryptographic logic (key derivation, trial/full decryption)
21
+ zcash-sync Async sync engine (lightwalletd / Zaino)
22
+ zcash-ffi-node Node.js / Electron native addon (napi-rs)
23
+ zcash-cli CLI binary (ledger-zcash-cli)
24
+ ```
25
+
26
+ See [`docs/architecture.md`](docs/architecture.md) for the dependency graph and
27
+ design decisions.
28
+
29
+ ## CLI usage
30
+
31
+ ```bash
32
+ # Key derivation
33
+ ledger-zcash-cli derive --mnemonic "abandon abandon ... about" --format json
34
+
35
+ # Query chain tip
36
+ ledger-zcash-cli tip --grpc-url https://zaino-zec-testnet.nodes.stg.ledger-test.com/
37
+
38
+ # Scan a block range
39
+ ledger-zcash-cli sync \
40
+ --grpc-url https://zaino-zec-testnet.nodes.stg.ledger-test.com/ \
41
+ --viewing-key uviewtest1... \
42
+ --start-height 280000 \
43
+ --end-height 285000 \
44
+ --network testnet \
45
+ --format json
46
+ ```
47
+
48
+ ### `derive` options
49
+
50
+ ```
51
+ --mnemonic <WORDS> BIP-39 mnemonic (reads from stdin if omitted)
52
+ --account <N> ZIP-32 account index [default: 0]
53
+ --xpub-path <PATH> BIP-32 xpub path [default: m/44'/133'/{account}']
54
+ --network mainnet|testnet [default: mainnet]
55
+ --no-sapling Exclude Sapling FVK from UFVK
56
+ --format human|json [default: human]
57
+ ```
58
+
59
+ ## Development
60
+
61
+ ```bash
62
+ # Run all logic tests
63
+ cargo test --package zcash-crypto
64
+
65
+ # Run CLI integration tests
66
+ cargo test --package zcash-cli
67
+
68
+ # Coverage (requires cargo install cargo-llvm-cov)
69
+ ./scripts/coverage.sh
70
+
71
+ # Type-check everything
72
+ cargo check --workspace
73
+ ```
74
+
75
+ ## Documentation
76
+
77
+ - [`docs/architecture.md`](docs/architecture.md) — workspace design
78
+ - [`docs/key-derivation.md`](docs/key-derivation.md) — BIP-39 → UFVK pipeline
79
+ - [`docs/block-sync.md`](docs/block-sync.md) — gRPC trial + full decryption
80
+ - [`docs/ffi-node.md`](docs/ffi-node.md) — Node.js/Electron integration
81
+ - [`docs/build-targets.md`](docs/build-targets.md) — build scripts reference
82
+
83
+ ## Release
84
+
85
+ Versioning is managed with [Changesets](https://github.com/changesets/action). Every merge to `main` triggers the CI workflow, which:
86
+
87
+ 1. Builds all artifacts in parallel on their respective platforms
88
+ 2. Either opens/updates a **"Version Packages"** PR if changesets are pending
89
+ 3. Or publishes immediately if the "Version Packages" PR has already been merged
90
+
91
+ ### Publishing a new version
92
+
93
+ ```bash
94
+ # 1. Describe the change (patch / minor / major)
95
+ pnpm changeset
96
+
97
+ # 2. Commit the generated .changeset file and push
98
+ git add .changeset/
99
+ git commit -m "chore: add changeset"
100
+ git push
101
+
102
+ # 3. Merge the PR → CI automatically opens a "Version Packages" PR
103
+ # 4. Merge the "Version Packages" PR → CI publishes
104
+ ```
105
+
106
+ ### Artifacts produced per release
107
+
108
+ | Artifact | Distribution | Platforms |
109
+ | ---------------------------------- | ------------------ | --------------------------- |
110
+ | `@ledgerhq/zcash-utils` | Ledger JFrog (npm) | All (bundled `.node` files) |
111
+ | `ledger-zcash-cli-macos-universal` | GitHub Release | macOS arm64 + x64 |
112
+ | `ledger-zcash-cli-linux-x86_64` | GitHub Release | Linux x64 (static musl) |
113
+
114
+ The `.node` binaries are built in a CI matrix for each OS/architecture target, collected in the publish job, and included in the npm package via the `files` field. The `index.js` NAPI-RS loader looks for a local `.node` file first, then falls back to a separate `@ledgerhq/zcash-utils-{platform}` package if needed.
115
+
116
+ The npm package is published to the internal Ledger JFrog Artifactory registry. The CI authenticates via OIDC (`LedgerHQ/actions-security/actions/jfrog-login`) and configures `.npmrc` to route `@ledgerhq` scoped packages to the Artifactory URL.
117
+
118
+ CLI binaries are attached to the tagged GitHub Release (`v{version}`) and are not part of the npm package.
119
+
120
+ ### Required secrets and variables
121
+
122
+ | Secret / Variable | Purpose |
123
+ | ------------------------------ | ----------------------------------------------------- |
124
+ | `GITHUB_TOKEN` | Automatically provided by GitHub Actions |
125
+ | `vars.ARTIFACTORY_PUBLISH_URL` | JFrog Artifactory registry URL (without `https://`) |
126
+
127
+ ## License
128
+
129
+ MIT OR Apache-2.0
package/index.d.ts ADDED
@@ -0,0 +1,112 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ /**
7
+ * Parameters for scanning a block range for shielded transactions.
8
+ *
9
+ * The UFVK must be obtained from the Ledger device — never derive it from
10
+ * a seed phrase directly in this layer.
11
+ */
12
+ export interface SyncParams {
13
+ /** gRPC endpoint URL (e.g. `"https://zaino-zec-testnet.nodes.stg.ledger-test.com/"`). */
14
+ grpcUrl: string
15
+ /** Unified Full Viewing Key (UFVK) for the account to scan. */
16
+ viewingKey: string
17
+ /** First block height to scan (inclusive). */
18
+ startHeight: number
19
+ /** Last block height to scan (inclusive). */
20
+ endHeight: number
21
+ /** `"mainnet"` or `"testnet"` (default: `"testnet"`). */
22
+ network?: string
23
+ /**
24
+ * When `true`, Sapling outputs are stripped before trial decryption.
25
+ * Only Orchard actions are processed — eliminates all Sapling crypto work.
26
+ * Set to `true` for Ledger wallets (Orchard-only support).
27
+ */
28
+ orchardOnly?: boolean
29
+ /**
30
+ * Maximum retry attempts per range on transient errors (timeout, 503).
31
+ * The failing range is split in half on each retry. Defaults to 3.
32
+ */
33
+ maxRetries?: number
34
+ /** Emit per-phase timing diagnostics to stderr every 10 seconds. */
35
+ verbose?: boolean
36
+ }
37
+ /** A single shielded note found during decryption. */
38
+ export interface ShieldedNote {
39
+ /** Amount in zatoshis (f64 for JS Number compatibility). */
40
+ amount: number
41
+ /** `"incoming"`, `"outgoing"`, or `"internal"`. */
42
+ transferType: string
43
+ /** Memo text decoded from the note. */
44
+ memo: string
45
+ }
46
+ /** A matched and fully-decrypted shielded transaction. */
47
+ export interface ShieldedTransaction {
48
+ /** Transaction ID in big-endian (display) hex order. */
49
+ txid: string
50
+ /** Raw transaction bytes as a hex string. */
51
+ hex: string
52
+ /** Block height at which this transaction was confirmed. */
53
+ blockHeight: number
54
+ /** Block hash in big-endian (display) hex order. */
55
+ blockHash: string
56
+ /** Block timestamp (Unix seconds). */
57
+ blockTime: number
58
+ /** Transaction fee in zatoshis (shielded bundles only). */
59
+ fee: number
60
+ /** Decrypted Sapling notes belonging to this account. */
61
+ saplingNotes: Array<ShieldedNote>
62
+ /** Decrypted Orchard notes belonging to this account. */
63
+ orchardNotes: Array<ShieldedNote>
64
+ }
65
+ /** Scan statistics returned once the stream is exhausted. */
66
+ export interface SyncStats {
67
+ blocksScanned: number
68
+ elapsedMs: number
69
+ }
70
+ /**
71
+ * Start scanning a range of compact blocks and return a transaction stream.
72
+ *
73
+ * The scan runs in the background immediately. Call `stream.next()` to
74
+ * consume transactions as they are found. Call `stream.stats()` after the
75
+ * stream is exhausted to retrieve scan statistics.
76
+ *
77
+ * Trial decryption runs entirely in Rust (no JS event loop blocking).
78
+ * `GetTransaction` is called only for matched transactions.
79
+ */
80
+ export declare function startSync(params: SyncParams): Promise<TransactionStream>
81
+ /** Returns the current chain tip height from the gRPC endpoint. */
82
+ export declare function getChainTip(grpcUrl: string): Promise<number>
83
+ /**
84
+ * Async iterator over matched shielded transactions.
85
+ *
86
+ * Usage (TypeScript):
87
+ * ```ts
88
+ * const stream = await startSync(params);
89
+ * let tx: ShieldedTransaction | null;
90
+ * while ((tx = await stream.next()) !== null) {
91
+ * console.log(tx);
92
+ * }
93
+ * const stats = await stream.stats();
94
+ * ```
95
+ */
96
+ export declare class TransactionStream {
97
+ /**
98
+ * Returns the next matched transaction, or `null` when the scan is complete.
99
+ *
100
+ * Safety: napi-rs requires `unsafe` for `&mut self` in async methods.
101
+ * This method is safe to call — it only mutates the internal channel receiver.
102
+ */
103
+ next(): Promise<ShieldedTransaction | null>
104
+ /**
105
+ * Returns scan statistics once the stream is exhausted (i.e. after `next()`
106
+ * returns `null`). Calling this before the stream is done will wait until
107
+ * the background sync task finishes.
108
+ *
109
+ * Safety: napi-rs requires `unsafe` for `&mut self` in async methods.
110
+ */
111
+ stats(): Promise<SyncStats>
112
+ }
Binary file
Binary file
package/index.js ADDED
@@ -0,0 +1,317 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'index.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./index.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('@ledgerhq/zcash-utils-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'index.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./index.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('@ledgerhq/zcash-utils-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'index.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./index.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('@ledgerhq/zcash-utils-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'index.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./index.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('@ledgerhq/zcash-utils-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'index.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./index.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('@ledgerhq/zcash-utils-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'index.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./index.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('@ledgerhq/zcash-utils-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'index.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./index.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('@ledgerhq/zcash-utils-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'index.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./index.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('@ledgerhq/zcash-utils-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'index.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./index.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('@ledgerhq/zcash-utils-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'index.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./index.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'index.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./index.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'index.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./index.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'index.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./index.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'index.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./index.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'index.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./index.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'index.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./index.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'index.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./index.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'index.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./index.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('@ledgerhq/zcash-utils-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { TransactionStream, startSync, getChainTip } = nativeBinding
314
+
315
+ module.exports.TransactionStream = TransactionStream
316
+ module.exports.startSync = startSync
317
+ module.exports.getChainTip = getChainTip
Binary file
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@ledgerhq/zcash-utils",
3
+ "version": "0.1.0",
4
+ "description": "Native Zcash key derivation and shielded sync — Node.js (NAPI)",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "index.d.ts",
10
+ "*.node"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/LedgerHQ/ledger-zcash-utils.git"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "devDependencies": {
20
+ "@changesets/cli": "^2.29.8",
21
+ "@napi-rs/cli": "^2.18.0",
22
+ "@types/node": "^25.5.2",
23
+ "tsx": "^4.21.0",
24
+ "typescript": "^6.0.2"
25
+ },
26
+ "license": "MIT",
27
+ "scripts": {
28
+ "// ── NAPI (Node.js / Electron) ──────────────────────────────────────": "",
29
+ "build": "./scripts/build-napi.sh",
30
+ "build:debug": "DEBUG=1 ./scripts/build-napi.sh",
31
+ "// ── CLI artifacts ───────────────────────────────────────────────────": "",
32
+ "build:cli:macos": "./scripts/build-cli-macos.sh",
33
+ "build:cli:linux": "./scripts/build-cli-linux.sh",
34
+ "// ── Rust checks & tests ────────────────────────────────────────────": "",
35
+ "check": "cargo check --workspace",
36
+ "test": "cargo test --package zcash-crypto",
37
+ "test:cli": "cargo test --package zcash-cli",
38
+ "test:all": "cargo test --workspace",
39
+ "// ── Coverage ───────────────────────────────────────────────────────": "",
40
+ "coverage": "./scripts/coverage.sh",
41
+ "coverage:open": "OPEN_REPORT=1 ./scripts/coverage.sh",
42
+ "// ── Rustdoc ────────────────────────────────────────────────────────": "",
43
+ "doc": "cargo doc --workspace --no-deps",
44
+ "doc:open": "cargo doc --workspace --no-deps --open",
45
+ "// ── CLI (ledger-zcash-cli) — usage: pnpm cli -- derive --mnemonic '...'": "",
46
+ "cli": "cargo run -q -p zcash-cli",
47
+ "cli:derive": "cargo run -q -p zcash-cli -- derive --help",
48
+ "cli:tip": "cargo run -q -p zcash-cli -- tip --help",
49
+ "cli:sync": "cargo run -q -p zcash-cli -- sync --help",
50
+ "// ── Node.js validation & benchmarks ───────────────────────────────": "",
51
+ "sync-tx": "tsx examples/sync-tx.node.ts --start-height 1130000 --end-height 1180000",
52
+ "bench": "tsx examples/bench.ts --start-height 280000 --end-height 285000"
53
+ }
54
+ }