@tokamak-private-dapps/private-state-cli 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -1
- package/README.md +95 -62
- package/assets/tx-fees.json +16 -16
- package/cli-assistant.html +35 -35
- package/lib/private-state-cli-command-registry.mjs +116 -34
- package/lib/private-state-runtime-management.mjs +2 -2
- package/package.json +2 -1
- package/private-state-bridge-cli.mjs +674 -203
|
@@ -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,
|
|
@@ -837,7 +863,7 @@ async function initializeChannelWorkspace({
|
|
|
837
863
|
throw new Error([
|
|
838
864
|
`Workspace recovery index is missing or unusable for channel ${channelName} on ${networkNameFromChainId(network.chainId)}.`,
|
|
839
865
|
"The CLI will not fall back to replaying channel logs from genesis unless explicitly requested.",
|
|
840
|
-
|
|
866
|
+
"Run channel recover-workspace --from-genesis or wallet recover-workspace --from-genesis to rebuild from channel genesis.",
|
|
841
867
|
].join(" "));
|
|
842
868
|
}
|
|
843
869
|
const reconstruction = localSnapshotReusable
|
|
@@ -928,7 +954,7 @@ async function initializeChannelWorkspace({
|
|
|
928
954
|
async function handleDepositBridge({ args, network, provider }) {
|
|
929
955
|
if (args.wallet !== undefined) {
|
|
930
956
|
throw new Error(
|
|
931
|
-
"--wallet is not supported by deposit-bridge. Channel wallet keys are set up only by join
|
|
957
|
+
"--wallet is not supported by account deposit-bridge. Channel wallet keys are set up only by channel join.",
|
|
932
958
|
);
|
|
933
959
|
}
|
|
934
960
|
const signer = requireL1Signer(args, provider);
|
|
@@ -952,7 +978,7 @@ async function handleDepositBridge({ args, network, provider }) {
|
|
|
952
978
|
const availableBalance = await bridgeTokenVault.availableBalanceOf(signer.address);
|
|
953
979
|
|
|
954
980
|
printJson({
|
|
955
|
-
action: "deposit-bridge",
|
|
981
|
+
action: "account deposit-bridge",
|
|
956
982
|
amountInput,
|
|
957
983
|
amountBaseUnits: amount.toString(),
|
|
958
984
|
l1Address: signer.address,
|
|
@@ -968,7 +994,7 @@ async function handleDepositBridge({ args, network, provider }) {
|
|
|
968
994
|
});
|
|
969
995
|
}
|
|
970
996
|
|
|
971
|
-
async function
|
|
997
|
+
async function handleAccountGetBridgeFund({ args, provider }) {
|
|
972
998
|
const signer = requireL1Signer(args, provider);
|
|
973
999
|
const chainId = Number((await provider.getNetwork()).chainId);
|
|
974
1000
|
const bridgeVaultContext = await loadBridgeVaultContext({ provider, chainId });
|
|
@@ -980,7 +1006,7 @@ async function handleGetMyBridgeFund({ args, provider }) {
|
|
|
980
1006
|
const availableBalance = await bridgeTokenVault.availableBalanceOf(signer.address);
|
|
981
1007
|
|
|
982
1008
|
printJson({
|
|
983
|
-
action: "get-
|
|
1009
|
+
action: "account get-bridge-fund",
|
|
984
1010
|
l1Address: signer.address,
|
|
985
1011
|
bridgeTokenVault: bridgeVaultContext.bridgeTokenVaultAddress,
|
|
986
1012
|
canonicalAsset: bridgeVaultContext.canonicalAsset,
|
|
@@ -1002,7 +1028,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1002
1028
|
walletName,
|
|
1003
1029
|
});
|
|
1004
1030
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
1005
|
-
const initialized = await
|
|
1031
|
+
const initialized = await syncChannelWorkspace({
|
|
1006
1032
|
workspaceName: channelName,
|
|
1007
1033
|
channelName,
|
|
1008
1034
|
network,
|
|
@@ -1012,7 +1038,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1012
1038
|
allowExistingWorkspaceSync: true,
|
|
1013
1039
|
useWorkspaceRecoveryIndex: true,
|
|
1014
1040
|
fromGenesis: args.fromGenesis === true,
|
|
1015
|
-
progressAction: "recover-
|
|
1041
|
+
progressAction: "wallet recover-workspace",
|
|
1016
1042
|
});
|
|
1017
1043
|
const context = {
|
|
1018
1044
|
workspaceName: channelName,
|
|
@@ -1050,13 +1076,41 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1050
1076
|
const leafIndex = deriveChannelTokenVaultLeafIndex(storageKey);
|
|
1051
1077
|
const registration = await context.channelManager.getChannelTokenVaultRegistration(signer.address);
|
|
1052
1078
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1079
|
+
if (!registration.exists) {
|
|
1080
|
+
const cleanup = removeLocalWalletArtifacts(walletName, context.workspace.network);
|
|
1081
|
+
if (cleanup.removed) {
|
|
1082
|
+
printJson({
|
|
1083
|
+
action: "wallet recover-workspace",
|
|
1084
|
+
status: "stale-wallet-removed",
|
|
1085
|
+
wallet: walletName,
|
|
1086
|
+
removedWalletDir: cleanup.removedWalletDir ? cleanup.walletDir : null,
|
|
1087
|
+
removedWalletSecretFile: cleanup.removedWalletSecret ? cleanup.walletSecretFile : null,
|
|
1088
|
+
walletSecretSource: resolvedWalletSecretSource(args),
|
|
1089
|
+
walletSecretFile: resolvedWalletSecretFile(network.name, walletName),
|
|
1090
|
+
workspace: context.workspaceName,
|
|
1091
|
+
channelName: context.workspace.channelName,
|
|
1092
|
+
channelId: context.workspace.channelId,
|
|
1093
|
+
l1Address: signer.address,
|
|
1094
|
+
l2Address: l2Identity.l2Address,
|
|
1095
|
+
l2StorageKey: storageKey,
|
|
1096
|
+
leafIndex: leafIndex.toString(),
|
|
1097
|
+
reason: "The local wallet existed, but the L1 address is no longer registered in the channel.",
|
|
1098
|
+
nextAction: buildRecoverWalletRemovedNextAction({
|
|
1099
|
+
channelName,
|
|
1100
|
+
networkName: network.name,
|
|
1101
|
+
accountName: args.account,
|
|
1102
|
+
}),
|
|
1103
|
+
});
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
expect(
|
|
1107
|
+
false,
|
|
1108
|
+
cliError(
|
|
1109
|
+
CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION,
|
|
1110
|
+
`No channelTokenVault registration exists for ${signer.address}. Run channel join first.`,
|
|
1111
|
+
),
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1060
1114
|
expect(
|
|
1061
1115
|
ethers.toBigInt(getAddress(registration.l2Address)) === ethers.toBigInt(getAddress(l2Identity.l2Address)),
|
|
1062
1116
|
"The existing channel registration L2 address does not match the derived L2 address.",
|
|
@@ -1100,10 +1154,10 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1100
1154
|
provider,
|
|
1101
1155
|
signer,
|
|
1102
1156
|
noteReceiveKeyMaterial,
|
|
1103
|
-
progressAction: "recover-
|
|
1157
|
+
progressAction: "wallet recover-workspace",
|
|
1104
1158
|
});
|
|
1105
1159
|
printJson({
|
|
1106
|
-
action: "recover-
|
|
1160
|
+
action: "wallet recover-workspace",
|
|
1107
1161
|
status: "already-recovered",
|
|
1108
1162
|
wallet: walletName,
|
|
1109
1163
|
walletDir: existingWallet.walletDir,
|
|
@@ -1125,7 +1179,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1125
1179
|
return;
|
|
1126
1180
|
}
|
|
1127
1181
|
|
|
1128
|
-
|
|
1182
|
+
fs.rmSync(walletPath(walletName, context.workspace.network), { recursive: true, force: true });
|
|
1129
1183
|
|
|
1130
1184
|
const walletContext = ensureWallet({
|
|
1131
1185
|
channelContext: context,
|
|
@@ -1147,11 +1201,11 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1147
1201
|
provider,
|
|
1148
1202
|
signer,
|
|
1149
1203
|
noteReceiveKeyMaterial,
|
|
1150
|
-
progressAction: "recover-
|
|
1204
|
+
progressAction: "wallet recover-workspace",
|
|
1151
1205
|
});
|
|
1152
1206
|
|
|
1153
1207
|
printJson({
|
|
1154
|
-
action: "recover-
|
|
1208
|
+
action: "wallet recover-workspace",
|
|
1155
1209
|
status: "recovered",
|
|
1156
1210
|
wallet: walletName,
|
|
1157
1211
|
walletDir: walletContext.walletDir,
|
|
@@ -1301,8 +1355,30 @@ function assertExistingRecoverableWallet({
|
|
|
1301
1355
|
);
|
|
1302
1356
|
}
|
|
1303
1357
|
|
|
1304
|
-
function
|
|
1305
|
-
|
|
1358
|
+
function removeLocalWalletArtifacts(walletName, networkName) {
|
|
1359
|
+
const walletDir = walletPath(walletName, networkName);
|
|
1360
|
+
const walletSecretFile = walletSecretPath(networkName, walletName);
|
|
1361
|
+
const walletSecretDir = path.dirname(walletSecretFile);
|
|
1362
|
+
const removedWalletDir = fs.existsSync(walletDir);
|
|
1363
|
+
const removedWalletSecret = fs.existsSync(walletSecretFile) || fs.existsSync(walletSecretDir);
|
|
1364
|
+
if (removedWalletDir) {
|
|
1365
|
+
fs.rmSync(walletDir, { recursive: true, force: true });
|
|
1366
|
+
}
|
|
1367
|
+
if (removedWalletSecret) {
|
|
1368
|
+
fs.rmSync(walletSecretDir, { recursive: true, force: true });
|
|
1369
|
+
}
|
|
1370
|
+
return {
|
|
1371
|
+
walletDir,
|
|
1372
|
+
walletSecretFile,
|
|
1373
|
+
removed: removedWalletDir || removedWalletSecret,
|
|
1374
|
+
removedWalletDir,
|
|
1375
|
+
removedWalletSecret,
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function buildRecoverWalletRemovedNextAction({ channelName, networkName, accountName }) {
|
|
1380
|
+
const account = accountName ? String(accountName) : "<ACCOUNT>";
|
|
1381
|
+
return `channel join --channel-name ${channelName} --network ${networkName} --account ${account} --wallet-secret-path <PATH>`;
|
|
1306
1382
|
}
|
|
1307
1383
|
|
|
1308
1384
|
async function handleInstallZkEvm({ args }) {
|
|
@@ -1740,10 +1816,10 @@ function trimFixedNumber(value, maxDecimals) {
|
|
|
1740
1816
|
return trimmed ? `${integer}.${trimmed}` : integer;
|
|
1741
1817
|
}
|
|
1742
1818
|
|
|
1743
|
-
function
|
|
1819
|
+
function handleAccountGetL1Address({ args }) {
|
|
1744
1820
|
const signer = requireL1Signer(args);
|
|
1745
1821
|
printJson({
|
|
1746
|
-
action: "get-
|
|
1822
|
+
action: "account get-l1-address",
|
|
1747
1823
|
l1Address: signer.address,
|
|
1748
1824
|
account: args.account ?? null,
|
|
1749
1825
|
});
|
|
@@ -1770,7 +1846,7 @@ function handleAccountImport({ args }) {
|
|
|
1770
1846
|
privateKeyPath,
|
|
1771
1847
|
}, 0o600);
|
|
1772
1848
|
printJson({
|
|
1773
|
-
action: "account
|
|
1849
|
+
action: "account import",
|
|
1774
1850
|
account,
|
|
1775
1851
|
network: networkName,
|
|
1776
1852
|
l1Address: getAddress(signer.address),
|
|
@@ -1791,7 +1867,7 @@ function handleListLocalWallets({ args }) {
|
|
|
1791
1867
|
});
|
|
1792
1868
|
|
|
1793
1869
|
printJson({
|
|
1794
|
-
action: "list
|
|
1870
|
+
action: "wallet list",
|
|
1795
1871
|
workspaceRoot,
|
|
1796
1872
|
filters: {
|
|
1797
1873
|
network: networkFilter,
|
|
@@ -1801,6 +1877,175 @@ function handleListLocalWallets({ args }) {
|
|
|
1801
1877
|
});
|
|
1802
1878
|
}
|
|
1803
1879
|
|
|
1880
|
+
function handleWalletExport({ args }) {
|
|
1881
|
+
const outputPath = path.resolve(String(requireArg(args.output, "--output")));
|
|
1882
|
+
expect(!fs.existsSync(outputPath), `Export output already exists: ${outputPath}.`);
|
|
1883
|
+
ensureDir(path.dirname(outputPath));
|
|
1884
|
+
|
|
1885
|
+
const includeNotes = args.includeNotes === true;
|
|
1886
|
+
const wallets = args.all === true
|
|
1887
|
+
? listLocalWallets({ networkFilter: "mainnet" }).filter((wallet) => wallet.hasEncryptedWallet)
|
|
1888
|
+
: [resolveExportWalletInfo({
|
|
1889
|
+
networkName: requireNetworkName(args),
|
|
1890
|
+
walletName: requireWalletName(args),
|
|
1891
|
+
})];
|
|
1892
|
+
|
|
1893
|
+
expect(
|
|
1894
|
+
wallets.length > 0,
|
|
1895
|
+
args.all === true
|
|
1896
|
+
? "No local mainnet wallets are available to export."
|
|
1897
|
+
: "No local wallet is available to export.",
|
|
1898
|
+
);
|
|
1899
|
+
|
|
1900
|
+
const archive = new AdmZip();
|
|
1901
|
+
const files = new Map();
|
|
1902
|
+
const exportedWallets = [];
|
|
1903
|
+
for (const wallet of wallets) {
|
|
1904
|
+
const normalized = normalizeExportWalletInfo(wallet);
|
|
1905
|
+
exportedWallets.push({
|
|
1906
|
+
network: normalized.network,
|
|
1907
|
+
channelName: normalized.channelName,
|
|
1908
|
+
wallet: normalized.wallet,
|
|
1909
|
+
});
|
|
1910
|
+
for (const filePath of walletExportFilePaths(normalized, { includeNotes })) {
|
|
1911
|
+
const archivePath = archivePathForLocalCliFile(filePath);
|
|
1912
|
+
if (!files.has(archivePath)) {
|
|
1913
|
+
files.set(archivePath, filePath);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
const manifest = {
|
|
1919
|
+
format: WALLET_EXPORT_FORMAT,
|
|
1920
|
+
formatVersion: WALLET_EXPORT_FORMAT_VERSION,
|
|
1921
|
+
createdAt: new Date().toISOString(),
|
|
1922
|
+
cliPackage: PRIVATE_STATE_CLI_PACKAGE_NAME,
|
|
1923
|
+
cliVersion: privateStateCliPackageJson.version,
|
|
1924
|
+
exportMode: args.all === true ? "all-mainnet" : "single-wallet",
|
|
1925
|
+
includeNotes,
|
|
1926
|
+
notes: includeNotes
|
|
1927
|
+
? [
|
|
1928
|
+
"Includes the channel workspace cache required for immediate wallet command use when the cache is still chain-aligned.",
|
|
1929
|
+
]
|
|
1930
|
+
: [
|
|
1931
|
+
"Includes wallet identity, encrypted wallet state, metadata, and wallet-local secret only.",
|
|
1932
|
+
"Run channel recover-workspace after import before wallet commands need channel state.",
|
|
1933
|
+
],
|
|
1934
|
+
wallets: exportedWallets,
|
|
1935
|
+
files: [...files.keys()].sort(),
|
|
1936
|
+
};
|
|
1937
|
+
|
|
1938
|
+
archive.addFile("manifest.json", Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"));
|
|
1939
|
+
for (const archivePath of manifest.files) {
|
|
1940
|
+
archive.addFile(archivePath, fs.readFileSync(files.get(archivePath)));
|
|
1941
|
+
}
|
|
1942
|
+
archive.writeZip(outputPath);
|
|
1943
|
+
protectSecretFile(outputPath, "wallet export ZIP");
|
|
1944
|
+
|
|
1945
|
+
printJson({
|
|
1946
|
+
action: "wallet export",
|
|
1947
|
+
output: outputPath,
|
|
1948
|
+
exportMode: manifest.exportMode,
|
|
1949
|
+
includeNotes,
|
|
1950
|
+
walletCount: exportedWallets.length,
|
|
1951
|
+
fileCount: manifest.files.length,
|
|
1952
|
+
wallets: exportedWallets.map(({ network, channelName, wallet }) => ({ network, channelName, wallet })),
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
function handleWalletImport({ args }) {
|
|
1957
|
+
const inputPath = path.resolve(String(requireArg(args.input, "--input")));
|
|
1958
|
+
expect(fs.existsSync(inputPath), `Import ZIP does not exist: ${inputPath}.`);
|
|
1959
|
+
|
|
1960
|
+
const { archive, manifest } = readWalletImportArchive(inputPath);
|
|
1961
|
+
|
|
1962
|
+
const archiveFiles = new Set(manifest.files);
|
|
1963
|
+
for (const entry of archive.getEntries()) {
|
|
1964
|
+
if (entry.isDirectory) {
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1967
|
+
expect(
|
|
1968
|
+
entry.entryName === "manifest.json" || archiveFiles.has(entry.entryName),
|
|
1969
|
+
`Unexpected file in wallet import ZIP: ${entry.entryName}.`,
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
const targetRoot = privateStateCliDataRoot();
|
|
1974
|
+
ensureDir(targetRoot);
|
|
1975
|
+
const plannedWrites = manifest.files.map((archivePath) => {
|
|
1976
|
+
validateWalletArchivePath(archivePath);
|
|
1977
|
+
const entry = archive.getEntry(archivePath);
|
|
1978
|
+
expect(entry && !entry.isDirectory, `Wallet import ZIP is missing ${archivePath}.`);
|
|
1979
|
+
const targetPath = path.resolve(targetRoot, archivePath);
|
|
1980
|
+
expectPathWithinRoot(targetPath, targetRoot, `Unsafe import target for ${archivePath}.`);
|
|
1981
|
+
expect(!fs.existsSync(targetPath), `Refusing to overwrite existing file: ${targetPath}.`);
|
|
1982
|
+
return {
|
|
1983
|
+
archivePath,
|
|
1984
|
+
targetPath,
|
|
1985
|
+
data: entry.getData(),
|
|
1986
|
+
};
|
|
1987
|
+
});
|
|
1988
|
+
|
|
1989
|
+
commitWalletImportFiles({ targetRoot, plannedWrites });
|
|
1990
|
+
|
|
1991
|
+
printJson({
|
|
1992
|
+
action: "wallet import",
|
|
1993
|
+
input: inputPath,
|
|
1994
|
+
exportMode: manifest.exportMode,
|
|
1995
|
+
includeNotes: Boolean(manifest.includeNotes),
|
|
1996
|
+
walletCount: manifest.wallets.length,
|
|
1997
|
+
fileCount: plannedWrites.length,
|
|
1998
|
+
wallets: manifest.wallets.map(({ network, channelName, wallet }) => ({ network, channelName, wallet })),
|
|
1999
|
+
nextStep: manifest.includeNotes
|
|
2000
|
+
? "Wallet commands can run immediately if the imported channel workspace cache is still chain-aligned."
|
|
2001
|
+
: "Run channel recover-workspace before wallet commands need channel state.",
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function readWalletImportArchive(inputPath) {
|
|
2006
|
+
try {
|
|
2007
|
+
const archive = new AdmZip(inputPath);
|
|
2008
|
+
const manifestEntry = archive.getEntry("manifest.json");
|
|
2009
|
+
expect(manifestEntry, "Wallet import ZIP is missing manifest.json.");
|
|
2010
|
+
const manifest = JSON.parse(manifestEntry.getData().toString("utf8"));
|
|
2011
|
+
validateWalletExportManifest(manifest);
|
|
2012
|
+
return { archive, manifest };
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
throw new Error(`Failed to read wallet import ZIP ${inputPath}: ${error.message}`);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
function commitWalletImportFiles({ targetRoot, plannedWrites }) {
|
|
2019
|
+
const stagingRoot = fs.mkdtempSync(path.join(targetRoot, ".wallet-import-"));
|
|
2020
|
+
const committedPaths = [];
|
|
2021
|
+
try {
|
|
2022
|
+
for (const write of plannedWrites) {
|
|
2023
|
+
write.stagingPath = path.resolve(stagingRoot, write.archivePath);
|
|
2024
|
+
expectPathWithinRoot(write.stagingPath, stagingRoot, `Unsafe staging target for ${write.archivePath}.`);
|
|
2025
|
+
ensureDir(path.dirname(write.stagingPath));
|
|
2026
|
+
fs.writeFileSync(write.stagingPath, write.data);
|
|
2027
|
+
applyImportedWalletFileMode(write.archivePath, write.stagingPath);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
for (const write of plannedWrites) {
|
|
2031
|
+
expect(!fs.existsSync(write.targetPath), `Refusing to overwrite existing file: ${write.targetPath}.`);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
for (const write of plannedWrites) {
|
|
2035
|
+
ensureDir(path.dirname(write.targetPath));
|
|
2036
|
+
fs.renameSync(write.stagingPath, write.targetPath);
|
|
2037
|
+
committedPaths.push(write.targetPath);
|
|
2038
|
+
}
|
|
2039
|
+
} catch (error) {
|
|
2040
|
+
for (const committedPath of committedPaths.reverse()) {
|
|
2041
|
+
fs.rmSync(committedPath, { force: true });
|
|
2042
|
+
}
|
|
2043
|
+
throw error;
|
|
2044
|
+
} finally {
|
|
2045
|
+
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
|
|
1804
2049
|
async function handleGuide({ args }) {
|
|
1805
2050
|
const guide = {
|
|
1806
2051
|
action: "guide",
|
|
@@ -1816,7 +2061,7 @@ async function handleGuide({ args }) {
|
|
|
1816
2061
|
nextSafeAction: null,
|
|
1817
2062
|
why: null,
|
|
1818
2063
|
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.",
|
|
2064
|
+
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
2065
|
};
|
|
1821
2066
|
|
|
1822
2067
|
guide.state.local = inspectGuideLocalState(args);
|
|
@@ -1839,12 +2084,12 @@ async function handleGuide({ args }) {
|
|
|
1839
2084
|
|
|
1840
2085
|
if (!args.network) {
|
|
1841
2086
|
setGuideNextAction(guide, {
|
|
1842
|
-
command: "guide --network <NAME>",
|
|
2087
|
+
command: "help guide --network <NAME>",
|
|
1843
2088
|
why: "Select a network before the guide can inspect RPC, deployment artifacts, channels, accounts, or wallets.",
|
|
1844
2089
|
candidates: [
|
|
1845
|
-
"guide --network mainnet",
|
|
1846
|
-
"guide --network sepolia",
|
|
1847
|
-
"guide --network anvil",
|
|
2090
|
+
"help guide --network mainnet",
|
|
2091
|
+
"help guide --network sepolia",
|
|
2092
|
+
"help guide --network anvil",
|
|
1848
2093
|
],
|
|
1849
2094
|
});
|
|
1850
2095
|
printJson(guide);
|
|
@@ -1857,7 +2102,7 @@ async function handleGuide({ args }) {
|
|
|
1857
2102
|
guide.checks.push(networkRuntime.check);
|
|
1858
2103
|
if (!networkRuntime.network) {
|
|
1859
2104
|
setGuideNextAction(guide, {
|
|
1860
|
-
command: "guide --network <NAME>",
|
|
2105
|
+
command: "help guide --network <NAME>",
|
|
1861
2106
|
why: `The requested network ${networkName} is not supported by the CLI network config.`,
|
|
1862
2107
|
});
|
|
1863
2108
|
printJson(guide);
|
|
@@ -2225,8 +2470,8 @@ async function inspectGuideWallet({ walletName, networkName, provider, artifacts
|
|
|
2225
2470
|
function applyGuideNextAction(guide) {
|
|
2226
2471
|
if (guide.state.local?.walletSelectorError && guide.selectors.network) {
|
|
2227
2472
|
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.",
|
|
2473
|
+
command: `wallet list --network ${guide.selectors.network}`,
|
|
2474
|
+
why: "The selected wallet name is malformed. List local wallets and retry help guide with an existing deterministic wallet name.",
|
|
2230
2475
|
});
|
|
2231
2476
|
return;
|
|
2232
2477
|
}
|
|
@@ -2254,14 +2499,14 @@ function applyGuideNextAction(guide) {
|
|
|
2254
2499
|
if (guide.selectors.channelName && guide.state.channel?.onchain?.exists === false) {
|
|
2255
2500
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2256
2501
|
setGuideNextAction(guide, {
|
|
2257
|
-
command: `
|
|
2502
|
+
command: `channel create --channel-name ${guide.selectors.channelName} --join-toll <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2258
2503
|
why: "The selected channel name is not registered on-chain yet.",
|
|
2259
2504
|
});
|
|
2260
2505
|
return;
|
|
2261
2506
|
}
|
|
2262
2507
|
if (guide.selectors.channelName && guide.state.channel?.onchain?.exists && !guide.state.channel?.local?.workspaceExists) {
|
|
2263
2508
|
setGuideNextAction(guide, {
|
|
2264
|
-
command: `recover-workspace --channel-name ${guide.selectors.channelName} --network ${guide.selectors.network} --from-genesis`,
|
|
2509
|
+
command: `channel recover-workspace --channel-name ${guide.selectors.channelName} --network ${guide.selectors.network} --from-genesis`,
|
|
2265
2510
|
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
2511
|
});
|
|
2267
2512
|
return;
|
|
@@ -2270,7 +2515,7 @@ function applyGuideNextAction(guide) {
|
|
|
2270
2515
|
const channelName = guide.selectors.channelName ?? guide.state.channel?.channelName ?? "<CHANNEL>";
|
|
2271
2516
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2272
2517
|
setGuideNextAction(guide, {
|
|
2273
|
-
command: `
|
|
2518
|
+
command: `channel join --channel-name ${channelName} --network ${guide.selectors.network} --account ${account} --wallet-secret-path <PATH>`,
|
|
2274
2519
|
why: "The selected local wallet does not exist. Join the channel to create the wallet and register the channel L2 identity.",
|
|
2275
2520
|
});
|
|
2276
2521
|
return;
|
|
@@ -2279,7 +2524,7 @@ function applyGuideNextAction(guide) {
|
|
|
2279
2524
|
const channelName = guide.state.wallet.channelName ?? guide.selectors.channelName ?? "<CHANNEL>";
|
|
2280
2525
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2281
2526
|
setGuideNextAction(guide, {
|
|
2282
|
-
command: `
|
|
2527
|
+
command: `channel join --channel-name ${channelName} --network ${guide.selectors.network} --account ${account} --wallet-secret-path <PATH>`,
|
|
2283
2528
|
why: "The local wallet exists, but the corresponding L1 address is not registered in the channel.",
|
|
2284
2529
|
});
|
|
2285
2530
|
return;
|
|
@@ -2296,46 +2541,46 @@ function applyGuideNextAction(guide) {
|
|
|
2296
2541
|
if (guide.state.wallet?.exists && bridgeBalance === 0n && (channelBalance === null || channelBalance === 0n) && unusedNotes === 0) {
|
|
2297
2542
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2298
2543
|
setGuideNextAction(guide, {
|
|
2299
|
-
command: `deposit-bridge --amount <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2544
|
+
command: `account deposit-bridge --amount <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2300
2545
|
why: "The wallet is joined, but there is no bridge balance, channel balance, or local unused note to spend.",
|
|
2301
2546
|
});
|
|
2302
2547
|
return;
|
|
2303
2548
|
}
|
|
2304
2549
|
if (guide.state.wallet?.exists && bridgeBalance !== null && bridgeBalance > 0n && channelBalance === 0n) {
|
|
2305
2550
|
setGuideNextAction(guide, {
|
|
2306
|
-
command: `deposit-channel --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amount <TOKENS>`,
|
|
2551
|
+
command: `wallet deposit-channel --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amount <TOKENS>`,
|
|
2307
2552
|
why: "The account has funds in the shared bridge vault, but the wallet has no channel L2 accounting balance.",
|
|
2308
2553
|
});
|
|
2309
2554
|
return;
|
|
2310
2555
|
}
|
|
2311
2556
|
if (guide.state.wallet?.exists && channelBalance !== null && channelBalance > 0n && unusedNotes === 0) {
|
|
2312
2557
|
setGuideNextAction(guide, {
|
|
2313
|
-
command: `mint-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amounts <A,B> [--tx-submitter <ACCOUNT>]`,
|
|
2558
|
+
command: `wallet mint-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --amounts <A,B> [--tx-submitter <ACCOUNT>]`,
|
|
2314
2559
|
why: "The wallet has channel L2 balance and no unused private notes yet. Use --tx-submitter for stronger transaction-submission privacy.",
|
|
2315
2560
|
});
|
|
2316
2561
|
return;
|
|
2317
2562
|
}
|
|
2318
2563
|
if (guide.state.wallet?.exists && unusedNotes !== null && unusedNotes > 0) {
|
|
2319
2564
|
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>]`,
|
|
2565
|
+
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
2566
|
why: "The wallet has unused private notes. It can transfer or redeem those notes. Use --tx-submitter for stronger transaction-submission privacy.",
|
|
2322
2567
|
candidates: [
|
|
2323
|
-
`get-
|
|
2324
|
-
`redeem-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <ID> [--tx-submitter <ACCOUNT>]`,
|
|
2568
|
+
`wallet get-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network}`,
|
|
2569
|
+
`wallet redeem-notes --wallet ${guide.selectors.wallet} --network ${guide.selectors.network} --note-ids <ID> [--tx-submitter <ACCOUNT>]`,
|
|
2325
2570
|
],
|
|
2326
2571
|
});
|
|
2327
2572
|
return;
|
|
2328
2573
|
}
|
|
2329
2574
|
if (guide.state.wallet?.exists && channelBalance === 0n) {
|
|
2330
2575
|
setGuideNextAction(guide, {
|
|
2331
|
-
command: `
|
|
2576
|
+
command: `channel exit --wallet ${guide.selectors.wallet} --network ${guide.selectors.network}`,
|
|
2332
2577
|
why: "The wallet has zero channel balance, so channel exit is allowed by both the CLI and bridge contract.",
|
|
2333
2578
|
});
|
|
2334
2579
|
return;
|
|
2335
2580
|
}
|
|
2336
2581
|
|
|
2337
2582
|
setGuideNextAction(guide, {
|
|
2338
|
-
command: "guide --network <NAME> --channel-name <CHANNEL> --account <ACCOUNT> --wallet <WALLET>",
|
|
2583
|
+
command: "help guide --network <NAME> --channel-name <CHANNEL> --account <ACCOUNT> --wallet <WALLET>",
|
|
2339
2584
|
why: "Provide more selectors so the guide can choose a single next safe action.",
|
|
2340
2585
|
});
|
|
2341
2586
|
}
|
|
@@ -2399,12 +2644,12 @@ function redactRpcUrl(rpcUrl) {
|
|
|
2399
2644
|
}
|
|
2400
2645
|
}
|
|
2401
2646
|
|
|
2402
|
-
async function
|
|
2647
|
+
async function handleWalletGetMeta({ args, provider }) {
|
|
2403
2648
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
2404
2649
|
const contextResult = await loadPreferredWalletChannelContext({
|
|
2405
2650
|
walletContext: wallet,
|
|
2406
2651
|
provider,
|
|
2407
|
-
progressAction: "get-
|
|
2652
|
+
progressAction: "wallet get-meta",
|
|
2408
2653
|
});
|
|
2409
2654
|
const context = contextResult.context;
|
|
2410
2655
|
const {
|
|
@@ -2420,7 +2665,7 @@ async function handleGetMyWalletMeta({ args, provider }) {
|
|
|
2420
2665
|
});
|
|
2421
2666
|
|
|
2422
2667
|
printJson({
|
|
2423
|
-
action: "get-
|
|
2668
|
+
action: "wallet get-meta",
|
|
2424
2669
|
wallet: wallet.walletName,
|
|
2425
2670
|
network: walletMetadata.network,
|
|
2426
2671
|
channelName: walletMetadata.channelName,
|
|
@@ -2490,7 +2735,7 @@ async function loadWalletChannelRegistrationState({
|
|
|
2490
2735
|
registration.exists,
|
|
2491
2736
|
cliError(
|
|
2492
2737
|
CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION,
|
|
2493
|
-
`No channelTokenVault registration exists for ${signer.address}. Run
|
|
2738
|
+
`No channelTokenVault registration exists for ${signer.address}. Run channel join first.`,
|
|
2494
2739
|
),
|
|
2495
2740
|
);
|
|
2496
2741
|
expect(
|
|
@@ -2508,7 +2753,7 @@ async function loadWalletChannelRegistrationState({
|
|
|
2508
2753
|
};
|
|
2509
2754
|
}
|
|
2510
2755
|
|
|
2511
|
-
async function
|
|
2756
|
+
async function handleWalletGetChannelFund({ args, provider }) {
|
|
2512
2757
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
2513
2758
|
const {
|
|
2514
2759
|
signer,
|
|
@@ -2520,11 +2765,11 @@ async function handleGetMyChannelFund({ args, provider }) {
|
|
|
2520
2765
|
} = await loadWalletChannelFundState({
|
|
2521
2766
|
walletContext: wallet,
|
|
2522
2767
|
provider,
|
|
2523
|
-
progressAction: "get-
|
|
2768
|
+
progressAction: "wallet get-channel-fund",
|
|
2524
2769
|
});
|
|
2525
2770
|
|
|
2526
2771
|
printJson({
|
|
2527
|
-
action: "get-
|
|
2772
|
+
action: "wallet get-channel-fund",
|
|
2528
2773
|
wallet: wallet.walletName,
|
|
2529
2774
|
network: walletMetadata.network,
|
|
2530
2775
|
channelName: walletMetadata.channelName,
|
|
@@ -2556,7 +2801,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2556
2801
|
!existingRegistration.exists,
|
|
2557
2802
|
[
|
|
2558
2803
|
`L1 address ${signer.address} is already registered in channel ${context.workspace.channelName}.`,
|
|
2559
|
-
"Use recover-
|
|
2804
|
+
"Use wallet recover-workspace or normal wallet commands for an existing channel registration.",
|
|
2560
2805
|
].join(" "),
|
|
2561
2806
|
);
|
|
2562
2807
|
const walletSecret = prepareJoinWalletSecretForName({
|
|
@@ -2593,7 +2838,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2593
2838
|
);
|
|
2594
2839
|
let nextNonce = await provider.getTransactionCount(signer.address, "pending");
|
|
2595
2840
|
printImmutableChannelPolicyWarning({
|
|
2596
|
-
action: "join
|
|
2841
|
+
action: "channel join",
|
|
2597
2842
|
channelName: context.workspace.channelName,
|
|
2598
2843
|
channelId: ethers.toBigInt(context.workspace.channelId),
|
|
2599
2844
|
channelManager: context.workspace.channelManager,
|
|
@@ -2629,7 +2874,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2629
2874
|
});
|
|
2630
2875
|
|
|
2631
2876
|
printJson({
|
|
2632
|
-
action: "join
|
|
2877
|
+
action: "channel join",
|
|
2633
2878
|
workspace: context.workspaceName,
|
|
2634
2879
|
wallet: walletContext.walletName,
|
|
2635
2880
|
walletSecretSource: resolvedWalletSecretSource(args),
|
|
@@ -2665,17 +2910,18 @@ async function handleExitChannel({ args, provider }) {
|
|
|
2665
2910
|
channelFund === 0n,
|
|
2666
2911
|
[
|
|
2667
2912
|
`The current channel fund for ${signer.address} is ${channelFund.toString()}.`,
|
|
2668
|
-
"
|
|
2669
|
-
"Run withdraw-channel first, then retry exit
|
|
2913
|
+
"channel exit requires a zero channel balance.",
|
|
2914
|
+
"Run wallet withdraw-channel first, then retry channel exit.",
|
|
2670
2915
|
].join(" "),
|
|
2671
2916
|
);
|
|
2672
2917
|
const [refundAmount, refundBps] = await context.channelManager.getExitTollRefundQuote(signer.address);
|
|
2673
2918
|
const receipt = await waitForReceipt(
|
|
2674
2919
|
await context.bridgeTokenVault.connect(signer).exitChannel(ethers.toBigInt(context.workspace.channelId)),
|
|
2675
2920
|
);
|
|
2921
|
+
const cleanup = removeLocalWalletArtifacts(walletContext.walletName, walletMetadata.network);
|
|
2676
2922
|
|
|
2677
2923
|
printJson({
|
|
2678
|
-
action: "exit
|
|
2924
|
+
action: "channel exit",
|
|
2679
2925
|
wallet: walletContext.walletName,
|
|
2680
2926
|
network: walletMetadata.network,
|
|
2681
2927
|
channelName: walletMetadata.channelName,
|
|
@@ -2690,15 +2936,17 @@ async function handleExitChannel({ args, provider }) {
|
|
|
2690
2936
|
gasUsed: receiptGasUsed(receipt),
|
|
2691
2937
|
txUrl: explorerTxUrl(network, receipt.hash),
|
|
2692
2938
|
receipt: sanitizeReceipt(receipt),
|
|
2939
|
+
removedWalletDir: cleanup.removedWalletDir ? cleanup.walletDir : null,
|
|
2940
|
+
removedWalletSecretFile: cleanup.removedWalletSecret ? cleanup.walletSecretFile : null,
|
|
2693
2941
|
});
|
|
2694
2942
|
}
|
|
2695
2943
|
|
|
2696
2944
|
async function handleGrothVaultMove({ args, provider, direction }) {
|
|
2697
|
-
const operationName = args.command === "withdraw-channel"
|
|
2698
|
-
? "withdraw-channel"
|
|
2945
|
+
const operationName = args.command === "wallet-withdraw-channel"
|
|
2946
|
+
? "wallet withdraw-channel"
|
|
2699
2947
|
: direction === "deposit"
|
|
2700
|
-
? "deposit-channel"
|
|
2701
|
-
: "withdraw";
|
|
2948
|
+
? "wallet deposit-channel"
|
|
2949
|
+
: "wallet withdraw-channel";
|
|
2702
2950
|
emitProgress(operationName, "loading");
|
|
2703
2951
|
const { wallet: walletContext } = loadUnlockedWalletWithMetadata(args);
|
|
2704
2952
|
const contextResult = await loadPreferredWalletChannelContext({
|
|
@@ -2729,7 +2977,7 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2729
2977
|
availableBalance >= amount,
|
|
2730
2978
|
[
|
|
2731
2979
|
`Deposit amount ${amount.toString()} exceeds the shared bridge-vault balance`,
|
|
2732
|
-
`${availableBalance.toString()} for ${signer.address}. Run deposit-bridge first.`,
|
|
2980
|
+
`${availableBalance.toString()} for ${signer.address}. Run account deposit-bridge first.`,
|
|
2733
2981
|
].join(" "),
|
|
2734
2982
|
);
|
|
2735
2983
|
}
|
|
@@ -2738,7 +2986,7 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2738
2986
|
registration.exists,
|
|
2739
2987
|
cliError(
|
|
2740
2988
|
CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION,
|
|
2741
|
-
`No channelTokenVault registration exists for ${signer.address}. Run
|
|
2989
|
+
`No channelTokenVault registration exists for ${signer.address}. Run channel join first.`,
|
|
2742
2990
|
),
|
|
2743
2991
|
);
|
|
2744
2992
|
expect(
|
|
@@ -2802,7 +3050,12 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2802
3050
|
sealWalletOperationDir(operationDir, walletContext.walletSecret);
|
|
2803
3051
|
|
|
2804
3052
|
context.currentSnapshot = transition.nextSnapshot;
|
|
2805
|
-
|
|
3053
|
+
await refreshPersistedWorkspaceAfterLocalTransaction({
|
|
3054
|
+
context,
|
|
3055
|
+
provider,
|
|
3056
|
+
receipt,
|
|
3057
|
+
progressAction: operationName,
|
|
3058
|
+
});
|
|
2806
3059
|
|
|
2807
3060
|
emitProgress(operationName, "done");
|
|
2808
3061
|
printJson({
|
|
@@ -2818,6 +3071,8 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2818
3071
|
updatedRoot: transition.update.updatedRoot,
|
|
2819
3072
|
gasUsed: receiptGasUsed(receipt),
|
|
2820
3073
|
txUrl: explorerTxUrl(network, receipt.hash),
|
|
3074
|
+
usedWorkspaceCache: contextResult.usingWorkspaceCache,
|
|
3075
|
+
recoveredWorkspace: contextResult.recoveredWorkspace,
|
|
2821
3076
|
});
|
|
2822
3077
|
}
|
|
2823
3078
|
|
|
@@ -2835,7 +3090,7 @@ async function handleWithdrawBridge({ args, network, provider }) {
|
|
|
2835
3090
|
const receipt = await waitForReceipt(await bridgeTokenVault.claimToWallet(amount));
|
|
2836
3091
|
|
|
2837
3092
|
printJson({
|
|
2838
|
-
action: "withdraw-bridge",
|
|
3093
|
+
action: "account withdraw-bridge",
|
|
2839
3094
|
l1Address: signer.address,
|
|
2840
3095
|
amountInput,
|
|
2841
3096
|
amountBaseUnits: amount.toString(),
|
|
@@ -2927,13 +3182,13 @@ async function handleMintNotes({ args, provider }) {
|
|
|
2927
3182
|
const { channelFund } = await loadWalletChannelFundState({
|
|
2928
3183
|
walletContext: wallet,
|
|
2929
3184
|
provider,
|
|
2930
|
-
progressAction: "mint-notes",
|
|
3185
|
+
progressAction: "wallet mint-notes",
|
|
2931
3186
|
});
|
|
2932
3187
|
expect(
|
|
2933
3188
|
totalMintAmount <= channelFund,
|
|
2934
3189
|
[
|
|
2935
3190
|
`Mint amount total ${totalMintAmount.toString()} exceeds the current channel fund`,
|
|
2936
|
-
`${channelFund.toString()}. Run get-
|
|
3191
|
+
`${channelFund.toString()}. Run wallet get-channel-fund to inspect the available balance.`,
|
|
2937
3192
|
].join(" "),
|
|
2938
3193
|
);
|
|
2939
3194
|
const templatePayload = buildMintNotesTemplatePayload({
|
|
@@ -2944,12 +3199,12 @@ async function handleMintNotes({ args, provider }) {
|
|
|
2944
3199
|
args,
|
|
2945
3200
|
wallet,
|
|
2946
3201
|
provider,
|
|
2947
|
-
operationName: "mint-notes",
|
|
3202
|
+
operationName: "wallet mint-notes",
|
|
2948
3203
|
templatePayload,
|
|
2949
3204
|
});
|
|
2950
3205
|
|
|
2951
3206
|
printJson({
|
|
2952
|
-
action: "mint-notes",
|
|
3207
|
+
action: "wallet mint-notes",
|
|
2953
3208
|
wallet: wallet.walletName,
|
|
2954
3209
|
workspace: execution.context.workspaceName,
|
|
2955
3210
|
operationDir: execution.operationDir,
|
|
@@ -2988,12 +3243,12 @@ async function handleRedeemNotes({ args, provider }) {
|
|
|
2988
3243
|
args,
|
|
2989
3244
|
wallet,
|
|
2990
3245
|
provider,
|
|
2991
|
-
operationName: "redeem-notes",
|
|
3246
|
+
operationName: "wallet redeem-notes",
|
|
2992
3247
|
templatePayload,
|
|
2993
3248
|
});
|
|
2994
3249
|
|
|
2995
3250
|
printJson({
|
|
2996
|
-
action: "redeem-notes",
|
|
3251
|
+
action: "wallet redeem-notes",
|
|
2997
3252
|
wallet: wallet.walletName,
|
|
2998
3253
|
workspace: execution.context.workspaceName,
|
|
2999
3254
|
operationDir: execution.operationDir,
|
|
@@ -3020,7 +3275,7 @@ async function handleRedeemNotes({ args, provider }) {
|
|
|
3020
3275
|
});
|
|
3021
3276
|
}
|
|
3022
3277
|
|
|
3023
|
-
async function
|
|
3278
|
+
async function handleWalletGetNotes({ args, provider }) {
|
|
3024
3279
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
3025
3280
|
expect(
|
|
3026
3281
|
typeof wallet.wallet.controller === "string" && wallet.wallet.controller.length > 0,
|
|
@@ -3030,7 +3285,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
3030
3285
|
const { context } = await loadPreferredWalletChannelContext({
|
|
3031
3286
|
walletContext: wallet,
|
|
3032
3287
|
provider,
|
|
3033
|
-
progressAction: "get-
|
|
3288
|
+
progressAction: "wallet get-notes",
|
|
3034
3289
|
});
|
|
3035
3290
|
const signer = restoreWalletSigner(wallet, provider);
|
|
3036
3291
|
const { recoveredDeliveryState } = await recoverWalletReceivedNotes({
|
|
@@ -3038,7 +3293,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
3038
3293
|
context,
|
|
3039
3294
|
provider,
|
|
3040
3295
|
signer,
|
|
3041
|
-
progressAction: "get-
|
|
3296
|
+
progressAction: "wallet get-notes",
|
|
3042
3297
|
});
|
|
3043
3298
|
|
|
3044
3299
|
const unusedTrackedNotes = wallet.wallet.notes.unusedOrder
|
|
@@ -3063,7 +3318,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
3063
3318
|
const spentTotal = spentTrackedNotes.reduce((sum, note) => sum + ethers.toBigInt(note.value), 0n);
|
|
3064
3319
|
|
|
3065
3320
|
printJson({
|
|
3066
|
-
action: "get-
|
|
3321
|
+
action: "wallet get-notes",
|
|
3067
3322
|
wallet: wallet.walletName,
|
|
3068
3323
|
network: walletMetadata.network,
|
|
3069
3324
|
channelName: walletMetadata.channelName,
|
|
@@ -3087,7 +3342,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
3087
3342
|
const preparedContextResult = await loadPreferredWalletChannelContext({
|
|
3088
3343
|
walletContext: wallet,
|
|
3089
3344
|
provider,
|
|
3090
|
-
progressAction: "transfer-notes",
|
|
3345
|
+
progressAction: "wallet transfer-notes",
|
|
3091
3346
|
});
|
|
3092
3347
|
const context = preparedContextResult.context;
|
|
3093
3348
|
const canonicalAssetDecimals = Number(wallet.wallet.canonicalAssetDecimals);
|
|
@@ -3123,7 +3378,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
3123
3378
|
args,
|
|
3124
3379
|
wallet,
|
|
3125
3380
|
provider,
|
|
3126
|
-
operationName: "transfer-notes",
|
|
3381
|
+
operationName: "wallet transfer-notes",
|
|
3127
3382
|
templatePayload,
|
|
3128
3383
|
});
|
|
3129
3384
|
const outputNotes = buildLifecycleTrackedOutputs({
|
|
@@ -3134,7 +3389,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
3134
3389
|
});
|
|
3135
3390
|
|
|
3136
3391
|
printJson({
|
|
3137
|
-
action: "transfer-notes",
|
|
3392
|
+
action: "wallet transfer-notes",
|
|
3138
3393
|
wallet: wallet.walletName,
|
|
3139
3394
|
workspace: execution.context.workspaceName,
|
|
3140
3395
|
operationDir: execution.operationDir,
|
|
@@ -3611,7 +3866,7 @@ function requireUsableWalletNoteReceiveRecoveryIndex({ walletContext, context, l
|
|
|
3611
3866
|
throw new Error([
|
|
3612
3867
|
`Wallet note recovery index is missing or unusable for wallet ${walletContext.walletName}.`,
|
|
3613
3868
|
`Expected noteReceiveLastScannedBlock to be an integer between ${genesisBlockNumber} and ${Number(latestBlock) + 1}.`,
|
|
3614
|
-
"Run recover-
|
|
3869
|
+
"Run wallet recover-workspace --from-genesis to rebuild wallet note state from channel genesis.",
|
|
3615
3870
|
].join(" "));
|
|
3616
3871
|
}
|
|
3617
3872
|
return nextBlock;
|
|
@@ -3851,16 +4106,16 @@ function buildRedeemNotesTemplatePayload({ wallet, inputNotes }) {
|
|
|
3851
4106
|
}
|
|
3852
4107
|
|
|
3853
4108
|
function selectMintNotesMethod(noteCount) {
|
|
3854
|
-
expect(noteCount >= 1, "mint-notes requires at least one output amount.");
|
|
4109
|
+
expect(noteCount >= 1, "wallet mint-notes requires at least one output amount.");
|
|
3855
4110
|
expect(
|
|
3856
4111
|
noteCount <= 2,
|
|
3857
|
-
"mint-notes supports only one or two output amounts with the currently registered DApp.",
|
|
4112
|
+
"wallet mint-notes supports only one or two output amounts with the currently registered DApp.",
|
|
3858
4113
|
);
|
|
3859
4114
|
return `mintNotes${noteCount}`;
|
|
3860
4115
|
}
|
|
3861
4116
|
|
|
3862
4117
|
function selectRedeemNotesMethod(noteCount) {
|
|
3863
|
-
expect(noteCount === 1, "redeem-notes supports exactly one input note with the currently registered DApp.");
|
|
4118
|
+
expect(noteCount === 1, "wallet redeem-notes supports exactly one input note with the currently registered DApp.");
|
|
3864
4119
|
return `redeemNotes${noteCount}`;
|
|
3865
4120
|
}
|
|
3866
4121
|
|
|
@@ -3979,7 +4234,7 @@ function selectTransferNotesMethod(inputCount, outputCount) {
|
|
|
3979
4234
|
if (inputCount === 2 && outputCount === 1) {
|
|
3980
4235
|
return "transferNotes2To1";
|
|
3981
4236
|
}
|
|
3982
|
-
throw new Error("transfer-notes supports only 1->1, 1->2, and 2->1 note transfers.");
|
|
4237
|
+
throw new Error("wallet transfer-notes supports only 1->1, 1->2, and 2->1 note transfers.");
|
|
3983
4238
|
}
|
|
3984
4239
|
|
|
3985
4240
|
function loadWalletUnusedInputNotes(walletContext, noteIds) {
|
|
@@ -4115,7 +4370,7 @@ async function recoverWalletChannelWorkspace({ walletContext, provider, progress
|
|
|
4115
4370
|
const networkName = walletContext.wallet.network ?? networkNameFromChainId(Number(walletContext.wallet.chainId));
|
|
4116
4371
|
const network = resolveCliNetwork(networkName);
|
|
4117
4372
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
4118
|
-
await
|
|
4373
|
+
await syncChannelWorkspace({
|
|
4119
4374
|
workspaceName: walletContext.wallet.channelName,
|
|
4120
4375
|
channelName: walletContext.wallet.channelName,
|
|
4121
4376
|
network,
|
|
@@ -4128,6 +4383,49 @@ async function recoverWalletChannelWorkspace({ walletContext, provider, progress
|
|
|
4128
4383
|
});
|
|
4129
4384
|
}
|
|
4130
4385
|
|
|
4386
|
+
async function refreshPersistedWorkspaceAfterLocalTransaction({
|
|
4387
|
+
context,
|
|
4388
|
+
provider,
|
|
4389
|
+
receipt,
|
|
4390
|
+
progressAction = null,
|
|
4391
|
+
}) {
|
|
4392
|
+
if (!context.persistChannelWorkspace || !context.workspaceDir) {
|
|
4393
|
+
return context;
|
|
4394
|
+
}
|
|
4395
|
+
const network = resolveCliNetwork(context.workspace.network);
|
|
4396
|
+
const bridgeResources = loadBridgeResources({ chainId: Number(context.workspace.chainId) });
|
|
4397
|
+
const refreshed = await syncChannelWorkspace({
|
|
4398
|
+
workspaceName: context.workspaceName,
|
|
4399
|
+
channelName: context.workspace.channelName,
|
|
4400
|
+
network,
|
|
4401
|
+
provider,
|
|
4402
|
+
bridgeResources,
|
|
4403
|
+
persist: true,
|
|
4404
|
+
allowExistingWorkspaceSync: true,
|
|
4405
|
+
useWorkspaceRecoveryIndex: true,
|
|
4406
|
+
minimumToBlock: receipt?.blockNumber ?? null,
|
|
4407
|
+
progressAction,
|
|
4408
|
+
});
|
|
4409
|
+
|
|
4410
|
+
context.workspaceDir = refreshed.workspaceDir;
|
|
4411
|
+
context.workspace = refreshed.workspace;
|
|
4412
|
+
context.currentSnapshot = refreshed.currentSnapshot;
|
|
4413
|
+
context.blockInfo = refreshed.blockInfo;
|
|
4414
|
+
context.contractCodes = refreshed.contractCodes;
|
|
4415
|
+
context.bridgeAbiManifest = bridgeResources.bridgeAbiManifest;
|
|
4416
|
+
context.channelManager = new Contract(
|
|
4417
|
+
refreshed.workspace.channelManager,
|
|
4418
|
+
bridgeResources.bridgeAbiManifest.contracts.channelManager.abi,
|
|
4419
|
+
provider,
|
|
4420
|
+
);
|
|
4421
|
+
context.bridgeTokenVault = new Contract(
|
|
4422
|
+
refreshed.workspace.bridgeTokenVault,
|
|
4423
|
+
bridgeResources.bridgeAbiManifest.contracts.bridgeTokenVault.abi,
|
|
4424
|
+
provider,
|
|
4425
|
+
);
|
|
4426
|
+
return context;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4131
4429
|
function isRecoverableWalletWorkspaceFailure(error) {
|
|
4132
4430
|
const message = String(error?.message ?? error);
|
|
4133
4431
|
return (message.includes("--verify") && message.includes("failed with exit code"))
|
|
@@ -4179,6 +4477,7 @@ async function executeWalletDirectTemplateCommand({
|
|
|
4179
4477
|
txSubmitterAccount,
|
|
4180
4478
|
l2Identity,
|
|
4181
4479
|
context: contextResult.context,
|
|
4480
|
+
provider,
|
|
4182
4481
|
operationName,
|
|
4183
4482
|
functionName: templatePayload.method,
|
|
4184
4483
|
templatePayload,
|
|
@@ -4210,6 +4509,7 @@ async function executeWalletDirectTemplateCommand({
|
|
|
4210
4509
|
txSubmitterAccount,
|
|
4211
4510
|
l2Identity,
|
|
4212
4511
|
context: contextResult.context,
|
|
4512
|
+
provider,
|
|
4213
4513
|
operationName,
|
|
4214
4514
|
functionName: templatePayload.method,
|
|
4215
4515
|
templatePayload,
|
|
@@ -4230,6 +4530,7 @@ async function executeWalletTemplateSend({
|
|
|
4230
4530
|
txSubmitterAccount,
|
|
4231
4531
|
l2Identity,
|
|
4232
4532
|
context,
|
|
4533
|
+
provider,
|
|
4233
4534
|
operationName,
|
|
4234
4535
|
functionName,
|
|
4235
4536
|
templatePayload,
|
|
@@ -4323,7 +4624,12 @@ async function executeWalletTemplateSend({
|
|
|
4323
4624
|
applyNoteLifecycleToWallet(wallet, noteLifecycle, functionName, receipt.hash);
|
|
4324
4625
|
context.currentSnapshot = nextSnapshot;
|
|
4325
4626
|
persistWallet(wallet);
|
|
4326
|
-
|
|
4627
|
+
await refreshPersistedWorkspaceAfterLocalTransaction({
|
|
4628
|
+
context,
|
|
4629
|
+
provider,
|
|
4630
|
+
receipt,
|
|
4631
|
+
progressAction: operationName,
|
|
4632
|
+
});
|
|
4327
4633
|
sealWalletOperationDir(operationDir, wallet.walletSecret);
|
|
4328
4634
|
|
|
4329
4635
|
return {
|
|
@@ -4383,7 +4689,7 @@ async function loadJoinChannelContext({ args, network, provider }) {
|
|
|
4383
4689
|
const channelName = requireArg(args.channelName, "--channel-name");
|
|
4384
4690
|
|
|
4385
4691
|
const bridgeResources = loadBridgeResources({ chainId });
|
|
4386
|
-
const initialized = await
|
|
4692
|
+
const initialized = await syncChannelWorkspace({
|
|
4387
4693
|
workspaceName: channelName,
|
|
4388
4694
|
channelName,
|
|
4389
4695
|
network: { chainId, name: resolvedNetworkName },
|
|
@@ -4477,7 +4783,7 @@ function assertWalletUsesChannelBoundDerivation(wallet, walletName) {
|
|
|
4477
4783
|
wallet.l2DerivationMode === CHANNEL_BOUND_L2_DERIVATION_MODE,
|
|
4478
4784
|
[
|
|
4479
4785
|
`Wallet ${walletName} was not created with the current channel-bound L2 derivation rule.`,
|
|
4480
|
-
"Create a fresh wallet with join
|
|
4786
|
+
"Create a fresh wallet with channel join.",
|
|
4481
4787
|
].join(" "),
|
|
4482
4788
|
);
|
|
4483
4789
|
expect(
|
|
@@ -5710,7 +6016,13 @@ function parseArgs(argv) {
|
|
|
5710
6016
|
}
|
|
5711
6017
|
|
|
5712
6018
|
parsed.command = parsed.positional[0];
|
|
5713
|
-
if (
|
|
6019
|
+
if (
|
|
6020
|
+
(parsed.command === "account"
|
|
6021
|
+
|| parsed.command === "channel"
|
|
6022
|
+
|| parsed.command === "wallet"
|
|
6023
|
+
|| parsed.command === "help")
|
|
6024
|
+
&& parsed.positional[1]
|
|
6025
|
+
) {
|
|
5714
6026
|
parsed.command = `${parsed.command}-${parsed.positional[1]}`;
|
|
5715
6027
|
parsed.positional = [parsed.command];
|
|
5716
6028
|
}
|
|
@@ -5871,7 +6183,7 @@ function resolveWalletDefaultSecret(networkName, walletName) {
|
|
|
5871
6183
|
CLI_ERROR_CODES.MISSING_WALLET_SECRET,
|
|
5872
6184
|
[
|
|
5873
6185
|
`Missing wallet default secret file: ${secretPath}.`,
|
|
5874
|
-
"Run
|
|
6186
|
+
"Run channel join with --wallet-secret-path before wallet commands.",
|
|
5875
6187
|
].join(" "),
|
|
5876
6188
|
);
|
|
5877
6189
|
}
|
|
@@ -5884,12 +6196,17 @@ function prepareJoinWalletSecretForName({
|
|
|
5884
6196
|
walletName,
|
|
5885
6197
|
}) {
|
|
5886
6198
|
const secretPath = walletSecretPath(networkName, walletName);
|
|
6199
|
+
const { channelName } = parseWalletName(walletName);
|
|
6200
|
+
const walletDir = walletPath(walletName, networkName);
|
|
5887
6201
|
expect(
|
|
5888
|
-
!walletConfigExists(
|
|
6202
|
+
!walletConfigExists(walletDir),
|
|
5889
6203
|
[
|
|
5890
6204
|
`Wallet ${walletName} already exists on ${networkName}.`,
|
|
5891
|
-
"
|
|
5892
|
-
"
|
|
6205
|
+
"channel join always creates a new local wallet.",
|
|
6206
|
+
"If this wallet was previously exited on-chain, run",
|
|
6207
|
+
`wallet recover-workspace --channel-name ${channelName} --network ${networkName} --account ${args.account ?? "<ACCOUNT>"}`,
|
|
6208
|
+
"once to remove the stale local wallet, then retry channel join.",
|
|
6209
|
+
"Use normal wallet commands for an existing active local wallet.",
|
|
5893
6210
|
].join(" "),
|
|
5894
6211
|
);
|
|
5895
6212
|
const sourcePath = path.resolve(String(requireArg(args.walletSecretPath, "--wallet-secret-path")));
|
|
@@ -5898,13 +6215,6 @@ function prepareJoinWalletSecretForName({
|
|
|
5898
6215
|
? readSecretFile(sourcePath, "--wallet-secret-path")
|
|
5899
6216
|
: readImportSecretSourceFile(sourcePath, "--wallet-secret-path");
|
|
5900
6217
|
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
6218
|
writeSecretFile(canonicalPath, walletSecret);
|
|
5909
6219
|
}
|
|
5910
6220
|
return walletSecret;
|
|
@@ -6027,6 +6337,148 @@ function listLocalWallets({ networkFilter = null, channelFilter = null } = {}) {
|
|
|
6027
6337
|
);
|
|
6028
6338
|
}
|
|
6029
6339
|
|
|
6340
|
+
function privateStateCliDataRoot() {
|
|
6341
|
+
const root = path.dirname(workspaceRoot);
|
|
6342
|
+
expect(
|
|
6343
|
+
path.dirname(secretRoot) === root,
|
|
6344
|
+
`Unexpected CLI data root layout: ${workspaceRoot} and ${secretRoot} are not siblings.`,
|
|
6345
|
+
);
|
|
6346
|
+
return root;
|
|
6347
|
+
}
|
|
6348
|
+
|
|
6349
|
+
function resolveExportWalletInfo({ networkName, walletName }) {
|
|
6350
|
+
resolveCliNetwork(networkName);
|
|
6351
|
+
const walletDir = walletPath(walletName, networkName);
|
|
6352
|
+
return {
|
|
6353
|
+
wallet: walletName,
|
|
6354
|
+
network: networkName,
|
|
6355
|
+
channelName: parseWalletName(walletName).channelName,
|
|
6356
|
+
walletDir,
|
|
6357
|
+
metadataPath: walletMetadataPath(walletDir),
|
|
6358
|
+
hasMetadata: fs.existsSync(walletMetadataPath(walletDir)),
|
|
6359
|
+
hasEncryptedWallet: walletConfigExists(walletDir),
|
|
6360
|
+
};
|
|
6361
|
+
}
|
|
6362
|
+
|
|
6363
|
+
function normalizeExportWalletInfo(walletInfo) {
|
|
6364
|
+
const wallet = requireWalletName({ wallet: walletInfo.wallet });
|
|
6365
|
+
const network = requireNetworkName({ network: walletInfo.network });
|
|
6366
|
+
const walletDir = walletInfo.walletDir ?? walletPath(wallet, network);
|
|
6367
|
+
const metadataPath = walletMetadataPath(walletDir);
|
|
6368
|
+
const encryptedWalletPath = walletConfigPath(walletDir);
|
|
6369
|
+
const metadata = readJsonIfExists(metadataPath);
|
|
6370
|
+
const channelName = metadata?.channelName ?? walletInfo.channelName ?? parseWalletName(wallet).channelName;
|
|
6371
|
+
const walletSecret = walletSecretPath(network, wallet);
|
|
6372
|
+
|
|
6373
|
+
expect(fs.existsSync(encryptedWalletPath), `Wallet export cannot find encrypted wallet file: ${encryptedWalletPath}.`);
|
|
6374
|
+
expect(fs.existsSync(metadataPath), `Wallet export cannot find wallet metadata file: ${metadataPath}.`);
|
|
6375
|
+
expect(fs.existsSync(walletSecret), `Wallet export cannot find wallet-local secret file: ${walletSecret}.`);
|
|
6376
|
+
expect(
|
|
6377
|
+
metadata.network === network,
|
|
6378
|
+
`Wallet export metadata network ${metadata.network} does not match ${network}.`,
|
|
6379
|
+
);
|
|
6380
|
+
expect(
|
|
6381
|
+
metadata.channelName === channelName,
|
|
6382
|
+
`Wallet export metadata channel ${metadata.channelName} does not match ${channelName}.`,
|
|
6383
|
+
);
|
|
6384
|
+
|
|
6385
|
+
return {
|
|
6386
|
+
network,
|
|
6387
|
+
channelName,
|
|
6388
|
+
wallet,
|
|
6389
|
+
walletDir,
|
|
6390
|
+
walletSecretPath: walletSecret,
|
|
6391
|
+
};
|
|
6392
|
+
}
|
|
6393
|
+
|
|
6394
|
+
function walletExportFilePaths(walletInfo, { includeNotes }) {
|
|
6395
|
+
const walletFiles = [
|
|
6396
|
+
walletInfo.walletSecretPath,
|
|
6397
|
+
walletConfigPath(walletInfo.walletDir),
|
|
6398
|
+
walletMetadataPath(walletInfo.walletDir),
|
|
6399
|
+
];
|
|
6400
|
+
if (!includeNotes) {
|
|
6401
|
+
return walletFiles;
|
|
6402
|
+
}
|
|
6403
|
+
|
|
6404
|
+
const workspaceDir = channelWorkspacePath(walletInfo.network, walletInfo.channelName);
|
|
6405
|
+
const currentDir = channelWorkspaceCurrentPath(workspaceDir);
|
|
6406
|
+
const workspaceFiles = [
|
|
6407
|
+
channelWorkspaceConfigPath(workspaceDir),
|
|
6408
|
+
path.join(currentDir, "state_snapshot.json"),
|
|
6409
|
+
path.join(currentDir, "state_snapshot.normalized.json"),
|
|
6410
|
+
path.join(currentDir, "block_info.json"),
|
|
6411
|
+
path.join(currentDir, "contract_codes.json"),
|
|
6412
|
+
];
|
|
6413
|
+
for (const filePath of workspaceFiles) {
|
|
6414
|
+
expect(
|
|
6415
|
+
fs.existsSync(filePath),
|
|
6416
|
+
[
|
|
6417
|
+
`wallet export --include-notes requires channel workspace cache file: ${filePath}.`,
|
|
6418
|
+
"Run channel recover-workspace first, or export without --include-notes.",
|
|
6419
|
+
].join(" "),
|
|
6420
|
+
);
|
|
6421
|
+
}
|
|
6422
|
+
return [...walletFiles, ...workspaceFiles];
|
|
6423
|
+
}
|
|
6424
|
+
|
|
6425
|
+
function archivePathForLocalCliFile(filePath) {
|
|
6426
|
+
const root = privateStateCliDataRoot();
|
|
6427
|
+
const absolutePath = path.resolve(filePath);
|
|
6428
|
+
expectPathWithinRoot(absolutePath, root, `Cannot export file outside CLI data root: ${absolutePath}.`);
|
|
6429
|
+
return path.relative(root, absolutePath).split(path.sep).join("/");
|
|
6430
|
+
}
|
|
6431
|
+
|
|
6432
|
+
function validateWalletExportManifest(manifest) {
|
|
6433
|
+
expect(manifest?.format === WALLET_EXPORT_FORMAT, "Wallet import ZIP has an unsupported format.");
|
|
6434
|
+
expect(
|
|
6435
|
+
Number(manifest.formatVersion) === WALLET_EXPORT_FORMAT_VERSION,
|
|
6436
|
+
`Wallet import ZIP format version ${manifest?.formatVersion} is not supported.`,
|
|
6437
|
+
);
|
|
6438
|
+
expect(Array.isArray(manifest.files), "Wallet import ZIP manifest is missing files[].");
|
|
6439
|
+
expect(Array.isArray(manifest.wallets), "Wallet import ZIP manifest is missing wallets[].");
|
|
6440
|
+
expect(typeof manifest.includeNotes === "boolean", "Wallet import ZIP manifest is missing includeNotes.");
|
|
6441
|
+
expect(manifest.wallets.length > 0, "Wallet import ZIP manifest does not list any wallets.");
|
|
6442
|
+
const uniqueFiles = new Set(manifest.files);
|
|
6443
|
+
expect(uniqueFiles.size === manifest.files.length, "Wallet import ZIP manifest contains duplicate file paths.");
|
|
6444
|
+
expect(manifest.files.length > 0, "Wallet import ZIP manifest does not list any files.");
|
|
6445
|
+
for (const filePath of manifest.files) {
|
|
6446
|
+
validateWalletArchivePath(filePath);
|
|
6447
|
+
}
|
|
6448
|
+
for (const wallet of manifest.wallets) {
|
|
6449
|
+
requireNetworkName({ network: wallet.network });
|
|
6450
|
+
requireWalletName({ wallet: wallet.wallet });
|
|
6451
|
+
requireArg(wallet.channelName, "wallets[].channelName");
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
|
|
6455
|
+
function validateWalletArchivePath(archivePath) {
|
|
6456
|
+
expect(typeof archivePath === "string" && archivePath.length > 0, "Wallet import ZIP contains an empty path.");
|
|
6457
|
+
expect(!archivePath.includes("\0"), `Wallet import ZIP contains an invalid path: ${archivePath}.`);
|
|
6458
|
+
expect(!archivePath.includes("\\"), `Wallet import ZIP path must use forward slashes: ${archivePath}.`);
|
|
6459
|
+
expect(!path.posix.isAbsolute(archivePath), `Wallet import ZIP path must be relative: ${archivePath}.`);
|
|
6460
|
+
expect(path.posix.normalize(archivePath) === archivePath, `Wallet import ZIP path is not normalized: ${archivePath}.`);
|
|
6461
|
+
expect(
|
|
6462
|
+
archivePath.startsWith("secrets/") || archivePath.startsWith("workspace/"),
|
|
6463
|
+
`Wallet import ZIP path must start with secrets/ or workspace/: ${archivePath}.`,
|
|
6464
|
+
);
|
|
6465
|
+
}
|
|
6466
|
+
|
|
6467
|
+
function expectPathWithinRoot(targetPath, rootPath, message) {
|
|
6468
|
+
const relative = path.relative(path.resolve(rootPath), path.resolve(targetPath));
|
|
6469
|
+
expect(relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative), message);
|
|
6470
|
+
}
|
|
6471
|
+
|
|
6472
|
+
function applyImportedWalletFileMode(archivePath, targetPath) {
|
|
6473
|
+
if (
|
|
6474
|
+
archivePath.startsWith("secrets/")
|
|
6475
|
+
|| archivePath.endsWith("/wallet.json")
|
|
6476
|
+
|| archivePath.endsWith("/wallet.metadata.json")
|
|
6477
|
+
) {
|
|
6478
|
+
protectSecretFile(targetPath, `imported wallet file ${archivePath}`);
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
|
|
6030
6482
|
function channelDataPath(workspaceDir) {
|
|
6031
6483
|
return workspaceChannelDir(workspaceDir);
|
|
6032
6484
|
}
|
|
@@ -6125,14 +6577,18 @@ function assertUninstallArgs(args) {
|
|
|
6125
6577
|
assertAllowedCommandSchema(args, "uninstall");
|
|
6126
6578
|
}
|
|
6127
6579
|
|
|
6580
|
+
function assertHelpCommandsArgs(args) {
|
|
6581
|
+
assertAllowedCommandSchema(args, "help-commands");
|
|
6582
|
+
}
|
|
6583
|
+
|
|
6128
6584
|
function assertUpdateArgs(args) {
|
|
6129
|
-
assertAllowedCommandSchema(args, "update");
|
|
6585
|
+
assertAllowedCommandSchema(args, "help-update");
|
|
6130
6586
|
}
|
|
6131
6587
|
|
|
6132
6588
|
function assertDoctorArgs(args) {
|
|
6133
|
-
assertAllowedCommandSchema(args, "doctor");
|
|
6589
|
+
assertAllowedCommandSchema(args, "help-doctor");
|
|
6134
6590
|
if (args.gpu !== undefined && args.gpu !== true) {
|
|
6135
|
-
throw new Error("doctor option --gpu does not accept a value.");
|
|
6591
|
+
throw new Error("help doctor option --gpu does not accept a value.");
|
|
6136
6592
|
}
|
|
6137
6593
|
}
|
|
6138
6594
|
|
|
@@ -6149,11 +6605,11 @@ function assertGuideArgs(args) {
|
|
|
6149
6605
|
if (args.wallet !== undefined) {
|
|
6150
6606
|
requireWalletName(args);
|
|
6151
6607
|
}
|
|
6152
|
-
assertAllowedCommandSchema(args, "guide");
|
|
6608
|
+
assertAllowedCommandSchema(args, "help-guide");
|
|
6153
6609
|
}
|
|
6154
6610
|
|
|
6155
6611
|
function assertTransactionFeesArgs(args) {
|
|
6156
|
-
assertAllowedCommandSchema(args, "transaction-fees");
|
|
6612
|
+
assertAllowedCommandSchema(args, "help-transaction-fees");
|
|
6157
6613
|
}
|
|
6158
6614
|
|
|
6159
6615
|
function assertAccountImportArgs(args) {
|
|
@@ -6161,7 +6617,7 @@ function assertAccountImportArgs(args) {
|
|
|
6161
6617
|
}
|
|
6162
6618
|
|
|
6163
6619
|
function assertMintNotesArgs(args) {
|
|
6164
|
-
assertAllowedCommandSchema(args, "mint-notes");
|
|
6620
|
+
assertAllowedCommandSchema(args, "wallet-mint-notes");
|
|
6165
6621
|
assertTxSubmitterArg(args);
|
|
6166
6622
|
parseAmountVector(args.amounts, {
|
|
6167
6623
|
allowZeroEntries: true,
|
|
@@ -6170,13 +6626,13 @@ function assertMintNotesArgs(args) {
|
|
|
6170
6626
|
}
|
|
6171
6627
|
|
|
6172
6628
|
function assertRedeemNotesArgs(args) {
|
|
6173
|
-
assertAllowedCommandSchema(args, "redeem-notes");
|
|
6629
|
+
assertAllowedCommandSchema(args, "wallet-redeem-notes");
|
|
6174
6630
|
assertTxSubmitterArg(args);
|
|
6175
6631
|
selectRedeemNotesMethod(parseNoteIdVector(args.noteIds).length);
|
|
6176
6632
|
}
|
|
6177
6633
|
|
|
6178
6634
|
function assertTransferNotesArgs(args) {
|
|
6179
|
-
assertAllowedCommandSchema(args, "transfer-notes");
|
|
6635
|
+
assertAllowedCommandSchema(args, "wallet-transfer-notes");
|
|
6180
6636
|
assertTxSubmitterArg(args);
|
|
6181
6637
|
const noteIds = parseNoteIdVector(args.noteIds);
|
|
6182
6638
|
const recipients = parseRecipientVector(args.recipients);
|
|
@@ -6197,28 +6653,28 @@ function assertTxSubmitterArg(args) {
|
|
|
6197
6653
|
}
|
|
6198
6654
|
}
|
|
6199
6655
|
|
|
6200
|
-
function
|
|
6201
|
-
assertWalletSecretArgs(args, "get-
|
|
6656
|
+
function assertWalletGetNotesArgs(args) {
|
|
6657
|
+
assertWalletSecretArgs(args, "wallet-get-notes");
|
|
6202
6658
|
}
|
|
6203
6659
|
|
|
6204
6660
|
function assertCreateChannelArgs(args) {
|
|
6205
|
-
assertAllowedCommandSchema(args, "create
|
|
6661
|
+
assertAllowedCommandSchema(args, "channel-create");
|
|
6206
6662
|
}
|
|
6207
6663
|
|
|
6208
6664
|
function assertRecoverWorkspaceArgs(args) {
|
|
6209
|
-
assertAllowedCommandSchema(args, "recover-workspace");
|
|
6665
|
+
assertAllowedCommandSchema(args, "channel-recover-workspace");
|
|
6210
6666
|
}
|
|
6211
6667
|
|
|
6212
6668
|
function assertGetChannelArgs(args) {
|
|
6213
|
-
assertAllowedCommandSchema(args, "get-
|
|
6669
|
+
assertAllowedCommandSchema(args, "channel-get-meta");
|
|
6214
6670
|
}
|
|
6215
6671
|
|
|
6216
6672
|
function assertDepositBridgeArgs(args) {
|
|
6217
|
-
assertAllowedCommandSchema(args, "deposit-bridge");
|
|
6673
|
+
assertAllowedCommandSchema(args, "account-deposit-bridge");
|
|
6218
6674
|
}
|
|
6219
6675
|
|
|
6220
|
-
function
|
|
6221
|
-
assertAllowedCommandSchema(args, "get-
|
|
6676
|
+
function assertAccountGetBridgeFundArgs(args) {
|
|
6677
|
+
assertAllowedCommandSchema(args, "account-get-bridge-fund");
|
|
6222
6678
|
}
|
|
6223
6679
|
|
|
6224
6680
|
function assertExplicitSignerCommandArgs(args, commandName) {
|
|
@@ -6226,22 +6682,22 @@ function assertExplicitSignerCommandArgs(args, commandName) {
|
|
|
6226
6682
|
}
|
|
6227
6683
|
|
|
6228
6684
|
function assertRecoverWalletArgs(args) {
|
|
6229
|
-
assertExplicitSignerCommandArgs(args, "recover-
|
|
6685
|
+
assertExplicitSignerCommandArgs(args, "wallet-recover-workspace");
|
|
6230
6686
|
if (args.fromGenesis !== undefined && args.fromGenesis !== true) {
|
|
6231
|
-
throw new Error("recover-
|
|
6687
|
+
throw new Error("wallet recover-workspace option --from-genesis does not accept a value.");
|
|
6232
6688
|
}
|
|
6233
6689
|
}
|
|
6234
6690
|
|
|
6235
6691
|
function assertJoinChannelArgs(args) {
|
|
6236
|
-
assertAllowedCommandSchema(args, "join
|
|
6692
|
+
assertAllowedCommandSchema(args, "channel-join");
|
|
6237
6693
|
}
|
|
6238
6694
|
|
|
6239
|
-
function
|
|
6240
|
-
assertWalletSecretArgs(args, "get-
|
|
6695
|
+
function assertWalletGetMetaArgs(args) {
|
|
6696
|
+
assertWalletSecretArgs(args, "wallet-get-meta");
|
|
6241
6697
|
}
|
|
6242
6698
|
|
|
6243
|
-
function
|
|
6244
|
-
assertAllowedCommandSchema(args, "get-
|
|
6699
|
+
function assertAccountGetL1AddressArgs(args) {
|
|
6700
|
+
assertAllowedCommandSchema(args, "account-get-l1-address");
|
|
6245
6701
|
}
|
|
6246
6702
|
|
|
6247
6703
|
function assertListLocalWalletsArgs(args) {
|
|
@@ -6251,19 +6707,45 @@ function assertListLocalWalletsArgs(args) {
|
|
|
6251
6707
|
if (args.channelName !== undefined) {
|
|
6252
6708
|
requireArg(args.channelName, "--channel-name");
|
|
6253
6709
|
}
|
|
6254
|
-
assertAllowedCommandSchema(args, "list
|
|
6710
|
+
assertAllowedCommandSchema(args, "wallet-list");
|
|
6711
|
+
}
|
|
6712
|
+
|
|
6713
|
+
function assertWalletExportArgs(args) {
|
|
6714
|
+
assertAllowedCommandSchema(args, "wallet-export");
|
|
6715
|
+
assertFlagOption(args, "all", "wallet export");
|
|
6716
|
+
assertFlagOption(args, "includeNotes", "wallet export");
|
|
6717
|
+
requireArg(args.output, "--output");
|
|
6718
|
+
if (args.all === true) {
|
|
6719
|
+
expect(
|
|
6720
|
+
args.network === undefined && args.wallet === undefined,
|
|
6721
|
+
"wallet export --all exports every local mainnet wallet and does not accept --network or --wallet.",
|
|
6722
|
+
);
|
|
6723
|
+
return;
|
|
6724
|
+
}
|
|
6725
|
+
requireNetworkName(args);
|
|
6726
|
+
requireWalletName(args);
|
|
6727
|
+
}
|
|
6728
|
+
|
|
6729
|
+
function assertWalletImportArgs(args) {
|
|
6730
|
+
assertAllowedCommandSchema(args, "wallet-import");
|
|
6731
|
+
}
|
|
6732
|
+
|
|
6733
|
+
function assertFlagOption(args, key, commandName) {
|
|
6734
|
+
if (args[key] !== undefined && args[key] !== true) {
|
|
6735
|
+
throw new Error(`${commandName} option --${toKebabCase(key)} does not accept a value.`);
|
|
6736
|
+
}
|
|
6255
6737
|
}
|
|
6256
6738
|
|
|
6257
6739
|
function assertWithdrawBridgeArgs(args) {
|
|
6258
|
-
assertAllowedCommandSchema(args, "withdraw-bridge");
|
|
6740
|
+
assertAllowedCommandSchema(args, "account-withdraw-bridge");
|
|
6259
6741
|
}
|
|
6260
6742
|
|
|
6261
|
-
function
|
|
6262
|
-
assertWalletSecretArgs(args, "get-
|
|
6743
|
+
function assertWalletGetChannelFundArgs(args) {
|
|
6744
|
+
assertWalletSecretArgs(args, "wallet-get-channel-fund");
|
|
6263
6745
|
}
|
|
6264
6746
|
|
|
6265
6747
|
function assertExitChannelArgs(args) {
|
|
6266
|
-
assertWalletSecretArgs(args, "exit
|
|
6748
|
+
assertWalletSecretArgs(args, "channel-exit");
|
|
6267
6749
|
}
|
|
6268
6750
|
|
|
6269
6751
|
function createWalletOperationDir(walletName, networkName, suffix) {
|
|
@@ -6289,17 +6771,6 @@ function persistWalletMetadata(context) {
|
|
|
6289
6771
|
});
|
|
6290
6772
|
}
|
|
6291
6773
|
|
|
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
6774
|
function printHelp() {
|
|
6304
6775
|
const commandHelp = PRIVATE_STATE_CLI_COMMANDS.map((command) => [
|
|
6305
6776
|
` ${privateStateCliCommandSynopsis(command)}`,
|
|
@@ -6313,10 +6784,10 @@ ${commandHelp}
|
|
|
6313
6784
|
Secret source options:
|
|
6314
6785
|
Use account import --private-key-file once to create a protected local account secret.
|
|
6315
6786
|
L1 signing commands use --account only.
|
|
6316
|
-
A wallet secret source file is arbitrary high-entropy secret text read once by join
|
|
6787
|
+
A wallet secret source file is arbitrary high-entropy secret text read once by channel join.
|
|
6317
6788
|
Create one before joining a channel, for example:
|
|
6318
6789
|
openssl rand -hex 32 > ./wallet-secret.txt
|
|
6319
|
-
private-state-cli
|
|
6790
|
+
private-state-cli channel join --channel-name <NAME> --network <NAME> --account <NAME> --wallet-secret-path ./wallet-secret.txt
|
|
6320
6791
|
Bridge-facing commands accept optional --rpc-url. When provided, it is saved to
|
|
6321
6792
|
~/tokamak-private-channels/secrets/<network>/.env as RPC_URL. When omitted, the CLI reads RPC_URL from that file.
|
|
6322
6793
|
Wallet commands use wallet-local default secret files only.
|
|
@@ -6331,7 +6802,7 @@ Options:
|
|
|
6331
6802
|
Print the command result as JSON. Without --json, commands print human-readable output.
|
|
6332
6803
|
|
|
6333
6804
|
--help
|
|
6334
|
-
Show this help
|
|
6805
|
+
Show this help. Equivalent to help commands.
|
|
6335
6806
|
`);
|
|
6336
6807
|
}
|
|
6337
6808
|
|
|
@@ -7056,18 +7527,18 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
7056
7527
|
|| message.includes("does not match the wallet channel")
|
|
7057
7528
|
|| message.includes("The provided wallet does not belong to the selected channel")
|
|
7058
7529
|
) {
|
|
7059
|
-
hints.push(`private-state-cli list
|
|
7060
|
-
hints.push(`private-state-cli guide --network ${networkName} --wallet ${walletName}`);
|
|
7530
|
+
hints.push(`private-state-cli wallet list --network ${networkName}`);
|
|
7531
|
+
hints.push(`private-state-cli help guide --network ${networkName} --wallet ${walletName}`);
|
|
7061
7532
|
}
|
|
7062
7533
|
|
|
7063
7534
|
if (error?.code === CLI_ERROR_CODES.MISSING_WALLET_SECRET) {
|
|
7064
7535
|
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}`);
|
|
7536
|
+
hints.push(`private-state-cli help guide --network ${networkName} --wallet ${walletName}`);
|
|
7066
7537
|
}
|
|
7067
7538
|
|
|
7068
7539
|
if (error?.code === CLI_ERROR_CODES.WALLET_DECRYPT_FAILED) {
|
|
7069
7540
|
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-
|
|
7541
|
+
hints.push("if the encrypted wallet file is corrupted but the wallet secret and L1 account secret still exist, rerun wallet recover-workspace.");
|
|
7071
7542
|
hints.push("if the wallet secret was lost, the local L2 key cannot be recovered from the encrypted wallet file.");
|
|
7072
7543
|
}
|
|
7073
7544
|
|
|
@@ -7076,7 +7547,7 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
7076
7547
|
|| message.includes("Missing --account.")
|
|
7077
7548
|
) {
|
|
7078
7549
|
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}`);
|
|
7550
|
+
hints.push(`private-state-cli help guide --network ${networkName} --account ${accountName}`);
|
|
7080
7551
|
}
|
|
7081
7552
|
|
|
7082
7553
|
if (
|
|
@@ -7084,31 +7555,31 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
7084
7555
|
|| message.includes("DApp deployment artifact")
|
|
7085
7556
|
) {
|
|
7086
7557
|
hints.push("private-state-cli install");
|
|
7087
|
-
hints.push("private-state-cli doctor --json");
|
|
7558
|
+
hints.push("private-state-cli help doctor --json");
|
|
7088
7559
|
}
|
|
7089
7560
|
|
|
7090
7561
|
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}`);
|
|
7562
|
+
hints.push(`private-state-cli channel join --channel-name ${channelName} --network ${networkName} --account ${accountName} --wallet-secret-path <PATH>`);
|
|
7563
|
+
hints.push(`private-state-cli help guide --network ${networkName} --channel-name ${channelName} --account ${accountName}`);
|
|
7093
7564
|
}
|
|
7094
7565
|
|
|
7095
7566
|
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}`);
|
|
7567
|
+
hints.push(`private-state-cli channel recover-workspace --channel-name ${channelName} --network ${networkName}`);
|
|
7568
|
+
hints.push(`private-state-cli help guide --network ${networkName} --channel-name ${channelName}`);
|
|
7098
7569
|
}
|
|
7099
7570
|
|
|
7100
7571
|
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-
|
|
7572
|
+
hints.push(`private-state-cli channel recover-workspace --channel-name ${channelName} --network ${networkName} --from-genesis`);
|
|
7573
|
+
hints.push(`private-state-cli wallet recover-workspace --channel-name ${channelName} --network ${networkName} --account ${accountName} --from-genesis`);
|
|
7103
7574
|
}
|
|
7104
7575
|
|
|
7105
7576
|
if (message.includes("Wallet note recovery index is missing or unusable")) {
|
|
7106
|
-
hints.push(`private-state-cli recover-
|
|
7577
|
+
hints.push(`private-state-cli wallet recover-workspace --channel-name ${channelName} --network ${networkName} --account ${accountName} --from-genesis`);
|
|
7107
7578
|
}
|
|
7108
7579
|
|
|
7109
7580
|
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>`);
|
|
7581
|
+
hints.push(`private-state-cli wallet list --network ${networkName}`);
|
|
7582
|
+
hints.push(`private-state-cli help guide --network ${networkName} --channel-name <CHANNEL> --wallet <WALLET>`);
|
|
7112
7583
|
}
|
|
7113
7584
|
|
|
7114
7585
|
return [...new Set(hints)];
|