@tokamak-private-dapps/private-state-cli 2.4.2 → 2.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +11 -1
- package/agents.md +3 -0
- package/commands/index.mjs +2 -6
- package/lib/private-state-cli-command-registry.mjs +5 -2
- package/lib/runtime.mjs +569 -151
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 2.4.3 - 2026-06-01
|
|
6
|
+
|
|
7
|
+
- Added pre-submit transaction dry-runs for every transaction-sending CLI command.
|
|
8
|
+
- Added post-proof local prechecks before dry-run for proof-backed commands.
|
|
9
|
+
- Improved dry-run and submit failure messages with decoded revert details when available.
|
|
10
|
+
- Standardized CLI `--json` output so final success and failure results are JSON objects on stdout.
|
|
11
|
+
- Changed progress, warning, and informational events to emit JSON Lines on stderr in `--json` mode.
|
|
12
|
+
- Added structured JSON command-reference output for `help commands --json` and `--help --json`.
|
|
13
|
+
|
|
5
14
|
## 2.4.2 - 2026-05-30
|
|
6
15
|
|
|
7
16
|
- Changed channel workspace recovery guidance so CLI help, guide output, agent instructions, and documentation direct
|
package/README.md
CHANGED
|
@@ -170,7 +170,8 @@ A common note-use flow after channel policy review is:
|
|
|
170
170
|
`channel join` pays any join toll directly from the L1 wallet; `account deposit-bridge` funds later channel liquidity and does not pay the join toll.
|
|
171
171
|
|
|
172
172
|
Use `private-state-cli help commands` for the full command list and required options. `private-state-cli --help`
|
|
173
|
-
continues to print the same command list for shell compatibility.
|
|
173
|
+
continues to print the same command list for shell compatibility. Add `--json` to either form to print the command
|
|
174
|
+
reference as structured JSON on stdout.
|
|
174
175
|
|
|
175
176
|
### Action-impact acknowledgement
|
|
176
177
|
|
|
@@ -524,6 +525,15 @@ LLM agents that guide users through this CLI should read [`agents.md`](agents.md
|
|
|
524
525
|
commands. That file contains the agent-specific operating rules, including secret-handling boundaries, onboarding
|
|
525
526
|
sequence, acknowledgement handling, recovery behavior, and error-response policy.
|
|
526
527
|
|
|
528
|
+
When `--json` is used, the CLI follows one output contract for all commands:
|
|
529
|
+
|
|
530
|
+
- the final success result is one JSON object on stdout
|
|
531
|
+
- command failures are one JSON object on stdout with `ok: false`
|
|
532
|
+
- progress, warning, and informational events are JSON Lines on stderr
|
|
533
|
+
- human-readable mode remains the default when `--json` is omitted
|
|
534
|
+
|
|
535
|
+
Agents should parse stdout for the final result and may stream stderr JSONL events to explain progress to the user.
|
|
536
|
+
|
|
527
537
|
## Artifacts
|
|
528
538
|
|
|
529
539
|
Proof-backed and channel-mutating commands require full installed bridge, DApp, and Groth16 artifacts. Run
|
package/agents.md
CHANGED
|
@@ -48,6 +48,9 @@ activity or as a bridge-wide disclosure rule for every DApp.
|
|
|
48
48
|
`private-state-cli help transaction-fees --network <NETWORK> --json` and answer from the returned `rows`. If the
|
|
49
49
|
network is unclear, ask which network to use. Do not tell the user to ask the developer unless the command fails after
|
|
50
50
|
following the CLI's printed corrective guidance.
|
|
51
|
+
- Prefer `--json` when running commands on behalf of a user. In JSON mode, parse stdout as the final success or failure
|
|
52
|
+
result. Failures use `ok: false` on stdout. Progress, warning, and informational events are emitted as JSON Lines on
|
|
53
|
+
stderr; stream or summarize those events for the human user instead of treating them as fatal by default.
|
|
51
54
|
- When `channel recover-workspace` or `wallet recover-workspace` is unexpectedly slow, first inspect the RPC provider
|
|
52
55
|
configured by `set rpc`. Explain that recovery speed is dominated by `eth_getLogs` block range cap and log request
|
|
53
56
|
rate. Suggest re-running `set rpc` with a provider that supports a larger block range cap, such as Ankr or Chainnodes
|
package/commands/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertHelpCommandsArgs,
|
|
3
3
|
assertVersionArgs,
|
|
4
|
+
cliOutput,
|
|
4
5
|
configureOutput,
|
|
5
|
-
formatCliErrorForDisplay,
|
|
6
6
|
parseArgs,
|
|
7
7
|
printHelp,
|
|
8
8
|
printVersion,
|
|
@@ -52,11 +52,7 @@ export async function runPrivateStateCli(argv) {
|
|
|
52
52
|
}
|
|
53
53
|
await command(args);
|
|
54
54
|
} catch (error) {
|
|
55
|
-
|
|
55
|
+
cliOutput.error(error, args);
|
|
56
56
|
process.exitCode = 1;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
export function privateStateCliDispatchTable() {
|
|
61
|
-
return COMMANDS;
|
|
62
|
-
}
|
|
@@ -313,8 +313,11 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
313
313
|
id: "help-commands",
|
|
314
314
|
display: "help commands",
|
|
315
315
|
description: "Show the private-state CLI command reference.",
|
|
316
|
-
fields: [],
|
|
317
|
-
usage: "
|
|
316
|
+
fields: ["json"],
|
|
317
|
+
usage: "optional --json",
|
|
318
|
+
help: [
|
|
319
|
+
"Use --json to emit the full command reference as structured JSON on stdout.",
|
|
320
|
+
],
|
|
318
321
|
},
|
|
319
322
|
{
|
|
320
323
|
id: "help-update",
|
package/lib/runtime.mjs
CHANGED
|
@@ -166,6 +166,8 @@ const CLI_ERROR_CODES = Object.freeze({
|
|
|
166
166
|
MISSING_CHANNEL_REGISTRATION: "MISSING_CHANNEL_REGISTRATION",
|
|
167
167
|
STALE_WORKSPACE: "STALE_WORKSPACE",
|
|
168
168
|
STALE_CHANNEL_ROOT: "STALE_CHANNEL_ROOT",
|
|
169
|
+
TX_DRY_RUN_FAILED: "TX_DRY_RUN_FAILED",
|
|
170
|
+
TX_SUBMIT_FAILED: "TX_SUBMIT_FAILED",
|
|
169
171
|
});
|
|
170
172
|
|
|
171
173
|
class PrivateStateCliError extends Error {
|
|
@@ -288,7 +290,13 @@ function printImmutableChannelPolicyWarning({
|
|
|
288
290
|
"Do not sign if any snapshot value is unexpected or has not been reviewed.",
|
|
289
291
|
);
|
|
290
292
|
}
|
|
291
|
-
|
|
293
|
+
cliOutput.warning("channel-policy", details.join("\n"), {
|
|
294
|
+
action,
|
|
295
|
+
channelName,
|
|
296
|
+
channelId: channelId.toString(),
|
|
297
|
+
channelManager,
|
|
298
|
+
policySnapshot,
|
|
299
|
+
});
|
|
292
300
|
}
|
|
293
301
|
|
|
294
302
|
const ACTION_IMPACT_SUMMARIES = Object.freeze({
|
|
@@ -483,7 +491,16 @@ function printActionImpactSummary(summary, details) {
|
|
|
483
491
|
lines.push(`- Exchange-controlled address warning: ${summary.exchangeControlledAddressWarning}`);
|
|
484
492
|
}
|
|
485
493
|
lines.push(`- Confirmation: pass --acknowledge-action-impact or type the exact confirmation phrase when prompted.`);
|
|
486
|
-
|
|
494
|
+
cliOutput.warning("action-impact", lines.join("\n"), {
|
|
495
|
+
command: summary.display,
|
|
496
|
+
l1PublicEvent: summary.l1PublicEvent,
|
|
497
|
+
privateNoteState: summary.privateNoteState,
|
|
498
|
+
publicFields: normalizeImpactLines(summary.publicFields, details),
|
|
499
|
+
notPublic: normalizeImpactLines(summary.notPublic, details),
|
|
500
|
+
noteProvenance: summary.noteProvenance,
|
|
501
|
+
policy: summary.policy,
|
|
502
|
+
exchangeControlledAddressWarning: summary.exchangeControlledAddressWarning ?? null,
|
|
503
|
+
});
|
|
487
504
|
}
|
|
488
505
|
|
|
489
506
|
function normalizeImpactLines(value, details) {
|
|
@@ -609,8 +626,15 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
609
626
|
channelId,
|
|
610
627
|
policySnapshot,
|
|
611
628
|
});
|
|
612
|
-
const receipt =
|
|
613
|
-
|
|
629
|
+
const receipt = await dryRunThenSubmitTransaction({
|
|
630
|
+
operationName: "channel create",
|
|
631
|
+
call: contractTxCall(
|
|
632
|
+
bridgeCore.createChannel,
|
|
633
|
+
[channelId, dappId, joinToll, dapp.metadataDigest],
|
|
634
|
+
undefined,
|
|
635
|
+
bridgeCore.interface,
|
|
636
|
+
),
|
|
637
|
+
});
|
|
614
638
|
const channelInfo = await bridgeCore.getChannel(channelId);
|
|
615
639
|
|
|
616
640
|
const workspaceResult = await syncChannelWorkspace({
|
|
@@ -625,7 +649,7 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
625
649
|
progressAction: "channel create",
|
|
626
650
|
});
|
|
627
651
|
|
|
628
|
-
|
|
652
|
+
cliOutput.result({
|
|
629
653
|
action: "channel create",
|
|
630
654
|
channelName,
|
|
631
655
|
channelId: channelId.toString(),
|
|
@@ -767,7 +791,7 @@ async function handleWorkspaceInit({ args, network, provider }) {
|
|
|
767
791
|
})
|
|
768
792
|
: null;
|
|
769
793
|
|
|
770
|
-
|
|
794
|
+
cliOutput.result({
|
|
771
795
|
action: "channel recover-workspace",
|
|
772
796
|
source: workspace.recoverySource ?? recoverySource,
|
|
773
797
|
workspace: workspaceName,
|
|
@@ -804,7 +828,7 @@ async function handleGetChannel({ args, network, provider }) {
|
|
|
804
828
|
channelInfo = await bridgeCore.getChannel(channelId);
|
|
805
829
|
} catch (error) {
|
|
806
830
|
if (isContractError(error, bridgeCore.interface, "UnknownChannel")) {
|
|
807
|
-
|
|
831
|
+
cliOutput.result({
|
|
808
832
|
action: "channel get-meta",
|
|
809
833
|
channelName,
|
|
810
834
|
channelId: channelId.toString(),
|
|
@@ -843,7 +867,7 @@ async function handleGetChannel({ args, network, provider }) {
|
|
|
843
867
|
readChannelRefundSchedule(channelManager),
|
|
844
868
|
]);
|
|
845
869
|
|
|
846
|
-
|
|
870
|
+
cliOutput.result({
|
|
847
871
|
action: "channel get-meta",
|
|
848
872
|
channelName,
|
|
849
873
|
channelId: channelId.toString(),
|
|
@@ -880,10 +904,18 @@ async function handleSetChannelWorkspaceMirror({ args, network, provider }) {
|
|
|
880
904
|
);
|
|
881
905
|
const channelId = deriveChannelIdFromName(channelName);
|
|
882
906
|
const previousUrl = await readChannelWorkspaceMirror({ bridgeCore, channelId });
|
|
883
|
-
const receipt = await
|
|
907
|
+
const receipt = await dryRunThenSubmitTransaction({
|
|
908
|
+
operationName: "channel set-workspace-mirror",
|
|
909
|
+
call: contractTxCall(
|
|
910
|
+
bridgeCore.setChannelWorkspaceMirror,
|
|
911
|
+
[channelId, url],
|
|
912
|
+
undefined,
|
|
913
|
+
bridgeCore.interface,
|
|
914
|
+
),
|
|
915
|
+
});
|
|
884
916
|
const currentUrl = await readChannelWorkspaceMirror({ bridgeCore, channelId });
|
|
885
917
|
|
|
886
|
-
|
|
918
|
+
cliOutput.result({
|
|
887
919
|
action: "channel set-workspace-mirror",
|
|
888
920
|
channelName,
|
|
889
921
|
channelId: channelId.toString(),
|
|
@@ -2210,12 +2242,28 @@ async function handleDepositBridge({ args, network, provider }) {
|
|
|
2210
2242
|
signer,
|
|
2211
2243
|
);
|
|
2212
2244
|
let nextNonce = await provider.getTransactionCount(signer.address, "pending");
|
|
2213
|
-
const approveReceipt =
|
|
2214
|
-
|
|
2215
|
-
|
|
2245
|
+
const approveReceipt = await dryRunThenSubmitTransaction({
|
|
2246
|
+
operationName: "account deposit-bridge approve",
|
|
2247
|
+
call: contractTxCall(
|
|
2248
|
+
asset.approve,
|
|
2249
|
+
[bridgeVaultContext.bridgeTokenVaultAddress, amount],
|
|
2250
|
+
{ nonce: nextNonce++ },
|
|
2251
|
+
asset.interface,
|
|
2252
|
+
),
|
|
2253
|
+
});
|
|
2254
|
+
const fundReceipt = await dryRunThenSubmitTransaction({
|
|
2255
|
+
operationName: "account deposit-bridge fund",
|
|
2256
|
+
call: contractTxCall(
|
|
2257
|
+
bridgeTokenVault.fund,
|
|
2258
|
+
[amount],
|
|
2259
|
+
{ nonce: nextNonce++ },
|
|
2260
|
+
bridgeTokenVault.interface,
|
|
2261
|
+
),
|
|
2262
|
+
submittedBefore: [submittedReceiptSummary("account deposit-bridge approve", approveReceipt)],
|
|
2263
|
+
});
|
|
2216
2264
|
const availableBalance = await bridgeTokenVault.availableBalanceOf(signer.address);
|
|
2217
2265
|
|
|
2218
|
-
|
|
2266
|
+
cliOutput.result({
|
|
2219
2267
|
action: "account deposit-bridge",
|
|
2220
2268
|
amountInput,
|
|
2221
2269
|
amountBaseUnits: amount.toString(),
|
|
@@ -2243,7 +2291,7 @@ async function handleAccountGetBridgeFund({ args, provider }) {
|
|
|
2243
2291
|
);
|
|
2244
2292
|
const availableBalance = await bridgeTokenVault.availableBalanceOf(signer.address);
|
|
2245
2293
|
|
|
2246
|
-
|
|
2294
|
+
cliOutput.result({
|
|
2247
2295
|
action: "account get-bridge-fund",
|
|
2248
2296
|
l1Address: signer.address,
|
|
2249
2297
|
bridgeTokenVault: bridgeVaultContext.bridgeTokenVaultAddress,
|
|
@@ -2393,7 +2441,7 @@ async function handleRecoverWallet({ args, network, provider, rpcUrl }) {
|
|
|
2393
2441
|
latestBlock: recoveryEventScan.scanRange.toBlock,
|
|
2394
2442
|
});
|
|
2395
2443
|
|
|
2396
|
-
|
|
2444
|
+
cliOutput.result({
|
|
2397
2445
|
action: "wallet recover-workspace",
|
|
2398
2446
|
status,
|
|
2399
2447
|
wallet: walletName,
|
|
@@ -2491,7 +2539,7 @@ async function handleInstallZkEvm({ args }) {
|
|
|
2491
2539
|
tokamakCliRuntime,
|
|
2492
2540
|
groth16Runtime,
|
|
2493
2541
|
});
|
|
2494
|
-
|
|
2542
|
+
cliOutput.result({
|
|
2495
2543
|
action: "install",
|
|
2496
2544
|
installMode,
|
|
2497
2545
|
selectedVersions,
|
|
@@ -2532,7 +2580,7 @@ async function handleUninstall() {
|
|
|
2532
2580
|
});
|
|
2533
2581
|
const globalPackage = uninstallGlobalPrivateStateCliPackage();
|
|
2534
2582
|
|
|
2535
|
-
|
|
2583
|
+
cliOutput.result({
|
|
2536
2584
|
action: "uninstall",
|
|
2537
2585
|
confirmationAccepted: true,
|
|
2538
2586
|
removedPrivateStateRoots,
|
|
@@ -2559,7 +2607,7 @@ async function handleSetRpc({ args }) {
|
|
|
2559
2607
|
LOG_CHUNK_SIZE: rpcScanLimits.blockRangeCap,
|
|
2560
2608
|
RPC_BLOCK_RANGE_CAP: rpcScanLimits.blockRangeCap,
|
|
2561
2609
|
});
|
|
2562
|
-
|
|
2610
|
+
cliOutput.result({
|
|
2563
2611
|
action: "set rpc",
|
|
2564
2612
|
network: networkName,
|
|
2565
2613
|
rpcConfigPath: rpcConfigEnvPath(networkName),
|
|
@@ -2964,12 +3012,12 @@ async function handleUpdate() {
|
|
|
2964
3012
|
};
|
|
2965
3013
|
|
|
2966
3014
|
if (!updateAvailable) {
|
|
2967
|
-
|
|
3015
|
+
cliOutput.result(result);
|
|
2968
3016
|
return;
|
|
2969
3017
|
}
|
|
2970
3018
|
|
|
2971
3019
|
if (runningFromRepositoryCheckout) {
|
|
2972
|
-
|
|
3020
|
+
cliOutput.result({
|
|
2973
3021
|
...result,
|
|
2974
3022
|
reason: "running from a repository checkout; update the checkout with git/npm instead of mutating source files",
|
|
2975
3023
|
});
|
|
@@ -2977,7 +3025,7 @@ async function handleUpdate() {
|
|
|
2977
3025
|
}
|
|
2978
3026
|
|
|
2979
3027
|
if (!globalPackage.installed) {
|
|
2980
|
-
|
|
3028
|
+
cliOutput.result({
|
|
2981
3029
|
...result,
|
|
2982
3030
|
reason: "global npm package is not installed; install or update the CLI with the printed command",
|
|
2983
3031
|
});
|
|
@@ -2991,7 +3039,7 @@ async function handleUpdate() {
|
|
|
2991
3039
|
stripAnsi(install.stderr || install.stdout).trim(),
|
|
2992
3040
|
].filter(Boolean).join(" "));
|
|
2993
3041
|
}
|
|
2994
|
-
|
|
3042
|
+
cliOutput.result({
|
|
2995
3043
|
...result,
|
|
2996
3044
|
attempted: true,
|
|
2997
3045
|
updated: true,
|
|
@@ -3043,18 +3091,14 @@ function isRepositoryCliPackageRoot(packageRoot) {
|
|
|
3043
3091
|
|
|
3044
3092
|
async function handleDoctor({ args }) {
|
|
3045
3093
|
const report = buildDoctorReport({ probeGpu: args.gpu === true });
|
|
3046
|
-
|
|
3047
|
-
printJson(report);
|
|
3048
|
-
} else {
|
|
3049
|
-
printDoctorHumanReport(report);
|
|
3050
|
-
}
|
|
3094
|
+
cliOutput.result(report);
|
|
3051
3095
|
if (!report.ok) {
|
|
3052
3096
|
process.exitCode = 1;
|
|
3053
3097
|
}
|
|
3054
3098
|
}
|
|
3055
3099
|
|
|
3056
3100
|
function handleObserver() {
|
|
3057
|
-
|
|
3101
|
+
cliOutput.result({
|
|
3058
3102
|
action: "observer",
|
|
3059
3103
|
url: PRIVATE_STATE_OBSERVER_URL,
|
|
3060
3104
|
scope: "Public monitoring observer for Tokamak Private App Channels and the private-state DApp.",
|
|
@@ -3069,7 +3113,7 @@ function handleInvestigator() {
|
|
|
3069
3113
|
const htmlPath = resolveInvestigatorIndexPath();
|
|
3070
3114
|
const fileUrl = pathToFileURL(htmlPath).href;
|
|
3071
3115
|
const browser = openFileInDefaultBrowser(fileUrl);
|
|
3072
|
-
|
|
3116
|
+
cliOutput.result({
|
|
3073
3117
|
action: "investigator",
|
|
3074
3118
|
htmlPath,
|
|
3075
3119
|
fileUrl,
|
|
@@ -3138,7 +3182,7 @@ async function handleTransactionFees({ network, provider, rpcUrl }) {
|
|
|
3138
3182
|
ethUsd,
|
|
3139
3183
|
});
|
|
3140
3184
|
|
|
3141
|
-
|
|
3185
|
+
cliOutput.result({
|
|
3142
3186
|
action: "transaction-fees",
|
|
3143
3187
|
generatedAt: new Date().toISOString(),
|
|
3144
3188
|
network: network.name,
|
|
@@ -3266,7 +3310,7 @@ function trimFixedNumber(value, maxDecimals) {
|
|
|
3266
3310
|
|
|
3267
3311
|
function handleAccountGetL1Address({ args }) {
|
|
3268
3312
|
const signer = requireL1Signer(args);
|
|
3269
|
-
|
|
3313
|
+
cliOutput.result({
|
|
3270
3314
|
action: "account get-l1-address",
|
|
3271
3315
|
l1Address: signer.address,
|
|
3272
3316
|
account: args.account ?? null,
|
|
@@ -3293,7 +3337,7 @@ function handleAccountImport({ args }) {
|
|
|
3293
3337
|
l1Address: getAddress(signer.address),
|
|
3294
3338
|
privateKeyPath,
|
|
3295
3339
|
}, 0o600);
|
|
3296
|
-
|
|
3340
|
+
cliOutput.result({
|
|
3297
3341
|
action: "account import",
|
|
3298
3342
|
account,
|
|
3299
3343
|
network: networkName,
|
|
@@ -3314,7 +3358,7 @@ function handleListLocalWallets({ args }) {
|
|
|
3314
3358
|
channelFilter,
|
|
3315
3359
|
});
|
|
3316
3360
|
|
|
3317
|
-
|
|
3361
|
+
cliOutput.result({
|
|
3318
3362
|
action: "wallet list",
|
|
3319
3363
|
workspaceRoot,
|
|
3320
3364
|
filters: {
|
|
@@ -3382,7 +3426,7 @@ function handleWalletExportBackup({ args }) {
|
|
|
3382
3426
|
archive.writeZip(outputPath);
|
|
3383
3427
|
protectSecretFile(outputPath, "wallet export ZIP");
|
|
3384
3428
|
|
|
3385
|
-
|
|
3429
|
+
cliOutput.result({
|
|
3386
3430
|
action: "wallet export backup",
|
|
3387
3431
|
output: outputPath,
|
|
3388
3432
|
exportMode: manifest.exportMode,
|
|
@@ -3407,7 +3451,7 @@ function handleWalletExportKey({ args, keyKind }) {
|
|
|
3407
3451
|
validateWalletKeyPayload(payload, keyKind);
|
|
3408
3452
|
fs.writeFileSync(outputPath, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
|
|
3409
3453
|
protectSecretFile(outputPath, `${keyKind} key export`);
|
|
3410
|
-
|
|
3454
|
+
cliOutput.result({
|
|
3411
3455
|
action: `wallet export ${keyKind}-key`,
|
|
3412
3456
|
wallet: wallet.walletName,
|
|
3413
3457
|
network: networkName,
|
|
@@ -3452,7 +3496,7 @@ function handleWalletImportBackup({ args }) {
|
|
|
3452
3496
|
|
|
3453
3497
|
commitWalletImportFiles({ targetRoot, plannedWrites });
|
|
3454
3498
|
|
|
3455
|
-
|
|
3499
|
+
cliOutput.result({
|
|
3456
3500
|
action: "wallet import backup",
|
|
3457
3501
|
input: inputPath,
|
|
3458
3502
|
exportMode: manifest.exportMode,
|
|
@@ -3495,7 +3539,7 @@ function handleWalletImportKey({ args, keyKind }) {
|
|
|
3495
3539
|
writeJson(metadataPath, metadata);
|
|
3496
3540
|
}
|
|
3497
3541
|
}
|
|
3498
|
-
|
|
3542
|
+
cliOutput.result({
|
|
3499
3543
|
action: `wallet import ${keyKind}-key`,
|
|
3500
3544
|
input: inputPath,
|
|
3501
3545
|
network: networkName,
|
|
@@ -3637,7 +3681,7 @@ async function handleGuide({ args }) {
|
|
|
3637
3681
|
"help guide --network anvil",
|
|
3638
3682
|
],
|
|
3639
3683
|
});
|
|
3640
|
-
|
|
3684
|
+
cliOutput.result(guide);
|
|
3641
3685
|
return;
|
|
3642
3686
|
}
|
|
3643
3687
|
|
|
@@ -3650,7 +3694,7 @@ async function handleGuide({ args }) {
|
|
|
3650
3694
|
command: "help guide --network <NAME>",
|
|
3651
3695
|
why: `The requested network ${networkName} is not supported by the CLI network config.`,
|
|
3652
3696
|
});
|
|
3653
|
-
|
|
3697
|
+
cliOutput.result(guide);
|
|
3654
3698
|
return;
|
|
3655
3699
|
}
|
|
3656
3700
|
|
|
@@ -3737,7 +3781,7 @@ async function handleGuide({ args }) {
|
|
|
3737
3781
|
|
|
3738
3782
|
destroyGuideProvider(provider);
|
|
3739
3783
|
applyGuideNextAction(guide);
|
|
3740
|
-
|
|
3784
|
+
cliOutput.result(guide);
|
|
3741
3785
|
}
|
|
3742
3786
|
|
|
3743
3787
|
function inspectGuideLocalState(args) {
|
|
@@ -4226,7 +4270,7 @@ async function handleWalletGetMeta({ args, provider }) {
|
|
|
4226
4270
|
provider,
|
|
4227
4271
|
});
|
|
4228
4272
|
|
|
4229
|
-
|
|
4273
|
+
cliOutput.result({
|
|
4230
4274
|
action: "wallet get-meta",
|
|
4231
4275
|
wallet: wallet.walletName,
|
|
4232
4276
|
...walletLifecycleMetadata(wallet.wallet),
|
|
@@ -4560,7 +4604,7 @@ async function handleWalletGetChannelFund({ args, provider }) {
|
|
|
4560
4604
|
provider,
|
|
4561
4605
|
});
|
|
4562
4606
|
|
|
4563
|
-
|
|
4607
|
+
cliOutput.result({
|
|
4564
4608
|
action: "wallet get-channel-fund",
|
|
4565
4609
|
wallet: wallet.walletName,
|
|
4566
4610
|
network: walletMetadata.network,
|
|
@@ -4641,20 +4685,32 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
4641
4685
|
channelId: context.workspace.channelId,
|
|
4642
4686
|
});
|
|
4643
4687
|
if (joinToll !== 0n) {
|
|
4644
|
-
approveReceipt = await
|
|
4645
|
-
|
|
4646
|
-
|
|
4688
|
+
approveReceipt = await dryRunThenSubmitTransaction({
|
|
4689
|
+
operationName: "channel join approve",
|
|
4690
|
+
call: contractTxCall(
|
|
4691
|
+
asset.approve,
|
|
4692
|
+
[context.workspace.bridgeTokenVault, joinToll],
|
|
4693
|
+
{ nonce: nextNonce++ },
|
|
4694
|
+
asset.interface,
|
|
4695
|
+
),
|
|
4696
|
+
});
|
|
4647
4697
|
}
|
|
4648
|
-
receipt = await
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4698
|
+
receipt = await dryRunThenSubmitTransaction({
|
|
4699
|
+
operationName: "channel join",
|
|
4700
|
+
call: contractTxCall(
|
|
4701
|
+
context.bridgeTokenVault.connect(signer).joinChannel,
|
|
4702
|
+
[
|
|
4703
|
+
ethers.toBigInt(context.workspace.channelId),
|
|
4704
|
+
l2Identity.l2Address,
|
|
4705
|
+
storageKey,
|
|
4706
|
+
leafIndex,
|
|
4707
|
+
noteReceiveKeyMaterial.noteReceivePubKey,
|
|
4708
|
+
],
|
|
4655
4709
|
{ nonce: nextNonce++ },
|
|
4710
|
+
context.bridgeTokenVault.interface,
|
|
4656
4711
|
),
|
|
4657
|
-
|
|
4712
|
+
submittedBefore: approveReceipt ? [submittedReceiptSummary("channel join approve", approveReceipt)] : [],
|
|
4713
|
+
});
|
|
4658
4714
|
const registered = await context.channelManager.getChannelTokenVaultRegistration(signer.address);
|
|
4659
4715
|
const lifecycleEpoch = await walletEpochFromJoinReceipt({
|
|
4660
4716
|
receipt,
|
|
@@ -4683,7 +4739,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
4683
4739
|
rpcUrl,
|
|
4684
4740
|
});
|
|
4685
4741
|
|
|
4686
|
-
|
|
4742
|
+
cliOutput.result({
|
|
4687
4743
|
action: "channel join",
|
|
4688
4744
|
workspace: context.workspaceName,
|
|
4689
4745
|
wallet: walletContext.walletName,
|
|
@@ -4733,16 +4789,22 @@ async function handleExitChannel({ args, provider }) {
|
|
|
4733
4789
|
].join(" "),
|
|
4734
4790
|
);
|
|
4735
4791
|
const [refundAmount, refundBps] = await context.channelManager.getExitTollRefundQuote(ownerSigner.address);
|
|
4736
|
-
const receipt = await
|
|
4737
|
-
|
|
4738
|
-
|
|
4792
|
+
const receipt = await dryRunThenSubmitTransaction({
|
|
4793
|
+
operationName: "channel exit",
|
|
4794
|
+
call: contractTxCall(
|
|
4795
|
+
context.bridgeTokenVault.connect(ownerSigner).exitChannel,
|
|
4796
|
+
[ethers.toBigInt(context.workspace.channelId)],
|
|
4797
|
+
undefined,
|
|
4798
|
+
context.bridgeTokenVault.interface,
|
|
4799
|
+
),
|
|
4800
|
+
});
|
|
4739
4801
|
const lifecycleEpoch = await markWalletEpochExited({
|
|
4740
4802
|
walletContext,
|
|
4741
4803
|
receipt,
|
|
4742
4804
|
provider,
|
|
4743
4805
|
});
|
|
4744
4806
|
|
|
4745
|
-
|
|
4807
|
+
cliOutput.result({
|
|
4746
4808
|
action: "channel exit",
|
|
4747
4809
|
wallet: walletContext.walletName,
|
|
4748
4810
|
network: walletMetadata.network,
|
|
@@ -4867,16 +4929,22 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
4867
4929
|
});
|
|
4868
4930
|
|
|
4869
4931
|
const methodName = direction === "deposit" ? "depositToChannelVault" : "withdrawFromChannelVault";
|
|
4870
|
-
await assertWorkspaceAlignedWithChain(context);
|
|
4871
4932
|
emitProgress(operationName, "submitting");
|
|
4872
|
-
const receipt = await
|
|
4933
|
+
const receipt = await dryRunThenSubmitTransaction({
|
|
4934
|
+
operationName,
|
|
4873
4935
|
context,
|
|
4874
4936
|
walletName: walletContext.walletName,
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4937
|
+
operationDir,
|
|
4938
|
+
precheck: () => precheckGrothRootUpdate({ context, transition, operationName }),
|
|
4939
|
+
call: contractTxCall(
|
|
4940
|
+
bridgeTokenVault[methodName],
|
|
4941
|
+
[
|
|
4942
|
+
ethers.toBigInt(context.workspace.channelId),
|
|
4943
|
+
transition.proof,
|
|
4944
|
+
transition.update,
|
|
4945
|
+
],
|
|
4946
|
+
undefined,
|
|
4947
|
+
bridgeTokenVault.interface,
|
|
4880
4948
|
),
|
|
4881
4949
|
});
|
|
4882
4950
|
const onchainRootVectorHash = normalizeBytes32Hex(await context.channelManager.currentRootVectorHash());
|
|
@@ -4900,7 +4968,7 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
4900
4968
|
});
|
|
4901
4969
|
|
|
4902
4970
|
emitProgress(operationName, "done");
|
|
4903
|
-
|
|
4971
|
+
cliOutput.result({
|
|
4904
4972
|
action: operationName,
|
|
4905
4973
|
workspace: context.workspaceName,
|
|
4906
4974
|
wallet: walletContext.walletName,
|
|
@@ -4934,9 +5002,17 @@ async function handleWithdrawBridge({ args, network, provider }) {
|
|
|
4934
5002
|
bridgeVaultContext.bridgeAbiManifest.contracts.bridgeTokenVault.abi,
|
|
4935
5003
|
signer,
|
|
4936
5004
|
);
|
|
4937
|
-
const receipt = await
|
|
5005
|
+
const receipt = await dryRunThenSubmitTransaction({
|
|
5006
|
+
operationName: "account withdraw-bridge",
|
|
5007
|
+
call: contractTxCall(
|
|
5008
|
+
bridgeTokenVault.claimToWallet,
|
|
5009
|
+
[amount],
|
|
5010
|
+
undefined,
|
|
5011
|
+
bridgeTokenVault.interface,
|
|
5012
|
+
),
|
|
5013
|
+
});
|
|
4938
5014
|
|
|
4939
|
-
|
|
5015
|
+
cliOutput.result({
|
|
4940
5016
|
action: "account withdraw-bridge",
|
|
4941
5017
|
l1Address: signer.address,
|
|
4942
5018
|
amountInput,
|
|
@@ -5067,7 +5143,7 @@ async function handleMintNotes({ args, provider }) {
|
|
|
5067
5143
|
preparedContextResult,
|
|
5068
5144
|
});
|
|
5069
5145
|
|
|
5070
|
-
|
|
5146
|
+
cliOutput.result({
|
|
5071
5147
|
action: "wallet mint-notes",
|
|
5072
5148
|
wallet: wallet.walletName,
|
|
5073
5149
|
workspace: execution.context.workspaceName,
|
|
@@ -5142,7 +5218,7 @@ async function handleRedeemNotes({ args, provider }) {
|
|
|
5142
5218
|
preparedContextResult,
|
|
5143
5219
|
});
|
|
5144
5220
|
|
|
5145
|
-
|
|
5221
|
+
cliOutput.result({
|
|
5146
5222
|
action: "wallet redeem-notes",
|
|
5147
5223
|
wallet: wallet.walletName,
|
|
5148
5224
|
workspace: execution.context.workspaceName,
|
|
@@ -5234,7 +5310,7 @@ async function handleWalletGetNotes({ args, provider }) {
|
|
|
5234
5310
|
})
|
|
5235
5311
|
: null;
|
|
5236
5312
|
|
|
5237
|
-
|
|
5313
|
+
cliOutput.result({
|
|
5238
5314
|
action: "wallet get-notes",
|
|
5239
5315
|
wallet: wallet.walletName,
|
|
5240
5316
|
network: walletMetadata.network,
|
|
@@ -6002,7 +6078,7 @@ async function handleTransferNotes({ args, provider }) {
|
|
|
6002
6078
|
counterpartyDirection: "sent",
|
|
6003
6079
|
});
|
|
6004
6080
|
|
|
6005
|
-
|
|
6081
|
+
cliOutput.result({
|
|
6006
6082
|
action: "wallet transfer-notes",
|
|
6007
6083
|
wallet: wallet.walletName,
|
|
6008
6084
|
workspace: execution.context.workspaceName,
|
|
@@ -7656,7 +7732,7 @@ async function executeWalletTemplateSend({
|
|
|
7656
7732
|
functionName,
|
|
7657
7733
|
templatePayload,
|
|
7658
7734
|
}) {
|
|
7659
|
-
await assertWorkspaceAlignedWithChain(context
|
|
7735
|
+
await assertWorkspaceAlignedWithChain(context);
|
|
7660
7736
|
assertWalletMatchesChannelContext(wallet, l2Identity, context);
|
|
7661
7737
|
await assertChannelProofBackendVersionCompatibility({ context, operationName });
|
|
7662
7738
|
|
|
@@ -7712,19 +7788,26 @@ async function executeWalletTemplateSend({
|
|
|
7712
7788
|
expectedFunctionRoot: context.workspace.functionRoot ?? context.workspace.policySnapshot?.functionRoot,
|
|
7713
7789
|
});
|
|
7714
7790
|
const aPubBlockHash = hashTokamakPublicInputs(payload.aPubBlock);
|
|
7715
|
-
expect(
|
|
7716
|
-
ethers.toBigInt(normalizeBytes32Hex(aPubBlockHash))
|
|
7717
|
-
=== ethers.toBigInt(normalizeBytes32Hex(context.workspace.aPubBlockHash)),
|
|
7718
|
-
"Generated Tokamak proof does not match the channel aPubBlockHash. Check the workspace block_info.json context.",
|
|
7719
|
-
);
|
|
7720
7791
|
|
|
7721
|
-
await assertWorkspaceAlignedWithChain(context);
|
|
7722
7792
|
emitProgress(operationName, "submitting");
|
|
7723
|
-
const receipt = await
|
|
7793
|
+
const receipt = await dryRunThenSubmitTransaction({
|
|
7794
|
+
operationName,
|
|
7724
7795
|
context,
|
|
7725
7796
|
walletName: wallet.walletName,
|
|
7726
|
-
|
|
7727
|
-
|
|
7797
|
+
operationDir,
|
|
7798
|
+
precheck: () => precheckTokamakExecution({
|
|
7799
|
+
context,
|
|
7800
|
+
payload,
|
|
7801
|
+
functionProof,
|
|
7802
|
+
aPubBlockHash,
|
|
7803
|
+
operationName,
|
|
7804
|
+
}),
|
|
7805
|
+
call: contractTxCall(
|
|
7806
|
+
context.channelManager.connect(txSubmitter).executeChannelTransaction,
|
|
7807
|
+
[payload, functionProof],
|
|
7808
|
+
undefined,
|
|
7809
|
+
context.channelManager.interface,
|
|
7810
|
+
),
|
|
7728
7811
|
});
|
|
7729
7812
|
await waitForProviderBlockAtLeast(provider, receipt.blockNumber, { action: operationName });
|
|
7730
7813
|
|
|
@@ -8399,25 +8482,219 @@ function isUnexpectedCurrentRootVectorError(error, context) {
|
|
|
8399
8482
|
return String(error?.message ?? error).includes("UnexpectedCurrentRootVector");
|
|
8400
8483
|
}
|
|
8401
8484
|
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8485
|
+
function precheckGrothRootUpdate({ context, transition, operationName }) {
|
|
8486
|
+
expect(transition && typeof transition === "object", `${operationName} proof precheck failed: missing transition.`);
|
|
8487
|
+
expect(transition.proof && typeof transition.proof === "object", `${operationName} proof precheck failed: missing Groth16 proof.`);
|
|
8488
|
+
expect(transition.update && typeof transition.update === "object", `${operationName} proof precheck failed: missing root update.`);
|
|
8489
|
+
expect(
|
|
8490
|
+
Array.isArray(transition.update.currentRootVector) && transition.update.currentRootVector.length > 0,
|
|
8491
|
+
`${operationName} proof precheck failed: missing current root vector.`,
|
|
8492
|
+
);
|
|
8493
|
+
expect(
|
|
8494
|
+
normalizeBytes32Hex(hashRootVector(transition.update.currentRootVector))
|
|
8495
|
+
=== normalizeBytes32Hex(hashRootVector(context.currentSnapshot.stateRoots)),
|
|
8496
|
+
`${operationName} proof precheck failed: root update does not match the local workspace snapshot.`,
|
|
8497
|
+
);
|
|
8498
|
+
normalizeBytes32Hex(transition.update.updatedRoot);
|
|
8499
|
+
normalizeBytes32Hex(transition.update.currentUserKey);
|
|
8500
|
+
normalizeBytes32Hex(transition.update.updatedUserKey);
|
|
8501
|
+
expect(transition.nextSnapshot?.stateRoots, `${operationName} proof precheck failed: missing next snapshot roots.`);
|
|
8502
|
+
}
|
|
8503
|
+
|
|
8504
|
+
function precheckTokamakExecution({ context, payload, functionProof, aPubBlockHash, operationName }) {
|
|
8505
|
+
expect(payload && typeof payload === "object", `${operationName} proof precheck failed: missing Tokamak payload.`);
|
|
8506
|
+
for (const key of [
|
|
8507
|
+
"proofPart1",
|
|
8508
|
+
"proofPart2",
|
|
8509
|
+
"functionPreprocessPart1",
|
|
8510
|
+
"functionPreprocessPart2",
|
|
8511
|
+
"aPubUser",
|
|
8512
|
+
"aPubBlock",
|
|
8513
|
+
]) {
|
|
8514
|
+
expect(Array.isArray(payload[key]) && payload[key].length > 0, `${operationName} proof precheck failed: payload.${key} is missing.`);
|
|
8515
|
+
}
|
|
8516
|
+
expect(functionProof?.metadata, `${operationName} proof precheck failed: missing function metadata proof.`);
|
|
8517
|
+
expect(Array.isArray(functionProof?.siblings), `${operationName} proof precheck failed: missing function metadata proof siblings.`);
|
|
8518
|
+
expect(
|
|
8519
|
+
ethers.toBigInt(normalizeBytes32Hex(aPubBlockHash))
|
|
8520
|
+
=== ethers.toBigInt(normalizeBytes32Hex(context.workspace.aPubBlockHash)),
|
|
8521
|
+
`${operationName} proof precheck failed: generated aPubBlockHash does not match the channel aPubBlockHash.`,
|
|
8522
|
+
);
|
|
8523
|
+
}
|
|
8524
|
+
|
|
8525
|
+
function contractTxCall(contractMethod, args = [], overrides = undefined, contractInterface = null) {
|
|
8526
|
+
expect(typeof contractMethod === "function", "Internal error: contract transaction method must be callable.");
|
|
8527
|
+
expect(
|
|
8528
|
+
typeof contractMethod.staticCall === "function",
|
|
8529
|
+
"Internal error: contract transaction method must support staticCall.",
|
|
8530
|
+
);
|
|
8531
|
+
const finalArgs = overrides === undefined ? [...args] : [...args, overrides];
|
|
8532
|
+
return {
|
|
8533
|
+
contractInterface,
|
|
8534
|
+
dryRun: () => contractMethod.staticCall(...finalArgs),
|
|
8535
|
+
submit: () => contractMethod(...finalArgs),
|
|
8536
|
+
};
|
|
8537
|
+
}
|
|
8538
|
+
|
|
8539
|
+
async function dryRunThenSubmitTransaction({
|
|
8405
8540
|
operationName,
|
|
8406
|
-
|
|
8541
|
+
call,
|
|
8542
|
+
precheck = null,
|
|
8543
|
+
context = null,
|
|
8544
|
+
walletName = null,
|
|
8545
|
+
operationDir = null,
|
|
8546
|
+
submittedBefore = [],
|
|
8407
8547
|
}) {
|
|
8548
|
+
expect(typeof call?.dryRun === "function", "Internal error: transaction dry-run callback is required.");
|
|
8549
|
+
expect(typeof call?.submit === "function", "Internal error: transaction submit callback is required.");
|
|
8550
|
+
if (precheck) {
|
|
8551
|
+
await precheck();
|
|
8552
|
+
}
|
|
8408
8553
|
try {
|
|
8409
|
-
|
|
8554
|
+
await call.dryRun();
|
|
8410
8555
|
} catch (error) {
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8556
|
+
throw transactionPreflightOrSubmitError({
|
|
8557
|
+
phase: "dry-run",
|
|
8558
|
+
operationName,
|
|
8559
|
+
cause: error,
|
|
8560
|
+
context,
|
|
8561
|
+
walletName,
|
|
8562
|
+
operationDir,
|
|
8563
|
+
submittedBefore,
|
|
8564
|
+
contractInterface: call.contractInterface,
|
|
8565
|
+
});
|
|
8566
|
+
}
|
|
8567
|
+
try {
|
|
8568
|
+
return await waitForReceipt(await call.submit());
|
|
8569
|
+
} catch (error) {
|
|
8570
|
+
throw transactionPreflightOrSubmitError({
|
|
8571
|
+
phase: "submit",
|
|
8572
|
+
operationName,
|
|
8573
|
+
cause: error,
|
|
8574
|
+
context,
|
|
8575
|
+
walletName,
|
|
8576
|
+
operationDir,
|
|
8577
|
+
submittedBefore,
|
|
8578
|
+
contractInterface: call.contractInterface,
|
|
8579
|
+
});
|
|
8580
|
+
}
|
|
8581
|
+
}
|
|
8582
|
+
|
|
8583
|
+
function transactionPreflightOrSubmitError({
|
|
8584
|
+
phase,
|
|
8585
|
+
operationName,
|
|
8586
|
+
cause,
|
|
8587
|
+
context = null,
|
|
8588
|
+
walletName = null,
|
|
8589
|
+
operationDir = null,
|
|
8590
|
+
submittedBefore = [],
|
|
8591
|
+
contractInterface = null,
|
|
8592
|
+
}) {
|
|
8593
|
+
if (context && isUnexpectedCurrentRootVectorError(cause, context)) {
|
|
8594
|
+
return staleChannelRootError({
|
|
8595
|
+
cause,
|
|
8596
|
+
context,
|
|
8597
|
+
walletName,
|
|
8598
|
+
operationName,
|
|
8599
|
+
phase,
|
|
8600
|
+
});
|
|
8601
|
+
}
|
|
8602
|
+
const decodedError = decodeTransactionContractError(cause, [
|
|
8603
|
+
contractInterface,
|
|
8604
|
+
context?.channelManager?.interface,
|
|
8605
|
+
context?.bridgeTokenVault?.interface,
|
|
8606
|
+
]);
|
|
8607
|
+
const details = [
|
|
8608
|
+
phase === "dry-run"
|
|
8609
|
+
? `${operationName} pre-submit dry-run failed. No ${operationName} transaction was submitted.`
|
|
8610
|
+
: `${operationName} transaction submission failed.`,
|
|
8611
|
+
];
|
|
8612
|
+
const submitted = normalizeSubmittedBefore(submittedBefore);
|
|
8613
|
+
if (submitted.length > 0) {
|
|
8614
|
+
details.push(`Already submitted before this failure: ${submitted.join("; ")}.`);
|
|
8615
|
+
}
|
|
8616
|
+
if (walletName) {
|
|
8617
|
+
details.push(`Wallet: ${walletName}.`);
|
|
8618
|
+
}
|
|
8619
|
+
if (operationDir) {
|
|
8620
|
+
details.push(`Operation directory: ${operationDir}.`);
|
|
8621
|
+
}
|
|
8622
|
+
if (decodedError) {
|
|
8623
|
+
details.push(`Decoded contract error: ${decodedError}.`);
|
|
8624
|
+
}
|
|
8625
|
+
details.push(`Provider error: ${extractProviderErrorMessage(cause)}.`);
|
|
8626
|
+
const error = cliError(
|
|
8627
|
+
phase === "dry-run" ? CLI_ERROR_CODES.TX_DRY_RUN_FAILED : CLI_ERROR_CODES.TX_SUBMIT_FAILED,
|
|
8628
|
+
details.join(" "),
|
|
8629
|
+
{ cause },
|
|
8630
|
+
);
|
|
8631
|
+
error.phase = phase;
|
|
8632
|
+
error.operationName = operationName;
|
|
8633
|
+
error.transactionSubmitted = phase !== "dry-run";
|
|
8634
|
+
error.submittedBefore = submitted;
|
|
8635
|
+
error.walletName = walletName;
|
|
8636
|
+
error.operationDir = operationDir;
|
|
8637
|
+
error.decodedContractError = decodedError;
|
|
8638
|
+
error.providerError = extractProviderErrorMessage(cause);
|
|
8639
|
+
if (context) {
|
|
8640
|
+
error.channelName = context.workspace?.channelName;
|
|
8641
|
+
error.networkName = context.workspace?.network;
|
|
8642
|
+
}
|
|
8643
|
+
return error;
|
|
8644
|
+
}
|
|
8645
|
+
|
|
8646
|
+
function submittedReceiptSummary(label, receipt) {
|
|
8647
|
+
return `${label} tx ${receipt?.hash ?? "<unknown>"} in block ${receipt?.blockNumber ?? "<unknown>"}`;
|
|
8648
|
+
}
|
|
8649
|
+
|
|
8650
|
+
function normalizeSubmittedBefore(entries) {
|
|
8651
|
+
return entries
|
|
8652
|
+
.filter(Boolean)
|
|
8653
|
+
.map((entry) => {
|
|
8654
|
+
if (typeof entry === "string") {
|
|
8655
|
+
return entry;
|
|
8656
|
+
}
|
|
8657
|
+
if (entry?.hash) {
|
|
8658
|
+
return submittedReceiptSummary(entry.label ?? "transaction", entry);
|
|
8659
|
+
}
|
|
8660
|
+
return String(entry);
|
|
8661
|
+
});
|
|
8662
|
+
}
|
|
8663
|
+
|
|
8664
|
+
function decodeTransactionContractError(error, contractInterfaces) {
|
|
8665
|
+
if (error?.revert?.name) {
|
|
8666
|
+
return formatDecodedContractError(error.revert.name, error.revert.args ?? []);
|
|
8667
|
+
}
|
|
8668
|
+
for (const contractInterface of contractInterfaces.filter(Boolean)) {
|
|
8669
|
+
for (const errorData of extractContractErrorDataCandidates(error)) {
|
|
8670
|
+
try {
|
|
8671
|
+
const parsed = contractInterface.parseError(errorData);
|
|
8672
|
+
if (parsed) {
|
|
8673
|
+
return formatDecodedContractError(parsed.name, parsed.args ?? []);
|
|
8674
|
+
}
|
|
8675
|
+
} catch {
|
|
8676
|
+
// Keep scanning provider error payloads and interfaces.
|
|
8677
|
+
}
|
|
8418
8678
|
}
|
|
8419
|
-
throw error;
|
|
8420
8679
|
}
|
|
8680
|
+
return null;
|
|
8681
|
+
}
|
|
8682
|
+
|
|
8683
|
+
function formatDecodedContractError(name, args) {
|
|
8684
|
+
const renderedArgs = Array.from(args ?? [])
|
|
8685
|
+
.map((value) => serializeBigInts(normalizeCliOutputValue(value, [])));
|
|
8686
|
+
return `${name}(${renderedArgs.map((value) => JSON.stringify(value)).join(", ")})`;
|
|
8687
|
+
}
|
|
8688
|
+
|
|
8689
|
+
function extractProviderErrorMessage(error) {
|
|
8690
|
+
return String(
|
|
8691
|
+
error?.shortMessage
|
|
8692
|
+
?? error?.reason
|
|
8693
|
+
?? error?.info?.error?.message
|
|
8694
|
+
?? error?.error?.message
|
|
8695
|
+
?? error?.message
|
|
8696
|
+
?? error,
|
|
8697
|
+
);
|
|
8421
8698
|
}
|
|
8422
8699
|
|
|
8423
8700
|
function staleChannelRootError({
|
|
@@ -8425,17 +8702,24 @@ function staleChannelRootError({
|
|
|
8425
8702
|
context,
|
|
8426
8703
|
walletName,
|
|
8427
8704
|
operationName,
|
|
8705
|
+
phase = "submit",
|
|
8428
8706
|
}) {
|
|
8429
8707
|
const message = [
|
|
8430
|
-
|
|
8708
|
+
phase === "dry-run"
|
|
8709
|
+
? `${operationName} pre-submit dry-run failed because the generated proof targets an older channel root. No ${operationName} transaction was submitted.`
|
|
8710
|
+
: `${operationName} failed because the submitted proof was generated for an older channel root.`,
|
|
8431
8711
|
"The rejected proof cannot be reused.",
|
|
8432
8712
|
"Do not change recipients, amounts, note counts, function arity, or split the command as recovery.",
|
|
8433
8713
|
"Refresh the channel workspace, re-check affected wallet state when the command uses notes, then rerun the original intended command so the CLI regenerates a proof from a fresh snapshot.",
|
|
8434
8714
|
].join(" ");
|
|
8435
8715
|
const error = cliError(CLI_ERROR_CODES.STALE_CHANNEL_ROOT, message, { cause });
|
|
8716
|
+
error.phase = phase;
|
|
8717
|
+
error.operationName = operationName;
|
|
8718
|
+
error.transactionSubmitted = phase !== "dry-run";
|
|
8436
8719
|
error.channelName = context.workspace.channelName;
|
|
8437
8720
|
error.networkName = context.workspace.network;
|
|
8438
8721
|
error.walletName = walletName;
|
|
8722
|
+
error.providerError = extractProviderErrorMessage(cause);
|
|
8439
8723
|
error.retryPolicy = "recover_workspace_then_regenerate_proof";
|
|
8440
8724
|
error.semanticMutationAllowed = false;
|
|
8441
8725
|
error.reuseProofAllowed = false;
|
|
@@ -9742,15 +10026,11 @@ function assertVersionArgs(args) {
|
|
|
9742
10026
|
}
|
|
9743
10027
|
|
|
9744
10028
|
function printVersion() {
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
});
|
|
9751
|
-
return;
|
|
9752
|
-
}
|
|
9753
|
-
console.log(privateStateCliPackageJson.version);
|
|
10029
|
+
cliOutput.result({
|
|
10030
|
+
action: "version",
|
|
10031
|
+
packageName: privateStateCliPackageJson.name,
|
|
10032
|
+
version: privateStateCliPackageJson.version,
|
|
10033
|
+
});
|
|
9754
10034
|
}
|
|
9755
10035
|
|
|
9756
10036
|
function requireWorkspaceName(args) {
|
|
@@ -10795,38 +11075,53 @@ function buildWalletViewingKeyMetadata(wallet) {
|
|
|
10795
11075
|
}
|
|
10796
11076
|
|
|
10797
11077
|
function printHelp() {
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
` ${command.description}`,
|
|
10801
|
-
...(command.help ?? []).map((line) => ` ${line}`),
|
|
10802
|
-
].join("\n")).join("\n\n");
|
|
10803
|
-
console.log(`
|
|
10804
|
-
Commands:
|
|
10805
|
-
${commandHelp}
|
|
10806
|
-
|
|
10807
|
-
Secret source options:
|
|
10808
|
-
Use account import --private-key-file once to create a protected local account secret.
|
|
10809
|
-
L1 signing commands use --account only.
|
|
10810
|
-
A wallet secret source file is arbitrary high-entropy secret text read once by channel join.
|
|
10811
|
-
Create one before joining a channel, for example:
|
|
10812
|
-
openssl rand -hex 32 > ./wallet-secret.txt
|
|
10813
|
-
private-state-cli channel join --channel-name <NAME> --network <NAME> --account <NAME> --wallet-secret-path ./wallet-secret.txt --acknowledge-action-impact
|
|
10814
|
-
Configure each network RPC endpoint once with set rpc. The CLI reads RPC_URL, LOG_CHUNK_SIZE,
|
|
10815
|
-
and LOG_REQUESTS_PER_SECOND from ~/tokamak-private-channels/workspace/<network>/rpc-config.env.
|
|
10816
|
-
Wallet commands use separate protected viewing-key and spending-key files when those capabilities are needed.
|
|
10817
|
-
Source files passed to --private-key-file and --wallet-secret-path are not required to use 0600 permissions, but
|
|
10818
|
-
canonical CLI secret files remain protected. On macOS/Linux this means 0600; on Windows the CLI repairs ACLs when possible.
|
|
10819
|
-
|
|
10820
|
-
Options:
|
|
10821
|
-
--version
|
|
10822
|
-
Print the private-state CLI package version and exit.
|
|
11078
|
+
cliOutput.result(buildHelpCommandsResult());
|
|
11079
|
+
}
|
|
10823
11080
|
|
|
10824
|
-
|
|
10825
|
-
|
|
11081
|
+
function buildHelpCommandsResult() {
|
|
11082
|
+
return {
|
|
11083
|
+
action: "help commands",
|
|
11084
|
+
commands: PRIVATE_STATE_CLI_COMMANDS.map(buildHelpCommandEntry),
|
|
11085
|
+
secretSourceOptions: [
|
|
11086
|
+
"Use account import --private-key-file once to create a protected local account secret.",
|
|
11087
|
+
"L1 signing commands use --account only.",
|
|
11088
|
+
"A wallet secret source file is arbitrary high-entropy secret text read once by channel join.",
|
|
11089
|
+
"Configure each network RPC endpoint once with set rpc.",
|
|
11090
|
+
"Wallet commands use separate protected viewing-key and spending-key files when those capabilities are needed.",
|
|
11091
|
+
"Source files passed to --private-key-file and --wallet-secret-path are not required to use 0600 permissions, but canonical CLI secret files remain protected.",
|
|
11092
|
+
],
|
|
11093
|
+
globalOptions: [
|
|
11094
|
+
{
|
|
11095
|
+
option: "--version",
|
|
11096
|
+
description: "Print the private-state CLI package version and exit.",
|
|
11097
|
+
},
|
|
11098
|
+
{
|
|
11099
|
+
option: "--json",
|
|
11100
|
+
description: "Print the final success or failure result as JSON on stdout. Progress, warning, and info events are JSONL on stderr.",
|
|
11101
|
+
},
|
|
11102
|
+
{
|
|
11103
|
+
option: "--help",
|
|
11104
|
+
description: "Show this help. Equivalent to help commands.",
|
|
11105
|
+
},
|
|
11106
|
+
],
|
|
11107
|
+
};
|
|
11108
|
+
}
|
|
10826
11109
|
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
11110
|
+
function buildHelpCommandEntry(command) {
|
|
11111
|
+
const requiredFields = privateStateCliCommandRequiredOptionKeys(command);
|
|
11112
|
+
const fields = command.fields ?? [];
|
|
11113
|
+
return {
|
|
11114
|
+
id: command.id,
|
|
11115
|
+
display: privateStateCliCommandDisplay(command),
|
|
11116
|
+
synopsis: privateStateCliCommandSynopsis(command),
|
|
11117
|
+
description: command.description,
|
|
11118
|
+
usage: command.usage,
|
|
11119
|
+
fields,
|
|
11120
|
+
requiredFields,
|
|
11121
|
+
optionalFields: fields.filter((field) => !requiredFields.includes(field)),
|
|
11122
|
+
installMode: privateStateCliCommandInstallMode(command),
|
|
11123
|
+
help: command.help ?? [],
|
|
11124
|
+
};
|
|
10830
11125
|
}
|
|
10831
11126
|
|
|
10832
11127
|
function readJson(filePath) {
|
|
@@ -11350,29 +11645,122 @@ function loadWalletCommandRuntime(args, { prepareArtifacts = false } = {}) {
|
|
|
11350
11645
|
}
|
|
11351
11646
|
|
|
11352
11647
|
const HUMAN_RESULT_RENDERERS = Object.freeze({
|
|
11648
|
+
doctor: printDoctorHumanReport,
|
|
11353
11649
|
guide: printGuideHumanResult,
|
|
11650
|
+
"help commands": printHelpCommandsHumanResult,
|
|
11354
11651
|
investigator: printInvestigatorHumanResult,
|
|
11355
11652
|
observer: printObserverHumanResult,
|
|
11356
11653
|
"transaction-fees": printTransactionFeesHumanResult,
|
|
11357
11654
|
update: printUpdateHumanResult,
|
|
11655
|
+
version: printVersionHumanResult,
|
|
11358
11656
|
});
|
|
11359
11657
|
|
|
11360
11658
|
function normalizePrivateKey(value) {
|
|
11361
11659
|
return value.startsWith("0x") ? value : `0x${value}`;
|
|
11362
11660
|
}
|
|
11363
11661
|
|
|
11364
|
-
|
|
11365
|
-
|
|
11662
|
+
const cliOutput = Object.freeze({
|
|
11663
|
+
result(value) {
|
|
11664
|
+
const normalized = normalizeCliOutput(value);
|
|
11665
|
+
if (isJsonOutputRequested()) {
|
|
11666
|
+
console.log(JSON.stringify(buildJsonSuccessPayload(normalized), null, 2));
|
|
11667
|
+
return;
|
|
11668
|
+
}
|
|
11669
|
+
const renderer = HUMAN_RESULT_RENDERERS[normalized?.action];
|
|
11670
|
+
if (renderer) {
|
|
11671
|
+
renderer(normalized);
|
|
11672
|
+
return;
|
|
11673
|
+
}
|
|
11674
|
+
printHumanResult(normalized);
|
|
11675
|
+
},
|
|
11676
|
+
error(error, args = {}) {
|
|
11677
|
+
if (isJsonOutputRequested()) {
|
|
11678
|
+
console.log(JSON.stringify(normalizeCliOutput(buildJsonErrorPayload(error, args)), null, 2));
|
|
11679
|
+
return;
|
|
11680
|
+
}
|
|
11681
|
+
console.error(formatHumanError(error, args));
|
|
11682
|
+
},
|
|
11683
|
+
progress(action, phase, details = {}) {
|
|
11684
|
+
emitOutputEvent({
|
|
11685
|
+
event: "progress",
|
|
11686
|
+
action,
|
|
11687
|
+
phase,
|
|
11688
|
+
message: details.message ?? `[${action}] ${phase}`,
|
|
11689
|
+
details,
|
|
11690
|
+
});
|
|
11691
|
+
},
|
|
11692
|
+
warning(kind, message, details = {}) {
|
|
11693
|
+
emitOutputEvent({
|
|
11694
|
+
event: "warning",
|
|
11695
|
+
kind,
|
|
11696
|
+
message,
|
|
11697
|
+
details,
|
|
11698
|
+
});
|
|
11699
|
+
},
|
|
11700
|
+
});
|
|
11701
|
+
|
|
11702
|
+
function buildJsonSuccessPayload(value) {
|
|
11703
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
11704
|
+
return Object.hasOwn(value, "ok") ? value : { ok: true, ...value };
|
|
11705
|
+
}
|
|
11706
|
+
return {
|
|
11707
|
+
ok: true,
|
|
11708
|
+
action: "result",
|
|
11709
|
+
value,
|
|
11710
|
+
};
|
|
11711
|
+
}
|
|
11712
|
+
|
|
11713
|
+
function emitOutputEvent(event) {
|
|
11714
|
+
const normalized = normalizeCliOutput({
|
|
11715
|
+
timestamp: new Date().toISOString(),
|
|
11716
|
+
...event,
|
|
11717
|
+
});
|
|
11366
11718
|
if (isJsonOutputRequested()) {
|
|
11367
|
-
console.
|
|
11719
|
+
console.error(JSON.stringify(normalized));
|
|
11368
11720
|
return;
|
|
11369
11721
|
}
|
|
11370
|
-
const
|
|
11371
|
-
if (
|
|
11372
|
-
|
|
11722
|
+
const message = event.message ?? `[${event.action ?? event.kind ?? "cli"}] ${event.phase ?? event.event}`;
|
|
11723
|
+
if (event.event === "warning") {
|
|
11724
|
+
console.error(message);
|
|
11373
11725
|
return;
|
|
11374
11726
|
}
|
|
11375
|
-
|
|
11727
|
+
console.log(message);
|
|
11728
|
+
}
|
|
11729
|
+
|
|
11730
|
+
function printVersionHumanResult(result) {
|
|
11731
|
+
console.log(result.version);
|
|
11732
|
+
}
|
|
11733
|
+
|
|
11734
|
+
function printHelpCommandsHumanResult(help) {
|
|
11735
|
+
const commandHelp = (help.commands ?? []).map((command) => [
|
|
11736
|
+
` ${command.synopsis}`,
|
|
11737
|
+
` ${command.description}`,
|
|
11738
|
+
...(command.help ?? []).map((line) => ` ${line}`),
|
|
11739
|
+
].join("\n")).join("\n\n");
|
|
11740
|
+
const globalOptions = (help.globalOptions ?? []).map((option) => [
|
|
11741
|
+
` ${option.option}`,
|
|
11742
|
+
` ${option.description}`,
|
|
11743
|
+
].join("\n")).join("\n\n");
|
|
11744
|
+
console.log(`
|
|
11745
|
+
Commands:
|
|
11746
|
+
${commandHelp}
|
|
11747
|
+
|
|
11748
|
+
Secret source options:
|
|
11749
|
+
Use account import --private-key-file once to create a protected local account secret.
|
|
11750
|
+
L1 signing commands use --account only.
|
|
11751
|
+
A wallet secret source file is arbitrary high-entropy secret text read once by channel join.
|
|
11752
|
+
Create one before joining a channel, for example:
|
|
11753
|
+
openssl rand -hex 32 > ./wallet-secret.txt
|
|
11754
|
+
private-state-cli channel join --channel-name <NAME> --network <NAME> --account <NAME> --wallet-secret-path ./wallet-secret.txt --acknowledge-action-impact
|
|
11755
|
+
Configure each network RPC endpoint once with set rpc. The CLI reads RPC_URL, LOG_CHUNK_SIZE,
|
|
11756
|
+
and LOG_REQUESTS_PER_SECOND from ~/tokamak-private-channels/workspace/<network>/rpc-config.env.
|
|
11757
|
+
Wallet commands use separate protected viewing-key and spending-key files when those capabilities are needed.
|
|
11758
|
+
Source files passed to --private-key-file and --wallet-secret-path are not required to use 0600 permissions, but
|
|
11759
|
+
canonical CLI secret files remain protected. On macOS/Linux this means 0600; on Windows the CLI repairs ACLs when possible.
|
|
11760
|
+
|
|
11761
|
+
Options:
|
|
11762
|
+
${globalOptions}
|
|
11763
|
+
`);
|
|
11376
11764
|
}
|
|
11377
11765
|
|
|
11378
11766
|
function printGuideHumanResult(guide) {
|
|
@@ -11604,12 +11992,7 @@ function humanizeLabel(value) {
|
|
|
11604
11992
|
}
|
|
11605
11993
|
|
|
11606
11994
|
function emitProgress(action, phase) {
|
|
11607
|
-
|
|
11608
|
-
if (isJsonOutputRequested()) {
|
|
11609
|
-
console.error(line);
|
|
11610
|
-
} else {
|
|
11611
|
-
console.log(line);
|
|
11612
|
-
}
|
|
11995
|
+
cliOutput.progress(action, phase);
|
|
11613
11996
|
}
|
|
11614
11997
|
|
|
11615
11998
|
function createByteDownloadProgress({ action, label, url }) {
|
|
@@ -11745,7 +12128,7 @@ function createRpcLogScanProgress({ action, label }) {
|
|
|
11745
12128
|
};
|
|
11746
12129
|
}
|
|
11747
12130
|
|
|
11748
|
-
function
|
|
12131
|
+
function formatHumanError(error, args = {}) {
|
|
11749
12132
|
const message = String(error?.message ?? error);
|
|
11750
12133
|
const hints = buildRecoveryHints(error, args);
|
|
11751
12134
|
if (hints.length === 0) {
|
|
@@ -11758,6 +12141,41 @@ function formatCliErrorForDisplay(error, args = {}) {
|
|
|
11758
12141
|
].join("\n");
|
|
11759
12142
|
}
|
|
11760
12143
|
|
|
12144
|
+
function buildJsonErrorPayload(error, args = {}) {
|
|
12145
|
+
const message = String(error?.message ?? error);
|
|
12146
|
+
const hints = buildRecoveryHints(error, args);
|
|
12147
|
+
return {
|
|
12148
|
+
ok: false,
|
|
12149
|
+
action: "error",
|
|
12150
|
+
error: {
|
|
12151
|
+
name: error?.name ?? "Error",
|
|
12152
|
+
code: error?.code ?? "ERROR",
|
|
12153
|
+
command: typeof args.command === "string" ? args.command : null,
|
|
12154
|
+
message,
|
|
12155
|
+
hints,
|
|
12156
|
+
phase: error?.phase ?? null,
|
|
12157
|
+
operationName: error?.operationName ?? null,
|
|
12158
|
+
transactionSubmitted: typeof error?.transactionSubmitted === "boolean"
|
|
12159
|
+
? error.transactionSubmitted
|
|
12160
|
+
: null,
|
|
12161
|
+
submittedBefore: Array.isArray(error?.submittedBefore) ? error.submittedBefore : [],
|
|
12162
|
+
decodedContractError: error?.decodedContractError ?? null,
|
|
12163
|
+
providerError: error?.providerError ?? null,
|
|
12164
|
+
channelName: error?.channelName ?? (typeof args.channelName === "string" ? args.channelName : null),
|
|
12165
|
+
networkName: error?.networkName ?? (typeof args.network === "string" ? args.network : null),
|
|
12166
|
+
walletName: error?.walletName ?? (typeof args.wallet === "string" ? args.wallet : null),
|
|
12167
|
+
operationDir: error?.operationDir ?? null,
|
|
12168
|
+
retryPolicy: error?.retryPolicy ?? null,
|
|
12169
|
+
semanticMutationAllowed: typeof error?.semanticMutationAllowed === "boolean"
|
|
12170
|
+
? error.semanticMutationAllowed
|
|
12171
|
+
: null,
|
|
12172
|
+
reuseProofAllowed: typeof error?.reuseProofAllowed === "boolean"
|
|
12173
|
+
? error.reuseProofAllowed
|
|
12174
|
+
: null,
|
|
12175
|
+
},
|
|
12176
|
+
};
|
|
12177
|
+
}
|
|
12178
|
+
|
|
11761
12179
|
function buildRecoveryHints(error, args = {}) {
|
|
11762
12180
|
const message = String(error?.message ?? error);
|
|
11763
12181
|
const hints = [];
|
|
@@ -11950,5 +12368,5 @@ export {
|
|
|
11950
12368
|
loadExplicitCommandRuntime,
|
|
11951
12369
|
loadWalletCommandRuntime,
|
|
11952
12370
|
assertProviderChainIdMatchesNetwork,
|
|
11953
|
-
|
|
12371
|
+
cliOutput,
|
|
11954
12372
|
};
|
package/package.json
CHANGED