@tokamak-private-dapps/private-state-cli 1.0.1 → 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 +61 -2
- package/README.md +118 -54
- package/assets/tx-fees.json +16 -16
- package/cli-assistant.html +35 -35
- package/lib/private-state-cli-command-registry.mjs +141 -36
- package/lib/private-state-runtime-management.mjs +4 -2
- package/package.json +2 -1
- package/private-state-bridge-cli.mjs +948 -259
|
@@ -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,
|
|
@@ -68,6 +69,7 @@ import {
|
|
|
68
69
|
installPrivateStateCliArtifacts,
|
|
69
70
|
installTokamakCliRuntimeForPrivateState,
|
|
70
71
|
inspectGroth16Runtime,
|
|
72
|
+
parseJsonReport,
|
|
71
73
|
printDoctorHumanReport,
|
|
72
74
|
privateStateCliArtifactPaths,
|
|
73
75
|
readTokamakCliPackageReport,
|
|
@@ -77,6 +79,7 @@ import {
|
|
|
77
79
|
resolveArtifactCacheBaseRoot,
|
|
78
80
|
resolvePrivateStateInstallRuntimeVersions,
|
|
79
81
|
resolveTokamakCliResourceDirForRuntimeRoot,
|
|
82
|
+
stripAnsi,
|
|
80
83
|
writePrivateStateCliInstallManifest,
|
|
81
84
|
} from "./lib/private-state-runtime-management.mjs";
|
|
82
85
|
import {
|
|
@@ -127,8 +130,11 @@ const secretRoot = path.resolve(os.homedir(), "tokamak-private-channels", "secre
|
|
|
127
130
|
const flatDeploymentArtifactPathsByChainId = new Map();
|
|
128
131
|
const PRIVATE_STATE_UNINSTALL_CONFIRMATION =
|
|
129
132
|
"I understand that the wallet secrets deleted due to this decision cannot be recovered";
|
|
133
|
+
const PRIVATE_STATE_CLI_PACKAGE_NAME = privateStateCliPackageJson.name;
|
|
130
134
|
const GROTH16_PACKAGE_NAME = "@tokamak-private-dapps/groth16";
|
|
131
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;
|
|
132
138
|
let jsonOutputRequested = false;
|
|
133
139
|
let activeCliArgs = {};
|
|
134
140
|
|
|
@@ -326,6 +332,12 @@ async function main() {
|
|
|
326
332
|
return;
|
|
327
333
|
}
|
|
328
334
|
|
|
335
|
+
if (args.command === "help-commands") {
|
|
336
|
+
assertHelpCommandsArgs(args);
|
|
337
|
+
printHelp();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
329
341
|
if (args.command === "install") {
|
|
330
342
|
assertInstallZkEvmArgs(args);
|
|
331
343
|
await handleInstallZkEvm({ args });
|
|
@@ -338,28 +350,34 @@ async function main() {
|
|
|
338
350
|
return;
|
|
339
351
|
}
|
|
340
352
|
|
|
341
|
-
if (args.command === "
|
|
353
|
+
if (args.command === "help-update") {
|
|
354
|
+
assertUpdateArgs(args);
|
|
355
|
+
await handleUpdate();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (args.command === "help-doctor") {
|
|
342
360
|
assertDoctorArgs(args);
|
|
343
361
|
await handleDoctor({ args });
|
|
344
362
|
return;
|
|
345
363
|
}
|
|
346
364
|
|
|
347
|
-
if (args.command === "guide") {
|
|
365
|
+
if (args.command === "help-guide") {
|
|
348
366
|
assertGuideArgs(args);
|
|
349
367
|
await handleGuide({ args });
|
|
350
368
|
return;
|
|
351
369
|
}
|
|
352
370
|
|
|
353
|
-
if (args.command === "transaction-fees") {
|
|
371
|
+
if (args.command === "help-transaction-fees") {
|
|
354
372
|
assertTransactionFeesArgs(args);
|
|
355
373
|
const { network, provider, rpcUrl } = loadExplicitCommandRuntime(args);
|
|
356
374
|
await handleTransactionFees({ network, provider, rpcUrl });
|
|
357
375
|
return;
|
|
358
376
|
}
|
|
359
377
|
|
|
360
|
-
if (args.command === "get-
|
|
361
|
-
|
|
362
|
-
|
|
378
|
+
if (args.command === "account-get-l1-address") {
|
|
379
|
+
assertAccountGetL1AddressArgs(args);
|
|
380
|
+
handleAccountGetL1Address({ args });
|
|
363
381
|
return;
|
|
364
382
|
}
|
|
365
383
|
|
|
@@ -369,46 +387,58 @@ async function main() {
|
|
|
369
387
|
return;
|
|
370
388
|
}
|
|
371
389
|
|
|
372
|
-
if (args.command === "list
|
|
390
|
+
if (args.command === "wallet-list") {
|
|
373
391
|
assertListLocalWalletsArgs(args);
|
|
374
392
|
handleListLocalWallets({ args });
|
|
375
393
|
return;
|
|
376
394
|
}
|
|
377
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
|
+
|
|
378
408
|
const walletCommandHandlers = {
|
|
379
|
-
"mint-notes": {
|
|
409
|
+
"wallet-mint-notes": {
|
|
380
410
|
assert: assertMintNotesArgs,
|
|
381
411
|
run: ({ provider }) => handleMintNotes({ args, provider }),
|
|
382
412
|
},
|
|
383
|
-
"redeem-notes": {
|
|
413
|
+
"wallet-redeem-notes": {
|
|
384
414
|
assert: assertRedeemNotesArgs,
|
|
385
415
|
run: ({ provider }) => handleRedeemNotes({ args, provider }),
|
|
386
416
|
},
|
|
387
|
-
"get-
|
|
388
|
-
assert:
|
|
389
|
-
run: ({ provider }) =>
|
|
417
|
+
"wallet-get-notes": {
|
|
418
|
+
assert: assertWalletGetNotesArgs,
|
|
419
|
+
run: ({ provider }) => handleWalletGetNotes({ args, provider }),
|
|
390
420
|
},
|
|
391
|
-
"transfer-notes": {
|
|
421
|
+
"wallet-transfer-notes": {
|
|
392
422
|
assert: assertTransferNotesArgs,
|
|
393
423
|
run: ({ provider }) => handleTransferNotes({ args, provider }),
|
|
394
424
|
},
|
|
395
|
-
"deposit-channel": {
|
|
396
|
-
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "deposit-channel"),
|
|
425
|
+
"wallet-deposit-channel": {
|
|
426
|
+
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "wallet-deposit-channel"),
|
|
397
427
|
run: ({ provider }) => handleGrothVaultMove({ args, provider, direction: "deposit" }),
|
|
398
428
|
},
|
|
399
|
-
"withdraw-channel": {
|
|
400
|
-
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "withdraw-channel"),
|
|
429
|
+
"wallet-withdraw-channel": {
|
|
430
|
+
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "wallet-withdraw-channel"),
|
|
401
431
|
run: ({ provider }) => handleGrothVaultMove({ args, provider, direction: "withdraw" }),
|
|
402
432
|
},
|
|
403
|
-
"get-
|
|
404
|
-
assert:
|
|
405
|
-
run: ({ provider }) =>
|
|
433
|
+
"wallet-get-meta": {
|
|
434
|
+
assert: assertWalletGetMetaArgs,
|
|
435
|
+
run: ({ provider }) => handleWalletGetMeta({ args, provider }),
|
|
406
436
|
},
|
|
407
|
-
"get-
|
|
408
|
-
assert:
|
|
409
|
-
run: ({ provider }) =>
|
|
437
|
+
"wallet-get-channel-fund": {
|
|
438
|
+
assert: assertWalletGetChannelFundArgs,
|
|
439
|
+
run: ({ provider }) => handleWalletGetChannelFund({ args, provider }),
|
|
410
440
|
},
|
|
411
|
-
"exit
|
|
441
|
+
"channel-exit": {
|
|
412
442
|
assert: assertExitChannelArgs,
|
|
413
443
|
run: ({ provider }) => handleExitChannel({ args, provider }),
|
|
414
444
|
},
|
|
@@ -422,56 +452,56 @@ async function main() {
|
|
|
422
452
|
}
|
|
423
453
|
|
|
424
454
|
switch (args.command) {
|
|
425
|
-
case "create
|
|
455
|
+
case "channel-create": {
|
|
426
456
|
assertCreateChannelArgs(args);
|
|
427
457
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
428
458
|
await prepareDeploymentArtifacts(network.chainId);
|
|
429
459
|
await handleChannelCreate({ args, network, provider });
|
|
430
460
|
return;
|
|
431
461
|
}
|
|
432
|
-
case "recover-workspace": {
|
|
462
|
+
case "channel-recover-workspace": {
|
|
433
463
|
assertRecoverWorkspaceArgs(args);
|
|
434
464
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
435
465
|
await prepareDeploymentArtifacts(network.chainId);
|
|
436
466
|
await handleWorkspaceInit({ args, network, provider });
|
|
437
467
|
return;
|
|
438
468
|
}
|
|
439
|
-
case "get-
|
|
469
|
+
case "channel-get-meta": {
|
|
440
470
|
assertGetChannelArgs(args);
|
|
441
471
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
442
472
|
await prepareDeploymentArtifacts(network.chainId);
|
|
443
473
|
await handleGetChannel({ args, network, provider });
|
|
444
474
|
return;
|
|
445
475
|
}
|
|
446
|
-
case "deposit-bridge": {
|
|
476
|
+
case "account-deposit-bridge": {
|
|
447
477
|
assertDepositBridgeArgs(args);
|
|
448
478
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
449
479
|
await prepareDeploymentArtifacts(network.chainId);
|
|
450
480
|
await handleDepositBridge({ args, network, provider });
|
|
451
481
|
return;
|
|
452
482
|
}
|
|
453
|
-
case "withdraw-bridge": {
|
|
483
|
+
case "account-withdraw-bridge": {
|
|
454
484
|
assertWithdrawBridgeArgs(args);
|
|
455
485
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
456
486
|
await prepareDeploymentArtifacts(network.chainId);
|
|
457
487
|
await handleWithdrawBridge({ args, network, provider });
|
|
458
488
|
return;
|
|
459
489
|
}
|
|
460
|
-
case "get-
|
|
461
|
-
|
|
490
|
+
case "account-get-bridge-fund": {
|
|
491
|
+
assertAccountGetBridgeFundArgs(args);
|
|
462
492
|
const { network, provider } = loadExplicitCommandRuntime(args);
|
|
463
493
|
await prepareDeploymentArtifacts(network.chainId);
|
|
464
|
-
await
|
|
494
|
+
await handleAccountGetBridgeFund({ args, provider });
|
|
465
495
|
return;
|
|
466
496
|
}
|
|
467
|
-
case "recover-
|
|
497
|
+
case "wallet-recover-workspace": {
|
|
468
498
|
assertRecoverWalletArgs(args);
|
|
469
499
|
const { network, provider, rpcUrl } = loadExplicitCommandRuntime(args);
|
|
470
500
|
await prepareDeploymentArtifacts(network.chainId);
|
|
471
501
|
await handleRecoverWallet({ args, network, provider, rpcUrl });
|
|
472
502
|
return;
|
|
473
503
|
}
|
|
474
|
-
case "join
|
|
504
|
+
case "channel-join": {
|
|
475
505
|
assertJoinChannelArgs(args);
|
|
476
506
|
const { network, provider, rpcUrl } = loadExplicitCommandRuntime(args);
|
|
477
507
|
await prepareDeploymentArtifacts(network.chainId);
|
|
@@ -509,7 +539,7 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
509
539
|
const policySnapshot = dapp.policySnapshot;
|
|
510
540
|
|
|
511
541
|
printImmutableChannelPolicyWarning({
|
|
512
|
-
action: "create
|
|
542
|
+
action: "channel create",
|
|
513
543
|
channelName,
|
|
514
544
|
channelId,
|
|
515
545
|
policySnapshot,
|
|
@@ -518,17 +548,20 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
518
548
|
await waitForReceipt(await bridgeCore.createChannel(channelId, dappId, joinToll, dapp.metadataDigest));
|
|
519
549
|
const channelInfo = await bridgeCore.getChannel(channelId);
|
|
520
550
|
|
|
521
|
-
const workspaceResult = await
|
|
551
|
+
const workspaceResult = await syncChannelWorkspace({
|
|
522
552
|
workspaceName,
|
|
523
553
|
channelName,
|
|
524
554
|
network,
|
|
525
555
|
provider,
|
|
526
556
|
bridgeResources,
|
|
527
557
|
persist: true,
|
|
558
|
+
fromGenesis: true,
|
|
559
|
+
minimumToBlock: receipt.blockNumber,
|
|
560
|
+
progressAction: "channel create",
|
|
528
561
|
});
|
|
529
562
|
|
|
530
563
|
printJson({
|
|
531
|
-
action: "create
|
|
564
|
+
action: "channel create",
|
|
532
565
|
channelName,
|
|
533
566
|
channelId: channelId.toString(),
|
|
534
567
|
dappId,
|
|
@@ -626,7 +659,7 @@ async function handleWorkspaceInit({ args, network, provider }) {
|
|
|
626
659
|
const workspaceName = channelName;
|
|
627
660
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
628
661
|
|
|
629
|
-
const { workspaceDir, workspace, currentSnapshot } = await
|
|
662
|
+
const { workspaceDir, workspace, currentSnapshot } = await syncChannelWorkspace({
|
|
630
663
|
workspaceName,
|
|
631
664
|
channelName,
|
|
632
665
|
network,
|
|
@@ -636,11 +669,11 @@ async function handleWorkspaceInit({ args, network, provider }) {
|
|
|
636
669
|
allowExistingWorkspaceSync: true,
|
|
637
670
|
useWorkspaceRecoveryIndex: true,
|
|
638
671
|
fromGenesis: args.fromGenesis === true,
|
|
639
|
-
progressAction: "recover-workspace",
|
|
672
|
+
progressAction: "channel recover-workspace",
|
|
640
673
|
});
|
|
641
674
|
|
|
642
675
|
printJson({
|
|
643
|
-
action: "recover-workspace",
|
|
676
|
+
action: "channel recover-workspace",
|
|
644
677
|
workspace: workspaceName,
|
|
645
678
|
workspaceDir,
|
|
646
679
|
channelName,
|
|
@@ -672,7 +705,7 @@ async function handleGetChannel({ args, network, provider }) {
|
|
|
672
705
|
} catch (error) {
|
|
673
706
|
if (isContractError(error, bridgeCore.interface, "UnknownChannel")) {
|
|
674
707
|
printJson({
|
|
675
|
-
action: "get-
|
|
708
|
+
action: "channel get-meta",
|
|
676
709
|
channelName,
|
|
677
710
|
channelId: channelId.toString(),
|
|
678
711
|
exists: false,
|
|
@@ -711,7 +744,7 @@ async function handleGetChannel({ args, network, provider }) {
|
|
|
711
744
|
]);
|
|
712
745
|
|
|
713
746
|
printJson({
|
|
714
|
-
action: "get-
|
|
747
|
+
action: "channel get-meta",
|
|
715
748
|
channelName,
|
|
716
749
|
channelId: channelId.toString(),
|
|
717
750
|
exists: true,
|
|
@@ -734,7 +767,7 @@ async function handleGetChannel({ args, network, provider }) {
|
|
|
734
767
|
});
|
|
735
768
|
}
|
|
736
769
|
|
|
737
|
-
async function
|
|
770
|
+
async function syncChannelWorkspace({
|
|
738
771
|
workspaceName,
|
|
739
772
|
channelName,
|
|
740
773
|
network,
|
|
@@ -744,6 +777,7 @@ async function initializeChannelWorkspace({
|
|
|
744
777
|
allowExistingWorkspaceSync = false,
|
|
745
778
|
useWorkspaceRecoveryIndex = false,
|
|
746
779
|
fromGenesis = false,
|
|
780
|
+
minimumToBlock = null,
|
|
747
781
|
progressAction = null,
|
|
748
782
|
}) {
|
|
749
783
|
const workspaceDir = channelWorkspacePath(networkNameFromChainId(network.chainId), workspaceName);
|
|
@@ -777,7 +811,10 @@ async function initializeChannelWorkspace({
|
|
|
777
811
|
const canonicalAssetDecimals = await fetchTokenDecimals(provider, canonicalAsset);
|
|
778
812
|
const currentRootVectorHash = normalizeBytes32Hex(await channelManager.currentRootVectorHash());
|
|
779
813
|
const genesisBlockNumber = Number(await channelManager.genesisBlockNumber());
|
|
780
|
-
const
|
|
814
|
+
const observedLatestBlock = await provider.getBlockNumber();
|
|
815
|
+
const latestBlock = minimumToBlock === null
|
|
816
|
+
? observedLatestBlock
|
|
817
|
+
: Math.max(observedLatestBlock, Number(minimumToBlock));
|
|
781
818
|
const managedStorageAddresses = normalizedAddressVector(await channelManager.getManagedStorageAddresses());
|
|
782
819
|
const policySnapshot = await readChannelPolicySnapshot({
|
|
783
820
|
channelManager,
|
|
@@ -822,6 +859,13 @@ async function initializeChannelWorkspace({
|
|
|
822
859
|
managedStorageAddresses,
|
|
823
860
|
})
|
|
824
861
|
: null;
|
|
862
|
+
if (useWorkspaceRecoveryIndex && !fromGenesis && !localSnapshotReusable && !recoveryIndex) {
|
|
863
|
+
throw new Error([
|
|
864
|
+
`Workspace recovery index is missing or unusable for channel ${channelName} on ${networkNameFromChainId(network.chainId)}.`,
|
|
865
|
+
"The CLI will not fall back to replaying channel logs from genesis unless explicitly requested.",
|
|
866
|
+
"Run channel recover-workspace --from-genesis or wallet recover-workspace --from-genesis to rebuild from channel genesis.",
|
|
867
|
+
].join(" "));
|
|
868
|
+
}
|
|
825
869
|
const reconstruction = localSnapshotReusable
|
|
826
870
|
? {
|
|
827
871
|
currentSnapshot: existingArtifacts.stateSnapshot,
|
|
@@ -910,7 +954,7 @@ async function initializeChannelWorkspace({
|
|
|
910
954
|
async function handleDepositBridge({ args, network, provider }) {
|
|
911
955
|
if (args.wallet !== undefined) {
|
|
912
956
|
throw new Error(
|
|
913
|
-
"--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.",
|
|
914
958
|
);
|
|
915
959
|
}
|
|
916
960
|
const signer = requireL1Signer(args, provider);
|
|
@@ -934,7 +978,7 @@ async function handleDepositBridge({ args, network, provider }) {
|
|
|
934
978
|
const availableBalance = await bridgeTokenVault.availableBalanceOf(signer.address);
|
|
935
979
|
|
|
936
980
|
printJson({
|
|
937
|
-
action: "deposit-bridge",
|
|
981
|
+
action: "account deposit-bridge",
|
|
938
982
|
amountInput,
|
|
939
983
|
amountBaseUnits: amount.toString(),
|
|
940
984
|
l1Address: signer.address,
|
|
@@ -950,7 +994,7 @@ async function handleDepositBridge({ args, network, provider }) {
|
|
|
950
994
|
});
|
|
951
995
|
}
|
|
952
996
|
|
|
953
|
-
async function
|
|
997
|
+
async function handleAccountGetBridgeFund({ args, provider }) {
|
|
954
998
|
const signer = requireL1Signer(args, provider);
|
|
955
999
|
const chainId = Number((await provider.getNetwork()).chainId);
|
|
956
1000
|
const bridgeVaultContext = await loadBridgeVaultContext({ provider, chainId });
|
|
@@ -962,7 +1006,7 @@ async function handleGetMyBridgeFund({ args, provider }) {
|
|
|
962
1006
|
const availableBalance = await bridgeTokenVault.availableBalanceOf(signer.address);
|
|
963
1007
|
|
|
964
1008
|
printJson({
|
|
965
|
-
action: "get-
|
|
1009
|
+
action: "account get-bridge-fund",
|
|
966
1010
|
l1Address: signer.address,
|
|
967
1011
|
bridgeTokenVault: bridgeVaultContext.bridgeTokenVaultAddress,
|
|
968
1012
|
canonicalAsset: bridgeVaultContext.canonicalAsset,
|
|
@@ -984,7 +1028,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
984
1028
|
walletName,
|
|
985
1029
|
});
|
|
986
1030
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
987
|
-
const initialized = await
|
|
1031
|
+
const initialized = await syncChannelWorkspace({
|
|
988
1032
|
workspaceName: channelName,
|
|
989
1033
|
channelName,
|
|
990
1034
|
network,
|
|
@@ -993,7 +1037,8 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
993
1037
|
persist: true,
|
|
994
1038
|
allowExistingWorkspaceSync: true,
|
|
995
1039
|
useWorkspaceRecoveryIndex: true,
|
|
996
|
-
|
|
1040
|
+
fromGenesis: args.fromGenesis === true,
|
|
1041
|
+
progressAction: "wallet recover-workspace",
|
|
997
1042
|
});
|
|
998
1043
|
const context = {
|
|
999
1044
|
workspaceName: channelName,
|
|
@@ -1031,13 +1076,41 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1031
1076
|
const leafIndex = deriveChannelTokenVaultLeafIndex(storageKey);
|
|
1032
1077
|
const registration = await context.channelManager.getChannelTokenVaultRegistration(signer.address);
|
|
1033
1078
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
+
}
|
|
1041
1114
|
expect(
|
|
1042
1115
|
ethers.toBigInt(getAddress(registration.l2Address)) === ethers.toBigInt(getAddress(l2Identity.l2Address)),
|
|
1043
1116
|
"The existing channel registration L2 address does not match the derived L2 address.",
|
|
@@ -1081,10 +1154,10 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1081
1154
|
provider,
|
|
1082
1155
|
signer,
|
|
1083
1156
|
noteReceiveKeyMaterial,
|
|
1084
|
-
progressAction: "recover-
|
|
1157
|
+
progressAction: "wallet recover-workspace",
|
|
1085
1158
|
});
|
|
1086
1159
|
printJson({
|
|
1087
|
-
action: "recover-
|
|
1160
|
+
action: "wallet recover-workspace",
|
|
1088
1161
|
status: "already-recovered",
|
|
1089
1162
|
wallet: walletName,
|
|
1090
1163
|
walletDir: existingWallet.walletDir,
|
|
@@ -1106,7 +1179,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1106
1179
|
return;
|
|
1107
1180
|
}
|
|
1108
1181
|
|
|
1109
|
-
|
|
1182
|
+
fs.rmSync(walletPath(walletName, context.workspace.network), { recursive: true, force: true });
|
|
1110
1183
|
|
|
1111
1184
|
const walletContext = ensureWallet({
|
|
1112
1185
|
channelContext: context,
|
|
@@ -1128,11 +1201,11 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
1128
1201
|
provider,
|
|
1129
1202
|
signer,
|
|
1130
1203
|
noteReceiveKeyMaterial,
|
|
1131
|
-
progressAction: "recover-
|
|
1204
|
+
progressAction: "wallet recover-workspace",
|
|
1132
1205
|
});
|
|
1133
1206
|
|
|
1134
1207
|
printJson({
|
|
1135
|
-
action: "recover-
|
|
1208
|
+
action: "wallet recover-workspace",
|
|
1136
1209
|
status: "recovered",
|
|
1137
1210
|
wallet: walletName,
|
|
1138
1211
|
walletDir: walletContext.walletDir,
|
|
@@ -1282,8 +1355,30 @@ function assertExistingRecoverableWallet({
|
|
|
1282
1355
|
);
|
|
1283
1356
|
}
|
|
1284
1357
|
|
|
1285
|
-
function
|
|
1286
|
-
|
|
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>`;
|
|
1287
1382
|
}
|
|
1288
1383
|
|
|
1289
1384
|
async function handleInstallZkEvm({ args }) {
|
|
@@ -1423,31 +1518,44 @@ function assertSafeManagedRoot(rootPath, label) {
|
|
|
1423
1518
|
}
|
|
1424
1519
|
}
|
|
1425
1520
|
|
|
1426
|
-
function
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1521
|
+
function npmCommandName() {
|
|
1522
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
function inspectGlobalPrivateStateCliPackage() {
|
|
1526
|
+
const list = runCaptured(npmCommandName(), ["ls", "-g", PRIVATE_STATE_CLI_PACKAGE_NAME, "--depth=0", "--json"]);
|
|
1527
|
+
const report = parseJsonReport(list.stdout);
|
|
1528
|
+
const packageReport = report?.dependencies?.[PRIVATE_STATE_CLI_PACKAGE_NAME] ?? null;
|
|
1529
|
+
const installed = Boolean(packageReport);
|
|
1530
|
+
if (!installed) {
|
|
1431
1531
|
const missing = /empty|missing|not found|not installed/iu.test(`${list.stdout}\n${list.stderr}`);
|
|
1432
1532
|
return {
|
|
1433
|
-
attempted: false,
|
|
1434
1533
|
installed: false,
|
|
1435
|
-
|
|
1534
|
+
version: null,
|
|
1436
1535
|
status: list.status,
|
|
1536
|
+
reason: missing || report ? "global package is not installed" : "unable to inspect global npm package",
|
|
1437
1537
|
stderr: stripAnsi(list.stderr).trim(),
|
|
1438
1538
|
};
|
|
1439
1539
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1540
|
+
return {
|
|
1541
|
+
installed: true,
|
|
1542
|
+
version: packageReport.version ?? null,
|
|
1543
|
+
status: list.status,
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function uninstallGlobalPrivateStateCliPackage() {
|
|
1548
|
+
const inspection = inspectGlobalPrivateStateCliPackage();
|
|
1549
|
+
if (!inspection.installed) {
|
|
1443
1550
|
return {
|
|
1444
1551
|
attempted: false,
|
|
1445
1552
|
installed: false,
|
|
1446
|
-
reason:
|
|
1447
|
-
status:
|
|
1553
|
+
reason: inspection.reason,
|
|
1554
|
+
status: inspection.status,
|
|
1555
|
+
stderr: inspection.stderr,
|
|
1448
1556
|
};
|
|
1449
1557
|
}
|
|
1450
|
-
const uninstall = runCaptured(
|
|
1558
|
+
const uninstall = runCaptured(npmCommandName(), ["uninstall", "-g", PRIVATE_STATE_CLI_PACKAGE_NAME]);
|
|
1451
1559
|
return {
|
|
1452
1560
|
attempted: true,
|
|
1453
1561
|
installed: true,
|
|
@@ -1458,6 +1566,106 @@ function uninstallGlobalPrivateStateCliPackage() {
|
|
|
1458
1566
|
};
|
|
1459
1567
|
}
|
|
1460
1568
|
|
|
1569
|
+
async function handleUpdate() {
|
|
1570
|
+
const currentVersion = privateStateCliPackageJson.version;
|
|
1571
|
+
const latestVersion = await fetchLatestPrivateStateCliVersion();
|
|
1572
|
+
const registryComparison = compareSemver(currentVersion, latestVersion);
|
|
1573
|
+
const globalPackage = inspectGlobalPrivateStateCliPackage();
|
|
1574
|
+
const runningFromRepositoryCheckout = isRepositoryCliPackageRoot(privateStateCliPackageRoot);
|
|
1575
|
+
const updateAvailable = registryComparison < 0;
|
|
1576
|
+
|
|
1577
|
+
const result = {
|
|
1578
|
+
action: "update",
|
|
1579
|
+
packageName: PRIVATE_STATE_CLI_PACKAGE_NAME,
|
|
1580
|
+
currentVersion,
|
|
1581
|
+
latestVersion,
|
|
1582
|
+
updateAvailable,
|
|
1583
|
+
registryState: registryComparison > 0 ? "local-version-ahead-of-registry" : "normal",
|
|
1584
|
+
runningFromRepositoryCheckout,
|
|
1585
|
+
globalPackage,
|
|
1586
|
+
attempted: false,
|
|
1587
|
+
updated: false,
|
|
1588
|
+
command: `npm install -g ${PRIVATE_STATE_CLI_PACKAGE_NAME}@latest`,
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
if (!updateAvailable) {
|
|
1592
|
+
printJson(result);
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
if (runningFromRepositoryCheckout) {
|
|
1597
|
+
printJson({
|
|
1598
|
+
...result,
|
|
1599
|
+
reason: "running from a repository checkout; update the checkout with git/npm instead of mutating source files",
|
|
1600
|
+
});
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
if (!globalPackage.installed) {
|
|
1605
|
+
printJson({
|
|
1606
|
+
...result,
|
|
1607
|
+
reason: "global npm package is not installed; install or update the CLI with the printed command",
|
|
1608
|
+
});
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const install = runCaptured(npmCommandName(), ["install", "-g", `${PRIVATE_STATE_CLI_PACKAGE_NAME}@latest`]);
|
|
1613
|
+
if (install.status !== 0) {
|
|
1614
|
+
throw new Error([
|
|
1615
|
+
`Unable to update ${PRIVATE_STATE_CLI_PACKAGE_NAME} to ${latestVersion}.`,
|
|
1616
|
+
stripAnsi(install.stderr || install.stdout).trim(),
|
|
1617
|
+
].filter(Boolean).join(" "));
|
|
1618
|
+
}
|
|
1619
|
+
printJson({
|
|
1620
|
+
...result,
|
|
1621
|
+
attempted: true,
|
|
1622
|
+
updated: true,
|
|
1623
|
+
installedVersion: latestVersion,
|
|
1624
|
+
stdout: stripAnsi(install.stdout).trim(),
|
|
1625
|
+
stderr: stripAnsi(install.stderr).trim(),
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
async function fetchLatestPrivateStateCliVersion() {
|
|
1630
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(PRIVATE_STATE_CLI_PACKAGE_NAME)}/latest`;
|
|
1631
|
+
const response = await fetch(url, {
|
|
1632
|
+
redirect: "follow",
|
|
1633
|
+
signal: typeof globalThis.AbortSignal?.timeout === "function" ? globalThis.AbortSignal.timeout(10_000) : undefined,
|
|
1634
|
+
});
|
|
1635
|
+
if (!response.ok) {
|
|
1636
|
+
throw new Error(`Unable to fetch ${PRIVATE_STATE_CLI_PACKAGE_NAME} latest version from npm registry: HTTP ${response.status}.`);
|
|
1637
|
+
}
|
|
1638
|
+
const metadata = await response.json();
|
|
1639
|
+
const version = metadata?.version;
|
|
1640
|
+
if (typeof version !== "string" || version.trim() === "") {
|
|
1641
|
+
throw new Error(`npm registry response for ${PRIVATE_STATE_CLI_PACKAGE_NAME} did not include a version.`);
|
|
1642
|
+
}
|
|
1643
|
+
return version;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
function compareSemver(left, right) {
|
|
1647
|
+
const parse = (value) => String(value).split("-", 1)[0].split(".").map((part) => {
|
|
1648
|
+
const parsed = Number.parseInt(part, 10);
|
|
1649
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1650
|
+
});
|
|
1651
|
+
const leftParts = parse(left);
|
|
1652
|
+
const rightParts = parse(right);
|
|
1653
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
1654
|
+
for (let index = 0; index < length; index += 1) {
|
|
1655
|
+
const delta = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
1656
|
+
if (delta !== 0) {
|
|
1657
|
+
return delta < 0 ? -1 : 1;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
return 0;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function isRepositoryCliPackageRoot(packageRoot) {
|
|
1664
|
+
const segments = path.resolve(packageRoot).split(path.sep);
|
|
1665
|
+
const suffix = ["packages", "apps", "private-state", "cli"];
|
|
1666
|
+
return suffix.every((segment, index) => segments[segments.length - suffix.length + index] === segment);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1461
1669
|
async function handleDoctor({ args }) {
|
|
1462
1670
|
const report = buildDoctorReport({ probeGpu: args.gpu === true });
|
|
1463
1671
|
if (isJsonOutputRequested()) {
|
|
@@ -1608,10 +1816,10 @@ function trimFixedNumber(value, maxDecimals) {
|
|
|
1608
1816
|
return trimmed ? `${integer}.${trimmed}` : integer;
|
|
1609
1817
|
}
|
|
1610
1818
|
|
|
1611
|
-
function
|
|
1819
|
+
function handleAccountGetL1Address({ args }) {
|
|
1612
1820
|
const signer = requireL1Signer(args);
|
|
1613
1821
|
printJson({
|
|
1614
|
-
action: "get-
|
|
1822
|
+
action: "account get-l1-address",
|
|
1615
1823
|
l1Address: signer.address,
|
|
1616
1824
|
account: args.account ?? null,
|
|
1617
1825
|
});
|
|
@@ -1638,7 +1846,7 @@ function handleAccountImport({ args }) {
|
|
|
1638
1846
|
privateKeyPath,
|
|
1639
1847
|
}, 0o600);
|
|
1640
1848
|
printJson({
|
|
1641
|
-
action: "account
|
|
1849
|
+
action: "account import",
|
|
1642
1850
|
account,
|
|
1643
1851
|
network: networkName,
|
|
1644
1852
|
l1Address: getAddress(signer.address),
|
|
@@ -1659,7 +1867,7 @@ function handleListLocalWallets({ args }) {
|
|
|
1659
1867
|
});
|
|
1660
1868
|
|
|
1661
1869
|
printJson({
|
|
1662
|
-
action: "list
|
|
1870
|
+
action: "wallet list",
|
|
1663
1871
|
workspaceRoot,
|
|
1664
1872
|
filters: {
|
|
1665
1873
|
network: networkFilter,
|
|
@@ -1669,6 +1877,175 @@ function handleListLocalWallets({ args }) {
|
|
|
1669
1877
|
});
|
|
1670
1878
|
}
|
|
1671
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
|
+
|
|
1672
2049
|
async function handleGuide({ args }) {
|
|
1673
2050
|
const guide = {
|
|
1674
2051
|
action: "guide",
|
|
@@ -1684,7 +2061,7 @@ async function handleGuide({ args }) {
|
|
|
1684
2061
|
nextSafeAction: null,
|
|
1685
2062
|
why: null,
|
|
1686
2063
|
candidateCommands: [],
|
|
1687
|
-
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.",
|
|
1688
2065
|
};
|
|
1689
2066
|
|
|
1690
2067
|
guide.state.local = inspectGuideLocalState(args);
|
|
@@ -1707,12 +2084,12 @@ async function handleGuide({ args }) {
|
|
|
1707
2084
|
|
|
1708
2085
|
if (!args.network) {
|
|
1709
2086
|
setGuideNextAction(guide, {
|
|
1710
|
-
command: "guide --network <NAME>",
|
|
2087
|
+
command: "help guide --network <NAME>",
|
|
1711
2088
|
why: "Select a network before the guide can inspect RPC, deployment artifacts, channels, accounts, or wallets.",
|
|
1712
2089
|
candidates: [
|
|
1713
|
-
"guide --network mainnet",
|
|
1714
|
-
"guide --network sepolia",
|
|
1715
|
-
"guide --network anvil",
|
|
2090
|
+
"help guide --network mainnet",
|
|
2091
|
+
"help guide --network sepolia",
|
|
2092
|
+
"help guide --network anvil",
|
|
1716
2093
|
],
|
|
1717
2094
|
});
|
|
1718
2095
|
printJson(guide);
|
|
@@ -1725,7 +2102,7 @@ async function handleGuide({ args }) {
|
|
|
1725
2102
|
guide.checks.push(networkRuntime.check);
|
|
1726
2103
|
if (!networkRuntime.network) {
|
|
1727
2104
|
setGuideNextAction(guide, {
|
|
1728
|
-
command: "guide --network <NAME>",
|
|
2105
|
+
command: "help guide --network <NAME>",
|
|
1729
2106
|
why: `The requested network ${networkName} is not supported by the CLI network config.`,
|
|
1730
2107
|
});
|
|
1731
2108
|
printJson(guide);
|
|
@@ -2093,8 +2470,8 @@ async function inspectGuideWallet({ walletName, networkName, provider, artifacts
|
|
|
2093
2470
|
function applyGuideNextAction(guide) {
|
|
2094
2471
|
if (guide.state.local?.walletSelectorError && guide.selectors.network) {
|
|
2095
2472
|
setGuideNextAction(guide, {
|
|
2096
|
-
command: `list
|
|
2097
|
-
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.",
|
|
2098
2475
|
});
|
|
2099
2476
|
return;
|
|
2100
2477
|
}
|
|
@@ -2122,15 +2499,15 @@ function applyGuideNextAction(guide) {
|
|
|
2122
2499
|
if (guide.selectors.channelName && guide.state.channel?.onchain?.exists === false) {
|
|
2123
2500
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2124
2501
|
setGuideNextAction(guide, {
|
|
2125
|
-
command: `
|
|
2502
|
+
command: `channel create --channel-name ${guide.selectors.channelName} --join-toll <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2126
2503
|
why: "The selected channel name is not registered on-chain yet.",
|
|
2127
2504
|
});
|
|
2128
2505
|
return;
|
|
2129
2506
|
}
|
|
2130
2507
|
if (guide.selectors.channelName && guide.state.channel?.onchain?.exists && !guide.state.channel?.local?.workspaceExists) {
|
|
2131
2508
|
setGuideNextAction(guide, {
|
|
2132
|
-
command: `recover-workspace --channel-name ${guide.selectors.channelName} --network ${guide.selectors.network}`,
|
|
2133
|
-
why: "The channel exists on-chain, but the local channel workspace has not been recovered yet.",
|
|
2509
|
+
command: `channel recover-workspace --channel-name ${guide.selectors.channelName} --network ${guide.selectors.network} --from-genesis`,
|
|
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.",
|
|
2134
2511
|
});
|
|
2135
2512
|
return;
|
|
2136
2513
|
}
|
|
@@ -2138,7 +2515,7 @@ function applyGuideNextAction(guide) {
|
|
|
2138
2515
|
const channelName = guide.selectors.channelName ?? guide.state.channel?.channelName ?? "<CHANNEL>";
|
|
2139
2516
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2140
2517
|
setGuideNextAction(guide, {
|
|
2141
|
-
command: `
|
|
2518
|
+
command: `channel join --channel-name ${channelName} --network ${guide.selectors.network} --account ${account} --wallet-secret-path <PATH>`,
|
|
2142
2519
|
why: "The selected local wallet does not exist. Join the channel to create the wallet and register the channel L2 identity.",
|
|
2143
2520
|
});
|
|
2144
2521
|
return;
|
|
@@ -2147,7 +2524,7 @@ function applyGuideNextAction(guide) {
|
|
|
2147
2524
|
const channelName = guide.state.wallet.channelName ?? guide.selectors.channelName ?? "<CHANNEL>";
|
|
2148
2525
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2149
2526
|
setGuideNextAction(guide, {
|
|
2150
|
-
command: `
|
|
2527
|
+
command: `channel join --channel-name ${channelName} --network ${guide.selectors.network} --account ${account} --wallet-secret-path <PATH>`,
|
|
2151
2528
|
why: "The local wallet exists, but the corresponding L1 address is not registered in the channel.",
|
|
2152
2529
|
});
|
|
2153
2530
|
return;
|
|
@@ -2164,46 +2541,46 @@ function applyGuideNextAction(guide) {
|
|
|
2164
2541
|
if (guide.state.wallet?.exists && bridgeBalance === 0n && (channelBalance === null || channelBalance === 0n) && unusedNotes === 0) {
|
|
2165
2542
|
const account = guide.selectors.account ?? "<ACCOUNT>";
|
|
2166
2543
|
setGuideNextAction(guide, {
|
|
2167
|
-
command: `deposit-bridge --amount <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2544
|
+
command: `account deposit-bridge --amount <TOKENS> --network ${guide.selectors.network} --account ${account}`,
|
|
2168
2545
|
why: "The wallet is joined, but there is no bridge balance, channel balance, or local unused note to spend.",
|
|
2169
2546
|
});
|
|
2170
2547
|
return;
|
|
2171
2548
|
}
|
|
2172
2549
|
if (guide.state.wallet?.exists && bridgeBalance !== null && bridgeBalance > 0n && channelBalance === 0n) {
|
|
2173
2550
|
setGuideNextAction(guide, {
|
|
2174
|
-
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>`,
|
|
2175
2552
|
why: "The account has funds in the shared bridge vault, but the wallet has no channel L2 accounting balance.",
|
|
2176
2553
|
});
|
|
2177
2554
|
return;
|
|
2178
2555
|
}
|
|
2179
2556
|
if (guide.state.wallet?.exists && channelBalance !== null && channelBalance > 0n && unusedNotes === 0) {
|
|
2180
2557
|
setGuideNextAction(guide, {
|
|
2181
|
-
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>]`,
|
|
2182
2559
|
why: "The wallet has channel L2 balance and no unused private notes yet. Use --tx-submitter for stronger transaction-submission privacy.",
|
|
2183
2560
|
});
|
|
2184
2561
|
return;
|
|
2185
2562
|
}
|
|
2186
2563
|
if (guide.state.wallet?.exists && unusedNotes !== null && unusedNotes > 0) {
|
|
2187
2564
|
setGuideNextAction(guide, {
|
|
2188
|
-
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>]`,
|
|
2189
2566
|
why: "The wallet has unused private notes. It can transfer or redeem those notes. Use --tx-submitter for stronger transaction-submission privacy.",
|
|
2190
2567
|
candidates: [
|
|
2191
|
-
`get-
|
|
2192
|
-
`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>]`,
|
|
2193
2570
|
],
|
|
2194
2571
|
});
|
|
2195
2572
|
return;
|
|
2196
2573
|
}
|
|
2197
2574
|
if (guide.state.wallet?.exists && channelBalance === 0n) {
|
|
2198
2575
|
setGuideNextAction(guide, {
|
|
2199
|
-
command: `
|
|
2576
|
+
command: `channel exit --wallet ${guide.selectors.wallet} --network ${guide.selectors.network}`,
|
|
2200
2577
|
why: "The wallet has zero channel balance, so channel exit is allowed by both the CLI and bridge contract.",
|
|
2201
2578
|
});
|
|
2202
2579
|
return;
|
|
2203
2580
|
}
|
|
2204
2581
|
|
|
2205
2582
|
setGuideNextAction(guide, {
|
|
2206
|
-
command: "guide --network <NAME> --channel-name <CHANNEL> --account <ACCOUNT> --wallet <WALLET>",
|
|
2583
|
+
command: "help guide --network <NAME> --channel-name <CHANNEL> --account <ACCOUNT> --wallet <WALLET>",
|
|
2207
2584
|
why: "Provide more selectors so the guide can choose a single next safe action.",
|
|
2208
2585
|
});
|
|
2209
2586
|
}
|
|
@@ -2267,25 +2644,28 @@ function redactRpcUrl(rpcUrl) {
|
|
|
2267
2644
|
}
|
|
2268
2645
|
}
|
|
2269
2646
|
|
|
2270
|
-
async function
|
|
2647
|
+
async function handleWalletGetMeta({ args, provider }) {
|
|
2271
2648
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
2272
|
-
const
|
|
2273
|
-
|
|
2274
|
-
args,
|
|
2275
|
-
networkName: walletMetadata.network,
|
|
2649
|
+
const contextResult = await loadPreferredWalletChannelContext({
|
|
2650
|
+
walletContext: wallet,
|
|
2276
2651
|
provider,
|
|
2652
|
+
progressAction: "wallet get-meta",
|
|
2653
|
+
});
|
|
2654
|
+
const context = contextResult.context;
|
|
2655
|
+
const {
|
|
2656
|
+
signer,
|
|
2657
|
+
l2Identity,
|
|
2658
|
+
registration,
|
|
2659
|
+
expectedStorageKey,
|
|
2660
|
+
matchesWallet,
|
|
2661
|
+
} = await loadWalletChannelRegistrationState({
|
|
2277
2662
|
walletContext: wallet,
|
|
2663
|
+
context,
|
|
2664
|
+
provider,
|
|
2278
2665
|
});
|
|
2279
2666
|
|
|
2280
|
-
const registration = await context.channelManager.getChannelTokenVaultRegistration(signer.address);
|
|
2281
|
-
const expectedStorageKey = deriveLiquidBalanceStorageKey(l2Identity.l2Address, context.workspace.liquidBalancesSlot);
|
|
2282
|
-
const matchesWallet = registration.exists
|
|
2283
|
-
&& ethers.toBigInt(getAddress(registration.l2Address)) === ethers.toBigInt(getAddress(l2Identity.l2Address))
|
|
2284
|
-
&& ethers.toBigInt(normalizeBytes32Hex(registration.channelTokenVaultKey))
|
|
2285
|
-
=== ethers.toBigInt(normalizeBytes32Hex(expectedStorageKey));
|
|
2286
|
-
|
|
2287
2667
|
printJson({
|
|
2288
|
-
action: "get-
|
|
2668
|
+
action: "wallet get-meta",
|
|
2289
2669
|
wallet: wallet.walletName,
|
|
2290
2670
|
network: walletMetadata.network,
|
|
2291
2671
|
channelName: walletMetadata.channelName,
|
|
@@ -2301,31 +2681,23 @@ async function handleGetMyWalletMeta({ args, provider }) {
|
|
|
2301
2681
|
}
|
|
2302
2682
|
|
|
2303
2683
|
async function loadWalletChannelFundState({ walletContext, provider, progressAction = null }) {
|
|
2304
|
-
const { signer, l2Identity } = restoreWalletParticipant(walletContext, provider);
|
|
2305
2684
|
const contextResult = await loadPreferredWalletChannelContext({
|
|
2306
2685
|
walletContext,
|
|
2307
2686
|
provider,
|
|
2308
2687
|
progressAction,
|
|
2309
2688
|
});
|
|
2310
2689
|
const context = contextResult.context;
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
"The local wallet L2 address does not match the registered channel L2 address.",
|
|
2323
|
-
);
|
|
2324
|
-
expect(
|
|
2325
|
-
ethers.toBigInt(normalizeBytes32Hex(registration.channelTokenVaultKey))
|
|
2326
|
-
=== ethers.toBigInt(normalizeBytes32Hex(expectedStorageKey)),
|
|
2327
|
-
"The local wallet L2 storage key does not match the registered channelTokenVault key.",
|
|
2328
|
-
);
|
|
2690
|
+
const {
|
|
2691
|
+
signer,
|
|
2692
|
+
l2Identity,
|
|
2693
|
+
registration,
|
|
2694
|
+
expectedStorageKey,
|
|
2695
|
+
} = await loadWalletChannelRegistrationState({
|
|
2696
|
+
walletContext,
|
|
2697
|
+
context,
|
|
2698
|
+
provider,
|
|
2699
|
+
requireRegistration: true,
|
|
2700
|
+
});
|
|
2329
2701
|
|
|
2330
2702
|
const stateManager = await buildStateManager(context.currentSnapshot, context.contractCodes);
|
|
2331
2703
|
const channelDeposit = await currentStorageBigInt(
|
|
@@ -2344,7 +2716,44 @@ async function loadWalletChannelFundState({ walletContext, provider, progressAct
|
|
|
2344
2716
|
};
|
|
2345
2717
|
}
|
|
2346
2718
|
|
|
2347
|
-
async function
|
|
2719
|
+
async function loadWalletChannelRegistrationState({
|
|
2720
|
+
walletContext,
|
|
2721
|
+
context,
|
|
2722
|
+
provider,
|
|
2723
|
+
requireRegistration = false,
|
|
2724
|
+
}) {
|
|
2725
|
+
const { signer, l2Identity } = restoreWalletParticipant(walletContext, provider);
|
|
2726
|
+
const registration = await context.channelManager.getChannelTokenVaultRegistration(signer.address);
|
|
2727
|
+
const expectedStorageKey = deriveLiquidBalanceStorageKey(l2Identity.l2Address, context.workspace.liquidBalancesSlot);
|
|
2728
|
+
const matchesWallet = registration.exists
|
|
2729
|
+
&& ethers.toBigInt(getAddress(registration.l2Address)) === ethers.toBigInt(getAddress(l2Identity.l2Address))
|
|
2730
|
+
&& ethers.toBigInt(normalizeBytes32Hex(registration.channelTokenVaultKey))
|
|
2731
|
+
=== ethers.toBigInt(normalizeBytes32Hex(expectedStorageKey));
|
|
2732
|
+
|
|
2733
|
+
if (requireRegistration) {
|
|
2734
|
+
expect(
|
|
2735
|
+
registration.exists,
|
|
2736
|
+
cliError(
|
|
2737
|
+
CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION,
|
|
2738
|
+
`No channelTokenVault registration exists for ${signer.address}. Run channel join first.`,
|
|
2739
|
+
),
|
|
2740
|
+
);
|
|
2741
|
+
expect(
|
|
2742
|
+
matchesWallet,
|
|
2743
|
+
"The local wallet L2 address or storage key does not match the registered channelTokenVault state.",
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
return {
|
|
2748
|
+
signer,
|
|
2749
|
+
l2Identity,
|
|
2750
|
+
registration,
|
|
2751
|
+
expectedStorageKey,
|
|
2752
|
+
matchesWallet,
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
async function handleWalletGetChannelFund({ args, provider }) {
|
|
2348
2757
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
2349
2758
|
const {
|
|
2350
2759
|
signer,
|
|
@@ -2353,10 +2762,14 @@ async function handleGetMyChannelFund({ args, provider }) {
|
|
|
2353
2762
|
registration,
|
|
2354
2763
|
expectedStorageKey,
|
|
2355
2764
|
channelFund,
|
|
2356
|
-
} = await loadWalletChannelFundState({
|
|
2765
|
+
} = await loadWalletChannelFundState({
|
|
2766
|
+
walletContext: wallet,
|
|
2767
|
+
provider,
|
|
2768
|
+
progressAction: "wallet get-channel-fund",
|
|
2769
|
+
});
|
|
2357
2770
|
|
|
2358
2771
|
printJson({
|
|
2359
|
-
action: "get-
|
|
2772
|
+
action: "wallet get-channel-fund",
|
|
2360
2773
|
wallet: wallet.walletName,
|
|
2361
2774
|
network: walletMetadata.network,
|
|
2362
2775
|
channelName: walletMetadata.channelName,
|
|
@@ -2376,9 +2789,9 @@ async function handleGetMyChannelFund({ args, provider }) {
|
|
|
2376
2789
|
}
|
|
2377
2790
|
|
|
2378
2791
|
async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
2379
|
-
const context = await
|
|
2792
|
+
const context = await loadJoinChannelContext({
|
|
2380
2793
|
args,
|
|
2381
|
-
|
|
2794
|
+
network,
|
|
2382
2795
|
provider,
|
|
2383
2796
|
});
|
|
2384
2797
|
const signer = requireL1Signer(args, provider);
|
|
@@ -2388,7 +2801,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2388
2801
|
!existingRegistration.exists,
|
|
2389
2802
|
[
|
|
2390
2803
|
`L1 address ${signer.address} is already registered in channel ${context.workspace.channelName}.`,
|
|
2391
|
-
"Use recover-
|
|
2804
|
+
"Use wallet recover-workspace or normal wallet commands for an existing channel registration.",
|
|
2392
2805
|
].join(" "),
|
|
2393
2806
|
);
|
|
2394
2807
|
const walletSecret = prepareJoinWalletSecretForName({
|
|
@@ -2425,7 +2838,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2425
2838
|
);
|
|
2426
2839
|
let nextNonce = await provider.getTransactionCount(signer.address, "pending");
|
|
2427
2840
|
printImmutableChannelPolicyWarning({
|
|
2428
|
-
action: "join
|
|
2841
|
+
action: "channel join",
|
|
2429
2842
|
channelName: context.workspace.channelName,
|
|
2430
2843
|
channelId: ethers.toBigInt(context.workspace.channelId),
|
|
2431
2844
|
channelManager: context.workspace.channelManager,
|
|
@@ -2461,7 +2874,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
2461
2874
|
});
|
|
2462
2875
|
|
|
2463
2876
|
printJson({
|
|
2464
|
-
action: "join
|
|
2877
|
+
action: "channel join",
|
|
2465
2878
|
workspace: context.workspaceName,
|
|
2466
2879
|
wallet: walletContext.walletName,
|
|
2467
2880
|
walletSecretSource: resolvedWalletSecretSource(args),
|
|
@@ -2497,17 +2910,18 @@ async function handleExitChannel({ args, provider }) {
|
|
|
2497
2910
|
channelFund === 0n,
|
|
2498
2911
|
[
|
|
2499
2912
|
`The current channel fund for ${signer.address} is ${channelFund.toString()}.`,
|
|
2500
|
-
"
|
|
2501
|
-
"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.",
|
|
2502
2915
|
].join(" "),
|
|
2503
2916
|
);
|
|
2504
2917
|
const [refundAmount, refundBps] = await context.channelManager.getExitTollRefundQuote(signer.address);
|
|
2505
2918
|
const receipt = await waitForReceipt(
|
|
2506
2919
|
await context.bridgeTokenVault.connect(signer).exitChannel(ethers.toBigInt(context.workspace.channelId)),
|
|
2507
2920
|
);
|
|
2921
|
+
const cleanup = removeLocalWalletArtifacts(walletContext.walletName, walletMetadata.network);
|
|
2508
2922
|
|
|
2509
2923
|
printJson({
|
|
2510
|
-
action: "exit
|
|
2924
|
+
action: "channel exit",
|
|
2511
2925
|
wallet: walletContext.walletName,
|
|
2512
2926
|
network: walletMetadata.network,
|
|
2513
2927
|
channelName: walletMetadata.channelName,
|
|
@@ -2522,15 +2936,17 @@ async function handleExitChannel({ args, provider }) {
|
|
|
2522
2936
|
gasUsed: receiptGasUsed(receipt),
|
|
2523
2937
|
txUrl: explorerTxUrl(network, receipt.hash),
|
|
2524
2938
|
receipt: sanitizeReceipt(receipt),
|
|
2939
|
+
removedWalletDir: cleanup.removedWalletDir ? cleanup.walletDir : null,
|
|
2940
|
+
removedWalletSecretFile: cleanup.removedWalletSecret ? cleanup.walletSecretFile : null,
|
|
2525
2941
|
});
|
|
2526
2942
|
}
|
|
2527
2943
|
|
|
2528
2944
|
async function handleGrothVaultMove({ args, provider, direction }) {
|
|
2529
|
-
const operationName = args.command === "withdraw-channel"
|
|
2530
|
-
? "withdraw-channel"
|
|
2945
|
+
const operationName = args.command === "wallet-withdraw-channel"
|
|
2946
|
+
? "wallet withdraw-channel"
|
|
2531
2947
|
: direction === "deposit"
|
|
2532
|
-
? "deposit-channel"
|
|
2533
|
-
: "withdraw";
|
|
2948
|
+
? "wallet deposit-channel"
|
|
2949
|
+
: "wallet withdraw-channel";
|
|
2534
2950
|
emitProgress(operationName, "loading");
|
|
2535
2951
|
const { wallet: walletContext } = loadUnlockedWalletWithMetadata(args);
|
|
2536
2952
|
const contextResult = await loadPreferredWalletChannelContext({
|
|
@@ -2561,7 +2977,7 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2561
2977
|
availableBalance >= amount,
|
|
2562
2978
|
[
|
|
2563
2979
|
`Deposit amount ${amount.toString()} exceeds the shared bridge-vault balance`,
|
|
2564
|
-
`${availableBalance.toString()} for ${signer.address}. Run deposit-bridge first.`,
|
|
2980
|
+
`${availableBalance.toString()} for ${signer.address}. Run account deposit-bridge first.`,
|
|
2565
2981
|
].join(" "),
|
|
2566
2982
|
);
|
|
2567
2983
|
}
|
|
@@ -2570,7 +2986,7 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2570
2986
|
registration.exists,
|
|
2571
2987
|
cliError(
|
|
2572
2988
|
CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION,
|
|
2573
|
-
`No channelTokenVault registration exists for ${signer.address}. Run
|
|
2989
|
+
`No channelTokenVault registration exists for ${signer.address}. Run channel join first.`,
|
|
2574
2990
|
),
|
|
2575
2991
|
);
|
|
2576
2992
|
expect(
|
|
@@ -2634,7 +3050,12 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2634
3050
|
sealWalletOperationDir(operationDir, walletContext.walletSecret);
|
|
2635
3051
|
|
|
2636
3052
|
context.currentSnapshot = transition.nextSnapshot;
|
|
2637
|
-
|
|
3053
|
+
await refreshPersistedWorkspaceAfterLocalTransaction({
|
|
3054
|
+
context,
|
|
3055
|
+
provider,
|
|
3056
|
+
receipt,
|
|
3057
|
+
progressAction: operationName,
|
|
3058
|
+
});
|
|
2638
3059
|
|
|
2639
3060
|
emitProgress(operationName, "done");
|
|
2640
3061
|
printJson({
|
|
@@ -2650,6 +3071,8 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
2650
3071
|
updatedRoot: transition.update.updatedRoot,
|
|
2651
3072
|
gasUsed: receiptGasUsed(receipt),
|
|
2652
3073
|
txUrl: explorerTxUrl(network, receipt.hash),
|
|
3074
|
+
usedWorkspaceCache: contextResult.usingWorkspaceCache,
|
|
3075
|
+
recoveredWorkspace: contextResult.recoveredWorkspace,
|
|
2653
3076
|
});
|
|
2654
3077
|
}
|
|
2655
3078
|
|
|
@@ -2667,7 +3090,7 @@ async function handleWithdrawBridge({ args, network, provider }) {
|
|
|
2667
3090
|
const receipt = await waitForReceipt(await bridgeTokenVault.claimToWallet(amount));
|
|
2668
3091
|
|
|
2669
3092
|
printJson({
|
|
2670
|
-
action: "withdraw-bridge",
|
|
3093
|
+
action: "account withdraw-bridge",
|
|
2671
3094
|
l1Address: signer.address,
|
|
2672
3095
|
amountInput,
|
|
2673
3096
|
amountBaseUnits: amount.toString(),
|
|
@@ -2759,13 +3182,13 @@ async function handleMintNotes({ args, provider }) {
|
|
|
2759
3182
|
const { channelFund } = await loadWalletChannelFundState({
|
|
2760
3183
|
walletContext: wallet,
|
|
2761
3184
|
provider,
|
|
2762
|
-
progressAction: "mint-notes",
|
|
3185
|
+
progressAction: "wallet mint-notes",
|
|
2763
3186
|
});
|
|
2764
3187
|
expect(
|
|
2765
3188
|
totalMintAmount <= channelFund,
|
|
2766
3189
|
[
|
|
2767
3190
|
`Mint amount total ${totalMintAmount.toString()} exceeds the current channel fund`,
|
|
2768
|
-
`${channelFund.toString()}. Run get-
|
|
3191
|
+
`${channelFund.toString()}. Run wallet get-channel-fund to inspect the available balance.`,
|
|
2769
3192
|
].join(" "),
|
|
2770
3193
|
);
|
|
2771
3194
|
const templatePayload = buildMintNotesTemplatePayload({
|
|
@@ -2776,12 +3199,12 @@ async function handleMintNotes({ args, provider }) {
|
|
|
2776
3199
|
args,
|
|
2777
3200
|
wallet,
|
|
2778
3201
|
provider,
|
|
2779
|
-
operationName: "mint-notes",
|
|
3202
|
+
operationName: "wallet mint-notes",
|
|
2780
3203
|
templatePayload,
|
|
2781
3204
|
});
|
|
2782
3205
|
|
|
2783
3206
|
printJson({
|
|
2784
|
-
action: "mint-notes",
|
|
3207
|
+
action: "wallet mint-notes",
|
|
2785
3208
|
wallet: wallet.walletName,
|
|
2786
3209
|
workspace: execution.context.workspaceName,
|
|
2787
3210
|
operationDir: execution.operationDir,
|
|
@@ -2820,12 +3243,12 @@ async function handleRedeemNotes({ args, provider }) {
|
|
|
2820
3243
|
args,
|
|
2821
3244
|
wallet,
|
|
2822
3245
|
provider,
|
|
2823
|
-
operationName: "redeem-notes",
|
|
3246
|
+
operationName: "wallet redeem-notes",
|
|
2824
3247
|
templatePayload,
|
|
2825
3248
|
});
|
|
2826
3249
|
|
|
2827
3250
|
printJson({
|
|
2828
|
-
action: "redeem-notes",
|
|
3251
|
+
action: "wallet redeem-notes",
|
|
2829
3252
|
wallet: wallet.walletName,
|
|
2830
3253
|
workspace: execution.context.workspaceName,
|
|
2831
3254
|
operationDir: execution.operationDir,
|
|
@@ -2852,7 +3275,7 @@ async function handleRedeemNotes({ args, provider }) {
|
|
|
2852
3275
|
});
|
|
2853
3276
|
}
|
|
2854
3277
|
|
|
2855
|
-
async function
|
|
3278
|
+
async function handleWalletGetNotes({ args, provider }) {
|
|
2856
3279
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
2857
3280
|
expect(
|
|
2858
3281
|
typeof wallet.wallet.controller === "string" && wallet.wallet.controller.length > 0,
|
|
@@ -2862,7 +3285,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
2862
3285
|
const { context } = await loadPreferredWalletChannelContext({
|
|
2863
3286
|
walletContext: wallet,
|
|
2864
3287
|
provider,
|
|
2865
|
-
progressAction: "get-
|
|
3288
|
+
progressAction: "wallet get-notes",
|
|
2866
3289
|
});
|
|
2867
3290
|
const signer = restoreWalletSigner(wallet, provider);
|
|
2868
3291
|
const { recoveredDeliveryState } = await recoverWalletReceivedNotes({
|
|
@@ -2870,7 +3293,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
2870
3293
|
context,
|
|
2871
3294
|
provider,
|
|
2872
3295
|
signer,
|
|
2873
|
-
progressAction: "get-
|
|
3296
|
+
progressAction: "wallet get-notes",
|
|
2874
3297
|
});
|
|
2875
3298
|
|
|
2876
3299
|
const unusedTrackedNotes = wallet.wallet.notes.unusedOrder
|
|
@@ -2895,7 +3318,7 @@ async function handleGetMyNotes({ args, provider }) {
|
|
|
2895
3318
|
const spentTotal = spentTrackedNotes.reduce((sum, note) => sum + ethers.toBigInt(note.value), 0n);
|
|
2896
3319
|
|
|
2897
3320
|
printJson({
|
|
2898
|
-
action: "get-
|
|
3321
|
+
action: "wallet get-notes",
|
|
2899
3322
|
wallet: wallet.walletName,
|
|
2900
3323
|
network: walletMetadata.network,
|
|
2901
3324
|
channelName: walletMetadata.channelName,
|
|
@@ -2919,7 +3342,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
2919
3342
|
const preparedContextResult = await loadPreferredWalletChannelContext({
|
|
2920
3343
|
walletContext: wallet,
|
|
2921
3344
|
provider,
|
|
2922
|
-
progressAction: "transfer-notes",
|
|
3345
|
+
progressAction: "wallet transfer-notes",
|
|
2923
3346
|
});
|
|
2924
3347
|
const context = preparedContextResult.context;
|
|
2925
3348
|
const canonicalAssetDecimals = Number(wallet.wallet.canonicalAssetDecimals);
|
|
@@ -2955,7 +3378,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
2955
3378
|
args,
|
|
2956
3379
|
wallet,
|
|
2957
3380
|
provider,
|
|
2958
|
-
operationName: "transfer-notes",
|
|
3381
|
+
operationName: "wallet transfer-notes",
|
|
2959
3382
|
templatePayload,
|
|
2960
3383
|
});
|
|
2961
3384
|
const outputNotes = buildLifecycleTrackedOutputs({
|
|
@@ -2966,7 +3389,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
2966
3389
|
});
|
|
2967
3390
|
|
|
2968
3391
|
printJson({
|
|
2969
|
-
action: "transfer-notes",
|
|
3392
|
+
action: "wallet transfer-notes",
|
|
2970
3393
|
wallet: wallet.walletName,
|
|
2971
3394
|
workspace: execution.context.workspaceName,
|
|
2972
3395
|
operationDir: execution.operationDir,
|
|
@@ -3323,11 +3746,12 @@ async function recoverDeliveredNotesFromEventLogs({
|
|
|
3323
3746
|
noteReceivePrivateKey,
|
|
3324
3747
|
progressAction = null,
|
|
3325
3748
|
}) {
|
|
3326
|
-
const scanStartBlock = Math.max(
|
|
3327
|
-
Number(walletContext.wallet.noteReceiveLastScannedBlock),
|
|
3328
|
-
Number(context.workspace.genesisBlockNumber),
|
|
3329
|
-
);
|
|
3330
3749
|
const latestBlock = await provider.getBlockNumber();
|
|
3750
|
+
const scanStartBlock = requireUsableWalletNoteReceiveRecoveryIndex({
|
|
3751
|
+
walletContext,
|
|
3752
|
+
context,
|
|
3753
|
+
latestBlock,
|
|
3754
|
+
});
|
|
3331
3755
|
const scanRange = {
|
|
3332
3756
|
fromBlock: scanStartBlock,
|
|
3333
3757
|
toBlock: latestBlock,
|
|
@@ -3431,6 +3855,23 @@ async function recoverDeliveredNotesFromEventLogs({
|
|
|
3431
3855
|
};
|
|
3432
3856
|
}
|
|
3433
3857
|
|
|
3858
|
+
function requireUsableWalletNoteReceiveRecoveryIndex({ walletContext, context, latestBlock }) {
|
|
3859
|
+
const nextBlock = Number(walletContext.wallet.noteReceiveLastScannedBlock);
|
|
3860
|
+
const genesisBlockNumber = Number(context.workspace.genesisBlockNumber);
|
|
3861
|
+
if (
|
|
3862
|
+
!Number.isInteger(nextBlock)
|
|
3863
|
+
|| nextBlock < genesisBlockNumber
|
|
3864
|
+
|| nextBlock > Number(latestBlock) + 1
|
|
3865
|
+
) {
|
|
3866
|
+
throw new Error([
|
|
3867
|
+
`Wallet note recovery index is missing or unusable for wallet ${walletContext.walletName}.`,
|
|
3868
|
+
`Expected noteReceiveLastScannedBlock to be an integer between ${genesisBlockNumber} and ${Number(latestBlock) + 1}.`,
|
|
3869
|
+
"Run wallet recover-workspace --from-genesis to rebuild wallet note state from channel genesis.",
|
|
3870
|
+
].join(" "));
|
|
3871
|
+
}
|
|
3872
|
+
return nextBlock;
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3434
3875
|
function extractEncryptedNoteValueFromBridgeLog(log) {
|
|
3435
3876
|
if (!Array.isArray(log?.topics) || log.topics.length !== 1) {
|
|
3436
3877
|
return null;
|
|
@@ -3665,16 +4106,16 @@ function buildRedeemNotesTemplatePayload({ wallet, inputNotes }) {
|
|
|
3665
4106
|
}
|
|
3666
4107
|
|
|
3667
4108
|
function selectMintNotesMethod(noteCount) {
|
|
3668
|
-
expect(noteCount >= 1, "mint-notes requires at least one output amount.");
|
|
4109
|
+
expect(noteCount >= 1, "wallet mint-notes requires at least one output amount.");
|
|
3669
4110
|
expect(
|
|
3670
4111
|
noteCount <= 2,
|
|
3671
|
-
"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.",
|
|
3672
4113
|
);
|
|
3673
4114
|
return `mintNotes${noteCount}`;
|
|
3674
4115
|
}
|
|
3675
4116
|
|
|
3676
4117
|
function selectRedeemNotesMethod(noteCount) {
|
|
3677
|
-
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.");
|
|
3678
4119
|
return `redeemNotes${noteCount}`;
|
|
3679
4120
|
}
|
|
3680
4121
|
|
|
@@ -3793,7 +4234,7 @@ function selectTransferNotesMethod(inputCount, outputCount) {
|
|
|
3793
4234
|
if (inputCount === 2 && outputCount === 1) {
|
|
3794
4235
|
return "transferNotes2To1";
|
|
3795
4236
|
}
|
|
3796
|
-
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.");
|
|
3797
4238
|
}
|
|
3798
4239
|
|
|
3799
4240
|
function loadWalletUnusedInputNotes(walletContext, noteIds) {
|
|
@@ -3929,7 +4370,7 @@ async function recoverWalletChannelWorkspace({ walletContext, provider, progress
|
|
|
3929
4370
|
const networkName = walletContext.wallet.network ?? networkNameFromChainId(Number(walletContext.wallet.chainId));
|
|
3930
4371
|
const network = resolveCliNetwork(networkName);
|
|
3931
4372
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
3932
|
-
await
|
|
4373
|
+
await syncChannelWorkspace({
|
|
3933
4374
|
workspaceName: walletContext.wallet.channelName,
|
|
3934
4375
|
channelName: walletContext.wallet.channelName,
|
|
3935
4376
|
network,
|
|
@@ -3942,6 +4383,49 @@ async function recoverWalletChannelWorkspace({ walletContext, provider, progress
|
|
|
3942
4383
|
});
|
|
3943
4384
|
}
|
|
3944
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
|
+
|
|
3945
4429
|
function isRecoverableWalletWorkspaceFailure(error) {
|
|
3946
4430
|
const message = String(error?.message ?? error);
|
|
3947
4431
|
return (message.includes("--verify") && message.includes("failed with exit code"))
|
|
@@ -3993,6 +4477,7 @@ async function executeWalletDirectTemplateCommand({
|
|
|
3993
4477
|
txSubmitterAccount,
|
|
3994
4478
|
l2Identity,
|
|
3995
4479
|
context: contextResult.context,
|
|
4480
|
+
provider,
|
|
3996
4481
|
operationName,
|
|
3997
4482
|
functionName: templatePayload.method,
|
|
3998
4483
|
templatePayload,
|
|
@@ -4024,6 +4509,7 @@ async function executeWalletDirectTemplateCommand({
|
|
|
4024
4509
|
txSubmitterAccount,
|
|
4025
4510
|
l2Identity,
|
|
4026
4511
|
context: contextResult.context,
|
|
4512
|
+
provider,
|
|
4027
4513
|
operationName,
|
|
4028
4514
|
functionName: templatePayload.method,
|
|
4029
4515
|
templatePayload,
|
|
@@ -4044,6 +4530,7 @@ async function executeWalletTemplateSend({
|
|
|
4044
4530
|
txSubmitterAccount,
|
|
4045
4531
|
l2Identity,
|
|
4046
4532
|
context,
|
|
4533
|
+
provider,
|
|
4047
4534
|
operationName,
|
|
4048
4535
|
functionName,
|
|
4049
4536
|
templatePayload,
|
|
@@ -4137,7 +4624,12 @@ async function executeWalletTemplateSend({
|
|
|
4137
4624
|
applyNoteLifecycleToWallet(wallet, noteLifecycle, functionName, receipt.hash);
|
|
4138
4625
|
context.currentSnapshot = nextSnapshot;
|
|
4139
4626
|
persistWallet(wallet);
|
|
4140
|
-
|
|
4627
|
+
await refreshPersistedWorkspaceAfterLocalTransaction({
|
|
4628
|
+
context,
|
|
4629
|
+
provider,
|
|
4630
|
+
receipt,
|
|
4631
|
+
progressAction: operationName,
|
|
4632
|
+
});
|
|
4141
4633
|
sealWalletOperationDir(operationDir, wallet.walletSecret);
|
|
4142
4634
|
|
|
4143
4635
|
return {
|
|
@@ -4191,27 +4683,13 @@ async function loadWorkspaceContext(workspaceName, networkName, provider) {
|
|
|
4191
4683
|
};
|
|
4192
4684
|
}
|
|
4193
4685
|
|
|
4194
|
-
async function
|
|
4686
|
+
async function loadJoinChannelContext({ args, network, provider }) {
|
|
4195
4687
|
const chainId = Number((await provider.getNetwork()).chainId);
|
|
4196
|
-
const resolvedNetworkName =
|
|
4197
|
-
const channelName = args.channelName
|
|
4198
|
-
if (args.channelName && walletContext) {
|
|
4199
|
-
expect(
|
|
4200
|
-
args.channelName === walletContext.wallet.channelName,
|
|
4201
|
-
[
|
|
4202
|
-
`The provided --channel-name (${args.channelName}) does not match the wallet channel`,
|
|
4203
|
-
`(${walletContext.wallet.channelName}).`,
|
|
4204
|
-
].join(" "),
|
|
4205
|
-
);
|
|
4206
|
-
}
|
|
4207
|
-
if (!channelName) {
|
|
4208
|
-
throw new Error(
|
|
4209
|
-
"Missing channel selector. Provide either --channel-name or --wallet bound to a channel.",
|
|
4210
|
-
);
|
|
4211
|
-
}
|
|
4688
|
+
const resolvedNetworkName = network?.name ?? networkNameFromChainId(chainId);
|
|
4689
|
+
const channelName = requireArg(args.channelName, "--channel-name");
|
|
4212
4690
|
|
|
4213
4691
|
const bridgeResources = loadBridgeResources({ chainId });
|
|
4214
|
-
const initialized = await
|
|
4692
|
+
const initialized = await syncChannelWorkspace({
|
|
4215
4693
|
workspaceName: channelName,
|
|
4216
4694
|
channelName,
|
|
4217
4695
|
network: { chainId, name: resolvedNetworkName },
|
|
@@ -4305,7 +4783,7 @@ function assertWalletUsesChannelBoundDerivation(wallet, walletName) {
|
|
|
4305
4783
|
wallet.l2DerivationMode === CHANNEL_BOUND_L2_DERIVATION_MODE,
|
|
4306
4784
|
[
|
|
4307
4785
|
`Wallet ${walletName} was not created with the current channel-bound L2 derivation rule.`,
|
|
4308
|
-
"Create a fresh wallet with join
|
|
4786
|
+
"Create a fresh wallet with channel join.",
|
|
4309
4787
|
].join(" "),
|
|
4310
4788
|
);
|
|
4311
4789
|
expect(
|
|
@@ -5538,7 +6016,13 @@ function parseArgs(argv) {
|
|
|
5538
6016
|
}
|
|
5539
6017
|
|
|
5540
6018
|
parsed.command = parsed.positional[0];
|
|
5541
|
-
if (
|
|
6019
|
+
if (
|
|
6020
|
+
(parsed.command === "account"
|
|
6021
|
+
|| parsed.command === "channel"
|
|
6022
|
+
|| parsed.command === "wallet"
|
|
6023
|
+
|| parsed.command === "help")
|
|
6024
|
+
&& parsed.positional[1]
|
|
6025
|
+
) {
|
|
5542
6026
|
parsed.command = `${parsed.command}-${parsed.positional[1]}`;
|
|
5543
6027
|
parsed.positional = [parsed.command];
|
|
5544
6028
|
}
|
|
@@ -5699,7 +6183,7 @@ function resolveWalletDefaultSecret(networkName, walletName) {
|
|
|
5699
6183
|
CLI_ERROR_CODES.MISSING_WALLET_SECRET,
|
|
5700
6184
|
[
|
|
5701
6185
|
`Missing wallet default secret file: ${secretPath}.`,
|
|
5702
|
-
"Run
|
|
6186
|
+
"Run channel join with --wallet-secret-path before wallet commands.",
|
|
5703
6187
|
].join(" "),
|
|
5704
6188
|
);
|
|
5705
6189
|
}
|
|
@@ -5712,12 +6196,17 @@ function prepareJoinWalletSecretForName({
|
|
|
5712
6196
|
walletName,
|
|
5713
6197
|
}) {
|
|
5714
6198
|
const secretPath = walletSecretPath(networkName, walletName);
|
|
6199
|
+
const { channelName } = parseWalletName(walletName);
|
|
6200
|
+
const walletDir = walletPath(walletName, networkName);
|
|
5715
6201
|
expect(
|
|
5716
|
-
!walletConfigExists(
|
|
6202
|
+
!walletConfigExists(walletDir),
|
|
5717
6203
|
[
|
|
5718
6204
|
`Wallet ${walletName} already exists on ${networkName}.`,
|
|
5719
|
-
"
|
|
5720
|
-
"
|
|
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.",
|
|
5721
6210
|
].join(" "),
|
|
5722
6211
|
);
|
|
5723
6212
|
const sourcePath = path.resolve(String(requireArg(args.walletSecretPath, "--wallet-secret-path")));
|
|
@@ -5726,13 +6215,6 @@ function prepareJoinWalletSecretForName({
|
|
|
5726
6215
|
? readSecretFile(sourcePath, "--wallet-secret-path")
|
|
5727
6216
|
: readImportSecretSourceFile(sourcePath, "--wallet-secret-path");
|
|
5728
6217
|
if (sourcePath !== canonicalPath) {
|
|
5729
|
-
expect(
|
|
5730
|
-
!fs.existsSync(canonicalPath),
|
|
5731
|
-
[
|
|
5732
|
-
`Wallet default secret file already exists: ${canonicalPath}.`,
|
|
5733
|
-
"Remove it before joining with a different --wallet-secret-path.",
|
|
5734
|
-
].join(" "),
|
|
5735
|
-
);
|
|
5736
6218
|
writeSecretFile(canonicalPath, walletSecret);
|
|
5737
6219
|
}
|
|
5738
6220
|
return walletSecret;
|
|
@@ -5855,6 +6337,148 @@ function listLocalWallets({ networkFilter = null, channelFilter = null } = {}) {
|
|
|
5855
6337
|
);
|
|
5856
6338
|
}
|
|
5857
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
|
+
|
|
5858
6482
|
function channelDataPath(workspaceDir) {
|
|
5859
6483
|
return workspaceChannelDir(workspaceDir);
|
|
5860
6484
|
}
|
|
@@ -5953,10 +6577,18 @@ function assertUninstallArgs(args) {
|
|
|
5953
6577
|
assertAllowedCommandSchema(args, "uninstall");
|
|
5954
6578
|
}
|
|
5955
6579
|
|
|
6580
|
+
function assertHelpCommandsArgs(args) {
|
|
6581
|
+
assertAllowedCommandSchema(args, "help-commands");
|
|
6582
|
+
}
|
|
6583
|
+
|
|
6584
|
+
function assertUpdateArgs(args) {
|
|
6585
|
+
assertAllowedCommandSchema(args, "help-update");
|
|
6586
|
+
}
|
|
6587
|
+
|
|
5956
6588
|
function assertDoctorArgs(args) {
|
|
5957
|
-
assertAllowedCommandSchema(args, "doctor");
|
|
6589
|
+
assertAllowedCommandSchema(args, "help-doctor");
|
|
5958
6590
|
if (args.gpu !== undefined && args.gpu !== true) {
|
|
5959
|
-
throw new Error("doctor option --gpu does not accept a value.");
|
|
6591
|
+
throw new Error("help doctor option --gpu does not accept a value.");
|
|
5960
6592
|
}
|
|
5961
6593
|
}
|
|
5962
6594
|
|
|
@@ -5973,11 +6605,11 @@ function assertGuideArgs(args) {
|
|
|
5973
6605
|
if (args.wallet !== undefined) {
|
|
5974
6606
|
requireWalletName(args);
|
|
5975
6607
|
}
|
|
5976
|
-
assertAllowedCommandSchema(args, "guide");
|
|
6608
|
+
assertAllowedCommandSchema(args, "help-guide");
|
|
5977
6609
|
}
|
|
5978
6610
|
|
|
5979
6611
|
function assertTransactionFeesArgs(args) {
|
|
5980
|
-
assertAllowedCommandSchema(args, "transaction-fees");
|
|
6612
|
+
assertAllowedCommandSchema(args, "help-transaction-fees");
|
|
5981
6613
|
}
|
|
5982
6614
|
|
|
5983
6615
|
function assertAccountImportArgs(args) {
|
|
@@ -5985,7 +6617,7 @@ function assertAccountImportArgs(args) {
|
|
|
5985
6617
|
}
|
|
5986
6618
|
|
|
5987
6619
|
function assertMintNotesArgs(args) {
|
|
5988
|
-
assertAllowedCommandSchema(args, "mint-notes");
|
|
6620
|
+
assertAllowedCommandSchema(args, "wallet-mint-notes");
|
|
5989
6621
|
assertTxSubmitterArg(args);
|
|
5990
6622
|
parseAmountVector(args.amounts, {
|
|
5991
6623
|
allowZeroEntries: true,
|
|
@@ -5994,13 +6626,13 @@ function assertMintNotesArgs(args) {
|
|
|
5994
6626
|
}
|
|
5995
6627
|
|
|
5996
6628
|
function assertRedeemNotesArgs(args) {
|
|
5997
|
-
assertAllowedCommandSchema(args, "redeem-notes");
|
|
6629
|
+
assertAllowedCommandSchema(args, "wallet-redeem-notes");
|
|
5998
6630
|
assertTxSubmitterArg(args);
|
|
5999
6631
|
selectRedeemNotesMethod(parseNoteIdVector(args.noteIds).length);
|
|
6000
6632
|
}
|
|
6001
6633
|
|
|
6002
6634
|
function assertTransferNotesArgs(args) {
|
|
6003
|
-
assertAllowedCommandSchema(args, "transfer-notes");
|
|
6635
|
+
assertAllowedCommandSchema(args, "wallet-transfer-notes");
|
|
6004
6636
|
assertTxSubmitterArg(args);
|
|
6005
6637
|
const noteIds = parseNoteIdVector(args.noteIds);
|
|
6006
6638
|
const recipients = parseRecipientVector(args.recipients);
|
|
@@ -6021,28 +6653,28 @@ function assertTxSubmitterArg(args) {
|
|
|
6021
6653
|
}
|
|
6022
6654
|
}
|
|
6023
6655
|
|
|
6024
|
-
function
|
|
6025
|
-
assertWalletSecretArgs(args, "get-
|
|
6656
|
+
function assertWalletGetNotesArgs(args) {
|
|
6657
|
+
assertWalletSecretArgs(args, "wallet-get-notes");
|
|
6026
6658
|
}
|
|
6027
6659
|
|
|
6028
6660
|
function assertCreateChannelArgs(args) {
|
|
6029
|
-
assertAllowedCommandSchema(args, "create
|
|
6661
|
+
assertAllowedCommandSchema(args, "channel-create");
|
|
6030
6662
|
}
|
|
6031
6663
|
|
|
6032
6664
|
function assertRecoverWorkspaceArgs(args) {
|
|
6033
|
-
assertAllowedCommandSchema(args, "recover-workspace");
|
|
6665
|
+
assertAllowedCommandSchema(args, "channel-recover-workspace");
|
|
6034
6666
|
}
|
|
6035
6667
|
|
|
6036
6668
|
function assertGetChannelArgs(args) {
|
|
6037
|
-
assertAllowedCommandSchema(args, "get-
|
|
6669
|
+
assertAllowedCommandSchema(args, "channel-get-meta");
|
|
6038
6670
|
}
|
|
6039
6671
|
|
|
6040
6672
|
function assertDepositBridgeArgs(args) {
|
|
6041
|
-
assertAllowedCommandSchema(args, "deposit-bridge");
|
|
6673
|
+
assertAllowedCommandSchema(args, "account-deposit-bridge");
|
|
6042
6674
|
}
|
|
6043
6675
|
|
|
6044
|
-
function
|
|
6045
|
-
assertAllowedCommandSchema(args, "get-
|
|
6676
|
+
function assertAccountGetBridgeFundArgs(args) {
|
|
6677
|
+
assertAllowedCommandSchema(args, "account-get-bridge-fund");
|
|
6046
6678
|
}
|
|
6047
6679
|
|
|
6048
6680
|
function assertExplicitSignerCommandArgs(args, commandName) {
|
|
@@ -6050,19 +6682,22 @@ function assertExplicitSignerCommandArgs(args, commandName) {
|
|
|
6050
6682
|
}
|
|
6051
6683
|
|
|
6052
6684
|
function assertRecoverWalletArgs(args) {
|
|
6053
|
-
assertExplicitSignerCommandArgs(args, "recover-
|
|
6685
|
+
assertExplicitSignerCommandArgs(args, "wallet-recover-workspace");
|
|
6686
|
+
if (args.fromGenesis !== undefined && args.fromGenesis !== true) {
|
|
6687
|
+
throw new Error("wallet recover-workspace option --from-genesis does not accept a value.");
|
|
6688
|
+
}
|
|
6054
6689
|
}
|
|
6055
6690
|
|
|
6056
6691
|
function assertJoinChannelArgs(args) {
|
|
6057
|
-
assertAllowedCommandSchema(args, "join
|
|
6692
|
+
assertAllowedCommandSchema(args, "channel-join");
|
|
6058
6693
|
}
|
|
6059
6694
|
|
|
6060
|
-
function
|
|
6061
|
-
assertWalletSecretArgs(args, "get-
|
|
6695
|
+
function assertWalletGetMetaArgs(args) {
|
|
6696
|
+
assertWalletSecretArgs(args, "wallet-get-meta");
|
|
6062
6697
|
}
|
|
6063
6698
|
|
|
6064
|
-
function
|
|
6065
|
-
assertAllowedCommandSchema(args, "get-
|
|
6699
|
+
function assertAccountGetL1AddressArgs(args) {
|
|
6700
|
+
assertAllowedCommandSchema(args, "account-get-l1-address");
|
|
6066
6701
|
}
|
|
6067
6702
|
|
|
6068
6703
|
function assertListLocalWalletsArgs(args) {
|
|
@@ -6072,19 +6707,45 @@ function assertListLocalWalletsArgs(args) {
|
|
|
6072
6707
|
if (args.channelName !== undefined) {
|
|
6073
6708
|
requireArg(args.channelName, "--channel-name");
|
|
6074
6709
|
}
|
|
6075
|
-
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
|
+
}
|
|
6076
6737
|
}
|
|
6077
6738
|
|
|
6078
6739
|
function assertWithdrawBridgeArgs(args) {
|
|
6079
|
-
assertAllowedCommandSchema(args, "withdraw-bridge");
|
|
6740
|
+
assertAllowedCommandSchema(args, "account-withdraw-bridge");
|
|
6080
6741
|
}
|
|
6081
6742
|
|
|
6082
|
-
function
|
|
6083
|
-
assertWalletSecretArgs(args, "get-
|
|
6743
|
+
function assertWalletGetChannelFundArgs(args) {
|
|
6744
|
+
assertWalletSecretArgs(args, "wallet-get-channel-fund");
|
|
6084
6745
|
}
|
|
6085
6746
|
|
|
6086
6747
|
function assertExitChannelArgs(args) {
|
|
6087
|
-
assertWalletSecretArgs(args, "exit
|
|
6748
|
+
assertWalletSecretArgs(args, "channel-exit");
|
|
6088
6749
|
}
|
|
6089
6750
|
|
|
6090
6751
|
function createWalletOperationDir(walletName, networkName, suffix) {
|
|
@@ -6110,17 +6771,6 @@ function persistWalletMetadata(context) {
|
|
|
6110
6771
|
});
|
|
6111
6772
|
}
|
|
6112
6773
|
|
|
6113
|
-
function persistCurrentState(context) {
|
|
6114
|
-
if (!context.persistChannelWorkspace || !context.workspaceDir) {
|
|
6115
|
-
return;
|
|
6116
|
-
}
|
|
6117
|
-
writeJson(path.join(channelWorkspaceCurrentPath(context.workspaceDir), "state_snapshot.json"), context.currentSnapshot);
|
|
6118
|
-
writeJson(
|
|
6119
|
-
path.join(channelWorkspaceCurrentPath(context.workspaceDir), "state_snapshot.normalized.json"),
|
|
6120
|
-
context.currentSnapshot,
|
|
6121
|
-
);
|
|
6122
|
-
}
|
|
6123
|
-
|
|
6124
6774
|
function printHelp() {
|
|
6125
6775
|
const commandHelp = PRIVATE_STATE_CLI_COMMANDS.map((command) => [
|
|
6126
6776
|
` ${privateStateCliCommandSynopsis(command)}`,
|
|
@@ -6134,10 +6784,10 @@ ${commandHelp}
|
|
|
6134
6784
|
Secret source options:
|
|
6135
6785
|
Use account import --private-key-file once to create a protected local account secret.
|
|
6136
6786
|
L1 signing commands use --account only.
|
|
6137
|
-
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.
|
|
6138
6788
|
Create one before joining a channel, for example:
|
|
6139
6789
|
openssl rand -hex 32 > ./wallet-secret.txt
|
|
6140
|
-
private-state-cli
|
|
6790
|
+
private-state-cli channel join --channel-name <NAME> --network <NAME> --account <NAME> --wallet-secret-path ./wallet-secret.txt
|
|
6141
6791
|
Bridge-facing commands accept optional --rpc-url. When provided, it is saved to
|
|
6142
6792
|
~/tokamak-private-channels/secrets/<network>/.env as RPC_URL. When omitted, the CLI reads RPC_URL from that file.
|
|
6143
6793
|
Wallet commands use wallet-local default secret files only.
|
|
@@ -6152,7 +6802,7 @@ Options:
|
|
|
6152
6802
|
Print the command result as JSON. Without --json, commands print human-readable output.
|
|
6153
6803
|
|
|
6154
6804
|
--help
|
|
6155
|
-
Show this help
|
|
6805
|
+
Show this help. Equivalent to help commands.
|
|
6156
6806
|
`);
|
|
6157
6807
|
}
|
|
6158
6808
|
|
|
@@ -6577,6 +7227,7 @@ function loadWalletCommandRuntime(args) {
|
|
|
6577
7227
|
const HUMAN_RESULT_RENDERERS = Object.freeze({
|
|
6578
7228
|
guide: printGuideHumanResult,
|
|
6579
7229
|
"transaction-fees": printTransactionFeesHumanResult,
|
|
7230
|
+
update: printUpdateHumanResult,
|
|
6580
7231
|
});
|
|
6581
7232
|
|
|
6582
7233
|
function normalizePrivateKey(value) {
|
|
@@ -6668,6 +7319,35 @@ function printTransactionFeesHumanResult(report) {
|
|
|
6668
7319
|
console.log(lines.join("\n"));
|
|
6669
7320
|
}
|
|
6670
7321
|
|
|
7322
|
+
function printUpdateHumanResult(report) {
|
|
7323
|
+
const lines = [
|
|
7324
|
+
"Private-State CLI Update",
|
|
7325
|
+
`Package: ${formatHumanValue(report.packageName)}`,
|
|
7326
|
+
`Current version: ${formatHumanValue(report.currentVersion)}`,
|
|
7327
|
+
`Latest registry version: ${formatHumanValue(report.latestVersion)}`,
|
|
7328
|
+
];
|
|
7329
|
+
if (report.registryState === "local-version-ahead-of-registry") {
|
|
7330
|
+
lines.push("Status: local version is newer than the npm registry latest tag.");
|
|
7331
|
+
} else if (!report.updateAvailable) {
|
|
7332
|
+
lines.push("Status: up to date.");
|
|
7333
|
+
} else if (report.updated) {
|
|
7334
|
+
lines.push("Status: updated global npm install.");
|
|
7335
|
+
} else {
|
|
7336
|
+
lines.push(
|
|
7337
|
+
"Status: update available.",
|
|
7338
|
+
`Reason: ${formatHumanValue(report.reason)}`,
|
|
7339
|
+
`Command: ${formatHumanValue(report.command)}`,
|
|
7340
|
+
);
|
|
7341
|
+
}
|
|
7342
|
+
lines.push(
|
|
7343
|
+
`Global install: ${report.globalPackage?.installed ? `yes (${formatHumanValue(report.globalPackage.version)})` : "no"}`,
|
|
7344
|
+
`Repository checkout: ${report.runningFromRepositoryCheckout ? "yes" : "no"}`,
|
|
7345
|
+
"",
|
|
7346
|
+
"Run with --json to inspect the full update report.",
|
|
7347
|
+
);
|
|
7348
|
+
console.log(lines.join("\n"));
|
|
7349
|
+
}
|
|
7350
|
+
|
|
6671
7351
|
function formatHumanTable(headers, rows) {
|
|
6672
7352
|
const values = [headers, ...rows].map((row) => row.map((value) => String(value ?? "")));
|
|
6673
7353
|
const widths = headers.map((_header, columnIndex) =>
|
|
@@ -6847,18 +7527,18 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
6847
7527
|
|| message.includes("does not match the wallet channel")
|
|
6848
7528
|
|| message.includes("The provided wallet does not belong to the selected channel")
|
|
6849
7529
|
) {
|
|
6850
|
-
hints.push(`private-state-cli list
|
|
6851
|
-
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}`);
|
|
6852
7532
|
}
|
|
6853
7533
|
|
|
6854
7534
|
if (error?.code === CLI_ERROR_CODES.MISSING_WALLET_SECRET) {
|
|
6855
7535
|
hints.push("restore the wallet-local default secret file from backup before running wallet commands.");
|
|
6856
|
-
hints.push(`private-state-cli guide --network ${networkName} --wallet ${walletName}`);
|
|
7536
|
+
hints.push(`private-state-cli help guide --network ${networkName} --wallet ${walletName}`);
|
|
6857
7537
|
}
|
|
6858
7538
|
|
|
6859
7539
|
if (error?.code === CLI_ERROR_CODES.WALLET_DECRYPT_FAILED) {
|
|
6860
7540
|
hints.push("verify that the wallet-local default secret file is the same secret used when the wallet was created.");
|
|
6861
|
-
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.");
|
|
6862
7542
|
hints.push("if the wallet secret was lost, the local L2 key cannot be recovered from the encrypted wallet file.");
|
|
6863
7543
|
}
|
|
6864
7544
|
|
|
@@ -6867,7 +7547,7 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
6867
7547
|
|| message.includes("Missing --account.")
|
|
6868
7548
|
) {
|
|
6869
7549
|
hints.push(`private-state-cli account import --account ${accountName} --network ${networkName} --private-key-file <PATH>`);
|
|
6870
|
-
hints.push(`private-state-cli guide --network ${networkName} --account ${accountName}`);
|
|
7550
|
+
hints.push(`private-state-cli help guide --network ${networkName} --account ${accountName}`);
|
|
6871
7551
|
}
|
|
6872
7552
|
|
|
6873
7553
|
if (
|
|
@@ -6875,22 +7555,31 @@ function buildRecoveryHints(error, args = {}) {
|
|
|
6875
7555
|
|| message.includes("DApp deployment artifact")
|
|
6876
7556
|
) {
|
|
6877
7557
|
hints.push("private-state-cli install");
|
|
6878
|
-
hints.push("private-state-cli doctor --json");
|
|
7558
|
+
hints.push("private-state-cli help doctor --json");
|
|
6879
7559
|
}
|
|
6880
7560
|
|
|
6881
7561
|
if (error?.code === CLI_ERROR_CODES.MISSING_CHANNEL_REGISTRATION) {
|
|
6882
|
-
hints.push(`private-state-cli
|
|
6883
|
-
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}`);
|
|
6884
7564
|
}
|
|
6885
7565
|
|
|
6886
7566
|
if (error?.code === CLI_ERROR_CODES.STALE_WORKSPACE) {
|
|
6887
|
-
hints.push(`private-state-cli recover-workspace --channel-name ${channelName} --network ${networkName}`);
|
|
6888
|
-
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}`);
|
|
7569
|
+
}
|
|
7570
|
+
|
|
7571
|
+
if (message.includes("Workspace recovery index is missing or unusable")) {
|
|
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`);
|
|
7574
|
+
}
|
|
7575
|
+
|
|
7576
|
+
if (message.includes("Wallet note recovery index is missing or unusable")) {
|
|
7577
|
+
hints.push(`private-state-cli wallet recover-workspace --channel-name ${channelName} --network ${networkName} --account ${accountName} --from-genesis`);
|
|
6889
7578
|
}
|
|
6890
7579
|
|
|
6891
7580
|
if (message.includes("Missing channel selector")) {
|
|
6892
|
-
hints.push(`private-state-cli list
|
|
6893
|
-
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>`);
|
|
6894
7583
|
}
|
|
6895
7584
|
|
|
6896
7585
|
return [...new Set(hints)];
|