@tokamak-private-dapps/private-state-cli 1.0.2 → 1.1.1
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 +64 -1
- package/README.md +100 -62
- package/assets/tx-fees.json +16 -16
- package/cli-assistant.html +35 -35
- package/lib/private-state-cli-command-registry.mjs +118 -34
- package/lib/private-state-runtime-management.mjs +2 -2
- package/package.json +2 -1
- package/private-state-bridge-cli.mjs +705 -216
|
@@ -7,6 +7,7 @@ import process from "node:process";
|
|
|
7
7
|
import readline from "node:readline/promises";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
|
+
import AdmZip from "adm-zip";
|
|
10
11
|
import {
|
|
11
12
|
createCipheriv,
|
|
12
13
|
createDecipheriv,
|
|
@@ -132,6 +133,8 @@ const PRIVATE_STATE_UNINSTALL_CONFIRMATION =
|
|
|
132
133
|
const PRIVATE_STATE_CLI_PACKAGE_NAME = privateStateCliPackageJson.name;
|
|
133
134
|
const GROTH16_PACKAGE_NAME = "@tokamak-private-dapps/groth16";
|
|
134
135
|
const TOKAMAK_ZKEVM_CLI_PACKAGE_NAME = "@tokamak-zk-evm/cli";
|
|
136
|
+
const WALLET_EXPORT_FORMAT = "tokamak-private-state-wallet-export";
|
|
137
|
+
const WALLET_EXPORT_FORMAT_VERSION = 1;
|
|
135
138
|
let jsonOutputRequested = false;
|
|
136
139
|
let activeCliArgs = {};
|
|
137
140
|
|
|
@@ -329,6 +332,12 @@ async function main() {
|
|
|
329
332
|
return;
|
|
330
333
|
}
|
|
331
334
|
|
|
335
|
+
if (args.command === "help-commands") {
|
|
336
|
+
assertHelpCommandsArgs(args);
|
|
337
|
+
printHelp();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
332
341
|
if (args.command === "install") {
|
|
333
342
|
assertInstallZkEvmArgs(args);
|
|
334
343
|
await handleInstallZkEvm({ args });
|
|
@@ -341,34 +350,34 @@ async function main() {
|
|
|
341
350
|
return;
|
|
342
351
|
}
|
|
343
352
|
|
|
344
|
-
if (args.command === "update") {
|
|
353
|
+
if (args.command === "help-update") {
|
|
345
354
|
assertUpdateArgs(args);
|
|
346
355
|
await handleUpdate();
|
|
347
356
|
return;
|
|
348
357
|
}
|
|
349
358
|
|
|
350
|
-
if (args.command === "doctor") {
|
|
359
|
+
if (args.command === "help-doctor") {
|
|
351
360
|
assertDoctorArgs(args);
|
|
352
361
|
await handleDoctor({ args });
|
|
353
362
|
return;
|
|
354
363
|
}
|
|
355
364
|
|
|
356
|
-
if (args.command === "guide") {
|
|
365
|
+
if (args.command === "help-guide") {
|
|
357
366
|
assertGuideArgs(args);
|
|
358
367
|
await handleGuide({ args });
|
|
359
368
|
return;
|
|
360
369
|
}
|
|
361
370
|
|
|
362
|
-
if (args.command === "transaction-fees") {
|
|
371
|
+
if (args.command === "help-transaction-fees") {
|
|
363
372
|
assertTransactionFeesArgs(args);
|
|
364
373
|
const { network, provider, rpcUrl } = loadExplicitCommandRuntime(args);
|
|
365
374
|
await handleTransactionFees({ network, provider, rpcUrl });
|
|
366
375
|
return;
|
|
367
376
|
}
|
|
368
377
|
|
|
369
|
-
if (args.command === "get-
|
|
370
|
-
|
|
371
|
-
|
|
378
|
+
if (args.command === "account-get-l1-address") {
|
|
379
|
+
assertAccountGetL1AddressArgs(args);
|
|
380
|
+
handleAccountGetL1Address({ args });
|
|
372
381
|
return;
|
|
373
382
|
}
|
|
374
383
|
|
|
@@ -378,46 +387,58 @@ async function main() {
|
|
|
378
387
|
return;
|
|
379
388
|
}
|
|
380
389
|
|
|
381
|
-
if (args.command === "list
|
|
390
|
+
if (args.command === "wallet-list") {
|
|
382
391
|
assertListLocalWalletsArgs(args);
|
|
383
392
|
handleListLocalWallets({ args });
|
|
384
393
|
return;
|
|
385
394
|
}
|
|
386
395
|
|
|
396
|
+
if (args.command === "wallet-export") {
|
|
397
|
+
assertWalletExportArgs(args);
|
|
398
|
+
handleWalletExport({ args });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (args.command === "wallet-import") {
|
|
403
|
+
assertWalletImportArgs(args);
|
|
404
|
+
handleWalletImport({ args });
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
387
408
|
const walletCommandHandlers = {
|
|
388
|
-
"mint-notes": {
|
|
409
|
+
"wallet-mint-notes": {
|
|
389
410
|
assert: assertMintNotesArgs,
|
|
390
411
|
run: ({ provider }) => handleMintNotes({ args, provider }),
|
|
391
412
|
},
|
|
392
|
-
"redeem-notes": {
|
|
413
|
+
"wallet-redeem-notes": {
|
|
393
414
|
assert: assertRedeemNotesArgs,
|
|
394
415
|
run: ({ provider }) => handleRedeemNotes({ args, provider }),
|
|
395
416
|
},
|
|
396
|
-
"get-
|
|
397
|
-
assert:
|
|
398
|
-
run: ({ provider }) =>
|
|
417
|
+
"wallet-get-notes": {
|
|
418
|
+
assert: assertWalletGetNotesArgs,
|
|
419
|
+
run: ({ provider }) => handleWalletGetNotes({ args, provider }),
|
|
399
420
|
},
|
|
400
|
-
"transfer-notes": {
|
|
421
|
+
"wallet-transfer-notes": {
|
|
401
422
|
assert: assertTransferNotesArgs,
|
|
402
423
|
run: ({ provider }) => handleTransferNotes({ args, provider }),
|
|
403
424
|
},
|
|
404
|
-
"deposit-channel": {
|
|
405
|
-
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "deposit-channel"),
|
|
425
|
+
"wallet-deposit-channel": {
|
|
426
|
+
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "wallet-deposit-channel"),
|
|
406
427
|
run: ({ provider }) => handleGrothVaultMove({ args, provider, direction: "deposit" }),
|
|
407
428
|
},
|
|
408
|
-
"withdraw-channel": {
|
|
409
|
-
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "withdraw-channel"),
|
|
429
|
+
"wallet-withdraw-channel": {
|
|
430
|
+
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "wallet-withdraw-channel"),
|
|
410
431
|
run: ({ provider }) => handleGrothVaultMove({ args, provider, direction: "withdraw" }),
|
|
411
432
|
},
|
|
412
|
-
"get-
|
|
413
|
-
assert:
|
|
414
|
-
run: ({ provider }) =>
|
|
433
|
+
"wallet-get-meta": {
|
|
434
|
+
assert: assertWalletGetMetaArgs,
|
|
435
|
+
run: ({ provider }) => handleWalletGetMeta({ args, provider }),
|
|
415
436
|
},
|
|
416
|
-
"get-
|
|
417
|
-
assert:
|
|
418
|
-
run: ({ provider }) =>
|
|
437
|
+
"wallet-get-channel-fund": {
|
|
438
|
+
assert: assertWalletGetChannelFundArgs,
|
|
439
|
+
run: ({ provider }) => handleWalletGetChannelFund({ args, provider }),
|
|
419
440
|
},
|
|
420
|
-
"exit
|
|
441
|
+
"channel-exit": {
|
|
421
442
|
assert: assertExitChannelArgs,
|
|
422
443
|
run: ({ provider }) => handleExitChannel({ args, provider }),
|
|
423
444
|
},
|
|
@@ -431,56 +452,56 @@ async function main() {
|
|
|
431
452
|
}
|
|
432
453
|
|
|
433
454
|
switch (args.command) {
|
|
434
|
-
case "create
|
|
455
|
+
case "channel-create": {
|
|
435
456
|
assertCreateChannelArgs(args);
|
|
436
457
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
437
458
|
await prepareDeploymentArtifacts(network.chainId);
|
|
438
459
|
await handleChannelCreate({ args, network, provider });
|
|
439
460
|
return;
|
|
440
461
|
}
|
|
441
|
-
case "recover-workspace": {
|
|
462
|
+
case "channel-recover-workspace": {
|
|
442
463
|
assertRecoverWorkspaceArgs(args);
|
|
443
464
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
444
465
|
await prepareDeploymentArtifacts(network.chainId);
|
|
445
466
|
await handleWorkspaceInit({ args, network, provider });
|
|
446
467
|
return;
|
|
447
468
|
}
|
|
448
|
-
case "get-
|
|
469
|
+
case "channel-get-meta": {
|
|
449
470
|
assertGetChannelArgs(args);
|
|
450
471
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
451
472
|
await prepareDeploymentArtifacts(network.chainId);
|
|
452
473
|
await handleGetChannel({ args, network, provider });
|
|
453
474
|
return;
|
|
454
475
|
}
|
|
455
|
-
case "deposit-bridge": {
|
|
476
|
+
case "account-deposit-bridge": {
|
|
456
477
|
assertDepositBridgeArgs(args);
|
|
457
478
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
458
479
|
await prepareDeploymentArtifacts(network.chainId);
|
|
459
480
|
await handleDepositBridge({ args, network, provider });
|
|
460
481
|
return;
|
|
461
482
|
}
|
|
462
|
-
case "withdraw-bridge": {
|
|
483
|
+
case "account-withdraw-bridge": {
|
|
463
484
|
assertWithdrawBridgeArgs(args);
|
|
464
485
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
465
486
|
await prepareDeploymentArtifacts(network.chainId);
|
|
466
487
|
await handleWithdrawBridge({ args, network, provider });
|
|
467
488
|
return;
|
|
468
489
|
}
|
|
469
|
-
case "get-
|
|
470
|
-
|
|
490
|
+
case "account-get-bridge-fund": {
|
|
491
|
+
assertAccountGetBridgeFundArgs(args);
|
|
471
492
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
472
493
|
await prepareDeploymentArtifacts(network.chainId);
|
|
473
|
-
await
|
|
494
|
+
await handleAccountGetBridgeFund({ args, provider });
|
|
474
495
|
return;
|
|
475
496
|
}
|
|
476
|
-
case "recover-
|
|
497
|
+
case "wallet-recover-workspace": {
|
|
477
498
|
assertRecoverWalletArgs(args);
|
|
478
499
|
const { network, provider, rpcUrl } = loadExplicitCommandRuntime(args);
|
|
479
500
|
await prepareDeploymentArtifacts(network.chainId);
|
|
480
501
|
await handleRecoverWallet({ args, network, provider, rpcUrl });
|
|
481
502
|
return;
|
|
482
503
|
}
|
|
483
|
-
case "join
|
|
504
|
+
case "channel-join": {
|
|
484
505
|
assertJoinChannelArgs(args);
|
|
485
506
|
const { network, provider, rpcUrl } = loadExplicitCommandRuntime(args);
|
|
486
507
|
await prepareDeploymentArtifacts(network.chainId);
|
|
@@ -518,7 +539,7 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
518
539
|
const policySnapshot = dapp.policySnapshot;
|
|
519
540
|
|
|
520
541
|
printImmutableChannelPolicyWarning({
|
|
521
|
-
action: "create
|
|
542
|
+
action: "channel create",
|
|
522
543
|
channelName,
|
|
523
544
|
channelId,
|
|
524
545
|
policySnapshot,
|
|
@@ -527,7 +548,7 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
527
548
|
await waitForReceipt(await bridgeCore.createChannel(channelId, dappId, joinToll, dapp.metadataDigest));
|
|
528
549
|
const channelInfo = await bridgeCore.getChannel(channelId);
|
|
529
550
|
|
|
530
|
-
const workspaceResult = await
|
|
551
|
+
const workspaceResult = await syncChannelWorkspace({
|
|
531
552
|
workspaceName,
|
|
532
553
|
channelName,
|
|
533
554
|
network,
|
|
@@ -535,11 +556,12 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
535
556
|
bridgeResources,
|
|
536
557
|
persist: true,
|
|
537
558
|
fromGenesis: true,
|
|
538
|
-
|
|
559
|
+
minimumToBlock: receipt.blockNumber,
|
|
560
|
+
progressAction: "channel create",
|
|
539
561
|
});
|
|
540
562
|
|
|
541
563
|
printJson({
|
|
542
|
-
action: "create
|
|
564
|
+
action: "channel create",
|
|
543
565
|
channelName,
|
|
544
566
|
channelId: channelId.toString(),
|
|
545
567
|
dappId,
|
|
@@ -637,7 +659,7 @@ async function handleWorkspaceInit({ args, network, provider }) {
|
|
|
637
659
|
const workspaceName = channelName;
|
|
638
660
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
639
661
|
|
|
640
|
-
const { workspaceDir, workspace, currentSnapshot } = await
|
|
662
|
+
const { workspaceDir, workspace, currentSnapshot } = await syncChannelWorkspace({
|
|
641
663
|
workspaceName,
|
|
642
664
|
channelName,
|
|
643
665
|
network,
|
|
@@ -647,11 +669,11 @@ async function handleWorkspaceInit({ args, network, provider }) {
|
|
|
647
669
|
allowExistingWorkspaceSync: true,
|
|
648
670
|
useWorkspaceRecoveryIndex: true,
|
|
649
671
|
fromGenesis: args.fromGenesis === true,
|
|
650
|
-
progressAction: "recover-workspace",
|
|
672
|
+
progressAction: "channel recover-workspace",
|
|
651
673
|
});
|
|
652
674
|
|
|
653
675
|
printJson({
|
|
654
|
-
action: "recover-workspace",
|
|
676
|
+
action: "channel recover-workspace",
|
|
655
677
|
workspace: workspaceName,
|
|
656
678
|
workspaceDir,
|
|
657
679
|
channelName,
|
|
@@ -683,7 +705,7 @@ async function handleGetChannel({ args, network, provider }) {
|
|
|
683
705
|
} catch (error) {
|
|
684
706
|
if (isContractError(error, bridgeCore.interface, "UnknownChannel")) {
|
|
685
707
|
printJson({
|
|
686
|
-
action: "get-
|
|
708
|
+
action: "channel get-meta",
|
|
687
709
|
channelName,
|
|
688
710
|
channelId: channelId.toString(),
|
|
689
711
|
exists: false,
|
|
@@ -722,7 +744,7 @@ async function handleGetChannel({ args, network, provider }) {
|
|
|
722
744
|
]);
|
|
723
745
|
|
|
724
746
|
printJson({
|
|
725
|
-
action: "get-
|
|
747
|
+
action: "channel get-meta",
|
|
726
748
|
channelName,
|
|
727
749
|
channelId: channelId.toString(),
|
|
728
750
|
exists: true,
|
|
@@ -745,7 +767,7 @@ async function handleGetChannel({ args, network, provider }) {
|
|
|
745
767
|
});
|
|
746
768
|
}
|
|
747
769
|
|
|
748
|
-
async function
|
|
770
|
+
async function syncChannelWorkspace({
|
|
749
771
|
workspaceName,
|
|
750
772
|
channelName,
|
|
751
773
|
network,
|
|
@@ -755,6 +777,7 @@ async function initializeChannelWorkspace({
|
|
|
755
777
|
allowExistingWorkspaceSync = false,
|
|
756
778
|
useWorkspaceRecoveryIndex = false,
|
|
757
779
|
fromGenesis = false,
|
|
780
|
+
minimumToBlock = null,
|
|
758
781
|
progressAction = null,
|
|
759
782
|
}) {
|
|
760
783
|
const workspaceDir = channelWorkspacePath(networkNameFromChainId(network.chainId), workspaceName);
|
|
@@ -788,7 +811,10 @@ async function initializeChannelWorkspace({
|
|
|
788
811
|
const canonicalAssetDecimals = await fetchTokenDecimals(provider, canonicalAsset);
|
|
789
812
|
const currentRootVectorHash = normalizeBytes32Hex(await channelManager.currentRootVectorHash());
|
|
790
813
|
const genesisBlockNumber = Number(await channelManager.genesisBlockNumber());
|
|
791
|
-
const
|
|
814
|
+
const observedLatestBlock = await provider.getBlockNumber();
|
|
815
|
+
const latestBlock = minimumToBlock === null
|
|
816
|
+
? observedLatestBlock
|
|
817
|
+
: Math.max(observedLatestBlock, Number(minimumToBlock));
|
|
792
818
|
const managedStorageAddresses = normalizedAddressVector(await channelManager.getManagedStorageAddresses());
|
|
793
819
|
const policySnapshot = await readChannelPolicySnapshot({
|
|
794
820
|
channelManager,
|
|
@@ -820,11 +846,6 @@ async function initializeChannelWorkspace({
|
|
|
820
846
|
ethers.toBigInt(derivedAPubBlockHash) === ethers.toBigInt(normalizeBytes32Hex(channelInfo.aPubBlockHash)),
|
|
821
847
|
`Derived channel block-info hash ${derivedAPubBlockHash} does not match onchain ${channelInfo.aPubBlockHash}.`,
|
|
822
848
|
);
|
|
823
|
-
const localSnapshotReusable = !fromGenesis && canReuseLocalWorkspaceSnapshot({
|
|
824
|
-
existingArtifacts,
|
|
825
|
-
currentRootVectorHash,
|
|
826
|
-
managedStorageAddresses,
|
|
827
|
-
});
|
|
828
849
|
const recoveryIndex = useWorkspaceRecoveryIndex && !fromGenesis
|
|
829
850
|
? getUsableWorkspaceRecoveryIndex({
|
|
830
851
|
existingArtifacts,
|
|
@@ -833,11 +854,17 @@ async function initializeChannelWorkspace({
|
|
|
833
854
|
managedStorageAddresses,
|
|
834
855
|
})
|
|
835
856
|
: null;
|
|
857
|
+
const localSnapshotReusable = !fromGenesis && (!useWorkspaceRecoveryIndex || recoveryIndex)
|
|
858
|
+
&& canReuseLocalWorkspaceSnapshot({
|
|
859
|
+
existingArtifacts,
|
|
860
|
+
currentRootVectorHash,
|
|
861
|
+
managedStorageAddresses,
|
|
862
|
+
});
|
|
836
863
|
if (useWorkspaceRecoveryIndex && !fromGenesis && !localSnapshotReusable && !recoveryIndex) {
|
|
837
864
|
throw new Error([
|
|
838
865
|
`Workspace recovery index is missing or unusable for channel ${channelName} on ${networkNameFromChainId(network.chainId)}.`,
|
|
839
866
|
"The CLI will not fall back to replaying channel logs from genesis unless explicitly requested.",
|
|
840
|
-
|
|
867
|
+
"Run channel recover-workspace --from-genesis or wallet recover-workspace --from-genesis to rebuild from channel genesis.",
|
|
841
868
|
].join(" "));
|
|
842
869
|
}
|
|
843
870
|
const reconstruction = localSnapshotReusable
|
|
@@ -928,7 +955,7 @@ async function initializeChannelWorkspace({
|
|
|
928
955
|
async function handleDepositBridge({ args, network, provider }) {
|
|
929
956
|
if (args.wallet !== undefined) {
|
|
930
957
|
throw new Error(
|
|
931
|
-
"--wallet is not supported by deposit-bridge. Channel wallet keys are set up only by join
|
|
958
|
+
"--wallet is not supported by account deposit-bridge. Channel wallet keys are set up only by channel join.",
|
|
932
959
|
);
|
|
933
960
|
}
|
|
934
961
|
const signer = requireL1Signer(args, provider);
|
|
@@ -952,7 +979,7 @@ async function handleDepositBridge({ args, network, provider }) {
|
|
|
952
979
|
const availableBalance = await bridgeTokenVault.availableBalanceOf(signer.address);
|
|
953
980
|
|
|
954
981
|
printJson({
|
|
955
|
-
action: "deposit-bridge",
|
|
982
|
+
action: "account deposit-bridge",
|
|
956
983
|
amountInput,
|
|
957
984
|
amountBaseUnits: amount.toString(),
|
|
958
985
|
l1Address: signer.address,
|
|
@@ -968,7 +995,7 @@ async function handleDepositBridge({ args, network, provider }) {
|
|
|
968
995
|
});
|
|
969
996
|
}
|
|
970
997
|
|
|
971
|
-
async function
|
|
998
|
+
async function handleAccountGetBridgeFund({ args, provider }) {
|
|
972
999
|
const signer = requireL1Signer(args, provider);
|
|
973
1000
|
const chainId = Number((await provider.getNetwork()).chainId);
|
|
974
1001
|
const bridgeVaultContext = await loadBridgeVaultContext({ provider, chainId });
|
|
@@ -980,7 +1007,7 @@ async function handleGetMyBridgeFund({ args, provider }) {
|
|
|
980
1007
|
const availableBalance = await bridgeTokenVault.availableBalanceOf(signer.address);
|
|
981
1008
|
|
|
982
1009
|
printJson({
|
|
983
|
-
action: "get-
|
|
1010
|
+
action: "account get-bridge-fund",
|
|
984
1011
|
l1Address: signer.address,
|
|
985
1012
|
bridgeTokenVault: bridgeVaultContext.bridgeTokenVaultAddress,
|
|
986
1013
|
canonicalAsset: bridgeVaultContext.canonicalAsset,
|
|
@@ -1002,7 +1029,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1002
1029
|
walletName,
|
|
1003
1030
|
});
|
|
1004
1031
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
1005
|
-
const initialized = await
|
|
1032
|
+
const initialized = await syncChannelWorkspace({
|
|
1006
1033
|
workspaceName: channelName,
|
|
1007
1034
|
channelName,
|
|
1008
1035
|
network,
|
|
@@ -1012,7 +1039,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1012
1039
|
allowExistingWorkspaceSync: true,
|
|
1013
1040
|
useWorkspaceRecoveryIndex: true,
|
|
1014
1041
|
fromGenesis: args.fromGenesis === true,
|
|
1015
|
-
progressAction: "recover-
|
|
1042
|
+
progressAction: "wallet recover-workspace",
|
|
1016
1043
|
});
|
|
1017
1044
|
const context = {
|
|
1018
1045
|
workspaceName: channelName,
|
|
@@ -1050,13 +1077,41 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1050
1077
|
const leafIndex = deriveChannelTokenVaultLeafIndex(storageKey);
|
|
1051
1078
|
const registration = await context.channelManager.getChannelTokenVaultRegistration(signer.address);
|
|
1052
1079
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1080
|
+
if (!registration.exists) {
|
|
1081
|
+
const cleanup = removeLocalWalletArtifacts(walletName, context.workspace.network);
|
|
1082
|
+
if (cleanup.removed) {
|
|
1083
|
+
printJson({
|
|
1084
|
+
action: "wallet recover-workspace",
|
|
1085
|
+
status: "stale-wallet-removed",
|
|
1086
|
+
wallet: walletName,
|
|
1087
|
+
removedWalletDir: cleanup.removedWalletDir ? cleanup.walletDir : null,
|
|
1088
|
+
removedWalletSecretFile: cleanup.removedWalletSecret ? cleanup.walletSecretFile : null,
|
|
1089
|
+
walletSecretSource: resolvedWalletSecretSource(args),
|
|
1090
|
+
walletSecretFile: resolvedWalletSecretFile(network.name, walletName),
|
|
1091
|
+
workspace: context.workspaceName,
|
|
1092
|
+
channelName: context.workspace.channelName,
|
|
1093
|
+
channelId: context.workspace.channelId,
|
|
1094
|
+
l1Address: signer.address,
|
|
1095
|
+
l2Address: l2Identity.l2Address,
|
|
1096
|
+
l2StorageKey: storageKey,
|
|
1097
|
+
leafIndex: leafIndex.toString(),
|
|
1098
|
+
reason: "The local wallet existed, but the L1 address is no longer registered in the channel.",
|
|
1099
|
+
nextAction: buildRecoverWalletRemovedNextAction({
|
|
1100
|
+
channelName,
|
|
1101
|
+
networkName: network.name,
|
|
1102
|
+
accountName: args.account,
|
|
1103
|
+
}),
|
|
1104
|
+
});
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
expect(
|
|
1108
|
+
false,
|
|
1109
|
+
cliError(
|
|
1110
|
+
CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION,
|
|
1111
|
+
`No channelTokenVault registration exists for ${signer.address}. Run channel join first.`,
|
|
1112
|
+
),
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1060
1115
|
expect(
|
|
1061
1116
|
ethers.toBigInt(getAddress(registration.l2Address)) === ethers.toBigInt(getAddress(l2Identity.l2Address)),
|
|
1062
1117
|
"The existing channel registration L2 address does not match the derived L2 address.",
|
|
@@ -1100,10 +1155,11 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1100
1155
|
provider,
|
|
1101
1156
|
signer,
|
|
1102
1157
|
noteReceiveKeyMaterial,
|
|
1103
|
-
progressAction: "recover-
|
|
1158
|
+
progressAction: "wallet recover-workspace",
|
|
1159
|
+
fromGenesis: args.fromGenesis === true,
|
|
1104
1160
|
});
|
|
1105
1161
|
printJson({
|
|
1106
|
-
action: "recover-
|
|
1162
|
+
action: "wallet recover-workspace",
|
|
1107
1163
|
status: "already-recovered",
|
|
1108
1164
|
wallet: walletName,
|
|
1109
1165
|
walletDir: existingWallet.walletDir,
|
|
@@ -1125,7 +1181,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1125
1181
|
return;
|
|
1126
1182
|
}
|
|
1127
1183
|
|
|
1128
|
-
|
|
1184
|
+
fs.rmSync(walletPath(walletName, context.workspace.network), { recursive: true, force: true });
|
|
1129
1185
|
|
|
1130
1186
|
const walletContext = ensureWallet({
|
|
1131
1187
|
channelContext: context,
|
|
@@ -1147,11 +1203,12 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1147
1203
|
provider,
|
|
1148
1204
|
signer,
|
|
1149
1205
|
noteReceiveKeyMaterial,
|
|
1150
|
-
progressAction: "recover-
|
|
1206
|
+
progressAction: "wallet recover-workspace",
|
|
1207
|
+
fromGenesis: args.fromGenesis === true,
|
|
1151
1208
|
});
|
|
1152
1209
|
|
|
1153
1210
|
printJson({
|
|
1154
|
-
action: "recover-
|
|
1211
|
+
action: "wallet recover-workspace",
|
|
1155
1212
|
status: "recovered",
|
|
1156
1213
|
wallet: walletName,
|
|
1157
1214
|
walletDir: walletContext.walletDir,
|
|
@@ -1301,8 +1358,30 @@ function assertExistingRecoverableWallet({
|
|
|
1301
1358
|
);
|
|
1302
1359
|
}
|
|
1303
1360
|
|
|
1304
|
-
function
|
|
1305
|
-
|
|
1361
|
+
function removeLocalWalletArtifacts(walletName, networkName) {
|
|
1362
|
+
const walletDir = walletPath(walletName, networkName);
|
|
1363
|
+
const walletSecretFile = walletSecretPath(networkName, walletName);
|
|
1364
|
+
const walletSecretDir = path.dirname(walletSecretFile);
|
|
1365
|
+
const removedWalletDir = fs.existsSync(walletDir);
|
|
1366
|
+
const removedWalletSecret = fs.existsSync(walletSecretFile) || fs.existsSync(walletSecretDir);
|
|
1367
|
+
if (removedWalletDir) {
|
|
1368
|
+
fs.rmSync(walletDir, { recursive: true, force: true });
|
|
1369
|
+
}
|
|
1370
|
+
if (removedWalletSecret) {
|
|
1371
|
+
fs.rmSync(walletSecretDir, { recursive: true, force: true });
|
|
1372
|
+
}
|
|
1373
|
+
return {
|
|
1374
|
+
walletDir,
|
|
1375
|
+
walletSecretFile,
|
|
1376
|
+
removed: removedWalletDir || removedWalletSecret,
|
|
1377
|
+
removedWalletDir,
|
|
1378
|
+
removedWalletSecret,
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function buildRecoverWalletRemovedNextAction({ channelName, networkName, accountName }) {
|
|
1383
|
+
const account = accountName ? String(accountName) : "<ACCOUNT>";
|
|
1384
|
+
return `channel join --channel-name ${channelName} --network ${networkName} --account ${account} --wallet-secret-path <PATH>`;
|
|
1306
1385
|
}
|
|
1307
1386
|
|
|
1308
1387
|
async function handleInstallZkEvm({ args }) {
|
|
@@ -1740,10 +1819,10 @@ function trimFixedNumber(value, maxDecimals) {
|
|
|
1740
1819
|
return trimmed ? `${integer}.${trimmed}` : integer;
|
|
1741
1820
|
}
|
|
1742
1821
|
|
|
1743
|
-
function
|
|
1822
|
+
function handleAccountGetL1Address({ args }) {
|
|
1744
1823
|
const signer = requireL1Signer(args);
|
|
1745
1824
|
printJson({
|
|
1746
|
-
action: "get-
|
|
1825
|
+
action: "account get-l1-address",
|
|
1747
1826
|
l1Address: signer.address,
|
|
1748
1827
|
account: args.account ?? null,
|
|
1749
1828
|
});
|
|
@@ -1770,7 +1849,7 @@ function handleAccountImport({ args }) {
|
|
|
1770
1849
|
privateKeyPath,
|
|
1771
1850
|
}, 0o600);
|
|
1772
1851
|
printJson({
|
|
1773
|
-
action: "account
|
|
1852
|
+
action: "account import",
|
|
1774
1853
|
account,
|
|
1775
1854
|
network: networkName,
|
|
1776
1855
|
l1Address: getAddress(signer.address),
|
|
@@ -1791,7 +1870,7 @@ function handleListLocalWallets({ args }) {
|
|
|
1791
1870
|
});
|
|
1792
1871
|
|
|
1793
1872
|
printJson({
|
|
1794
|
-
action: "list
|
|
1873
|
+
action: "wallet list",
|
|
1795
1874
|
workspaceRoot,
|
|
1796
1875
|
filters: {
|
|
1797
1876
|
network: networkFilter,
|
|
@@ -1801,6 +1880,175 @@ function handleListLocalWallets({ args }) {
|
|
|
1801
1880
|
});
|
|
1802
1881
|
}
|
|
1803
1882
|
|
|
1883
|
+
function handleWalletExport({ args }) {
|
|
1884
|
+
const outputPath = path.resolve(String(requireArg(args.output, "--output")));
|
|
1885
|
+
expect(!fs.existsSync(outputPath), `Export output already exists: ${outputPath}.`);
|
|
1886
|
+
ensureDir(path.dirname(outputPath));
|
|
1887
|
+
|
|
1888
|
+
const includeNotes = args.includeNotes === true;
|
|
1889
|
+
const wallets = args.all === true
|
|
1890
|
+
? listLocalWallets({ networkFilter: "mainnet" }).filter((wallet) => wallet.hasEncryptedWallet)
|
|
1891
|
+
: [resolveExportWalletInfo({
|
|
1892
|
+
networkName: requireNetworkName(args),
|
|
1893
|
+
walletName: requireWalletName(args),
|
|
1894
|
+
})];
|
|
1895
|
+
|
|
1896
|
+
expect(
|
|
1897
|
+
wallets.length > 0,
|
|
1898
|
+
args.all === true
|
|
1899
|
+
? "No local mainnet wallets are available to export."
|
|
1900
|
+
: "No local wallet is available to export.",
|
|
1901
|
+
);
|
|
1902
|
+
|
|
1903
|
+
const archive = new AdmZip();
|
|
1904
|
+
const files = new Map();
|
|
1905
|
+
const exportedWallets = [];
|
|
1906
|
+
for (const wallet of wallets) {
|
|
1907
|
+
const normalized = normalizeExportWalletInfo(wallet);
|
|
1908
|
+
exportedWallets.push({
|
|
1909
|
+
network: normalized.network,
|
|
1910
|
+
channelName: normalized.channelName,
|
|
1911
|
+
wallet: normalized.wallet,
|
|
1912
|
+
});
|
|
1913
|
+
for (const filePath of walletExportFilePaths(normalized, { includeNotes })) {
|
|
1914
|
+
const archivePath = archivePathForLocalCliFile(filePath);
|
|
1915
|
+
if (!files.has(archivePath)) {
|
|
1916
|
+
files.set(archivePath, filePath);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
const manifest = {
|
|
1922
|
+
format: WALLET_EXPORT_FORMAT,
|
|
1923
|
+
formatVersion: WALLET_EXPORT_FORMAT_VERSION,
|
|
1924
|
+
createdAt: new Date().toISOString(),
|
|
1925
|
+
cliPackage: PRIVATE_STATE_CLI_PACKAGE_NAME,
|
|
1926
|
+
cliVersion: privateStateCliPackageJson.version,
|
|
1927
|
+
exportMode: args.all === true ? "all-mainnet" : "single-wallet",
|
|
1928
|
+
includeNotes,
|
|
1929
|
+
notes: includeNotes
|
|
1930
|
+
? [
|
|
1931
|
+
"Includes the channel workspace cache required for immediate wallet command use when the cache is still chain-aligned.",
|
|
1932
|
+
]
|
|
1933
|
+
: [
|
|
1934
|
+
"Includes wallet identity, encrypted wallet state, metadata, and wallet-local secret only.",
|
|
1935
|
+
"Run channel recover-workspace after import before wallet commands need channel state.",
|
|
1936
|
+
],
|
|
1937
|
+
wallets: exportedWallets,
|
|
1938
|
+
files: [...files.keys()].sort(),
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
archive.addFile("manifest.json", Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"));
|
|
1942
|
+
for (const archivePath of manifest.files) {
|
|
1943
|
+
archive.addFile(archivePath, fs.readFileSync(files.get(archivePath)));
|
|
1944
|
+
}
|
|
1945
|
+
archive.writeZip(outputPath);
|
|
1946
|
+
protectSecretFile(outputPath, "wallet export ZIP");
|
|
1947
|
+
|
|
1948
|
+
printJson({
|
|
1949
|
+
action: "wallet export",
|
|
1950
|
+
output: outputPath,
|
|
1951
|
+
exportMode: manifest.exportMode,
|
|
1952
|
+
includeNotes,
|
|
1953
|
+
walletCount: exportedWallets.length,
|
|
1954
|
+
fileCount: manifest.files.length,
|
|
1955
|
+
wallets: exportedWallets.map(({ network, channelName, wallet }) => ({ network, channelName, wallet })),
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
function handleWalletImport({ args }) {
|
|
1960
|
+
const inputPath = path.resolve(String(requireArg(args.input, "--input")));
|
|
1961
|
+
expect(fs.existsSync(inputPath), `Import ZIP does not exist: ${inputPath}.`);
|
|
1962
|
+
|
|
1963
|
+
const { archive, manifest } = readWalletImportArchive(inputPath);
|
|
1964
|
+
|
|
1965
|
+
const archiveFiles = new Set(manifest.files);
|
|
1966
|
+
for (const entry of archive.getEntries()) {
|
|
1967
|
+
if (entry.isDirectory) {
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
expect(
|
|
1971
|
+
entry.entryName === "manifest.json" || archiveFiles.has(entry.entryName),
|
|
1972
|
+
`Unexpected file in wallet import ZIP: ${entry.entryName}.`,
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
const targetRoot = privateStateCliDataRoot();
|
|
1977
|
+
ensureDir(targetRoot);
|
|
1978
|
+
const plannedWrites = manifest.files.map((archivePath) => {
|
|
1979
|
+
validateWalletArchivePath(archivePath);
|
|
1980
|
+
const entry = archive.getEntry(archivePath);
|
|
1981
|
+
expect(entry && !entry.isDirectory, `Wallet import ZIP is missing ${archivePath}.`);
|
|
1982
|
+
const targetPath = path.resolve(targetRoot, archivePath);
|
|
1983
|
+
expectPathWithinRoot(targetPath, targetRoot, `Unsafe import target for ${archivePath}.`);
|
|
1984
|
+
expect(!fs.existsSync(targetPath), `Refusing to overwrite existing file: ${targetPath}.`);
|
|
1985
|
+
return {
|
|
1986
|
+
archivePath,
|
|
1987
|
+
targetPath,
|
|
1988
|
+
data: entry.getData(),
|
|
1989
|
+
};
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1992
|
+
commitWalletImportFiles({ targetRoot, plannedWrites });
|
|
1993
|
+
|
|
1994
|
+
printJson({
|
|
1995
|
+
action: "wallet import",
|
|
1996
|
+
input: inputPath,
|
|
1997
|
+
exportMode: manifest.exportMode,
|
|
1998
|
+
includeNotes: Boolean(manifest.includeNotes),
|
|
1999
|
+
walletCount: manifest.wallets.length,
|
|
2000
|
+
fileCount: plannedWrites.length,
|
|
2001
|
+
wallets: manifest.wallets.map(({ network, channelName, wallet }) => ({ network, channelName, wallet })),
|
|
2002
|
+
nextStep: manifest.includeNotes
|
|
2003
|
+
? "Wallet commands can run immediately if the imported channel workspace cache is still chain-aligned."
|
|
2004
|
+
: "Run channel recover-workspace before wallet commands need channel state.",
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
function readWalletImportArchive(inputPath) {
|
|
2009
|
+
try {
|
|
2010
|
+
const archive = new AdmZip(inputPath);
|
|
2011
|
+
const manifestEntry = archive.getEntry("manifest.json");
|
|
2012
|
+
expect(manifestEntry, "Wallet import ZIP is missing manifest.json.");
|
|
2013
|
+
const manifest = JSON.parse(manifestEntry.getData().toString("utf8"));
|
|
2014
|
+
validateWalletExportManifest(manifest);
|
|
2015
|
+
return { archive, manifest };
|
|
2016
|
+
} catch (error) {
|
|
2017
|
+
throw new Error(`Failed to read wallet import ZIP ${inputPath}: ${error.message}`);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
function commitWalletImportFiles({ targetRoot, plannedWrites }) {
|
|
2022
|
+
const stagingRoot = fs.mkdtempSync(path.join(targetRoot, ".wallet-import-"));
|
|
2023
|
+
const committedPaths = [];
|
|
2024
|
+
try {
|
|
2025
|
+
for (const write of plannedWrites) {
|
|
2026
|
+
write.stagingPath = path.resolve(stagingRoot, write.archivePath);
|
|
2027
|
+
expectPathWithinRoot(write.stagingPath, stagingRoot, `Unsafe staging target for ${write.archivePath}.`);
|
|
2028
|
+
ensureDir(path.dirname(write.stagingPath));
|
|
2029
|
+
fs.writeFileSync(write.stagingPath, write.data);
|
|
2030
|
+
applyImportedWalletFileMode(write.archivePath, write.stagingPath);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
for (const write of plannedWrites) {
|
|
2034
|
+
expect(!fs.existsSync(write.targetPath), `Refusing to overwrite existing file: ${write.targetPath}.`);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
for (const write of plannedWrites) {
|
|
2038
|
+
ensureDir(path.dirname(write.targetPath));
|
|
2039
|
+
fs.renameSync(write.stagingPath, write.targetPath);
|
|
2040
|
+
committedPaths.push(write.targetPath);
|
|
2041
|
+
}
|
|
2042
|
+
} catch (error) {
|
|
2043
|
+
for (const committedPath of committedPaths.reverse()) {
|
|
2044
|
+
fs.rmSync(committedPath, { force: true });
|
|
2045
|
+
}
|
|
2046
|
+
throw error;
|
|
2047
|
+
} finally {
|
|
2048
|
+
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
|
|
1804
2052
|
async function handleGuide({ args }) {
|
|
1805
2053
|
const guide = {
|
|
1806
2054
|
action: "guide",
|
|
@@ -1816,7 +2064,7 @@ async function handleGuide({ args }) {
|
|
|
1816
2064
|
nextSafeAction: null,
|
|
1817
2065
|
why: null,
|
|
1818
2066
|
candidateCommands: [],
|
|
1819
|
-
privacyTip: "For mint-notes, transfer-notes, and redeem-notes, add --tx-submitter <ACCOUNT> to let a separate local L1 account submit executeChannelTransaction and pay gas.",
|
|
2067
|
+
privacyTip: "For wallet mint-notes, wallet transfer-notes, and wallet redeem-notes, add --tx-submitter <ACCOUNT> to let a separate local L1 account submit executeChannelTransaction and pay gas.",
|
|
1820
2068
|
};
|
|
1821
2069
|
|
|
1822
2070
|
guide.state.local = inspectGuideLocalState(args);
|
|
@@ -1839,12 +2087,12 @@ async function handleGuide({ args }) {
|
|
|
1839
2087
|
|
|
1840
2088
|
if (!args.network) {
|
|
1841
2089
|
setGuideNextAction(guide, {
|
|
1842
|
-
command: "guide --network <NAME>",
|
|
2090
|
+
command: "help guide --network <NAME>",
|
|
1843
2091
|
why: "Select a network before the guide can inspect RPC, deployment artifacts, channels, accounts, or wallets.",
|
|
1844
2092
|
candidates: [
|
|
1845
|
-
"guide --network mainnet",
|
|
1846
|
-
"guide --network sepolia",
|
|
1847
|
-
"guide --network anvil",
|
|
2093
|
+
"help guide --network mainnet",
|
|
2094
|
+
"help guide --network sepolia",
|
|
2095
|
+
"help guide --network anvil",
|
|
1848
2096
|
],
|
|
1849
2097
|
});
|
|
1850
2098
|
printJson(guide);
|
|
@@ -1857,7 +2105,7 @@ async function handleGuide({ args }) {
|
|
|
1857
2105
|
guide.checks.push(networkRuntime.check);
|
|
1858
2106
|
if (!networkRuntime.network) {
|
|
1859
2107
|
setGuideNextAction(guide, {
|
|
1860
|
-
command: "guide --network <NAME>",
|
|
2108
|
+
command: "help guide --network <NAME>",
|
|
1861
2109
|
why: `The requested network ${networkName} is not supported by the CLI network config.`,
|
|
1862
2110
|
});
|
|
1863
2111
|
printJson(guide);
|
|
@@ -2225,8 +2473,8 @@ async function inspectGuideWallet({ walletName, networkName, provider, artifacts
|
|
|
2225
2473
|
function applyGuideNextAction(guide) {
|
|
2226
2474
|
if (guide.state.local?.walletSelectorError && guide.selectors.network) {
|
|
2227
2475
|
setGuideNextAction(guide, {
|
|
2228
|
-
command: `list
|
|
2229
|
-
why: "The selected wallet name is malformed. List local wallets and retry guide with an existing deterministic wallet name.",
|
|
2476
|
+
command: `wallet list --network ${guide.selectors.network}`,
|
|
2477
|
+
why: "The selected wallet name is malformed. List local wallets and retry help guide with an existing deterministic wallet name.",
|
|
2230
2478
|
});
|
|
2231
2479
|
return;
|
|
2232
2480
|
}
|
|
@@ -2254,14 +2502,14 @@ function applyGuideNextAction(guide) {
|
|
|
2254
2502
|
if (guide.selectors.channelName && guide.state.channel?.onchain?.exists === false) {
|
|
2255
2503
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2256
2504
|
setGuideNextAction(guide, {
|
|
2257
|
-
command: `
|
|
2505
|
+
command: `channel create --channel-name ${guide.selectors.channelName} --join-toll <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2258
2506
|
why: "The selected channel name is not registered on-chain yet.",
|
|
2259
2507
|
});
|
|
2260
2508
|
return;
|
|
2261
2509
|
}
|
|
2262
2510
|
if (guide.selectors.channelName && guide.state.channel?.onchain?.exists && !guide.state.channel?.local?.workspaceExists) {
|
|
2263
2511
|
setGuideNextAction(guide, {
|
|
2264
|
-
command: `recover-workspace --channel-name ${guide.selectors.channelName} --network ${guide.selectors.network} --from-genesis`,
|
|
2512
|
+
command: `channel recover-workspace --channel-name ${guide.selectors.channelName} --network ${guide.selectors.network} --from-genesis`,
|
|
2265
2513
|
why: "The channel exists on-chain, but the local channel workspace has not been recovered yet, so there is no local recovery index to resume from.",
|
|
2266
2514
|
});
|
|
2267
2515
|
return;
|
|
@@ -2270,7 +2518,7 @@ function applyGuideNextAction(guide) {
|
|
|
2270
2518
|
const channelName = guide.selectors.channelName ?? guide.state.channel?.channelName ?? "<CHANNEL>";
|
|
2271
2519
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2272
2520
|
setGuideNextAction(guide, {
|
|
2273
|
-
command: `
|
|
2521
|
+
command: `channel join --channel-name ${channelName} --network ${guide.selectors.network} --account ${account} --wallet-secret-path <PATH>`,
|
|
2274
2522
|
why: "The selected local wallet does not exist. Join the channel to create the wallet and register the channel L2 identity.",
|
|
2275
2523
|
});
|
|
2276
2524
|
return;
|
|
@@ -2279,7 +2527,7 @@ function applyGuideNextAction(guide) {
|
|
|
2279
2527
|
const channelName = guide.state.wallet.channelName ?? guide.selectors.channelName ?? "<CHANNEL>";
|
|
2280
2528
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2281
2529
|
setGuideNextAction(guide, {
|
|
2282
|
-
command: `
|
|
2530
|
+
command: `channel join --channel-name ${channelName} --network ${guide.selectors.network} --account ${account} --wallet-secret-path <PATH>`,
|
|
2283
2531
|
why: "The local wallet exists, but the corresponding L1 address is not registered in the channel.",
|
|
2284
2532
|
});
|
|
2285
2533
|
return;
|
|
@@ -2296,46 +2544,46 @@ function applyGuideNextAction(guide) {
|
|
|
2296
2544
|
if (guide.state.wallet?.exists && bridgeBalance === 0n && (channelBalance === null || channelBalance === 0n) && unusedNotes === 0) {
|
|
2297
2545
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2298
2546
|
setGuideNextAction(guide, {
|
|
2299
|
-
command: `deposit-bridge --amount <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2547
|
+
command: `account deposit-bridge --amount <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2300
2548
|
why: "The wallet is joined, but there is no bridge balance, channel balance, or local unused note to spend.",
|
|
2301
2549
|
});
|
|
2302
2550
|
return;
|
|
2303
2551
|
}
|
|
2304
2552
|
if (guide.state.wallet?.exists && bridgeBalance !== null && bridgeBalance > 0n && channelBalance === 0n) {
|
|
2305
2553
|
setGuideNextAction(guide, {
|
|
2306
|
-
command: `deposit-channel --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amount <TOKENS>`,
|
|
2554
|
+
command: `wallet deposit-channel --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amount <TOKENS>`,
|
|
2307
2555
|
why: "The account has funds in the shared bridge vault, but the wallet has no channel L2 accounting balance.",
|
|
2308
2556
|
});
|
|
2309
2557
|
return;
|
|
2310
2558
|
}
|
|
2311
2559
|
if (guide.state.wallet?.exists && channelBalance !== null && channelBalance > 0n && unusedNotes === 0) {
|
|
2312
2560
|
setGuideNextAction(guide, {
|
|
2313
|
-
command: `mint-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amounts <A,B> [--tx-submitter <ACCOUNT>]`,
|
|
2561
|
+
command: `wallet mint-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amounts <A,B> [--tx-submitter <ACCOUNT>]`,
|
|
2314
2562
|
why: "The wallet has channel L2 balance and no unused private notes yet. Use --tx-submitter for stronger transaction-submission privacy.",
|
|
2315
2563
|
});
|
|
2316
2564
|
return;
|
|
2317
2565
|
}
|
|
2318
2566
|
if (guide.state.wallet?.exists && unusedNotes !== null && unusedNotes > 0) {
|
|
2319
2567
|
setGuideNextAction(guide, {
|
|
2320
|
-
command: `transfer-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <ID,ID> --recipients <ADDR,ADDR> --amounts <A,B> [--tx-submitter <ACCOUNT>]`,
|
|
2568
|
+
command: `wallet transfer-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <ID,ID> --recipients <ADDR,ADDR> --amounts <A,B> [--tx-submitter <ACCOUNT>]`,
|
|
2321
2569
|
why: "The wallet has unused private notes. It can transfer or redeem those notes. Use --tx-submitter for stronger transaction-submission privacy.",
|
|
2322
2570
|
candidates: [
|
|
2323
|
-
`get-
|
|
2324
|
-
`redeem-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <ID> [--tx-submitter <ACCOUNT>]`,
|
|
2571
|
+
`wallet get-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network}`,
|
|
2572
|
+
`wallet redeem-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <ID> [--tx-submitter <ACCOUNT>]`,
|
|
2325
2573
|
],
|
|
2326
2574
|
});
|
|
2327
2575
|
return;
|
|
2328
2576
|
}
|
|
2329
2577
|
if (guide.state.wallet?.exists && channelBalance === 0n) {
|
|
2330
2578
|
setGuideNextAction(guide, {
|
|
2331
|
-
command: `
|
|
2579
|
+
command: `channel exit --wallet ${guide.selectors.wallet} --network ${guide.selectors.network}`,
|
|
2332
2580
|
why: "The wallet has zero channel balance, so channel exit is allowed by both the CLI and bridge contract.",
|
|
2333
2581
|
});
|
|
2334
2582
|
return;
|
|
2335
2583
|
}
|
|
2336
2584
|
|
|
2337
2585
|
setGuideNextAction(guide, {
|
|
2338
|
-
command: "guide --network <NAME> --channel-name <CHANNEL> --account <ACCOUNT> --wallet <WALLET>",
|
|
2586
|
+
command: "help guide --network <NAME> --channel-name <CHANNEL> --account <ACCOUNT> --wallet <WALLET>",
|
|
2339
2587
|
why: "Provide more selectors so the guide can choose a single next safe action.",
|
|
2340
2588
|
});
|
|
2341
2589
|
}
|
|
@@ -2399,12 +2647,12 @@ function redactRpcUrl(rpcUrl) {
|
|
|
2399
2647
|
}
|
|
2400
2648
|
}
|
|
2401
2649
|
|
|
2402
|
-
async function
|
|
2650
|
+
async function handleWalletGetMeta({ args, provider }) {
|
|
2403
2651
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
2404
2652
|
const contextResult = await loadPreferredWalletChannelContext({
|
|
2405
2653
|
walletContext: wallet,
|
|
2406
2654
|
provider,
|
|
2407
|
-
progressAction: "get-
|
|
2655
|
+
progressAction: "wallet get-meta",
|
|
2408
2656
|
});
|
|
2409
2657
|
const context = contextResult.context;
|
|
2410
2658
|
const {
|
|
@@ -2420,7 +2668,7 @@ async function handleGetMyWalletMeta({ args, provider }) {
|
|
|
2420
2668
|
});
|
|
2421
2669
|
|
|
2422
2670
|
printJson({
|
|
2423
|
-
action: "get-
|
|
2671
|
+
action: "wallet get-meta",
|
|
2424
2672
|
wallet: wallet.walletName,
|
|
2425
2673
|
network: walletMetadata.network,
|
|
2426
2674
|
channelName: walletMetadata.channelName,
|
|
@@ -2490,7 +2738,7 @@ async function loadWalletChannelRegistrationState({
|
|
|
2490
2738
|
registration.exists,
|
|
2491
2739
|
cliError(
|
|
2492
2740
|
CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION,
|
|
2493
|
-
`No channelTokenVault registration exists for ${signer.address}. Run
|
|
2741
|
+
`No channelTokenVault registration exists for ${signer.address}. Run channel join first.`,
|
|
2494
2742
|
),
|
|
2495
2743
|
);
|
|
2496
2744
|
expect(
|
|
@@ -2508,7 +2756,7 @@ async function loadWalletChannelRegistrationState({
|
|
|
2508
2756
|
};
|
|
2509
2757
|
}
|
|
2510
2758
|
|
|
2511
|
-
async function
|
|
2759
|
+
async function handleWalletGetChannelFund({ args, provider }) {
|
|
2512
2760
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
2513
2761
|
const {
|
|
2514
2762
|
signer,
|
|
@@ -2520,11 +2768,11 @@ async function handleGetMyChannelFund({ args, provider }) {
|
|
|
2520
2768
|
} = await loadWalletChannelFundState({
|
|
2521
2769
|
walletContext: wallet,
|
|
2522
2770
|
provider,
|
|
2523
|
-
progressAction: "get-
|
|
2771
|
+
progressAction: "wallet get-channel-fund",
|
|
2524
2772
|
});
|
|
2525
2773
|
|
|
2526
2774
|
printJson({
|
|
2527
|
-
action: "get-
|
|
2775
|
+
action: "wallet get-channel-fund",
|
|
2528
2776
|
wallet: wallet.walletName,
|
|
2529
2777
|
network: walletMetadata.network,
|
|
2530
2778
|
channelName: walletMetadata.channelName,
|
|
@@ -2556,7 +2804,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2556
2804
|
!existingRegistration.exists,
|
|
2557
2805
|
[
|
|
2558
2806
|
`L1 address ${signer.address} is already registered in channel ${context.workspace.channelName}.`,
|
|
2559
|
-
"Use recover-
|
|
2807
|
+
"Use wallet recover-workspace or normal wallet commands for an existing channel registration.",
|
|
2560
2808
|
].join(" "),
|
|
2561
2809
|
);
|
|
2562
2810
|
const walletSecret = prepareJoinWalletSecretForName({
|
|
@@ -2593,7 +2841,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2593
2841
|
);
|
|
2594
2842
|
let nextNonce = await provider.getTransactionCount(signer.address, "pending");
|
|
2595
2843
|
printImmutableChannelPolicyWarning({
|
|
2596
|
-
action: "join
|
|
2844
|
+
action: "channel join",
|
|
2597
2845
|
channelName: context.workspace.channelName,
|
|
2598
2846
|
channelId: ethers.toBigInt(context.workspace.channelId),
|
|
2599
2847
|
channelManager: context.workspace.channelManager,
|
|
@@ -2616,6 +2864,13 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2616
2864
|
);
|
|
2617
2865
|
status = "joined";
|
|
2618
2866
|
|
|
2867
|
+
await refreshPersistedWorkspaceAfterLocalTransaction({
|
|
2868
|
+
context,
|
|
2869
|
+
provider,
|
|
2870
|
+
receipt,
|
|
2871
|
+
progressAction: "channel join",
|
|
2872
|
+
});
|
|
2873
|
+
|
|
2619
2874
|
const walletContext = ensureWallet({
|
|
2620
2875
|
channelContext: context,
|
|
2621
2876
|
signerAddress: signer.address,
|
|
@@ -2629,7 +2884,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2629
2884
|
});
|
|
2630
2885
|
|
|
2631
2886
|
printJson({
|
|
2632
|
-
action: "join
|
|
2887
|
+
action: "channel join",
|
|
2633
2888
|
workspace: context.workspaceName,
|
|
2634
2889
|
wallet: walletContext.walletName,
|
|
2635
2890
|
walletSecretSource: resolvedWalletSecretSource(args),
|
|
@@ -2665,17 +2920,18 @@ async function handleExitChannel({ args, provider }) {
|
|
|
2665
2920
|
channelFund === 0n,
|
|
2666
2921
|
[
|
|
2667
2922
|
`The current channel fund for ${signer.address} is ${channelFund.toString()}.`,
|
|
2668
|
-
"
|
|
2669
|
-
"Run withdraw-channel first, then retry exit
|
|
2923
|
+
"channel exit requires a zero channel balance.",
|
|
2924
|
+
"Run wallet withdraw-channel first, then retry channel exit.",
|
|
2670
2925
|
].join(" "),
|
|
2671
2926
|
);
|
|
2672
2927
|
const [refundAmount, refundBps] = await context.channelManager.getExitTollRefundQuote(signer.address);
|
|
2673
2928
|
const receipt = await waitForReceipt(
|
|
2674
2929
|
await context.bridgeTokenVault.connect(signer).exitChannel(ethers.toBigInt(context.workspace.channelId)),
|
|
2675
2930
|
);
|
|
2931
|
+
const cleanup = removeLocalWalletArtifacts(walletContext.walletName, walletMetadata.network);
|
|
2676
2932
|
|
|
2677
2933
|
printJson({
|
|
2678
|
-
action: "exit
|
|
2934
|
+
action: "channel exit",
|
|
2679
2935
|
wallet: walletContext.walletName,
|
|
2680
2936
|
network: walletMetadata.network,
|
|
2681
2937
|
channelName: walletMetadata.channelName,
|
|
@@ -2690,15 +2946,17 @@ async function handleExitChannel({ args, provider }) {
|
|
|
2690
2946
|
gasUsed: receiptGasUsed(receipt),
|
|
2691
2947
|
txUrl: explorerTxUrl(network, receipt.hash),
|
|
2692
2948
|
receipt: sanitizeReceipt(receipt),
|
|
2949
|
+
removedWalletDir: cleanup.removedWalletDir ? cleanup.walletDir : null,
|
|
2950
|
+
removedWalletSecretFile: cleanup.removedWalletSecret ? cleanup.walletSecretFile : null,
|
|
2693
2951
|
});
|
|
2694
2952
|
}
|
|
2695
2953
|
|
|
2696
2954
|
async function handleGrothVaultMove({ args, provider, direction }) {
|
|
2697
|
-
const operationName = args.command === "withdraw-channel"
|
|
2698
|
-
? "withdraw-channel"
|
|
2955
|
+
const operationName = args.command === "wallet-withdraw-channel"
|
|
2956
|
+
? "wallet withdraw-channel"
|
|
2699
2957
|
: direction === "deposit"
|
|
2700
|
-
? "deposit-channel"
|
|
2701
|
-
: "withdraw";
|
|
2958
|
+
? "wallet deposit-channel"
|
|
2959
|
+
: "wallet withdraw-channel";
|
|
2702
2960
|
emitProgress(operationName, "loading");
|
|
2703
2961
|
const { wallet: walletContext } = loadUnlockedWalletWithMetadata(args);
|
|
2704
2962
|
const contextResult = await loadPreferredWalletChannelContext({
|
|
@@ -2729,7 +2987,7 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2729
2987
|
availableBalance >= amount,
|
|
2730
2988
|
[
|
|
2731
2989
|
`Deposit amount ${amount.toString()} exceeds the shared bridge-vault balance`,
|
|
2732
|
-
`${availableBalance.toString()} for ${signer.address}. Run deposit-bridge first.`,
|
|
2990
|
+
`${availableBalance.toString()} for ${signer.address}. Run account deposit-bridge first.`,
|
|
2733
2991
|
].join(" "),
|
|
2734
2992
|
);
|
|
2735
2993
|
}
|
|
@@ -2738,7 +2996,7 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2738
2996
|
registration.exists,
|
|
2739
2997
|
cliError(
|
|
2740
2998
|
CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION,
|
|
2741
|
-
`No channelTokenVault registration exists for ${signer.address}. Run
|
|
2999
|
+
`No channelTokenVault registration exists for ${signer.address}. Run channel join first.`,
|
|
2742
3000
|
),
|
|
2743
3001
|
);
|
|
2744
3002
|
expect(
|
|
@@ -2802,7 +3060,12 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2802
3060
|
sealWalletOperationDir(operationDir, walletContext.walletSecret);
|
|
2803
3061
|
|
|
2804
3062
|
context.currentSnapshot = transition.nextSnapshot;
|
|
2805
|
-
|
|
3063
|
+
await refreshPersistedWorkspaceAfterLocalTransaction({
|
|
3064
|
+
context,
|
|
3065
|
+
provider,
|
|
3066
|
+
receipt,
|
|
3067
|
+
progressAction: operationName,
|
|
3068
|
+
});
|
|
2806
3069
|
|
|
2807
3070
|
emitProgress(operationName, "done");
|
|
2808
3071
|
printJson({
|
|
@@ -2818,6 +3081,8 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2818
3081
|
updatedRoot: transition.update.updatedRoot,
|
|
2819
3082
|
gasUsed: receiptGasUsed(receipt),
|
|
2820
3083
|
txUrl: explorerTxUrl(network, receipt.hash),
|
|
3084
|
+
usedWorkspaceCache: contextResult.usingWorkspaceCache,
|
|
3085
|
+
recoveredWorkspace: contextResult.recoveredWorkspace,
|
|
2821
3086
|
});
|
|
2822
3087
|
}
|
|
2823
3088
|
|
|
@@ -2835,7 +3100,7 @@ async function handleWithdrawBridge({ args, network, provider }) {
|
|
|
2835
3100
|
const receipt = await waitForReceipt(await bridgeTokenVault.claimToWallet(amount));
|
|
2836
3101
|
|
|
2837
3102
|
printJson({
|
|
2838
|
-
action: "withdraw-bridge",
|
|
3103
|
+
action: "account withdraw-bridge",
|
|
2839
3104
|
l1Address: signer.address,
|
|
2840
3105
|
amountInput,
|
|
2841
3106
|
amountBaseUnits: amount.toString(),
|
|
@@ -2927,13 +3192,13 @@ async function handleMintNotes({ args, provider }) {
|
|
|
2927
3192
|
const { channelFund } = await loadWalletChannelFundState({
|
|
2928
3193
|
walletContext: wallet,
|
|
2929
3194
|
provider,
|
|
2930
|
-
progressAction: "mint-notes",
|
|
3195
|
+
progressAction: "wallet mint-notes",
|
|
2931
3196
|
});
|
|
2932
3197
|
expect(
|
|
2933
3198
|
totalMintAmount <= channelFund,
|
|
2934
3199
|
[
|
|
2935
3200
|
`Mint amount total ${totalMintAmount.toString()} exceeds the current channel fund`,
|
|
2936
|
-
`${channelFund.toString()}. Run get-
|
|
3201
|
+
`${channelFund.toString()}. Run wallet get-channel-fund to inspect the available balance.`,
|
|
2937
3202
|
].join(" "),
|
|
2938
3203
|
);
|
|
2939
3204
|
const templatePayload = buildMintNotesTemplatePayload({
|
|
@@ -2944,12 +3209,12 @@ async function handleMintNotes({ args, provider }) {
|
|
|
2944
3209
|
args,
|
|
2945
3210
|
wallet,
|
|
2946
3211
|
provider,
|
|
2947
|
-
operationName: "mint-notes",
|
|
3212
|
+
operationName: "wallet mint-notes",
|
|
2948
3213
|
templatePayload,
|
|
2949
3214
|
});
|
|
2950
3215
|
|
|
2951
3216
|
printJson({
|
|
2952
|
-
action: "mint-notes",
|
|
3217
|
+
action: "wallet mint-notes",
|
|
2953
3218
|
wallet: wallet.walletName,
|
|
2954
3219
|
workspace: execution.context.workspaceName,
|
|
2955
3220
|
operationDir: execution.operationDir,
|
|
@@ -2988,12 +3253,12 @@ async function handleRedeemNotes({ args, provider }) {
|
|
|
2988
3253
|
args,
|
|
2989
3254
|
wallet,
|
|
2990
3255
|
provider,
|
|
2991
|
-
operationName: "redeem-notes",
|
|
3256
|
+
operationName: "wallet redeem-notes",
|
|
2992
3257
|
templatePayload,
|
|
2993
3258
|
});
|
|
2994
3259
|
|
|
2995
3260
|
printJson({
|
|
2996
|
-
action: "redeem-notes",
|
|
3261
|
+
action: "wallet redeem-notes",
|
|
2997
3262
|
wallet: wallet.walletName,
|
|
2998
3263
|
workspace: execution.context.workspaceName,
|
|
2999
3264
|
operationDir: execution.operationDir,
|
|
@@ -3020,7 +3285,7 @@ async function handleRedeemNotes({ args, provider }) {
|
|
|
3020
3285
|
});
|
|
3021
3286
|
}
|
|
3022
3287
|
|
|
3023
|
-
async function
|
|
3288
|
+
async function handleWalletGetNotes({ args, provider }) {
|
|
3024
3289
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
3025
3290
|
expect(
|
|
3026
3291
|
typeof wallet.wallet.controller === "string" && wallet.wallet.controller.length > 0,
|
|
@@ -3030,7 +3295,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
3030
3295
|
const { context } = await loadPreferredWalletChannelContext({
|
|
3031
3296
|
walletContext: wallet,
|
|
3032
3297
|
provider,
|
|
3033
|
-
progressAction: "get-
|
|
3298
|
+
progressAction: "wallet get-notes",
|
|
3034
3299
|
});
|
|
3035
3300
|
const signer = restoreWalletSigner(wallet, provider);
|
|
3036
3301
|
const { recoveredDeliveryState } = await recoverWalletReceivedNotes({
|
|
@@ -3038,7 +3303,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
3038
3303
|
context,
|
|
3039
3304
|
provider,
|
|
3040
3305
|
signer,
|
|
3041
|
-
progressAction: "get-
|
|
3306
|
+
progressAction: "wallet get-notes",
|
|
3042
3307
|
});
|
|
3043
3308
|
|
|
3044
3309
|
const unusedTrackedNotes = wallet.wallet.notes.unusedOrder
|
|
@@ -3063,7 +3328,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
3063
3328
|
const spentTotal = spentTrackedNotes.reduce((sum, note) => sum + ethers.toBigInt(note.value), 0n);
|
|
3064
3329
|
|
|
3065
3330
|
printJson({
|
|
3066
|
-
action: "get-
|
|
3331
|
+
action: "wallet get-notes",
|
|
3067
3332
|
wallet: wallet.walletName,
|
|
3068
3333
|
network: walletMetadata.network,
|
|
3069
3334
|
channelName: walletMetadata.channelName,
|
|
@@ -3087,7 +3352,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
3087
3352
|
const preparedContextResult = await loadPreferredWalletChannelContext({
|
|
3088
3353
|
walletContext: wallet,
|
|
3089
3354
|
provider,
|
|
3090
|
-
progressAction: "transfer-notes",
|
|
3355
|
+
progressAction: "wallet transfer-notes",
|
|
3091
3356
|
});
|
|
3092
3357
|
const context = preparedContextResult.context;
|
|
3093
3358
|
const canonicalAssetDecimals = Number(wallet.wallet.canonicalAssetDecimals);
|
|
@@ -3123,7 +3388,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
3123
3388
|
args,
|
|
3124
3389
|
wallet,
|
|
3125
3390
|
provider,
|
|
3126
|
-
operationName: "transfer-notes",
|
|
3391
|
+
operationName: "wallet transfer-notes",
|
|
3127
3392
|
templatePayload,
|
|
3128
3393
|
});
|
|
3129
3394
|
const outputNotes = buildLifecycleTrackedOutputs({
|
|
@@ -3134,7 +3399,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
3134
3399
|
});
|
|
3135
3400
|
|
|
3136
3401
|
printJson({
|
|
3137
|
-
action: "transfer-notes",
|
|
3402
|
+
action: "wallet transfer-notes",
|
|
3138
3403
|
wallet: wallet.walletName,
|
|
3139
3404
|
workspace: execution.context.workspaceName,
|
|
3140
3405
|
operationDir: execution.operationDir,
|
|
@@ -3463,6 +3728,7 @@ async function recoverWalletReceivedNotes({
|
|
|
3463
3728
|
signer,
|
|
3464
3729
|
noteReceiveKeyMaterial = null,
|
|
3465
3730
|
progressAction = null,
|
|
3731
|
+
fromGenesis = false,
|
|
3466
3732
|
}) {
|
|
3467
3733
|
const resolvedNoteReceiveKeyMaterial = noteReceiveKeyMaterial ?? await deriveNoteReceiveKeyMaterial({
|
|
3468
3734
|
signer,
|
|
@@ -3477,6 +3743,7 @@ async function recoverWalletReceivedNotes({
|
|
|
3477
3743
|
provider,
|
|
3478
3744
|
noteReceivePrivateKey: resolvedNoteReceiveKeyMaterial.privateKey,
|
|
3479
3745
|
progressAction,
|
|
3746
|
+
fromGenesis,
|
|
3480
3747
|
});
|
|
3481
3748
|
return {
|
|
3482
3749
|
noteReceiveKeyMaterial: resolvedNoteReceiveKeyMaterial,
|
|
@@ -3490,13 +3757,16 @@ async function recoverDeliveredNotesFromEventLogs({
|
|
|
3490
3757
|
provider,
|
|
3491
3758
|
noteReceivePrivateKey,
|
|
3492
3759
|
progressAction = null,
|
|
3760
|
+
fromGenesis = false,
|
|
3493
3761
|
}) {
|
|
3494
3762
|
const latestBlock = await provider.getBlockNumber();
|
|
3495
|
-
const scanStartBlock =
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3763
|
+
const scanStartBlock = fromGenesis
|
|
3764
|
+
? Number(context.workspace.genesisBlockNumber)
|
|
3765
|
+
: requireUsableWalletNoteReceiveRecoveryIndex({
|
|
3766
|
+
walletContext,
|
|
3767
|
+
context,
|
|
3768
|
+
latestBlock,
|
|
3769
|
+
});
|
|
3500
3770
|
const scanRange = {
|
|
3501
3771
|
fromBlock: scanStartBlock,
|
|
3502
3772
|
toBlock: latestBlock,
|
|
@@ -3611,7 +3881,7 @@ function requireUsableWalletNoteReceiveRecoveryIndex({ walletContext, context, l
|
|
|
3611
3881
|
throw new Error([
|
|
3612
3882
|
`Wallet note recovery index is missing or unusable for wallet ${walletContext.walletName}.`,
|
|
3613
3883
|
`Expected noteReceiveLastScannedBlock to be an integer between ${genesisBlockNumber} and ${Number(latestBlock) + 1}.`,
|
|
3614
|
-
"Run recover-
|
|
3884
|
+
"Run wallet recover-workspace --from-genesis to rebuild wallet note state from channel genesis.",
|
|
3615
3885
|
].join(" "));
|
|
3616
3886
|
}
|
|
3617
3887
|
return nextBlock;
|
|
@@ -3851,16 +4121,16 @@ function buildRedeemNotesTemplatePayload({ wallet, inputNotes }) {
|
|
|
3851
4121
|
}
|
|
3852
4122
|
|
|
3853
4123
|
function selectMintNotesMethod(noteCount) {
|
|
3854
|
-
expect(noteCount >= 1, "mint-notes requires at least one output amount.");
|
|
4124
|
+
expect(noteCount >= 1, "wallet mint-notes requires at least one output amount.");
|
|
3855
4125
|
expect(
|
|
3856
4126
|
noteCount <= 2,
|
|
3857
|
-
"mint-notes supports only one or two output amounts with the currently registered DApp.",
|
|
4127
|
+
"wallet mint-notes supports only one or two output amounts with the currently registered DApp.",
|
|
3858
4128
|
);
|
|
3859
4129
|
return `mintNotes${noteCount}`;
|
|
3860
4130
|
}
|
|
3861
4131
|
|
|
3862
4132
|
function selectRedeemNotesMethod(noteCount) {
|
|
3863
|
-
expect(noteCount === 1, "redeem-notes supports exactly one input note with the currently registered DApp.");
|
|
4133
|
+
expect(noteCount === 1, "wallet redeem-notes supports exactly one input note with the currently registered DApp.");
|
|
3864
4134
|
return `redeemNotes${noteCount}`;
|
|
3865
4135
|
}
|
|
3866
4136
|
|
|
@@ -3979,7 +4249,7 @@ function selectTransferNotesMethod(inputCount, outputCount) {
|
|
|
3979
4249
|
if (inputCount === 2 && outputCount === 1) {
|
|
3980
4250
|
return "transferNotes2To1";
|
|
3981
4251
|
}
|
|
3982
|
-
throw new Error("transfer-notes supports only 1->1, 1->2, and 2->1 note transfers.");
|
|
4252
|
+
throw new Error("wallet transfer-notes supports only 1->1, 1->2, and 2->1 note transfers.");
|
|
3983
4253
|
}
|
|
3984
4254
|
|
|
3985
4255
|
function loadWalletUnusedInputNotes(walletContext, noteIds) {
|
|
@@ -4115,7 +4385,7 @@ async function recoverWalletChannelWorkspace({ walletContext, provider, progress
|
|
|
4115
4385
|
const networkName = walletContext.wallet.network ?? networkNameFromChainId(Number(walletContext.wallet.chainId));
|
|
4116
4386
|
const network = resolveCliNetwork(networkName);
|
|
4117
4387
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
4118
|
-
await
|
|
4388
|
+
await syncChannelWorkspace({
|
|
4119
4389
|
workspaceName: walletContext.wallet.channelName,
|
|
4120
4390
|
channelName: walletContext.wallet.channelName,
|
|
4121
4391
|
network,
|
|
@@ -4128,6 +4398,49 @@ async function recoverWalletChannelWorkspace({ walletContext, provider, progress
|
|
|
4128
4398
|
});
|
|
4129
4399
|
}
|
|
4130
4400
|
|
|
4401
|
+
async function refreshPersistedWorkspaceAfterLocalTransaction({
|
|
4402
|
+
context,
|
|
4403
|
+
provider,
|
|
4404
|
+
receipt,
|
|
4405
|
+
progressAction = null,
|
|
4406
|
+
}) {
|
|
4407
|
+
if (!context.persistChannelWorkspace || !context.workspaceDir) {
|
|
4408
|
+
return context;
|
|
4409
|
+
}
|
|
4410
|
+
const network = resolveCliNetwork(context.workspace.network);
|
|
4411
|
+
const bridgeResources = loadBridgeResources({ chainId: Number(context.workspace.chainId) });
|
|
4412
|
+
const refreshed = await syncChannelWorkspace({
|
|
4413
|
+
workspaceName: context.workspaceName,
|
|
4414
|
+
channelName: context.workspace.channelName,
|
|
4415
|
+
network,
|
|
4416
|
+
provider,
|
|
4417
|
+
bridgeResources,
|
|
4418
|
+
persist: true,
|
|
4419
|
+
allowExistingWorkspaceSync: true,
|
|
4420
|
+
useWorkspaceRecoveryIndex: true,
|
|
4421
|
+
minimumToBlock: receipt?.blockNumber ?? null,
|
|
4422
|
+
progressAction,
|
|
4423
|
+
});
|
|
4424
|
+
|
|
4425
|
+
context.workspaceDir = refreshed.workspaceDir;
|
|
4426
|
+
context.workspace = refreshed.workspace;
|
|
4427
|
+
context.currentSnapshot = refreshed.currentSnapshot;
|
|
4428
|
+
context.blockInfo = refreshed.blockInfo;
|
|
4429
|
+
context.contractCodes = refreshed.contractCodes;
|
|
4430
|
+
context.bridgeAbiManifest = bridgeResources.bridgeAbiManifest;
|
|
4431
|
+
context.channelManager = new Contract(
|
|
4432
|
+
refreshed.workspace.channelManager,
|
|
4433
|
+
bridgeResources.bridgeAbiManifest.contracts.channelManager.abi,
|
|
4434
|
+
provider,
|
|
4435
|
+
);
|
|
4436
|
+
context.bridgeTokenVault = new Contract(
|
|
4437
|
+
refreshed.workspace.bridgeTokenVault,
|
|
4438
|
+
bridgeResources.bridgeAbiManifest.contracts.bridgeTokenVault.abi,
|
|
4439
|
+
provider,
|
|
4440
|
+
);
|
|
4441
|
+
return context;
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4131
4444
|
function isRecoverableWalletWorkspaceFailure(error) {
|
|
4132
4445
|
const message = String(error?.message ?? error);
|
|
4133
4446
|
return (message.includes("--verify") && message.includes("failed with exit code"))
|
|
@@ -4179,6 +4492,7 @@ async function executeWalletDirectTemplateCommand({
|
|
|
4179
4492
|
txSubmitterAccount,
|
|
4180
4493
|
l2Identity,
|
|
4181
4494
|
context: contextResult.context,
|
|
4495
|
+
provider,
|
|
4182
4496
|
operationName,
|
|
4183
4497
|
functionName: templatePayload.method,
|
|
4184
4498
|
templatePayload,
|
|
@@ -4210,6 +4524,7 @@ async function executeWalletDirectTemplateCommand({
|
|
|
4210
4524
|
txSubmitterAccount,
|
|
4211
4525
|
l2Identity,
|
|
4212
4526
|
context: contextResult.context,
|
|
4527
|
+
provider,
|
|
4213
4528
|
operationName,
|
|
4214
4529
|
functionName: templatePayload.method,
|
|
4215
4530
|
templatePayload,
|
|
@@ -4230,6 +4545,7 @@ async function executeWalletTemplateSend({
|
|
|
4230
4545
|
txSubmitterAccount,
|
|
4231
4546
|
l2Identity,
|
|
4232
4547
|
context,
|
|
4548
|
+
provider,
|
|
4233
4549
|
operationName,
|
|
4234
4550
|
functionName,
|
|
4235
4551
|
templatePayload,
|
|
@@ -4323,7 +4639,12 @@ async function executeWalletTemplateSend({
|
|
|
4323
4639
|
applyNoteLifecycleToWallet(wallet, noteLifecycle, functionName, receipt.hash);
|
|
4324
4640
|
context.currentSnapshot = nextSnapshot;
|
|
4325
4641
|
persistWallet(wallet);
|
|
4326
|
-
|
|
4642
|
+
await refreshPersistedWorkspaceAfterLocalTransaction({
|
|
4643
|
+
context,
|
|
4644
|
+
provider,
|
|
4645
|
+
receipt,
|
|
4646
|
+
progressAction: operationName,
|
|
4647
|
+
});
|
|
4327
4648
|
sealWalletOperationDir(operationDir, wallet.walletSecret);
|
|
4328
4649
|
|
|
4329
4650
|
return {
|
|
@@ -4383,19 +4704,22 @@ async function loadJoinChannelContext({ args, network, provider }) {
|
|
|
4383
4704
|
const channelName = requireArg(args.channelName, "--channel-name");
|
|
4384
4705
|
|
|
4385
4706
|
const bridgeResources = loadBridgeResources({ chainId });
|
|
4386
|
-
const initialized = await
|
|
4707
|
+
const initialized = await syncChannelWorkspace({
|
|
4387
4708
|
workspaceName: channelName,
|
|
4388
4709
|
channelName,
|
|
4389
4710
|
network: { chainId, name: resolvedNetworkName },
|
|
4390
4711
|
provider,
|
|
4391
4712
|
bridgeResources,
|
|
4392
|
-
persist:
|
|
4713
|
+
persist: true,
|
|
4714
|
+
allowExistingWorkspaceSync: true,
|
|
4715
|
+
useWorkspaceRecoveryIndex: true,
|
|
4716
|
+
progressAction: "channel join",
|
|
4393
4717
|
});
|
|
4394
4718
|
|
|
4395
4719
|
return {
|
|
4396
4720
|
workspaceName: channelName,
|
|
4397
|
-
workspaceDir:
|
|
4398
|
-
persistChannelWorkspace:
|
|
4721
|
+
workspaceDir: initialized.workspaceDir,
|
|
4722
|
+
persistChannelWorkspace: true,
|
|
4399
4723
|
workspace: initialized.workspace,
|
|
4400
4724
|
bridgeAbiManifest: bridgeResources.bridgeAbiManifest,
|
|
4401
4725
|
currentSnapshot: initialized.currentSnapshot,
|
|
@@ -4477,7 +4801,7 @@ function assertWalletUsesChannelBoundDerivation(wallet, walletName) {
|
|
|
4477
4801
|
wallet.l2DerivationMode === CHANNEL_BOUND_L2_DERIVATION_MODE,
|
|
4478
4802
|
[
|
|
4479
4803
|
`Wallet ${walletName} was not created with the current channel-bound L2 derivation rule.`,
|
|
4480
|
-
"Create a fresh wallet with join
|
|
4804
|
+
"Create a fresh wallet with channel join.",
|
|
4481
4805
|
].join(" "),
|
|
4482
4806
|
);
|
|
4483
4807
|
expect(
|
|
@@ -5710,7 +6034,13 @@ function parseArgs(argv) {
|
|
|
5710
6034
|
}
|
|
5711
6035
|
|
|
5712
6036
|
parsed.command = parsed.positional[0];
|
|
5713
|
-
if (
|
|
6037
|
+
if (
|
|
6038
|
+
(parsed.command === "account"
|
|
6039
|
+
|| parsed.command === "channel"
|
|
6040
|
+
|| parsed.command === "wallet"
|
|
6041
|
+
|| parsed.command === "help")
|
|
6042
|
+
&& parsed.positional[1]
|
|
6043
|
+
) {
|
|
5714
6044
|
parsed.command = `${parsed.command}-${parsed.positional[1]}`;
|
|
5715
6045
|
parsed.positional = [parsed.command];
|
|
5716
6046
|
}
|
|
@@ -5871,7 +6201,7 @@ function resolveWalletDefaultSecret(networkName, walletName) {
|
|
|
5871
6201
|
CLI_ERROR_CODES.MISSING_WALLET_SECRET,
|
|
5872
6202
|
[
|
|
5873
6203
|
`Missing wallet default secret file: ${secretPath}.`,
|
|
5874
|
-
"Run
|
|
6204
|
+
"Run channel join with --wallet-secret-path before wallet commands.",
|
|
5875
6205
|
].join(" "),
|
|
5876
6206
|
);
|
|
5877
6207
|
}
|
|
@@ -5884,12 +6214,17 @@ function prepareJoinWalletSecretForName({
|
|
|
5884
6214
|
walletName,
|
|
5885
6215
|
}) {
|
|
5886
6216
|
const secretPath = walletSecretPath(networkName, walletName);
|
|
6217
|
+
const { channelName } = parseWalletName(walletName);
|
|
6218
|
+
const walletDir = walletPath(walletName, networkName);
|
|
5887
6219
|
expect(
|
|
5888
|
-
!walletConfigExists(
|
|
6220
|
+
!walletConfigExists(walletDir),
|
|
5889
6221
|
[
|
|
5890
6222
|
`Wallet ${walletName} already exists on ${networkName}.`,
|
|
5891
|
-
"
|
|
5892
|
-
"
|
|
6223
|
+
"channel join always creates a new local wallet.",
|
|
6224
|
+
"If this wallet was previously exited on-chain, run",
|
|
6225
|
+
`wallet recover-workspace --channel-name ${channelName} --network ${networkName} --account ${args.account ?? "<ACCOUNT>"}`,
|
|
6226
|
+
"once to remove the stale local wallet, then retry channel join.",
|
|
6227
|
+
"Use normal wallet commands for an existing active local wallet.",
|
|
5893
6228
|
].join(" "),
|
|
5894
6229
|
);
|
|
5895
6230
|
const sourcePath = path.resolve(String(requireArg(args.walletSecretPath, "--wallet-secret-path")));
|
|
@@ -5898,13 +6233,6 @@ function prepareJoinWalletSecretForName({
|
|
|
5898
6233
|
? readSecretFile(sourcePath, "--wallet-secret-path")
|
|
5899
6234
|
: readImportSecretSourceFile(sourcePath, "--wallet-secret-path");
|
|
5900
6235
|
if (sourcePath !== canonicalPath) {
|
|
5901
|
-
expect(
|
|
5902
|
-
!fs.existsSync(canonicalPath),
|
|
5903
|
-
[
|
|
5904
|
-
`Wallet default secret file already exists: ${canonicalPath}.`,
|
|
5905
|
-
"Remove it before joining with a different --wallet-secret-path.",
|
|
5906
|
-
].join(" "),
|
|
5907
|
-
);
|
|
5908
6236
|
writeSecretFile(canonicalPath, walletSecret);
|
|
5909
6237
|
}
|
|
5910
6238
|
return walletSecret;
|
|
@@ -6027,6 +6355,148 @@ function listLocalWallets({ networkFilter = null, channelFilter = null } = {}) {
|
|
|
6027
6355
|
);
|
|
6028
6356
|
}
|
|
6029
6357
|
|
|
6358
|
+
function privateStateCliDataRoot() {
|
|
6359
|
+
const root = path.dirname(workspaceRoot);
|
|
6360
|
+
expect(
|
|
6361
|
+
path.dirname(secretRoot) === root,
|
|
6362
|
+
`Unexpected CLI data root layout: ${workspaceRoot} and ${secretRoot} are not siblings.`,
|
|
6363
|
+
);
|
|
6364
|
+
return root;
|
|
6365
|
+
}
|
|
6366
|
+
|
|
6367
|
+
function resolveExportWalletInfo({ networkName, walletName }) {
|
|
6368
|
+
resolveCliNetwork(networkName);
|
|
6369
|
+
const walletDir = walletPath(walletName, networkName);
|
|
6370
|
+
return {
|
|
6371
|
+
wallet: walletName,
|
|
6372
|
+
network: networkName,
|
|
6373
|
+
channelName: parseWalletName(walletName).channelName,
|
|
6374
|
+
walletDir,
|
|
6375
|
+
metadataPath: walletMetadataPath(walletDir),
|
|
6376
|
+
hasMetadata: fs.existsSync(walletMetadataPath(walletDir)),
|
|
6377
|
+
hasEncryptedWallet: walletConfigExists(walletDir),
|
|
6378
|
+
};
|
|
6379
|
+
}
|
|
6380
|
+
|
|
6381
|
+
function normalizeExportWalletInfo(walletInfo) {
|
|
6382
|
+
const wallet = requireWalletName({ wallet: walletInfo.wallet });
|
|
6383
|
+
const network = requireNetworkName({ network: walletInfo.network });
|
|
6384
|
+
const walletDir = walletInfo.walletDir ?? walletPath(wallet, network);
|
|
6385
|
+
const metadataPath = walletMetadataPath(walletDir);
|
|
6386
|
+
const encryptedWalletPath = walletConfigPath(walletDir);
|
|
6387
|
+
const metadata = readJsonIfExists(metadataPath);
|
|
6388
|
+
const channelName = metadata?.channelName ?? walletInfo.channelName ?? parseWalletName(wallet).channelName;
|
|
6389
|
+
const walletSecret = walletSecretPath(network, wallet);
|
|
6390
|
+
|
|
6391
|
+
expect(fs.existsSync(encryptedWalletPath), `Wallet export cannot find encrypted wallet file: ${encryptedWalletPath}.`);
|
|
6392
|
+
expect(fs.existsSync(metadataPath), `Wallet export cannot find wallet metadata file: ${metadataPath}.`);
|
|
6393
|
+
expect(fs.existsSync(walletSecret), `Wallet export cannot find wallet-local secret file: ${walletSecret}.`);
|
|
6394
|
+
expect(
|
|
6395
|
+
metadata.network === network,
|
|
6396
|
+
`Wallet export metadata network ${metadata.network} does not match ${network}.`,
|
|
6397
|
+
);
|
|
6398
|
+
expect(
|
|
6399
|
+
metadata.channelName === channelName,
|
|
6400
|
+
`Wallet export metadata channel ${metadata.channelName} does not match ${channelName}.`,
|
|
6401
|
+
);
|
|
6402
|
+
|
|
6403
|
+
return {
|
|
6404
|
+
network,
|
|
6405
|
+
channelName,
|
|
6406
|
+
wallet,
|
|
6407
|
+
walletDir,
|
|
6408
|
+
walletSecretPath: walletSecret,
|
|
6409
|
+
};
|
|
6410
|
+
}
|
|
6411
|
+
|
|
6412
|
+
function walletExportFilePaths(walletInfo, { includeNotes }) {
|
|
6413
|
+
const walletFiles = [
|
|
6414
|
+
walletInfo.walletSecretPath,
|
|
6415
|
+
walletConfigPath(walletInfo.walletDir),
|
|
6416
|
+
walletMetadataPath(walletInfo.walletDir),
|
|
6417
|
+
];
|
|
6418
|
+
if (!includeNotes) {
|
|
6419
|
+
return walletFiles;
|
|
6420
|
+
}
|
|
6421
|
+
|
|
6422
|
+
const workspaceDir = channelWorkspacePath(walletInfo.network, walletInfo.channelName);
|
|
6423
|
+
const currentDir = channelWorkspaceCurrentPath(workspaceDir);
|
|
6424
|
+
const workspaceFiles = [
|
|
6425
|
+
channelWorkspaceConfigPath(workspaceDir),
|
|
6426
|
+
path.join(currentDir, "state_snapshot.json"),
|
|
6427
|
+
path.join(currentDir, "state_snapshot.normalized.json"),
|
|
6428
|
+
path.join(currentDir, "block_info.json"),
|
|
6429
|
+
path.join(currentDir, "contract_codes.json"),
|
|
6430
|
+
];
|
|
6431
|
+
for (const filePath of workspaceFiles) {
|
|
6432
|
+
expect(
|
|
6433
|
+
fs.existsSync(filePath),
|
|
6434
|
+
[
|
|
6435
|
+
`wallet export --include-notes requires channel workspace cache file: ${filePath}.`,
|
|
6436
|
+
"Run channel recover-workspace first, or export without --include-notes.",
|
|
6437
|
+
].join(" "),
|
|
6438
|
+
);
|
|
6439
|
+
}
|
|
6440
|
+
return [...walletFiles, ...workspaceFiles];
|
|
6441
|
+
}
|
|
6442
|
+
|
|
6443
|
+
function archivePathForLocalCliFile(filePath) {
|
|
6444
|
+
const root = privateStateCliDataRoot();
|
|
6445
|
+
const absolutePath = path.resolve(filePath);
|
|
6446
|
+
expectPathWithinRoot(absolutePath, root, `Cannot export file outside CLI data root: ${absolutePath}.`);
|
|
6447
|
+
return path.relative(root, absolutePath).split(path.sep).join("/");
|
|
6448
|
+
}
|
|
6449
|
+
|
|
6450
|
+
function validateWalletExportManifest(manifest) {
|
|
6451
|
+
expect(manifest?.format === WALLET_EXPORT_FORMAT, "Wallet import ZIP has an unsupported format.");
|
|
6452
|
+
expect(
|
|
6453
|
+
Number(manifest.formatVersion) === WALLET_EXPORT_FORMAT_VERSION,
|
|
6454
|
+
`Wallet import ZIP format version ${manifest?.formatVersion} is not supported.`,
|
|
6455
|
+
);
|
|
6456
|
+
expect(Array.isArray(manifest.files), "Wallet import ZIP manifest is missing files[].");
|
|
6457
|
+
expect(Array.isArray(manifest.wallets), "Wallet import ZIP manifest is missing wallets[].");
|
|
6458
|
+
expect(typeof manifest.includeNotes === "boolean", "Wallet import ZIP manifest is missing includeNotes.");
|
|
6459
|
+
expect(manifest.wallets.length > 0, "Wallet import ZIP manifest does not list any wallets.");
|
|
6460
|
+
const uniqueFiles = new Set(manifest.files);
|
|
6461
|
+
expect(uniqueFiles.size === manifest.files.length, "Wallet import ZIP manifest contains duplicate file paths.");
|
|
6462
|
+
expect(manifest.files.length > 0, "Wallet import ZIP manifest does not list any files.");
|
|
6463
|
+
for (const filePath of manifest.files) {
|
|
6464
|
+
validateWalletArchivePath(filePath);
|
|
6465
|
+
}
|
|
6466
|
+
for (const wallet of manifest.wallets) {
|
|
6467
|
+
requireNetworkName({ network: wallet.network });
|
|
6468
|
+
requireWalletName({ wallet: wallet.wallet });
|
|
6469
|
+
requireArg(wallet.channelName, "wallets[].channelName");
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
|
|
6473
|
+
function validateWalletArchivePath(archivePath) {
|
|
6474
|
+
expect(typeof archivePath === "string" && archivePath.length > 0, "Wallet import ZIP contains an empty path.");
|
|
6475
|
+
expect(!archivePath.includes("\0"), `Wallet import ZIP contains an invalid path: ${archivePath}.`);
|
|
6476
|
+
expect(!archivePath.includes("\\"), `Wallet import ZIP path must use forward slashes: ${archivePath}.`);
|
|
6477
|
+
expect(!path.posix.isAbsolute(archivePath), `Wallet import ZIP path must be relative: ${archivePath}.`);
|
|
6478
|
+
expect(path.posix.normalize(archivePath) === archivePath, `Wallet import ZIP path is not normalized: ${archivePath}.`);
|
|
6479
|
+
expect(
|
|
6480
|
+
archivePath.startsWith("secrets/") || archivePath.startsWith("workspace/"),
|
|
6481
|
+
`Wallet import ZIP path must start with secrets/ or workspace/: ${archivePath}.`,
|
|
6482
|
+
);
|
|
6483
|
+
}
|
|
6484
|
+
|
|
6485
|
+
function expectPathWithinRoot(targetPath, rootPath, message) {
|
|
6486
|
+
const relative = path.relative(path.resolve(rootPath), path.resolve(targetPath));
|
|
6487
|
+
expect(relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative), message);
|
|
6488
|
+
}
|
|
6489
|
+
|
|
6490
|
+
function applyImportedWalletFileMode(archivePath, targetPath) {
|
|
6491
|
+
if (
|
|
6492
|
+
archivePath.startsWith("secrets/")
|
|
6493
|
+
|| archivePath.endsWith("/wallet.json")
|
|
6494
|
+
|| archivePath.endsWith("/wallet.metadata.json")
|
|
6495
|
+
) {
|
|
6496
|
+
protectSecretFile(targetPath, `imported wallet file ${archivePath}`);
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6030
6500
|
function channelDataPath(workspaceDir) {
|
|
6031
6501
|
return workspaceChannelDir(workspaceDir);
|
|
6032
6502
|
}
|
|
@@ -6125,14 +6595,18 @@ function assertUninstallArgs(args) {
|
|
|
6125
6595
|
assertAllowedCommandSchema(args, "uninstall");
|
|
6126
6596
|
}
|
|
6127
6597
|
|
|
6598
|
+
function assertHelpCommandsArgs(args) {
|
|
6599
|
+
assertAllowedCommandSchema(args, "help-commands");
|
|
6600
|
+
}
|
|
6601
|
+
|
|
6128
6602
|
function assertUpdateArgs(args) {
|
|
6129
|
-
assertAllowedCommandSchema(args, "update");
|
|
6603
|
+
assertAllowedCommandSchema(args, "help-update");
|
|
6130
6604
|
}
|
|
6131
6605
|
|
|
6132
6606
|
function assertDoctorArgs(args) {
|
|
6133
|
-
assertAllowedCommandSchema(args, "doctor");
|
|
6607
|
+
assertAllowedCommandSchema(args, "help-doctor");
|
|
6134
6608
|
if (args.gpu !== undefined && args.gpu !== true) {
|
|
6135
|
-
throw new Error("doctor option --gpu does not accept a value.");
|
|
6609
|
+
throw new Error("help doctor option --gpu does not accept a value.");
|
|
6136
6610
|
}
|
|
6137
6611
|
}
|
|
6138
6612
|
|
|
@@ -6149,11 +6623,11 @@ function assertGuideArgs(args) {
|
|
|
6149
6623
|
if (args.wallet !== undefined) {
|
|
6150
6624
|
requireWalletName(args);
|
|
6151
6625
|
}
|
|
6152
|
-
assertAllowedCommandSchema(args, "guide");
|
|
6626
|
+
assertAllowedCommandSchema(args, "help-guide");
|
|
6153
6627
|
}
|
|
6154
6628
|
|
|
6155
6629
|
function assertTransactionFeesArgs(args) {
|
|
6156
|
-
assertAllowedCommandSchema(args, "transaction-fees");
|
|
6630
|
+
assertAllowedCommandSchema(args, "help-transaction-fees");
|
|
6157
6631
|
}
|
|
6158
6632
|
|
|
6159
6633
|
function assertAccountImportArgs(args) {
|
|
@@ -6161,7 +6635,7 @@ function assertAccountImportArgs(args) {
|
|
|
6161
6635
|
}
|
|
6162
6636
|
|
|
6163
6637
|
function assertMintNotesArgs(args) {
|
|
6164
|
-
assertAllowedCommandSchema(args, "mint-notes");
|
|
6638
|
+
assertAllowedCommandSchema(args, "wallet-mint-notes");
|
|
6165
6639
|
assertTxSubmitterArg(args);
|
|
6166
6640
|
parseAmountVector(args.amounts, {
|
|
6167
6641
|
allowZeroEntries: true,
|
|
@@ -6170,13 +6644,13 @@ function assertMintNotesArgs(args) {
|
|
|
6170
6644
|
}
|
|
6171
6645
|
|
|
6172
6646
|
function assertRedeemNotesArgs(args) {
|
|
6173
|
-
assertAllowedCommandSchema(args, "redeem-notes");
|
|
6647
|
+
assertAllowedCommandSchema(args, "wallet-redeem-notes");
|
|
6174
6648
|
assertTxSubmitterArg(args);
|
|
6175
6649
|
selectRedeemNotesMethod(parseNoteIdVector(args.noteIds).length);
|
|
6176
6650
|
}
|
|
6177
6651
|
|
|
6178
6652
|
function assertTransferNotesArgs(args) {
|
|
6179
|
-
assertAllowedCommandSchema(args, "transfer-notes");
|
|
6653
|
+
assertAllowedCommandSchema(args, "wallet-transfer-notes");
|
|
6180
6654
|
assertTxSubmitterArg(args);
|
|
6181
6655
|
const noteIds = parseNoteIdVector(args.noteIds);
|
|
6182
6656
|
const recipients = parseRecipientVector(args.recipients);
|
|
@@ -6197,28 +6671,28 @@ function assertTxSubmitterArg(args) {
|
|
|
6197
6671
|
}
|
|
6198
6672
|
}
|
|
6199
6673
|
|
|
6200
|
-
function
|
|
6201
|
-
assertWalletSecretArgs(args, "get-
|
|
6674
|
+
function assertWalletGetNotesArgs(args) {
|
|
6675
|
+
assertWalletSecretArgs(args, "wallet-get-notes");
|
|
6202
6676
|
}
|
|
6203
6677
|
|
|
6204
6678
|
function assertCreateChannelArgs(args) {
|
|
6205
|
-
assertAllowedCommandSchema(args, "create
|
|
6679
|
+
assertAllowedCommandSchema(args, "channel-create");
|
|
6206
6680
|
}
|
|
6207
6681
|
|
|
6208
6682
|
function assertRecoverWorkspaceArgs(args) {
|
|
6209
|
-
assertAllowedCommandSchema(args, "recover-workspace");
|
|
6683
|
+
assertAllowedCommandSchema(args, "channel-recover-workspace");
|
|
6210
6684
|
}
|
|
6211
6685
|
|
|
6212
6686
|
function assertGetChannelArgs(args) {
|
|
6213
|
-
assertAllowedCommandSchema(args, "get-
|
|
6687
|
+
assertAllowedCommandSchema(args, "channel-get-meta");
|
|
6214
6688
|
}
|
|
6215
6689
|
|
|
6216
6690
|
function assertDepositBridgeArgs(args) {
|
|
6217
|
-
assertAllowedCommandSchema(args, "deposit-bridge");
|
|
6691
|
+
assertAllowedCommandSchema(args, "account-deposit-bridge");
|
|
6218
6692
|
}
|
|
6219
6693
|
|
|
6220
|
-
function
|
|
6221
|
-
assertAllowedCommandSchema(args, "get-
|
|
6694
|
+
function assertAccountGetBridgeFundArgs(args) {
|
|
6695
|
+
assertAllowedCommandSchema(args, "account-get-bridge-fund");
|
|
6222
6696
|
}
|
|
6223
6697
|
|
|
6224
6698
|
function assertExplicitSignerCommandArgs(args, commandName) {
|
|
@@ -6226,22 +6700,22 @@ function assertExplicitSignerCommandArgs(args, commandName) {
|
|
|
6226
6700
|
}
|
|
6227
6701
|
|
|
6228
6702
|
function assertRecoverWalletArgs(args) {
|
|
6229
|
-
assertExplicitSignerCommandArgs(args, "recover-
|
|
6703
|
+
assertExplicitSignerCommandArgs(args, "wallet-recover-workspace");
|
|
6230
6704
|
if (args.fromGenesis !== undefined && args.fromGenesis !== true) {
|
|
6231
|
-
throw new Error("recover-
|
|
6705
|
+
throw new Error("wallet recover-workspace option --from-genesis does not accept a value.");
|
|
6232
6706
|
}
|
|
6233
6707
|
}
|
|
6234
6708
|
|
|
6235
6709
|
function assertJoinChannelArgs(args) {
|
|
6236
|
-
assertAllowedCommandSchema(args, "join
|
|
6710
|
+
assertAllowedCommandSchema(args, "channel-join");
|
|
6237
6711
|
}
|
|
6238
6712
|
|
|
6239
|
-
function
|
|
6240
|
-
assertWalletSecretArgs(args, "get-
|
|
6713
|
+
function assertWalletGetMetaArgs(args) {
|
|
6714
|
+
assertWalletSecretArgs(args, "wallet-get-meta");
|
|
6241
6715
|
}
|
|
6242
6716
|
|
|
6243
|
-
function
|
|
6244
|
-
assertAllowedCommandSchema(args, "get-
|
|
6717
|
+
function assertAccountGetL1AddressArgs(args) {
|
|
6718
|
+
assertAllowedCommandSchema(args, "account-get-l1-address");
|
|
6245
6719
|
}
|
|
6246
6720
|
|
|
6247
6721
|
function assertListLocalWalletsArgs(args) {
|
|
@@ -6251,19 +6725,45 @@ function assertListLocalWalletsArgs(args) {
|
|
|
6251
6725
|
if (args.channelName !== undefined) {
|
|
6252
6726
|
requireArg(args.channelName, "--channel-name");
|
|
6253
6727
|
}
|
|
6254
|
-
assertAllowedCommandSchema(args, "list
|
|
6728
|
+
assertAllowedCommandSchema(args, "wallet-list");
|
|
6729
|
+
}
|
|
6730
|
+
|
|
6731
|
+
function assertWalletExportArgs(args) {
|
|
6732
|
+
assertAllowedCommandSchema(args, "wallet-export");
|
|
6733
|
+
assertFlagOption(args, "all", "wallet export");
|
|
6734
|
+
assertFlagOption(args, "includeNotes", "wallet export");
|
|
6735
|
+
requireArg(args.output, "--output");
|
|
6736
|
+
if (args.all === true) {
|
|
6737
|
+
expect(
|
|
6738
|
+
args.network === undefined && args.wallet === undefined,
|
|
6739
|
+
"wallet export --all exports every local mainnet wallet and does not accept --network or --wallet.",
|
|
6740
|
+
);
|
|
6741
|
+
return;
|
|
6742
|
+
}
|
|
6743
|
+
requireNetworkName(args);
|
|
6744
|
+
requireWalletName(args);
|
|
6745
|
+
}
|
|
6746
|
+
|
|
6747
|
+
function assertWalletImportArgs(args) {
|
|
6748
|
+
assertAllowedCommandSchema(args, "wallet-import");
|
|
6749
|
+
}
|
|
6750
|
+
|
|
6751
|
+
function assertFlagOption(args, key, commandName) {
|
|
6752
|
+
if (args[key] !== undefined && args[key] !== true) {
|
|
6753
|
+
throw new Error(`${commandName} option --${toKebabCase(key)} does not accept a value.`);
|
|
6754
|
+
}
|
|
6255
6755
|
}
|
|
6256
6756
|
|
|
6257
6757
|
function assertWithdrawBridgeArgs(args) {
|
|
6258
|
-
assertAllowedCommandSchema(args, "withdraw-bridge");
|
|
6758
|
+
assertAllowedCommandSchema(args, "account-withdraw-bridge");
|
|
6259
6759
|
}
|
|
6260
6760
|
|
|
6261
|
-
function
|
|
6262
|
-
assertWalletSecretArgs(args, "get-
|
|
6761
|
+
function assertWalletGetChannelFundArgs(args) {
|
|
6762
|
+
assertWalletSecretArgs(args, "wallet-get-channel-fund");
|
|
6263
6763
|
}
|
|
6264
6764
|
|
|
6265
6765
|
function assertExitChannelArgs(args) {
|
|
6266
|
-
assertWalletSecretArgs(args, "exit
|
|
6766
|
+
assertWalletSecretArgs(args, "channel-exit");
|
|
6267
6767
|
}
|
|
6268
6768
|
|
|
6269
6769
|
function createWalletOperationDir(walletName, networkName, suffix) {
|
|
@@ -6289,17 +6789,6 @@ function persistWalletMetadata(context) {
|
|
|
6289
6789
|
});
|
|
6290
6790
|
}
|
|
6291
6791
|
|
|
6292
|
-
function persistCurrentState(context) {
|
|
6293
|
-
if (!context.persistChannelWorkspace || !context.workspaceDir) {
|
|
6294
|
-
return;
|
|
6295
|
-
}
|
|
6296
|
-
writeJson(path.join(channelWorkspaceCurrentPath(context.workspaceDir), "state_snapshot.json"), context.currentSnapshot);
|
|
6297
|
-
writeJson(
|
|
6298
|
-
path.join(channelWorkspaceCurrentPath(context.workspaceDir), "state_snapshot.normalized.json"),
|
|
6299
|
-
context.currentSnapshot,
|
|
6300
|
-
);
|
|
6301
|
-
}
|
|
6302
|
-
|
|
6303
6792
|
function printHelp() {
|
|
6304
6793
|
const commandHelp = PRIVATE_STATE_CLI_COMMANDS.map((command) => [
|
|
6305
6794
|
` ${privateStateCliCommandSynopsis(command)}`,
|
|
@@ -6313,10 +6802,10 @@ ${commandHelp}
|
|
|
6313
6802
|
Secret source options:
|
|
6314
6803
|
Use account import --private-key-file once to create a protected local account secret.
|
|
6315
6804
|
L1 signing commands use --account only.
|
|
6316
|
-
A wallet secret source file is arbitrary high-entropy secret text read once by join
|
|
6805
|
+
A wallet secret source file is arbitrary high-entropy secret text read once by channel join.
|
|
6317
6806
|
Create one before joining a channel, for example:
|
|
6318
6807
|
openssl rand -hex 32 > ./wallet-secret.txt
|
|
6319
|
-
private-state-cli
|
|
6808
|
+
private-state-cli channel join --channel-name <NAME> --network <NAME> --account <NAME> --wallet-secret-path ./wallet-secret.txt
|
|
6320
6809
|
Bridge-facing commands accept optional --rpc-url. When provided, it is saved to
|
|
6321
6810
|
~/tokamak-private-channels/secrets/<network>/.env as RPC_URL. When omitted, the CLI reads RPC_URL from that file.
|
|
6322
6811
|
Wallet commands use wallet-local default secret files only.
|
|
@@ -6331,7 +6820,7 @@ Options:
|
|
|
6331
6820
|
Print the command result as JSON. Without --json, commands print human-readable output.
|
|
6332
6821
|
|
|
6333
6822
|
--help
|
|
6334
|
-
Show this help
|
|
6823
|
+
Show this help. Equivalent to help commands.
|
|
6335
6824
|
`);
|
|
6336
6825
|
}
|
|
6337
6826
|
|
|
@@ -7056,18 +7545,18 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
7056
7545
|
|| message.includes("does not match the wallet channel")
|
|
7057
7546
|
|| message.includes("The provided wallet does not belong to the selected channel")
|
|
7058
7547
|
) {
|
|
7059
|
-
hints.push(`private-state-cli list
|
|
7060
|
-
hints.push(`private-state-cli guide --network ${networkName} --wallet ${walletName}`);
|
|
7548
|
+
hints.push(`private-state-cli wallet list --network ${networkName}`);
|
|
7549
|
+
hints.push(`private-state-cli help guide --network ${networkName} --wallet ${walletName}`);
|
|
7061
7550
|
}
|
|
7062
7551
|
|
|
7063
7552
|
if (error?.code === CLI_ERROR_CODES.MISSING_WALLET_SECRET) {
|
|
7064
7553
|
hints.push("restore the wallet-local default secret file from backup before running wallet commands.");
|
|
7065
|
-
hints.push(`private-state-cli guide --network ${networkName} --wallet ${walletName}`);
|
|
7554
|
+
hints.push(`private-state-cli help guide --network ${networkName} --wallet ${walletName}`);
|
|
7066
7555
|
}
|
|
7067
7556
|
|
|
7068
7557
|
if (error?.code === CLI_ERROR_CODES.WALLET_DECRYPT_FAILED) {
|
|
7069
7558
|
hints.push("verify that the wallet-local default secret file is the same secret used when the wallet was created.");
|
|
7070
|
-
hints.push("if the encrypted wallet file is corrupted but the wallet secret and L1 account secret still exist, rerun recover-
|
|
7559
|
+
hints.push("if the encrypted wallet file is corrupted but the wallet secret and L1 account secret still exist, rerun wallet recover-workspace.");
|
|
7071
7560
|
hints.push("if the wallet secret was lost, the local L2 key cannot be recovered from the encrypted wallet file.");
|
|
7072
7561
|
}
|
|
7073
7562
|
|
|
@@ -7076,7 +7565,7 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
7076
7565
|
|| message.includes("Missing --account.")
|
|
7077
7566
|
) {
|
|
7078
7567
|
hints.push(`private-state-cli account import --account ${accountName} --network ${networkName} --private-key-file <PATH>`);
|
|
7079
|
-
hints.push(`private-state-cli guide --network ${networkName} --account ${accountName}`);
|
|
7568
|
+
hints.push(`private-state-cli help guide --network ${networkName} --account ${accountName}`);
|
|
7080
7569
|
}
|
|
7081
7570
|
|
|
7082
7571
|
if (
|
|
@@ -7084,31 +7573,31 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
7084
7573
|
|| message.includes("DApp deployment artifact")
|
|
7085
7574
|
) {
|
|
7086
7575
|
hints.push("private-state-cli install");
|
|
7087
|
-
hints.push("private-state-cli doctor --json");
|
|
7576
|
+
hints.push("private-state-cli help doctor --json");
|
|
7088
7577
|
}
|
|
7089
7578
|
|
|
7090
7579
|
if (error?.code === CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION) {
|
|
7091
|
-
hints.push(`private-state-cli
|
|
7092
|
-
hints.push(`private-state-cli guide --network ${networkName} --channel-name ${channelName} --account ${accountName}`);
|
|
7580
|
+
hints.push(`private-state-cli channel join --channel-name ${channelName} --network ${networkName} --account ${accountName} --wallet-secret-path <PATH>`);
|
|
7581
|
+
hints.push(`private-state-cli help guide --network ${networkName} --channel-name ${channelName} --account ${accountName}`);
|
|
7093
7582
|
}
|
|
7094
7583
|
|
|
7095
7584
|
if (error?.code === CLI_ERROR_CODES.STALE_WORKSPACE) {
|
|
7096
|
-
hints.push(`private-state-cli recover-workspace --channel-name ${channelName} --network ${networkName}`);
|
|
7097
|
-
hints.push(`private-state-cli guide --network ${networkName} --channel-name ${channelName}`);
|
|
7585
|
+
hints.push(`private-state-cli channel recover-workspace --channel-name ${channelName} --network ${networkName}`);
|
|
7586
|
+
hints.push(`private-state-cli help guide --network ${networkName} --channel-name ${channelName}`);
|
|
7098
7587
|
}
|
|
7099
7588
|
|
|
7100
7589
|
if (message.includes("Workspace recovery index is missing or unusable")) {
|
|
7101
|
-
hints.push(`private-state-cli recover-workspace --channel-name ${channelName} --network ${networkName} --from-genesis`);
|
|
7102
|
-
hints.push(`private-state-cli recover-
|
|
7590
|
+
hints.push(`private-state-cli channel recover-workspace --channel-name ${channelName} --network ${networkName} --from-genesis`);
|
|
7591
|
+
hints.push(`private-state-cli wallet recover-workspace --channel-name ${channelName} --network ${networkName} --account ${accountName} --from-genesis`);
|
|
7103
7592
|
}
|
|
7104
7593
|
|
|
7105
7594
|
if (message.includes("Wallet note recovery index is missing or unusable")) {
|
|
7106
|
-
hints.push(`private-state-cli recover-
|
|
7595
|
+
hints.push(`private-state-cli wallet recover-workspace --channel-name ${channelName} --network ${networkName} --account ${accountName} --from-genesis`);
|
|
7107
7596
|
}
|
|
7108
7597
|
|
|
7109
7598
|
if (message.includes("Missing channel selector")) {
|
|
7110
|
-
hints.push(`private-state-cli list
|
|
7111
|
-
hints.push(`private-state-cli guide --network ${networkName} --channel-name <CHANNEL> --wallet <WALLET>`);
|
|
7599
|
+
hints.push(`private-state-cli wallet list --network ${networkName}`);
|
|
7600
|
+
hints.push(`private-state-cli help guide --network ${networkName} --channel-name <CHANNEL> --wallet <WALLET>`);
|
|
7112
7601
|
}
|
|
7113
7602
|
|
|
7114
7603
|
return [...new Set(hints)];
|