@tokamak-private-dapps/private-state-cli 2.3.1 → 2.3.3
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/CHANGELOG.md +17 -0
- package/README.md +37 -11
- package/cli-assistant.html +3 -2
- package/lib/private-state-cli-command-registry.mjs +28 -18
- package/lib/runtime.mjs +181 -171
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 2.3.3 - 2026-05-27
|
|
6
|
+
|
|
7
|
+
- Changed `help observer` and monitoring references to use the public observer URL
|
|
8
|
+
`https://observer.tonnel.io`.
|
|
9
|
+
- Clarified CLI help, guide output, and README guidance so channel join tolls are paid directly
|
|
10
|
+
from the L1 wallet, while `account deposit-bridge` is only for channel liquidity.
|
|
11
|
+
- Clarified README and `help commands` guidance so AI agents answer user gas, fee, and USD cost
|
|
12
|
+
questions by running `help transaction-fees --json` instead of escalating to developers.
|
|
13
|
+
|
|
14
|
+
## 2.3.2 - 2026-05-21
|
|
15
|
+
|
|
16
|
+
- Clarified `wallet transfer-notes` JSON-array argument formats in CLI help and README guidance.
|
|
17
|
+
- Changed wallet note freshness to use the fresh channel workspace recovery frontier instead of the
|
|
18
|
+
provider's latest L1 block, so unrelated L1 blocks do not make wallet workspaces stale.
|
|
19
|
+
- Simplified wallet recovery and command-argument validation logic while preserving the channel-frontier
|
|
20
|
+
recovery model.
|
|
21
|
+
|
|
5
22
|
## 2.3.1 - 2026-05-20
|
|
6
23
|
|
|
7
24
|
- Added `wallet recover-workspace --wallet-secret-path` support for rederiving and storing an active
|
package/README.md
CHANGED
|
@@ -131,11 +131,11 @@ and the global CLI npm package when npm reports that it is globally installed.
|
|
|
131
131
|
|
|
132
132
|
## Commands
|
|
133
133
|
|
|
134
|
-
A common
|
|
134
|
+
A common note-use flow after channel policy review is:
|
|
135
135
|
|
|
136
136
|
1. `channel create`
|
|
137
|
-
2. `
|
|
138
|
-
3. `
|
|
137
|
+
2. `channel join`
|
|
138
|
+
3. `account deposit-bridge`
|
|
139
139
|
4. `wallet deposit-channel`
|
|
140
140
|
5. `wallet mint-notes`
|
|
141
141
|
6. `wallet transfer-notes`
|
|
@@ -145,6 +145,8 @@ A common private-state flow is:
|
|
|
145
145
|
10. `channel exit`
|
|
146
146
|
11. `account withdraw-bridge`
|
|
147
147
|
|
|
148
|
+
`channel join` pays any join toll directly from the L1 wallet; `account deposit-bridge` funds later channel liquidity and does not pay the join toll.
|
|
149
|
+
|
|
148
150
|
Use `private-state-cli help commands` for the full command list and required options. `private-state-cli --help`
|
|
149
151
|
continues to print the same command list for shell compatibility.
|
|
150
152
|
|
|
@@ -209,9 +211,11 @@ saved index instead of silently replaying from genesis.
|
|
|
209
211
|
Wallet commands that need channel state, including `wallet recover-workspace`, `wallet get-meta`,
|
|
210
212
|
`wallet get-channel-fund`, and `wallet get-notes`, refresh stale local channel workspaces through saved recovery
|
|
211
213
|
indexes before reading state. `wallet get-notes` and `wallet recover-workspace` also refresh received-note logs
|
|
212
|
-
through the saved wallet note recovery index.
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
through the saved wallet note recovery index. Wallet note freshness is measured against the fresh channel workspace
|
|
215
|
+
frontier, not the provider's latest L1 block, so unrelated new L1 blocks do not make a wallet stale by themselves.
|
|
216
|
+
Automatic refresh never replays from channel genesis and only runs when the recovery delta fits within the 7,200-block
|
|
217
|
+
pre-command budget. If a saved index is missing, unusable, or too far behind, the command stops and asks the user to
|
|
218
|
+
run the appropriate recovery command first.
|
|
215
219
|
|
|
216
220
|
Wallet note-delivery recovery checkpoints after each RPC log chunk by updating
|
|
217
221
|
`noteReceiveLastScannedBlock`. If an ordinary `wallet recover-workspace` run is interrupted during note recovery, the
|
|
@@ -310,7 +314,8 @@ private-state-cli help transaction-fees --network mainnet
|
|
|
310
314
|
`help transaction-fees` uses the measured gas data packaged in `assets/tx-fees.json`, the selected network's live fee data,
|
|
311
315
|
and live ETH/USD pricing to print an ETH/USD fee table for transaction-sending commands. The table separates typical
|
|
312
316
|
cost, based on the RPC `gasPrice`, from worst-case cost, based on `maxFeePerGas` when the network reports EIP-1559 fee
|
|
313
|
-
data.
|
|
317
|
+
data. AI agents answering user questions about gas, transaction fees, transaction cost, or USD cost should run
|
|
318
|
+
`private-state-cli help transaction-fees --network <NETWORK> --json` and answer from the returned table.
|
|
314
319
|
|
|
315
320
|
Proof-backed note commands can use a separate L1 transaction submitter:
|
|
316
321
|
|
|
@@ -323,6 +328,23 @@ note ownership and builds the ZK proof, but the selected local account submits `
|
|
|
323
328
|
Use this option when a separate imported local account should submit the L1 transaction and pay gas for a proof-backed
|
|
324
329
|
note command.
|
|
325
330
|
|
|
331
|
+
`wallet transfer-notes` takes JSON arrays for note selection and outputs. `--note-ids` is a JSON array of input note
|
|
332
|
+
commitment IDs from `wallet get-notes`; `--recipients` is a JSON array of recipient L2 addresses; `--amounts` is a JSON
|
|
333
|
+
array of token amounts. Quote decimal amounts to avoid shell or JSON ambiguity. The recipient count must match the
|
|
334
|
+
amount count, only `1->1`, `1->2`, and `2->1` transfer shapes are supported, and the output amount sum must equal the
|
|
335
|
+
selected input note value sum.
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
private-state-cli wallet transfer-notes \
|
|
339
|
+
--wallet <WALLET> \
|
|
340
|
+
--network mainnet \
|
|
341
|
+
--note-ids '["0xNOTE1","0xNOTE2"]' \
|
|
342
|
+
--recipients '["0xL2RECIPIENT1","0xL2RECIPIENT2"]' \
|
|
343
|
+
--amounts '["1.5","2"]' \
|
|
344
|
+
--acknowledge-action-impact \
|
|
345
|
+
--tx-submitter <ACCOUNT>
|
|
346
|
+
```
|
|
347
|
+
|
|
326
348
|
Channel policy warning:
|
|
327
349
|
|
|
328
350
|
- `channel create` commits to an immutable channel policy: verifier bindings, DApp execution metadata, function layout,
|
|
@@ -510,6 +532,10 @@ Operating rules:
|
|
|
510
532
|
recovery can be very slow because it scans channel logs from the creation block. If a channel workspace mirror is
|
|
511
533
|
available, try mirror-based recovery first, and use RPC genesis replay only when mirror recovery is unavailable or
|
|
512
534
|
unsuitable.
|
|
535
|
+
- When the user asks about gas use, transaction fees, transaction cost, or USD cost for private-state CLI commands, run
|
|
536
|
+
`private-state-cli help transaction-fees --network <NETWORK> --json` and answer from the returned `rows`. If the
|
|
537
|
+
network is unclear, ask which network to use. Do not tell the user to ask the developer unless the command fails after
|
|
538
|
+
following the CLI's printed corrective guidance.
|
|
513
539
|
- When `channel recover-workspace` or `wallet recover-workspace` is unexpectedly slow, first inspect the RPC provider
|
|
514
540
|
configured by `set rpc`. Explain that recovery speed is dominated by `eth_getLogs` block range cap and log request
|
|
515
541
|
rate. Suggest re-running `set rpc` with a provider that supports a larger block range cap, such as Ankr or Chainnodes
|
|
@@ -534,7 +560,7 @@ Operating rules:
|
|
|
534
560
|
8. prepare a wallet secret source file locally, for example with `openssl rand -hex 32 > ./wallet-secret.txt`
|
|
535
561
|
9. inspect the channel with `channel get-meta` if it already exists, or create it with `channel create` if the user is
|
|
536
562
|
the channel creator
|
|
537
|
-
10. explain the immutable policy warning
|
|
563
|
+
10. explain the immutable policy warning and that the join toll is paid directly from the L1 wallet, not bridge-deposited balance
|
|
538
564
|
11. run `channel join --channel-name <CHANNEL> --network <NETWORK> --account <ACCOUNT> --wallet-secret-path <PATH> --acknowledge-action-impact`
|
|
539
565
|
- Before executing any command for a user that requires an `--acknowledge-*` option, strongly warn the user in plain
|
|
540
566
|
language about what that acknowledgement means and ask for explicit confirmation. Do not add
|
|
@@ -566,7 +592,7 @@ Operating rules:
|
|
|
566
592
|
- Do not present one fixed command sequence as universally correct. Some flows start from an existing channel or wallet,
|
|
567
593
|
while others require creating or joining a channel first.
|
|
568
594
|
- When the user asks for a transfer, first determine whether the sender has minted notes available. If not, guide them
|
|
569
|
-
through
|
|
595
|
+
through joining or recovering the channel wallet, funding the bridge for channel liquidity, depositing into the channel, and minting notes.
|
|
570
596
|
- When generating commands, use placeholders for secrets and explicit values for public fields. Show one command at a
|
|
571
597
|
time unless the user asks for a batch.
|
|
572
598
|
|
|
@@ -577,10 +603,10 @@ Suggested interaction flow:
|
|
|
577
603
|
3. Identify the sender and recipient wallets or local account names.
|
|
578
604
|
4. Run `help doctor`.
|
|
579
605
|
5. Run `wallet list` and relevant metadata or balance checks.
|
|
580
|
-
6. If needed, guide the user through `channel create`, `account deposit-bridge`, `
|
|
606
|
+
6. If needed, guide the user through `channel create`, `channel join`, `account deposit-bridge`, `wallet deposit-channel`, and
|
|
581
607
|
`wallet mint-notes`.
|
|
582
608
|
7. For a confidential note transfer, select available note IDs from `wallet get-notes`, find the recipient L2 address from
|
|
583
|
-
`wallet get-meta`, then build `wallet transfer-notes`.
|
|
609
|
+
`wallet get-meta`, then build `wallet transfer-notes` with JSON arrays for `--note-ids`, `--recipients`, and `--amounts`.
|
|
584
610
|
8. After transfer, guide the recipient to run `wallet get-notes`; it refreshes received notes from the saved recovery index when the delta fits the 7,200-block pre-command budget. If the index is missing or too far behind, explain `wallet recover-workspace`.
|
|
585
611
|
|
|
586
612
|
Example onboarding explanation for `channel join`:
|
package/cli-assistant.html
CHANGED
|
@@ -422,11 +422,12 @@
|
|
|
422
422
|
<li>
|
|
423
423
|
<span class="guide-label">Join a channel:</span> <code>channel join</code>
|
|
424
424
|
<div class="guide-example">
|
|
425
|
-
<code>channel join --channel-name 'my-private-channel' --network 'sepolia' --account 'my-account' --wallet-secret-path '/path/to/wallet-secret'</code
|
|
425
|
+
<code>channel join --channel-name 'my-private-channel' --network 'sepolia' --account 'my-account' --wallet-secret-path '/path/to/wallet-secret'</code><br />
|
|
426
|
+
Pays the join toll directly from the L1 wallet, not from bridge-deposited balance.
|
|
426
427
|
</div>
|
|
427
428
|
</li>
|
|
428
429
|
<li>
|
|
429
|
-
<span class="guide-label">
|
|
430
|
+
<span class="guide-label">Fund note balance:</span> <code>account deposit-bridge</code> -> <code>wallet deposit-channel</code> -> <code>wallet mint-notes</code>
|
|
430
431
|
<div class="guide-example">
|
|
431
432
|
<code>account deposit-bridge --amount '100' --network 'sepolia' --account 'my-account'</code><br />
|
|
432
433
|
<code>wallet deposit-channel --wallet 'my-private-channel-0xYourL1Address' --network 'sepolia' --amount '100'</code><br />
|
|
@@ -140,22 +140,25 @@ export const PRIVATE_STATE_CLI_FIELD_CATALOG = Object.freeze({
|
|
|
140
140
|
amounts: {
|
|
141
141
|
label: "Amounts",
|
|
142
142
|
type: "textarea",
|
|
143
|
-
placeholder: "[1
|
|
144
|
-
valueLabel: "<
|
|
143
|
+
placeholder: "[\"1\",\"2\",\"3\"]",
|
|
144
|
+
valueLabel: "<JSON_ARRAY>",
|
|
145
|
+
hint: "JSON array of token amounts. Use quoted strings for decimal amounts, for example [\"1.5\",\"2\"].",
|
|
145
146
|
option: "--amounts",
|
|
146
147
|
},
|
|
147
148
|
noteIds: {
|
|
148
149
|
label: "Note IDs",
|
|
149
150
|
type: "textarea",
|
|
150
151
|
placeholder: "[\"0x...\"]",
|
|
151
|
-
valueLabel: "<
|
|
152
|
+
valueLabel: "<JSON_ARRAY>",
|
|
153
|
+
hint: "JSON array of note commitment IDs from wallet get-notes.",
|
|
152
154
|
option: "--note-ids",
|
|
153
155
|
},
|
|
154
156
|
recipients: {
|
|
155
157
|
label: "Recipients JSON",
|
|
156
158
|
type: "textarea",
|
|
157
159
|
placeholder: "[\"0xRecipientL2Address\"]",
|
|
158
|
-
valueLabel: "<
|
|
160
|
+
valueLabel: "<JSON_ARRAY>",
|
|
161
|
+
hint: "JSON array of recipient L2 addresses. Its length must match --amounts.",
|
|
159
162
|
option: "--recipients",
|
|
160
163
|
},
|
|
161
164
|
docker: {
|
|
@@ -327,7 +330,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
327
330
|
fields: ["network", "channelName", "account", "wallet"],
|
|
328
331
|
optionalFields: ["network", "channelName", "account", "wallet"],
|
|
329
332
|
usage: "optional --network, --channel-name, --account, and --wallet",
|
|
330
|
-
help: ["Does not accept --rpc-url and never writes RPC configuration"],
|
|
333
|
+
help: ["Does not accept --rpc-url and never writes RPC configuration", "Recommends bridge deposits only after a wallet is joined and needs channel liquidity"],
|
|
331
334
|
},
|
|
332
335
|
{
|
|
333
336
|
id: "help-observer",
|
|
@@ -349,6 +352,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
349
352
|
help: [
|
|
350
353
|
"Uses packages/apps/private-state/cli/assets/tx-fees.json as the measured gas source packaged with the CLI",
|
|
351
354
|
"Reads live fee data from the selected network RPC and live ETH/USD from CoinGecko",
|
|
355
|
+
"AI agents should run this command with --json when users ask about gas, transaction fees, transaction cost, or USD cost",
|
|
352
356
|
],
|
|
353
357
|
},
|
|
354
358
|
{
|
|
@@ -458,7 +462,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
458
462
|
usage: "--amount, --network, --account, --acknowledge-action-impact",
|
|
459
463
|
help: [
|
|
460
464
|
"Action impact: emits public L1 approval and bridge funding events that expose the local L1 account, bridge vault, amount, and transaction hashes.",
|
|
461
|
-
"Private note state is not changed by this command.",
|
|
465
|
+
"Private note state is not changed by this command; it does not pay a channel join toll.",
|
|
462
466
|
ACTION_IMPACT_HELP.exchangeControlledAddress,
|
|
463
467
|
ACTION_IMPACT_HELP.illegalUse,
|
|
464
468
|
ACTION_IMPACT_HELP.acknowledgement,
|
|
@@ -508,9 +512,10 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
508
512
|
fields: ["channelName", "network", "account", "walletSecretPath", "acknowledgeActionImpact"],
|
|
509
513
|
usage: "--channel-name, --network, --account, --wallet-secret-path, --acknowledge-action-impact",
|
|
510
514
|
help: [
|
|
511
|
-
"Refreshes the local channel workspace through the saved recovery index before joining when the scan fits the
|
|
515
|
+
"Refreshes the local channel workspace through the saved recovery index before joining when the scan fits the 7,200-block pre-command budget",
|
|
512
516
|
"Fails instead of replaying from genesis; run channel recover-workspace --source rpc --from-genesis when a genesis rebuild is required",
|
|
513
517
|
"--wallet-secret-path is read once for channel-bound L2 spending-key derivation and is not stored in the wallet workspace",
|
|
518
|
+
"Pays any join toll directly from the L1 wallet, not from bridge-deposited balance",
|
|
514
519
|
"Prints the immutable policy snapshot before first registration",
|
|
515
520
|
"Action impact: emits public channel join and token-vault registration events exposing the L1 account, L2 address pair, note-receive public key, join toll, and channel id.",
|
|
516
521
|
"Private note state is not changed by this command.",
|
|
@@ -528,7 +533,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
528
533
|
fields: ["wallet", "network"],
|
|
529
534
|
usage: "--wallet and --network",
|
|
530
535
|
help: [
|
|
531
|
-
"Refreshes the local channel workspace through the saved recovery index before reading registration metadata when the scan fits the
|
|
536
|
+
"Refreshes the local channel workspace through the saved recovery index before reading registration metadata when the scan fits the 7,200-block pre-command budget",
|
|
532
537
|
"Reports the selected local wallet epoch and lifecycle status when the workspace uses the epoch-aware wallet format",
|
|
533
538
|
],
|
|
534
539
|
},
|
|
@@ -598,7 +603,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
598
603
|
fields: ["wallet", "network", "amount", "acknowledgeActionImpact"],
|
|
599
604
|
usage: "--wallet, --network, --amount, and --acknowledge-action-impact",
|
|
600
605
|
help: [
|
|
601
|
-
"Refreshes the local channel workspace through the saved recovery index before proving the deposit when the scan fits the
|
|
606
|
+
"Refreshes the local channel workspace through the saved recovery index before proving the deposit when the scan fits the 7,200-block pre-command budget",
|
|
602
607
|
"Action impact: emits public proof-backed bridge/channel accounting events exposing the L1 submitter, registered L2 address, amount, channel id, and transaction hash.",
|
|
603
608
|
"Private note state is not changed by this command.",
|
|
604
609
|
ACTION_IMPACT_HELP.policy,
|
|
@@ -615,7 +620,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
615
620
|
fields: ["wallet", "network", "amount", "acknowledgeActionImpact"],
|
|
616
621
|
usage: "--wallet, --network, --amount, and --acknowledge-action-impact",
|
|
617
622
|
help: [
|
|
618
|
-
"Refreshes the local channel workspace through the saved recovery index before proving the withdrawal when the scan fits the
|
|
623
|
+
"Refreshes the local channel workspace through the saved recovery index before proving the withdrawal when the scan fits the 7,200-block pre-command budget",
|
|
619
624
|
"Action impact: emits public proof-backed bridge/channel accounting events exposing the L1 submitter, registered L2 address, amount, channel id, and transaction hash.",
|
|
620
625
|
"Private note state is not changed by this command; prior note provenance is not public by default.",
|
|
621
626
|
ACTION_IMPACT_HELP.provenance,
|
|
@@ -632,7 +637,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
632
637
|
installMode: "read-only",
|
|
633
638
|
fields: ["wallet", "network"],
|
|
634
639
|
usage: "--wallet and --network",
|
|
635
|
-
help: ["Refreshes the local channel workspace through the saved recovery index before reading the L2 accounting balance when the scan fits the
|
|
640
|
+
help: ["Refreshes the local channel workspace through the saved recovery index before reading the L2 accounting balance when the scan fits the 7,200-block pre-command budget"],
|
|
636
641
|
},
|
|
637
642
|
{
|
|
638
643
|
id: "channel-exit",
|
|
@@ -642,7 +647,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
642
647
|
fields: ["wallet", "network"],
|
|
643
648
|
usage: "--wallet and --network",
|
|
644
649
|
help: [
|
|
645
|
-
"Refreshes the local channel workspace through the saved recovery index before checking the channel balance when the scan fits the
|
|
650
|
+
"Refreshes the local channel workspace through the saved recovery index before checking the channel balance when the scan fits the 7,200-block pre-command budget",
|
|
646
651
|
"Marks the current local wallet epoch as exited and keeps its note metadata available for historical evidence export",
|
|
647
652
|
],
|
|
648
653
|
},
|
|
@@ -654,7 +659,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
654
659
|
fields: ["wallet", "network", "amounts", "acknowledgeActionImpact", "txSubmitter"],
|
|
655
660
|
usage: "--wallet, --network, --amounts, --acknowledge-action-impact, and optional --tx-submitter",
|
|
656
661
|
help: [
|
|
657
|
-
"Refreshes the local channel workspace through the saved recovery index before proving the mint when the scan fits the
|
|
662
|
+
"Refreshes the local channel workspace through the saved recovery index before proving the mint when the scan fits the 7,200-block pre-command budget",
|
|
658
663
|
"Requires both viewing and spending key capability so the accepted mint can be recovered through the normal note event path",
|
|
659
664
|
"Use --tx-submitter <ACCOUNT> to let a separate local L1 account pay gas for stronger transaction privacy",
|
|
660
665
|
"Action impact: emits public accepted-transition, commitment, encrypted note-delivery, root update, and transaction events.",
|
|
@@ -672,9 +677,14 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
672
677
|
description: "Spend input notes into the registered 1->1, 1->2, or 2->1 private transfer shapes.",
|
|
673
678
|
installMode: "full",
|
|
674
679
|
fields: ["wallet", "network", "noteIds", "recipients", "amounts", "acknowledgeActionImpact", "txSubmitter"],
|
|
675
|
-
usage: "--wallet, --network, --note-ids
|
|
680
|
+
usage: "--wallet, --network, --note-ids <JSON_ARRAY>, --recipients <JSON_ARRAY>, --amounts <JSON_ARRAY>, --acknowledge-action-impact, and optional --tx-submitter",
|
|
676
681
|
help: [
|
|
677
|
-
"
|
|
682
|
+
"--note-ids must be a JSON array of input note commitment IDs from wallet get-notes, for example '[\"0xNOTE1\",\"0xNOTE2\"]'",
|
|
683
|
+
"--recipients must be a JSON array of recipient L2 addresses, for example '[\"0xL2RECIPIENT1\",\"0xL2RECIPIENT2\"]'",
|
|
684
|
+
"--amounts must be a JSON array of token amounts, preferably quoted for decimals, for example '[\"1.5\",\"2\"]'",
|
|
685
|
+
"--recipients length must equal --amounts length; supported transfer shapes are 1->1, 1->2, and 2->1",
|
|
686
|
+
"The sum of output amounts must equal the sum of the selected input note values",
|
|
687
|
+
"Refreshes the local channel workspace and received-note logs through saved recovery indexes before proving the transfer when scans fit the 7,200-block pre-command budget",
|
|
678
688
|
"Use --tx-submitter <ACCOUNT> to let a separate local L1 account pay gas for stronger transaction privacy",
|
|
679
689
|
"Action impact: emits public accepted-transition, input nullifier, output commitment, encrypted note-delivery, root update, and transaction events.",
|
|
680
690
|
"Private note state changes by consuming selected input notes and creating output notes; sender-recipient relationship, note plaintext, and note provenance are not public by default.",
|
|
@@ -693,7 +703,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
693
703
|
fields: ["wallet", "network", "noteIds", "acknowledgeActionImpact", "txSubmitter"],
|
|
694
704
|
usage: "--wallet, --network, --note-ids, --acknowledge-action-impact, and optional --tx-submitter",
|
|
695
705
|
help: [
|
|
696
|
-
"Refreshes the local channel workspace and received-note logs through saved recovery indexes before proving the redeem when scans fit the
|
|
706
|
+
"Refreshes the local channel workspace and received-note logs through saved recovery indexes before proving the redeem when scans fit the 7,200-block pre-command budget",
|
|
697
707
|
"Use --tx-submitter <ACCOUNT> to let a separate local L1 account pay gas for stronger transaction privacy",
|
|
698
708
|
"Action impact: emits public accepted-transition, note nullifier, accounting update, root update, and transaction events.",
|
|
699
709
|
"Private note state changes by consuming selected notes; prior note provenance is not public by default.",
|
|
@@ -712,8 +722,8 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
712
722
|
fields: ["wallet", "network", "exportEvidence", "acknowledgeFullNotePlaintextExport"],
|
|
713
723
|
usage: "--wallet, --network, optional --export-evidence, and optional --acknowledge-full-note-plaintext-export",
|
|
714
724
|
help: [
|
|
715
|
-
"Refreshes the local channel workspace through the saved recovery index before reading notes when the scan fits the
|
|
716
|
-
"Refreshes received-note logs through the saved wallet note recovery index when the scan fits the
|
|
725
|
+
"Refreshes the local channel workspace through the saved recovery index before reading notes when the scan fits the 7,200-block pre-command budget",
|
|
726
|
+
"Refreshes received-note logs through the saved wallet note recovery index when the scan fits the 7,200-block pre-command budget",
|
|
717
727
|
"Fails instead of replaying from genesis; run wallet recover-workspace first when explicit wallet recovery is required",
|
|
718
728
|
"Use --export-evidence <PATH> with --acknowledge-full-note-plaintext-export to write a local full-note evidence ZIP for private-state-cli investigator",
|
|
719
729
|
"Evidence export includes all local epochs for the selected wallet, including exited epochs retained for dispute evidence",
|
package/lib/runtime.mjs
CHANGED
|
@@ -137,7 +137,7 @@ const PRIVATE_STATE_UNINSTALL_CONFIRMATION =
|
|
|
137
137
|
const ACTION_IMPACT_CONFIRMATION =
|
|
138
138
|
"I understand the public and private impact of this action";
|
|
139
139
|
const PRIVATE_STATE_CLI_PACKAGE_NAME = privateStateCliPackageJson.name;
|
|
140
|
-
const PRIVATE_STATE_OBSERVER_URL = "https://
|
|
140
|
+
const PRIVATE_STATE_OBSERVER_URL = "https://observer.tonnel.io";
|
|
141
141
|
const GROTH16_PACKAGE_NAME = "@tokamak-private-dapps/groth16";
|
|
142
142
|
const TOKAMAK_ZKEVM_CLI_PACKAGE_NAME = "@tokamak-zk-evm/cli";
|
|
143
143
|
const WALLET_BACKUP_EXPORT_FORMAT = "tokamak-private-state-wallet-backup-export";
|
|
@@ -327,7 +327,7 @@ const ACTION_IMPACT_SUMMARIES = Object.freeze({
|
|
|
327
327
|
},
|
|
328
328
|
"channel-join": {
|
|
329
329
|
display: "channel join",
|
|
330
|
-
l1PublicEvent: "Yes. Channel join and token-vault registration transactions are public L1 data.",
|
|
330
|
+
l1PublicEvent: "Yes. Channel join and token-vault registration transactions are public L1 data; any join toll is paid directly from the L1 wallet.",
|
|
331
331
|
privateNoteState: "No. This action registers identity and note-receive metadata; it does not create or spend notes.",
|
|
332
332
|
publicFields: ({ l1Address, l2Address, noteReceivePubKey, joinToll, channelName, channelId }) => [
|
|
333
333
|
`Channel: ${channelName} (${channelId})`,
|
|
@@ -2338,10 +2338,12 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
2338
2338
|
context,
|
|
2339
2339
|
registration,
|
|
2340
2340
|
});
|
|
2341
|
+
const walletRecoveryTargetBlock = walletNoteReceiveTargetBlock(context);
|
|
2341
2342
|
const recoveryEventScan = await scanWalletRecoveryEvents({
|
|
2342
2343
|
context,
|
|
2343
2344
|
provider,
|
|
2344
2345
|
l1Address: signer.address,
|
|
2346
|
+
toBlock: walletRecoveryTargetBlock,
|
|
2345
2347
|
progressAction: "wallet recover-workspace",
|
|
2346
2348
|
});
|
|
2347
2349
|
const lifecycleEpoch = selectWalletLifecycleEpoch({
|
|
@@ -2400,66 +2402,8 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
2400
2402
|
walletDir,
|
|
2401
2403
|
})
|
|
2402
2404
|
: null;
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
existingWallet.wallet.noteReceivePrivateKey = noteReceiveKeyMaterial.privateKey;
|
|
2406
|
-
applyWalletLifecycleEpoch(existingWallet.wallet, lifecycleEpoch);
|
|
2407
|
-
if (recoveredSpendingIdentity) {
|
|
2408
|
-
existingWallet.wallet.l2PrivateKey = ethers.hexlify(recoveredSpendingIdentity.l2PrivateKey);
|
|
2409
|
-
existingWallet.wallet.l2PublicKey = ethers.hexlify(recoveredSpendingIdentity.l2PublicKey);
|
|
2410
|
-
existingWallet.wallet.l2Address = recoveredSpendingIdentity.l2Address;
|
|
2411
|
-
existingWallet.wallet.l2DerivationMode = CHANNEL_BOUND_L2_DERIVATION_MODE;
|
|
2412
|
-
existingWallet.wallet.l2DerivationChannelName = channelName;
|
|
2413
|
-
existingWallet.wallet.l2StorageKey = storageKey;
|
|
2414
|
-
}
|
|
2415
|
-
persistWalletKeys(existingWallet);
|
|
2416
|
-
persistWallet(existingWallet);
|
|
2417
|
-
persistWalletIndexForContext(existingWallet);
|
|
2418
|
-
const noteScanStartBlock = args.fromGenesis === true
|
|
2419
|
-
? Number(context.workspace.genesisBlockNumber)
|
|
2420
|
-
: requireUsableWalletNoteReceiveRecoveryIndex({
|
|
2421
|
-
walletContext: existingWallet,
|
|
2422
|
-
context,
|
|
2423
|
-
latestBlock: recoveryEventScan.scanRange.toBlock,
|
|
2424
|
-
});
|
|
2425
|
-
const recoveredDeliveryState = await recoverDeliveredNotesFromCollectedLogs({
|
|
2426
|
-
walletContext: existingWallet,
|
|
2427
|
-
context,
|
|
2428
|
-
noteReceivePrivateKey: noteReceiveKeyMaterial.privateKey,
|
|
2429
|
-
logs: recoveryEventScan.deliveryLogs,
|
|
2430
|
-
storageObservationLogs: recoveryEventScan.storageObservationLogs,
|
|
2431
|
-
scanStartBlock: noteScanStartBlock,
|
|
2432
|
-
latestBlock: recoveryEventScan.scanRange.toBlock,
|
|
2433
|
-
});
|
|
2434
|
-
printJson({
|
|
2435
|
-
action: "wallet recover-workspace",
|
|
2436
|
-
status: "already-recovered",
|
|
2437
|
-
wallet: walletName,
|
|
2438
|
-
walletDir: existingWallet.walletDir,
|
|
2439
|
-
recoveredChannelWorkspace: channelContextResult.recoveredWorkspace,
|
|
2440
|
-
channelAutoRecoveryBlockDelta: channelContextResult.autoRecoveryBlockDelta,
|
|
2441
|
-
workspace: context.workspaceName,
|
|
2442
|
-
channelName: context.workspace.channelName,
|
|
2443
|
-
channelId: context.workspace.channelId,
|
|
2444
|
-
l1Address: signer.address,
|
|
2445
|
-
l2Address: l2Identity.l2Address,
|
|
2446
|
-
l2StorageKey: storageKey,
|
|
2447
|
-
spendingKeyRecovered: Boolean(recoveredSpendingIdentity),
|
|
2448
|
-
leafIndex: lifecycleEpoch.leafIndex.toString(),
|
|
2449
|
-
epochId: lifecycleEpoch.epochId,
|
|
2450
|
-
lifecycleStatus: lifecycleEpoch.lifecycleStatus,
|
|
2451
|
-
exitedAtTxHash: lifecycleEpoch.exitedAtTxHash,
|
|
2452
|
-
noteReceivePubKey: noteReceiveKeyMaterial.noteReceivePubKey,
|
|
2453
|
-
l2Nonce: existingWallet.wallet.l2Nonce,
|
|
2454
|
-
recoveredFromLogs: recoveredDeliveryState.importedNotes,
|
|
2455
|
-
scannedDeliveryLogs: recoveredDeliveryState.scannedLogs,
|
|
2456
|
-
linkedEvidence: recoveredDeliveryState.linkedEvidence,
|
|
2457
|
-
noteReceiveScanRange: recoveredDeliveryState.scanRange,
|
|
2458
|
-
});
|
|
2459
|
-
return;
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
|
-
const walletContext = ensureWallet({
|
|
2405
|
+
const status = existingWallet ? "already-recovered" : "recovered";
|
|
2406
|
+
const walletContext = existingWallet ?? ensureWallet({
|
|
2463
2407
|
channelContext: context,
|
|
2464
2408
|
signerAddress: signer.address,
|
|
2465
2409
|
signerPrivateKey: signer.privateKey,
|
|
@@ -2471,16 +2415,29 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
2471
2415
|
lifecycleEpoch,
|
|
2472
2416
|
rpcUrl,
|
|
2473
2417
|
});
|
|
2474
|
-
|
|
2475
|
-
|
|
2418
|
+
if (existingWallet) {
|
|
2419
|
+
walletContext.wallet.noteReceivePrivateKey = noteReceiveKeyMaterial.privateKey;
|
|
2420
|
+
applyWalletLifecycleEpoch(walletContext.wallet, lifecycleEpoch);
|
|
2421
|
+
if (recoveredSpendingIdentity) {
|
|
2422
|
+
walletContext.wallet.l2PrivateKey = ethers.hexlify(recoveredSpendingIdentity.l2PrivateKey);
|
|
2423
|
+
walletContext.wallet.l2PublicKey = ethers.hexlify(recoveredSpendingIdentity.l2PublicKey);
|
|
2424
|
+
walletContext.wallet.l2Address = recoveredSpendingIdentity.l2Address;
|
|
2425
|
+
walletContext.wallet.l2DerivationMode = CHANNEL_BOUND_L2_DERIVATION_MODE;
|
|
2426
|
+
walletContext.wallet.l2DerivationChannelName = channelName;
|
|
2427
|
+
walletContext.wallet.l2StorageKey = storageKey;
|
|
2428
|
+
}
|
|
2429
|
+
persistWalletKeys(walletContext);
|
|
2430
|
+
persistWallet(walletContext);
|
|
2431
|
+
persistWalletIndexForContext(walletContext);
|
|
2432
|
+
}
|
|
2476
2433
|
|
|
2477
2434
|
const noteScanStartBlock = args.fromGenesis === true
|
|
2478
2435
|
? Number(context.workspace.genesisBlockNumber)
|
|
2479
|
-
:
|
|
2436
|
+
: walletNoteReceiveCursorDelta({
|
|
2480
2437
|
walletContext,
|
|
2481
2438
|
context,
|
|
2482
|
-
|
|
2483
|
-
});
|
|
2439
|
+
targetNextBlock: recoveryEventScan.scanRange.toBlock + 1,
|
|
2440
|
+
}).localNextBlock;
|
|
2484
2441
|
const recoveredDeliveryState = await recoverDeliveredNotesFromCollectedLogs({
|
|
2485
2442
|
walletContext,
|
|
2486
2443
|
context,
|
|
@@ -2493,7 +2450,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
2493
2450
|
|
|
2494
2451
|
printJson({
|
|
2495
2452
|
action: "wallet recover-workspace",
|
|
2496
|
-
status
|
|
2453
|
+
status,
|
|
2497
2454
|
wallet: walletName,
|
|
2498
2455
|
walletDir: walletContext.walletDir,
|
|
2499
2456
|
recoveredChannelWorkspace: channelContextResult.recoveredWorkspace,
|
|
@@ -4163,7 +4120,7 @@ function applyGuideNextAction(guide) {
|
|
|
4163
4120
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
4164
4121
|
setGuideNextAction(guide, {
|
|
4165
4122
|
command: `channel join --channel-name ${channelName} --network ${guide.selectors.network} --account ${account} --wallet-secret-path <PATH> --acknowledge-action-impact`,
|
|
4166
|
-
why: "The selected local wallet does not exist. Join the channel to create the wallet
|
|
4123
|
+
why: "The selected local wallet does not exist. Join the channel to create the wallet, register the channel L2 identity, and pay any join toll directly from the L1 wallet.",
|
|
4167
4124
|
});
|
|
4168
4125
|
return;
|
|
4169
4126
|
}
|
|
@@ -4172,7 +4129,7 @@ function applyGuideNextAction(guide) {
|
|
|
4172
4129
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
4173
4130
|
setGuideNextAction(guide, {
|
|
4174
4131
|
command: `channel join --channel-name ${channelName} --network ${guide.selectors.network} --account ${account} --wallet-secret-path <PATH> --acknowledge-action-impact`,
|
|
4175
|
-
why: "The local wallet exists, but the corresponding L1 address is not registered in the channel.",
|
|
4132
|
+
why: "The local wallet exists, but the corresponding L1 address is not registered in the channel; joining pays any join toll directly from the L1 wallet.",
|
|
4176
4133
|
});
|
|
4177
4134
|
return;
|
|
4178
4135
|
}
|
|
@@ -4189,7 +4146,7 @@ function applyGuideNextAction(guide) {
|
|
|
4189
4146
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
4190
4147
|
setGuideNextAction(guide, {
|
|
4191
4148
|
command: `account deposit-bridge --amount <TOKENS> --network ${guide.selectors.network} --account ${account} --acknowledge-action-impact`,
|
|
4192
|
-
why: "The wallet is joined, but there is no bridge balance, channel balance, or local unused note to spend.",
|
|
4149
|
+
why: "The wallet is joined, but there is no bridge balance, channel balance, or local unused note to spend; bridge deposits fund channel liquidity and do not pay join tolls.",
|
|
4193
4150
|
});
|
|
4194
4151
|
return;
|
|
4195
4152
|
}
|
|
@@ -4202,18 +4159,18 @@ function applyGuideNextAction(guide) {
|
|
|
4202
4159
|
}
|
|
4203
4160
|
if (guide.state.wallet?.exists && channelBalance !== null && channelBalance > 0n && unusedNotes === 0) {
|
|
4204
4161
|
setGuideNextAction(guide, {
|
|
4205
|
-
command: `wallet mint-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amounts <
|
|
4162
|
+
command: `wallet mint-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amounts <JSON_ARRAY> --acknowledge-action-impact [--tx-submitter <ACCOUNT>]`,
|
|
4206
4163
|
why: "The wallet has channel L2 balance and no unused private notes yet. Use --tx-submitter for stronger transaction-submission privacy.",
|
|
4207
4164
|
});
|
|
4208
4165
|
return;
|
|
4209
4166
|
}
|
|
4210
4167
|
if (guide.state.wallet?.exists && unusedNotes !== null && unusedNotes > 0) {
|
|
4211
4168
|
setGuideNextAction(guide, {
|
|
4212
|
-
command: `wallet transfer-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <
|
|
4169
|
+
command: `wallet transfer-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <JSON_ARRAY> --recipients <JSON_ARRAY> --amounts <JSON_ARRAY> --acknowledge-action-impact [--tx-submitter <ACCOUNT>]`,
|
|
4213
4170
|
why: "The wallet has unused private notes. It can transfer or redeem those notes. Use --tx-submitter for stronger transaction-submission privacy.",
|
|
4214
4171
|
candidates: [
|
|
4215
4172
|
`wallet get-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network}`,
|
|
4216
|
-
`wallet redeem-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <
|
|
4173
|
+
`wallet redeem-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <JSON_ARRAY> --acknowledge-action-impact [--tx-submitter <ACCOUNT>]`,
|
|
4217
4174
|
],
|
|
4218
4175
|
});
|
|
4219
4176
|
return;
|
|
@@ -4500,9 +4457,13 @@ async function walletEpochFromJoinReceipt({ receipt, context, provider, l1Addres
|
|
|
4500
4457
|
return epoch;
|
|
4501
4458
|
}
|
|
4502
4459
|
|
|
4503
|
-
async function scanWalletRecoveryEvents({ context, provider, l1Address, progressAction = null }) {
|
|
4460
|
+
async function scanWalletRecoveryEvents({ context, provider, l1Address, toBlock, progressAction = null }) {
|
|
4504
4461
|
const fromBlock = Number(context.workspace.genesisBlockNumber ?? 0);
|
|
4505
|
-
const
|
|
4462
|
+
const normalizedToBlock = Number(toBlock);
|
|
4463
|
+
expect(
|
|
4464
|
+
Number.isInteger(normalizedToBlock) && normalizedToBlock >= fromBlock - 1,
|
|
4465
|
+
"Wallet recovery event scan target block is invalid.",
|
|
4466
|
+
);
|
|
4506
4467
|
const registeredTopic = context.channelManager.interface.getEvent("ChannelTokenVaultIdentityRegistered").topicHash;
|
|
4507
4468
|
const exitedTopic = context.channelManager.interface.getEvent("ChannelTokenVaultIdentityExited").topicHash;
|
|
4508
4469
|
const normalizedRegisteredTopic = normalizeBytes32Hex(registeredTopic);
|
|
@@ -4519,7 +4480,7 @@ async function scanWalletRecoveryEvents({ context, provider, l1Address, progress
|
|
|
4519
4480
|
address: context.workspace.channelManager,
|
|
4520
4481
|
topics: [[registeredTopic, exitedTopic, NOTE_VALUE_ENCRYPTED_TOPIC, CONTROLLER_STORAGE_KEY_OBSERVED_TOPIC]],
|
|
4521
4482
|
fromBlock,
|
|
4522
|
-
toBlock,
|
|
4483
|
+
toBlock: normalizedToBlock,
|
|
4523
4484
|
collectLogs: false,
|
|
4524
4485
|
onProgress: progressAction
|
|
4525
4486
|
? createRpcLogScanProgress({ action: progressAction, label: "wallet-recovery events" })
|
|
@@ -4564,7 +4525,7 @@ async function scanWalletRecoveryEvents({ context, provider, l1Address, progress
|
|
|
4564
4525
|
storageObservationLogs,
|
|
4565
4526
|
scanRange: {
|
|
4566
4527
|
fromBlock,
|
|
4567
|
-
toBlock,
|
|
4528
|
+
toBlock: normalizedToBlock,
|
|
4568
4529
|
},
|
|
4569
4530
|
};
|
|
4570
4531
|
}
|
|
@@ -5268,7 +5229,8 @@ async function handleWalletGetNotes({ args, provider }) {
|
|
|
5268
5229
|
})
|
|
5269
5230
|
: {
|
|
5270
5231
|
nextBlock: wallet.wallet.noteReceiveLastScannedBlock,
|
|
5271
|
-
|
|
5232
|
+
targetBlock: walletNoteReceiveTargetBlock(context),
|
|
5233
|
+
targetNextBlock: channelWorkspaceRecoveryTargetNextBlock(context),
|
|
5272
5234
|
recoveredWalletWorkspace: false,
|
|
5273
5235
|
recoveredDeliveryState: null,
|
|
5274
5236
|
};
|
|
@@ -5320,7 +5282,8 @@ async function handleWalletGetNotes({ args, provider }) {
|
|
|
5320
5282
|
spentTotalTokens: spentTotal === null ? null : ethers.formatUnits(spentTotal, canonicalAssetDecimals),
|
|
5321
5283
|
bridgeStatusMismatches: [...unusedNotes, ...spentNotes].filter((note) => !note.walletStatusMatchesBridge).length,
|
|
5322
5284
|
noteReceiveLastScannedBlock: noteReceiveFreshness.nextBlock,
|
|
5323
|
-
|
|
5285
|
+
noteReceiveTargetBlock: noteReceiveFreshness.targetBlock,
|
|
5286
|
+
noteReceiveTargetNextBlock: noteReceiveFreshness.targetNextBlock,
|
|
5324
5287
|
viewingKeyAvailable: Boolean(wallet.wallet.noteReceivePrivateKey),
|
|
5325
5288
|
recoveredWalletWorkspace: noteReceiveFreshness.recoveredWalletWorkspace,
|
|
5326
5289
|
recoveredFromLogs: noteReceiveFreshness.recoveredDeliveryState?.importedNotes ?? 0,
|
|
@@ -6581,6 +6544,7 @@ async function recoverWalletReceivedNotes({
|
|
|
6581
6544
|
provider,
|
|
6582
6545
|
signer,
|
|
6583
6546
|
noteReceiveKeyMaterial = null,
|
|
6547
|
+
toBlock = null,
|
|
6584
6548
|
progressAction = null,
|
|
6585
6549
|
fromGenesis = false,
|
|
6586
6550
|
}) {
|
|
@@ -6594,6 +6558,7 @@ async function recoverWalletReceivedNotes({
|
|
|
6594
6558
|
context,
|
|
6595
6559
|
provider,
|
|
6596
6560
|
noteReceivePrivateKey: resolvedNoteReceiveKeyMaterial.privateKey,
|
|
6561
|
+
toBlock,
|
|
6597
6562
|
progressAction,
|
|
6598
6563
|
fromGenesis,
|
|
6599
6564
|
});
|
|
@@ -6608,18 +6573,23 @@ async function recoverDeliveredNotesFromEventLogs({
|
|
|
6608
6573
|
context,
|
|
6609
6574
|
provider,
|
|
6610
6575
|
noteReceivePrivateKey,
|
|
6576
|
+
toBlock = null,
|
|
6611
6577
|
storageObservationLogs = null,
|
|
6612
6578
|
progressAction = null,
|
|
6613
6579
|
fromGenesis = false,
|
|
6614
6580
|
}) {
|
|
6615
|
-
const latestBlock = await fetchFreshBlockNumber(provider);
|
|
6581
|
+
const latestBlock = toBlock === null ? await fetchFreshBlockNumber(provider) : Number(toBlock);
|
|
6582
|
+
expect(
|
|
6583
|
+
Number.isInteger(latestBlock) && latestBlock >= Number(context.workspace.genesisBlockNumber) - 1,
|
|
6584
|
+
"Wallet note recovery target block is invalid.",
|
|
6585
|
+
);
|
|
6616
6586
|
const scanStartBlock = fromGenesis
|
|
6617
6587
|
? Number(context.workspace.genesisBlockNumber)
|
|
6618
|
-
:
|
|
6588
|
+
: walletNoteReceiveCursorDelta({
|
|
6619
6589
|
walletContext,
|
|
6620
6590
|
context,
|
|
6621
|
-
latestBlock,
|
|
6622
|
-
});
|
|
6591
|
+
targetNextBlock: latestBlock + 1,
|
|
6592
|
+
}).localNextBlock;
|
|
6623
6593
|
const scanRange = {
|
|
6624
6594
|
fromBlock: scanStartBlock,
|
|
6625
6595
|
toBlock: latestBlock,
|
|
@@ -6636,7 +6606,10 @@ async function recoverDeliveredNotesFromEventLogs({
|
|
|
6636
6606
|
context,
|
|
6637
6607
|
storageObservationLogs: storageObservationLogs ?? [],
|
|
6638
6608
|
});
|
|
6639
|
-
walletContext.wallet.noteReceiveLastScannedBlock =
|
|
6609
|
+
walletContext.wallet.noteReceiveLastScannedBlock = Math.max(
|
|
6610
|
+
Number(walletContext.wallet.noteReceiveLastScannedBlock),
|
|
6611
|
+
latestBlock + 1,
|
|
6612
|
+
);
|
|
6640
6613
|
persistWallet(walletContext);
|
|
6641
6614
|
return {
|
|
6642
6615
|
importedNotes: [],
|
|
@@ -6746,7 +6719,10 @@ async function recoverDeliveredNotesFromCollectedLogs({
|
|
|
6746
6719
|
context,
|
|
6747
6720
|
storageObservationLogs,
|
|
6748
6721
|
});
|
|
6749
|
-
walletContext.wallet.noteReceiveLastScannedBlock =
|
|
6722
|
+
walletContext.wallet.noteReceiveLastScannedBlock = Math.max(
|
|
6723
|
+
Number(walletContext.wallet.noteReceiveLastScannedBlock),
|
|
6724
|
+
latestBlock + 1,
|
|
6725
|
+
);
|
|
6750
6726
|
persistWallet(walletContext);
|
|
6751
6727
|
return {
|
|
6752
6728
|
importedNotes: [],
|
|
@@ -6873,40 +6849,88 @@ async function recoverDeliveredNoteCandidatesFromLogs({
|
|
|
6873
6849
|
return importedCandidates;
|
|
6874
6850
|
}
|
|
6875
6851
|
|
|
6876
|
-
function
|
|
6877
|
-
const
|
|
6852
|
+
function channelWorkspaceRecoveryTargetNextBlock(context) {
|
|
6853
|
+
const targetNextBlock = Number(context.workspace.recoveryLastScannedBlock);
|
|
6878
6854
|
const genesisBlockNumber = Number(context.workspace.genesisBlockNumber);
|
|
6855
|
+
expect(
|
|
6856
|
+
Number.isInteger(targetNextBlock) && targetNextBlock >= genesisBlockNumber,
|
|
6857
|
+
"Channel workspace recovery frontier is missing or unusable.",
|
|
6858
|
+
);
|
|
6859
|
+
return targetNextBlock;
|
|
6860
|
+
}
|
|
6861
|
+
|
|
6862
|
+
function walletNoteReceiveTargetBlock(context) {
|
|
6863
|
+
return channelWorkspaceRecoveryTargetNextBlock(context) - 1;
|
|
6864
|
+
}
|
|
6865
|
+
|
|
6866
|
+
function computeRecoveryCursorDelta({
|
|
6867
|
+
localNextBlock,
|
|
6868
|
+
targetNextBlock,
|
|
6869
|
+
genesisBlockNumber,
|
|
6870
|
+
label,
|
|
6871
|
+
}) {
|
|
6872
|
+
const normalizedLocalNextBlock = Number(localNextBlock);
|
|
6873
|
+
const normalizedTargetNextBlock = Number(targetNextBlock);
|
|
6874
|
+
const normalizedGenesisBlockNumber = Number(genesisBlockNumber);
|
|
6879
6875
|
if (
|
|
6880
|
-
!Number.isInteger(
|
|
6881
|
-
||
|
|
6882
|
-
||
|
|
6876
|
+
!Number.isInteger(normalizedLocalNextBlock)
|
|
6877
|
+
|| !Number.isInteger(normalizedTargetNextBlock)
|
|
6878
|
+
|| !Number.isInteger(normalizedGenesisBlockNumber)
|
|
6879
|
+
|| normalizedLocalNextBlock < normalizedGenesisBlockNumber
|
|
6880
|
+
|| normalizedTargetNextBlock < normalizedGenesisBlockNumber
|
|
6883
6881
|
) {
|
|
6882
|
+
throw new Error([
|
|
6883
|
+
`${label} recovery cursor is missing or unusable.`,
|
|
6884
|
+
`Expected localNextBlock and targetNextBlock to be integers greater than or equal to ${normalizedGenesisBlockNumber}.`,
|
|
6885
|
+
].join(" "));
|
|
6886
|
+
}
|
|
6887
|
+
const fromBlock = normalizedLocalNextBlock;
|
|
6888
|
+
const toBlock = normalizedTargetNextBlock - 1;
|
|
6889
|
+
const blockDelta = Math.max(0, normalizedTargetNextBlock - normalizedLocalNextBlock);
|
|
6890
|
+
return {
|
|
6891
|
+
fresh: normalizedLocalNextBlock >= normalizedTargetNextBlock,
|
|
6892
|
+
localNextBlock: normalizedLocalNextBlock,
|
|
6893
|
+
targetNextBlock: normalizedTargetNextBlock,
|
|
6894
|
+
fromBlock,
|
|
6895
|
+
toBlock,
|
|
6896
|
+
blockDelta,
|
|
6897
|
+
};
|
|
6898
|
+
}
|
|
6899
|
+
|
|
6900
|
+
function walletNoteReceiveCursorDelta({ walletContext, context, targetNextBlock = channelWorkspaceRecoveryTargetNextBlock(context) }) {
|
|
6901
|
+
const nextBlock = Number(walletContext.wallet.noteReceiveLastScannedBlock);
|
|
6902
|
+
const genesisBlockNumber = Number(context.workspace.genesisBlockNumber);
|
|
6903
|
+
try {
|
|
6904
|
+
return computeRecoveryCursorDelta({
|
|
6905
|
+
localNextBlock: nextBlock,
|
|
6906
|
+
targetNextBlock,
|
|
6907
|
+
genesisBlockNumber,
|
|
6908
|
+
label: `Wallet note workspace ${walletContext.walletName}`,
|
|
6909
|
+
});
|
|
6910
|
+
} catch (error) {
|
|
6884
6911
|
throw new Error([
|
|
6885
6912
|
`Wallet note recovery index is missing or unusable for wallet ${walletContext.walletName}.`,
|
|
6886
|
-
`Expected noteReceiveLastScannedBlock to be an integer
|
|
6913
|
+
`Expected noteReceiveLastScannedBlock to be an integer greater than or equal to ${genesisBlockNumber}.`,
|
|
6887
6914
|
"Run wallet recover-workspace --from-genesis to restart received-note scanning from channel genesis.",
|
|
6915
|
+
`Details: ${error.message}`,
|
|
6888
6916
|
].join(" "));
|
|
6889
6917
|
}
|
|
6890
|
-
return nextBlock;
|
|
6891
6918
|
}
|
|
6892
6919
|
|
|
6893
|
-
|
|
6894
|
-
const
|
|
6895
|
-
|
|
6896
|
-
walletContext,
|
|
6897
|
-
context,
|
|
6898
|
-
latestBlock,
|
|
6899
|
-
});
|
|
6900
|
-
if (nextBlock !== latestBlock + 1) {
|
|
6920
|
+
function assertWalletNoteReceiveStateFresh({ walletContext, context }) {
|
|
6921
|
+
const cursorDelta = walletNoteReceiveCursorDelta({ walletContext, context });
|
|
6922
|
+
if (!cursorDelta.fresh) {
|
|
6901
6923
|
throw new Error([
|
|
6902
6924
|
`Wallet note workspace is stale for wallet ${walletContext.walletName}.`,
|
|
6903
|
-
`noteReceiveLastScannedBlock is ${
|
|
6925
|
+
`noteReceiveLastScannedBlock is ${cursorDelta.localNextBlock}, but channel workspace recovery frontier requires ${cursorDelta.targetNextBlock}.`,
|
|
6904
6926
|
"Run wallet recover-workspace before using commands that read or spend wallet notes.",
|
|
6905
6927
|
].join(" "));
|
|
6906
6928
|
}
|
|
6907
6929
|
return {
|
|
6908
|
-
|
|
6909
|
-
|
|
6930
|
+
targetBlock: cursorDelta.toBlock,
|
|
6931
|
+
targetNextBlock: cursorDelta.targetNextBlock,
|
|
6932
|
+
nextBlock: cursorDelta.localNextBlock,
|
|
6933
|
+
blockDelta: cursorDelta.blockDelta,
|
|
6910
6934
|
};
|
|
6911
6935
|
}
|
|
6912
6936
|
|
|
@@ -6918,14 +6942,9 @@ async function ensureWalletNoteReceiveStateCurrent({
|
|
|
6918
6942
|
progressAction = null,
|
|
6919
6943
|
preConsumedBlockDelta = 0,
|
|
6920
6944
|
}) {
|
|
6921
|
-
|
|
6922
|
-
let nextBlock;
|
|
6945
|
+
let cursorDelta;
|
|
6923
6946
|
try {
|
|
6924
|
-
|
|
6925
|
-
walletContext,
|
|
6926
|
-
context,
|
|
6927
|
-
latestBlock,
|
|
6928
|
-
});
|
|
6947
|
+
cursorDelta = walletNoteReceiveCursorDelta({ walletContext, context });
|
|
6929
6948
|
} catch (indexError) {
|
|
6930
6949
|
throw new Error([
|
|
6931
6950
|
`Wallet note recovery index is missing or unusable for wallet ${walletContext.walletName}.`,
|
|
@@ -6935,10 +6954,11 @@ async function ensureWalletNoteReceiveStateCurrent({
|
|
|
6935
6954
|
].join(" "));
|
|
6936
6955
|
}
|
|
6937
6956
|
|
|
6938
|
-
if (
|
|
6957
|
+
if (cursorDelta.fresh) {
|
|
6939
6958
|
return {
|
|
6940
|
-
|
|
6941
|
-
|
|
6959
|
+
targetBlock: cursorDelta.toBlock,
|
|
6960
|
+
targetNextBlock: cursorDelta.targetNextBlock,
|
|
6961
|
+
nextBlock: cursorDelta.localNextBlock,
|
|
6942
6962
|
recoveredWalletWorkspace: false,
|
|
6943
6963
|
recoveredDeliveryState: null,
|
|
6944
6964
|
autoRecoveryBlockDelta: 0,
|
|
@@ -6947,8 +6967,8 @@ async function ensureWalletNoteReceiveStateCurrent({
|
|
|
6947
6967
|
const remainingBlockBudget = AUTO_RECOVERY_BLOCK_BUDGET - Math.max(0, Number(preConsumedBlockDelta));
|
|
6948
6968
|
const autoRecoveryBlockDelta = assertAutoRecoveryBlockBudget({
|
|
6949
6969
|
label: `wallet note workspace ${walletContext.walletName}`,
|
|
6950
|
-
fromBlock:
|
|
6951
|
-
toBlock:
|
|
6970
|
+
fromBlock: cursorDelta.fromBlock,
|
|
6971
|
+
toBlock: cursorDelta.toBlock,
|
|
6952
6972
|
recoveryCommand: `wallet recover-workspace --channel-name ${context.workspace.channelName} --network ${context.workspace.network} --account <ACCOUNT>`,
|
|
6953
6973
|
blockBudget: remainingBlockBudget,
|
|
6954
6974
|
});
|
|
@@ -6961,6 +6981,7 @@ async function ensureWalletNoteReceiveStateCurrent({
|
|
|
6961
6981
|
context,
|
|
6962
6982
|
provider,
|
|
6963
6983
|
signer: resolvedSigner,
|
|
6984
|
+
toBlock: cursorDelta.toBlock,
|
|
6964
6985
|
progressAction,
|
|
6965
6986
|
fromGenesis: false,
|
|
6966
6987
|
}));
|
|
@@ -6972,22 +6993,12 @@ async function ensureWalletNoteReceiveStateCurrent({
|
|
|
6972
6993
|
`Details: ${recoveryError.message}`,
|
|
6973
6994
|
].join(" "));
|
|
6974
6995
|
}
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
autoRecoveryBlockDelta,
|
|
6982
|
-
};
|
|
6983
|
-
} catch (postRecoveryError) {
|
|
6984
|
-
throw new Error([
|
|
6985
|
-
`Wallet workspace is still stale after recovery-index sync for wallet ${walletContext.walletName}.`,
|
|
6986
|
-
"Automatic wallet recovery will not replay from genesis.",
|
|
6987
|
-
`Run wallet recover-workspace --channel-name ${context.workspace.channelName} --network ${context.workspace.network} --account <ACCOUNT> --from-genesis if received-note scanning must restart from channel genesis.`,
|
|
6988
|
-
`Details: ${postRecoveryError.message}`,
|
|
6989
|
-
].join(" "));
|
|
6990
|
-
}
|
|
6996
|
+
return {
|
|
6997
|
+
...assertWalletNoteReceiveStateFresh({ walletContext, context }),
|
|
6998
|
+
recoveredWalletWorkspace: true,
|
|
6999
|
+
recoveredDeliveryState,
|
|
7000
|
+
autoRecoveryBlockDelta,
|
|
7001
|
+
};
|
|
6991
7002
|
}
|
|
6992
7003
|
|
|
6993
7004
|
function extractEncryptedNoteValueFromBridgeLog(log) {
|
|
@@ -7511,13 +7522,19 @@ async function requireChannelWorkspaceRecoveryIndexForAutoRefresh({
|
|
|
7511
7522
|
if (!recoveryIndex) {
|
|
7512
7523
|
fail(`Channel workspace recovery index is unusable for ${channelName} on ${networkName}.`);
|
|
7513
7524
|
}
|
|
7514
|
-
|
|
7525
|
+
const cursorDelta = computeRecoveryCursorDelta({
|
|
7526
|
+
localNextBlock: recoveryIndex.nextBlock,
|
|
7527
|
+
targetNextBlock: Number(latestBlock) + 1,
|
|
7528
|
+
genesisBlockNumber,
|
|
7529
|
+
label: `Channel workspace ${channelName} on ${networkName}`,
|
|
7530
|
+
});
|
|
7531
|
+
if (cursorDelta.fresh) {
|
|
7515
7532
|
fail(`Channel workspace recovery index has already scanned through block ${recoveryIndex.nextBlock - 1}, but the local snapshot is not current.`);
|
|
7516
7533
|
}
|
|
7517
7534
|
const autoRecoveryBlockDelta = assertAutoRecoveryBlockBudget({
|
|
7518
7535
|
label: `channel workspace ${channelName} on ${networkName}`,
|
|
7519
|
-
fromBlock:
|
|
7520
|
-
toBlock:
|
|
7536
|
+
fromBlock: cursorDelta.fromBlock,
|
|
7537
|
+
toBlock: cursorDelta.toBlock,
|
|
7521
7538
|
recoveryCommand: `channel recover-workspace --channel-name ${channelName} --network ${networkName}`,
|
|
7522
7539
|
});
|
|
7523
7540
|
return { alreadyCurrent: false, autoRecoveryBlockDelta };
|
|
@@ -7585,19 +7602,19 @@ async function recoverLocalWorkspacesAfterAcceptedNoteTransaction({
|
|
|
7585
7602
|
walletContext: wallet,
|
|
7586
7603
|
context,
|
|
7587
7604
|
provider,
|
|
7605
|
+
toBlock: walletNoteReceiveTargetBlock(context),
|
|
7588
7606
|
progressAction,
|
|
7589
7607
|
fromGenesis: false,
|
|
7590
7608
|
});
|
|
7591
7609
|
const freshness = await assertWalletNoteReceiveStateFresh({
|
|
7592
7610
|
walletContext: wallet,
|
|
7593
7611
|
context,
|
|
7594
|
-
provider,
|
|
7595
7612
|
});
|
|
7596
7613
|
return {
|
|
7597
7614
|
channelRecoveryLastScannedBlock: context.workspace.recoveryLastScannedBlock,
|
|
7598
7615
|
channelRecoveryScanRange: context.workspace.recoveryScanRange,
|
|
7599
7616
|
walletNoteReceiveNextBlock: freshness.nextBlock,
|
|
7600
|
-
|
|
7617
|
+
walletTargetBlock: freshness.targetBlock,
|
|
7601
7618
|
recoveredFromLogs: recoveredDeliveryState.importedNotes,
|
|
7602
7619
|
scannedDeliveryLogs: recoveredDeliveryState.scannedLogs,
|
|
7603
7620
|
linkedEvidence: recoveredDeliveryState.linkedEvidence,
|
|
@@ -10268,15 +10285,19 @@ function assertAllowedCommandKeys(args, commandName, allowedKeys, acceptedUsage)
|
|
|
10268
10285
|
`${commandName} only accepts ${acceptedUsage}. Unsupported option(s): ${unsupported.join(", ")}.`,
|
|
10269
10286
|
);
|
|
10270
10287
|
}
|
|
10271
|
-
|
|
10272
|
-
throw new Error(`${commandName} option --json does not accept a value.`);
|
|
10273
|
-
}
|
|
10288
|
+
assertBooleanFlag(args, "json", `${commandName} option --json`);
|
|
10274
10289
|
expect(
|
|
10275
10290
|
(args.positional ?? []).length === 1,
|
|
10276
10291
|
`${commandName} does not accept positional arguments beyond the command name.`,
|
|
10277
10292
|
);
|
|
10278
10293
|
}
|
|
10279
10294
|
|
|
10295
|
+
function assertBooleanFlag(args, key, label) {
|
|
10296
|
+
if (args[key] !== undefined && args[key] !== true) {
|
|
10297
|
+
throw new Error(`${label} does not accept a value.`);
|
|
10298
|
+
}
|
|
10299
|
+
}
|
|
10300
|
+
|
|
10280
10301
|
function assertWalletChannelMoveArgs(args, commandName) {
|
|
10281
10302
|
assertAllowedCommandSchema(args, commandName);
|
|
10282
10303
|
assertActionImpactArg(args, COMMAND_ARG_SCHEMAS[commandName]?.label ?? commandName);
|
|
@@ -10284,9 +10305,7 @@ function assertWalletChannelMoveArgs(args, commandName) {
|
|
|
10284
10305
|
|
|
10285
10306
|
function assertInstallZkEvmArgs(args) {
|
|
10286
10307
|
assertAllowedCommandSchema(args, "install");
|
|
10287
|
-
|
|
10288
|
-
throw new Error("install option --read-only does not accept a value.");
|
|
10289
|
-
}
|
|
10308
|
+
assertBooleanFlag(args, "readOnly", "install option --read-only");
|
|
10290
10309
|
if (args.readOnly === true && args.docker !== undefined) {
|
|
10291
10310
|
throw new Error("install --read-only does not accept --docker because proof runtimes are not installed.");
|
|
10292
10311
|
}
|
|
@@ -10327,9 +10346,7 @@ function assertUpdateArgs(args) {
|
|
|
10327
10346
|
|
|
10328
10347
|
function assertDoctorArgs(args) {
|
|
10329
10348
|
assertAllowedCommandSchema(args, "help-doctor");
|
|
10330
|
-
|
|
10331
|
-
throw new Error("help doctor option --gpu does not accept a value.");
|
|
10332
|
-
}
|
|
10349
|
+
assertBooleanFlag(args, "gpu", "help doctor option --gpu");
|
|
10333
10350
|
}
|
|
10334
10351
|
|
|
10335
10352
|
function assertGuideArgs(args) {
|
|
@@ -10405,12 +10422,7 @@ function assertTxSubmitterArg(args) {
|
|
|
10405
10422
|
}
|
|
10406
10423
|
|
|
10407
10424
|
function assertActionImpactArg(args, commandName) {
|
|
10408
|
-
|
|
10409
|
-
args.acknowledgeActionImpact !== undefined
|
|
10410
|
-
&& args.acknowledgeActionImpact !== true
|
|
10411
|
-
) {
|
|
10412
|
-
throw new Error(`${commandName} option --acknowledge-action-impact does not accept a value.`);
|
|
10413
|
-
}
|
|
10425
|
+
assertBooleanFlag(args, "acknowledgeActionImpact", `${commandName} option --acknowledge-action-impact`);
|
|
10414
10426
|
if (args.acknowledgeActionImpact !== true && !process.stdin.isTTY) {
|
|
10415
10427
|
throw new Error(`${commandName} requires --acknowledge-action-impact after reviewing the action-impact warning.`);
|
|
10416
10428
|
}
|
|
@@ -10426,12 +10438,11 @@ function assertWalletGetNotesArgs(args) {
|
|
|
10426
10438
|
);
|
|
10427
10439
|
}
|
|
10428
10440
|
}
|
|
10429
|
-
|
|
10430
|
-
args
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
}
|
|
10441
|
+
assertBooleanFlag(
|
|
10442
|
+
args,
|
|
10443
|
+
"acknowledgeFullNotePlaintextExport",
|
|
10444
|
+
"wallet get-notes option --acknowledge-full-note-plaintext-export",
|
|
10445
|
+
);
|
|
10435
10446
|
}
|
|
10436
10447
|
|
|
10437
10448
|
function assertCreateChannelArgs(args) {
|
|
@@ -10441,12 +10452,8 @@ function assertCreateChannelArgs(args) {
|
|
|
10441
10452
|
function assertRecoverWorkspaceArgs(args) {
|
|
10442
10453
|
assertAllowedCommandSchema(args, "channel-recover-workspace");
|
|
10443
10454
|
const source = resolveWorkspaceRecoverySource(args);
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
}
|
|
10447
|
-
if (args.outputRaw !== undefined && args.outputRaw !== true) {
|
|
10448
|
-
throw new Error("channel recover-workspace option --output-raw does not accept a value.");
|
|
10449
|
-
}
|
|
10455
|
+
assertBooleanFlag(args, "fromGenesis", "channel recover-workspace option --from-genesis");
|
|
10456
|
+
assertBooleanFlag(args, "outputRaw", "channel recover-workspace option --output-raw");
|
|
10450
10457
|
if (args.outputRaw === true && source !== "rpc") {
|
|
10451
10458
|
throw new Error("channel recover-workspace option --output-raw requires --source rpc.");
|
|
10452
10459
|
}
|
|
@@ -10464,9 +10471,7 @@ function assertSetWorkspaceMirrorArgs(args) {
|
|
|
10464
10471
|
function assertPublishWorkspaceMirrorArgs(args) {
|
|
10465
10472
|
assertAllowedCommandSchema(args, "channel-publish-workspace-mirror");
|
|
10466
10473
|
requireArg(args.output, "--output");
|
|
10467
|
-
|
|
10468
|
-
throw new Error("channel publish-workspace-mirror option --force does not accept a value.");
|
|
10469
|
-
}
|
|
10474
|
+
assertBooleanFlag(args, "force", "channel publish-workspace-mirror option --force");
|
|
10470
10475
|
}
|
|
10471
10476
|
|
|
10472
10477
|
function assertDepositBridgeArgs(args) {
|
|
@@ -10480,9 +10485,7 @@ function assertAccountGetBridgeFundArgs(args) {
|
|
|
10480
10485
|
|
|
10481
10486
|
function assertRecoverWalletArgs(args) {
|
|
10482
10487
|
assertAllowedCommandSchema(args, "wallet-recover-workspace");
|
|
10483
|
-
|
|
10484
|
-
throw new Error("wallet recover-workspace option --from-genesis does not accept a value.");
|
|
10485
|
-
}
|
|
10488
|
+
assertBooleanFlag(args, "fromGenesis", "wallet recover-workspace option --from-genesis");
|
|
10486
10489
|
}
|
|
10487
10490
|
|
|
10488
10491
|
function assertJoinChannelArgs(args) {
|
|
@@ -11185,7 +11188,14 @@ function getUsableWorkspaceRecoveryIndex({
|
|
|
11185
11188
|
return null;
|
|
11186
11189
|
}
|
|
11187
11190
|
const recoveryRootVectorHash = normalizeBytes32Hex(workspace.recoveryRootVectorHash);
|
|
11188
|
-
|
|
11191
|
+
try {
|
|
11192
|
+
computeRecoveryCursorDelta({
|
|
11193
|
+
localNextBlock: nextBlock,
|
|
11194
|
+
targetNextBlock: Number(latestBlock) + 1,
|
|
11195
|
+
genesisBlockNumber,
|
|
11196
|
+
label: "Channel workspace",
|
|
11197
|
+
});
|
|
11198
|
+
} catch {
|
|
11189
11199
|
return null;
|
|
11190
11200
|
}
|
|
11191
11201
|
if (recoveryRootVectorHash === null) {
|
package/package.json
CHANGED