@tokamak-private-dapps/private-state-cli 2.1.0 → 2.1.2

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.
@@ -18,8 +18,34 @@ export const PRIVATE_STATE_CLI_FIELD_CATALOG = Object.freeze({
18
18
  type: "password",
19
19
  placeholder: "https://example-rpc",
20
20
  valueLabel: "<URL>",
21
- hint: "Optional. When omitted, the CLI reads RPC_URL from ~/tokamak-private-channels/secrets/<network>/.env.",
21
+ hint: "Required by set rpc. Saved to ~/tokamak-private-channels/workspace/<network>/rpc-config.env.",
22
22
  option: "--rpc-url",
23
+ },
24
+ provider: {
25
+ label: "RPC Provider",
26
+ type: "select",
27
+ options: ["alchemy", "ankr", "chainstack", "chainnodes", "quicknode"],
28
+ valueLabel: "<PROVIDER>",
29
+ hint: "Optional for set rpc. Uses the built-in provider table for eth_getLogs request rate and block range cap.",
30
+ option: "--provider",
31
+ optional: true,
32
+ },
33
+ logRequestsPerSecond: {
34
+ label: "Log Requests Per Second",
35
+ type: "text",
36
+ placeholder: "7.497",
37
+ valueLabel: "<N>",
38
+ hint: "Optional for set rpc. Required with --block-range-cap when --provider is not used.",
39
+ option: "--log-requests-per-second",
40
+ optional: true,
41
+ },
42
+ blockRangeCap: {
43
+ label: "Block Range Cap",
44
+ type: "text",
45
+ placeholder: "10",
46
+ valueLabel: "<N>",
47
+ hint: "Optional for set rpc. Required with --log-requests-per-second when --provider is not used.",
48
+ option: "--block-range-cap",
23
49
  optional: true,
24
50
  },
25
51
  account: {
@@ -236,6 +262,18 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
236
262
  fields: [],
237
263
  usage: "no options",
238
264
  },
265
+ {
266
+ id: "set-rpc",
267
+ display: "set rpc",
268
+ description: "Configure the network RPC URL and fixed eth_getLogs scan limits.",
269
+ fields: ["network", "rpcUrl", "provider", "logRequestsPerSecond", "blockRangeCap"],
270
+ usage: "--network, --rpc-url, and either --provider or both --log-requests-per-second and --block-range-cap",
271
+ help: [
272
+ "Writes ~/tokamak-private-channels/workspace/<network>/rpc-config.env",
273
+ "Built-in provider limits: ankr=27 calls/s and 3000 blocks, chainstack=22.5 calls/s and 100 blocks, chainnodes=22.5 calls/s and 20000 blocks, quicknode=13.5 calls/s and 5 blocks, alchemy=7.497 calls/s and 10 blocks",
274
+ "All bridge-facing and wallet commands read RPC settings from this file and do not accept --rpc-url",
275
+ ],
276
+ },
239
277
  {
240
278
  id: "help-commands",
241
279
  display: "help commands",
@@ -278,8 +316,8 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
278
316
  id: "help-transaction-fees",
279
317
  display: "help transaction-fees",
280
318
  description: "Estimate ETH and USD fees for transaction-sending commands from packaged measured gas data and live network fee data.",
281
- fields: ["network", "rpcUrl", "json"],
282
- usage: "--network, optional --rpc-url, and optional --json",
319
+ fields: ["network", "json"],
320
+ usage: "--network and optional --json",
283
321
  help: [
284
322
  "Uses packages/apps/private-state/cli/assets/tx-fees.json as the measured gas source packaged with the CLI",
285
323
  "Reads live fee data from the selected network RPC and live ETH/USD from CoinGecko",
@@ -314,15 +352,15 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
314
352
  id: "account-get-bridge-fund",
315
353
  display: "account get-bridge-fund",
316
354
  description: "Read the local account's current shared bridge vault balance.",
317
- fields: ["network", "account", "rpcUrl"],
318
- usage: "--network, --account, and optional --rpc-url",
355
+ fields: ["network", "account"],
356
+ usage: "--network, --account",
319
357
  },
320
358
  {
321
359
  id: "channel-create",
322
360
  display: "channel create",
323
361
  description: "Create a bridge channel and initialize its workspace.",
324
- fields: ["channelName", "joinToll", "network", "account", "rpcUrl"],
325
- usage: "--channel-name, --join-toll, --network, --account, and optional --rpc-url",
362
+ fields: ["channelName", "joinToll", "network", "account"],
363
+ usage: "--channel-name, --join-toll, --network, --account",
326
364
  help: [
327
365
  "Prints the immutable policy snapshot before sending the transaction",
328
366
  "Initializes the local channel workspace by replaying channel logs from channel genesis",
@@ -332,13 +370,16 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
332
370
  id: "channel-recover-workspace",
333
371
  display: "channel recover-workspace",
334
372
  description: "Rebuild the local channel workspace from bridge state.",
335
- fields: ["channelName", "network", "source", "fromGenesis", "rpcUrl"],
336
- usage: "--channel-name, --network, optional --source, optional --from-genesis, and optional --rpc-url",
373
+ fields: ["channelName", "network", "source", "fromGenesis"],
374
+ usage: "--channel-name, --network, optional --source, optional --from-genesis",
337
375
  help: [
338
376
  "By default, --source rpc resumes RPC log scanning from the workspace recovery index when available",
339
377
  "--source mirror validates the channel leader's registered checkpoint manifest, downloads only the needed checkpoint or delta bundle, and then replays RPC logs to latest",
378
+ "RPC recovery writes a usable channel workspace checkpoint after each RPC log chunk, so interrupted runs can resume from the last completed chunk",
379
+ "Mirror recovery uses a matching delta bundle when available; otherwise a newer verified full checkpoint replaces the local checkpoint before RPC catch-up",
340
380
  "Fails instead of falling back to genesis when no usable recovery index exists",
341
381
  "Use --source rpc --from-genesis to ignore the recovery index and replay logs from channel genesis",
382
+ "--from-genesis moves the existing local channel workspace to workspace-rebuild-backups before writing the current-format workspace; local secrets are preserved",
342
383
  "Prints RPC log scan progress while rebuilding the workspace",
343
384
  ],
344
385
  },
@@ -346,8 +387,8 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
346
387
  id: "channel-set-workspace-mirror",
347
388
  display: "channel set-workspace-mirror",
348
389
  description: "Register or update the channel leader's workspace mirror base URL.",
349
- fields: ["channelName", "network", "account", "url", "rpcUrl"],
350
- usage: "--channel-name, --network, --account, --url, and optional --rpc-url",
390
+ fields: ["channelName", "network", "account", "url"],
391
+ usage: "--channel-name, --network, --account, --url",
351
392
  help: [
352
393
  "Only the on-chain channel leader can update the registered mirror URL",
353
394
  "The URL points to a server implementing the private-state channel workspace mirror protocol",
@@ -357,8 +398,8 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
357
398
  id: "channel-publish-workspace-mirror",
358
399
  display: "channel publish-workspace-mirror",
359
400
  description: "Build static workspace mirror files for the registered mirror URL.",
360
- fields: ["channelName", "network", "account", "output", "force", "rpcUrl"],
361
- usage: "--channel-name, --network, --account, --output, optional --force, and optional --rpc-url",
401
+ fields: ["channelName", "network", "account", "output", "force"],
402
+ usage: "--channel-name, --network, --account, --output, optional --force",
362
403
  help: [
363
404
  "Requires the local channel workspace to be current and ahead of the registered mirror checkpoint",
364
405
  "--force ignores an unreadable or invalid existing mirror manifest and publishes a full checkpoint without a delta",
@@ -370,15 +411,15 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
370
411
  id: "channel-get-meta",
371
412
  display: "channel get-meta",
372
413
  description: "Read channel existence, manager, vault, toll, refund schedule, and immutable policy snapshot.",
373
- fields: ["channelName", "network", "rpcUrl"],
374
- usage: "--channel-name, --network, and optional --rpc-url",
414
+ fields: ["channelName", "network"],
415
+ usage: "--channel-name, --network",
375
416
  },
376
417
  {
377
418
  id: "account-deposit-bridge",
378
419
  display: "account deposit-bridge",
379
420
  description: "Deposit canonical tokens into the shared bridge vault.",
380
- fields: ["amount", "network", "account", "acknowledgeActionImpact", "rpcUrl"],
381
- usage: "--amount, --network, --account, --acknowledge-action-impact, and optional --rpc-url",
421
+ fields: ["amount", "network", "account", "acknowledgeActionImpact"],
422
+ usage: "--amount, --network, --account, --acknowledge-action-impact",
382
423
  help: [
383
424
  "Action impact: emits public L1 approval and bridge funding events that expose the local L1 account, bridge vault, amount, and transaction hashes.",
384
425
  "Private note state is not changed by this command.",
@@ -391,8 +432,8 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
391
432
  id: "account-withdraw-bridge",
392
433
  display: "account withdraw-bridge",
393
434
  description: "Withdraw tokens from the shared bridge vault back to the wallet.",
394
- fields: ["amount", "network", "account", "acknowledgeActionImpact", "rpcUrl"],
395
- usage: "--amount, --network, --account, --acknowledge-action-impact, and optional --rpc-url",
435
+ fields: ["amount", "network", "account", "acknowledgeActionImpact"],
436
+ usage: "--amount, --network, --account, --acknowledge-action-impact",
396
437
  help: [
397
438
  "Action impact: emits a public L1 bridge withdrawal event that exposes the local L1 recipient, bridge vault, amount, and transaction hash.",
398
439
  "Private note state is not changed by this command; prior note provenance is not public by default.",
@@ -405,23 +446,24 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
405
446
  id: "wallet-recover-workspace",
406
447
  display: "wallet recover-workspace",
407
448
  description: "Rebuild a recoverable local wallet from on-chain channel state.",
408
- fields: ["channelName", "network", "account", "fromGenesis", "rpcUrl"],
409
- usage: "--channel-name, --network, --account, optional --from-genesis, and optional --rpc-url",
449
+ fields: ["channelName", "network", "account", "fromGenesis"],
450
+ usage: "--channel-name, --network, --account, optional --from-genesis",
410
451
  help: [
411
452
  "Rebuilds backup metadata from channel state without recreating the spending key",
412
453
  "Derives and stores the viewing key when the local account signer can reproduce the registered viewing public key",
413
- "By default, resumes RPC log scanning from the workspace recovery index when available",
414
- "Fails instead of falling back to genesis when no usable recovery index exists",
415
- "Use --from-genesis to ignore the recovery index and replay channel logs from channel genesis",
416
- "Prints RPC log scan progress while rebuilding channel state and received-note state",
454
+ "Before wallet recovery, refreshes stale channel workspace state only when the saved recovery index delta fits the pre-command budget",
455
+ "Fails and asks for channel recover-workspace first when the channel workspace is missing, unusable, or too stale for automatic recovery",
456
+ "Use --from-genesis to restart received-note scanning from channel genesis; it does not rebuild the channel workspace from genesis",
457
+ "Received-note recovery checkpoints after each RPC log chunk during ordinary recovery; --from-genesis still starts received-note scanning from channel genesis",
458
+ "Prints RPC log scan progress while refreshing channel state and rebuilding received-note state",
417
459
  ],
418
460
  },
419
461
  {
420
462
  id: "channel-join",
421
463
  display: "channel join",
422
464
  description: "Pay the channel join toll and bind a wallet to a channel-specific L2 identity.",
423
- fields: ["channelName", "network", "account", "walletSecretPath", "acknowledgeActionImpact", "rpcUrl"],
424
- usage: "--channel-name, --network, --account, --wallet-secret-path, --acknowledge-action-impact, and optional --rpc-url",
465
+ fields: ["channelName", "network", "account", "walletSecretPath", "acknowledgeActionImpact"],
466
+ usage: "--channel-name, --network, --account, --wallet-secret-path, --acknowledge-action-impact",
425
467
  help: [
426
468
  "Refreshes the local channel workspace through the saved recovery index before joining when the scan fits the 10 second pre-command budget",
427
469
  "Fails instead of replaying from genesis; run channel recover-workspace --source rpc --from-genesis when a genesis rebuild is required",
@@ -564,6 +606,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
564
606
  usage: "--wallet, --network, --amounts, --acknowledge-action-impact, and optional --tx-submitter",
565
607
  help: [
566
608
  "Refreshes the local channel workspace through the saved recovery index before proving the mint when the scan fits the 10 second pre-command budget",
609
+ "Requires both viewing and spending key capability so the accepted mint can be recovered through the normal note event path",
567
610
  "Use --tx-submitter <ACCOUNT> to let a separate local L1 account pay gas for stronger transaction privacy",
568
611
  "Action impact: emits public accepted-transition, commitment, encrypted note-delivery, root update, and transaction events.",
569
612
  "Private note state changes by creating local note plaintext and public commitments; note owner/value/salt are not public by default.",
@@ -619,7 +662,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
619
662
  help: [
620
663
  "Refreshes the local channel workspace through the saved recovery index before reading notes when the scan fits the 10 second pre-command budget",
621
664
  "Refreshes received-note logs through the saved wallet note recovery index when the scan fits the 10 second pre-command budget",
622
- "Fails instead of replaying from genesis; run wallet recover-workspace --from-genesis when a genesis rebuild is required",
665
+ "Fails instead of replaying from genesis; run wallet recover-workspace first when explicit wallet recovery is required",
623
666
  "Use --export-evidence <PATH> with --acknowledge-full-note-plaintext-export to write a local full-note evidence ZIP for private-state-cli investigator",
624
667
  "Evidence export includes all local epochs for the selected wallet, including exited epochs retained for dispute evidence",
625
668
  ],
@@ -1,7 +1,12 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { AbiCoder, ethers } from "ethers";
3
- import { deriveL2KeysFromSignature, poseidon } from "tokamak-l2js";
3
+ import { deriveL2KeysFromSignature } from "tokamak-l2js";
4
4
  import { jubjub } from "@noble/curves/jubjub";
5
+ import {
6
+ normalizeBytesHex,
7
+ normalizeBytes32Hex,
8
+ poseidonHexFromBytes,
9
+ } from "@tokamak-private-dapps/common-library/tokamak-l2-helpers";
5
10
 
6
11
  const abiCoder = AbiCoder.defaultAbiCoder();
7
12
 
@@ -42,18 +47,6 @@ function expect(condition, message) {
42
47
  }
43
48
  }
44
49
 
45
- function normalizeBytes32Hex(value) {
46
- return ethers.hexlify(ethers.zeroPadValue(ethers.hexlify(value), 32)).toLowerCase();
47
- }
48
-
49
- function normalizeBytes16Hex(value) {
50
- return ethers.hexlify(ethers.zeroPadValue(ethers.hexlify(value), 16)).toLowerCase();
51
- }
52
-
53
- function poseidonHexFromBytes(bytesLike) {
54
- return ethers.hexlify(poseidon(ethers.getBytes(bytesLike))).toLowerCase();
55
- }
56
-
57
50
  function noteReceivePubKeyFromPoint(point) {
58
51
  const affine = point.toAffine();
59
52
  return {
@@ -371,7 +364,7 @@ function decryptFieldEncryptedNoteValue({
371
364
  encryptionInfo,
372
365
  });
373
366
  expect(
374
- normalizeBytes16Hex(expectedTag) === normalizeBytes16Hex(normalized.tag),
367
+ normalizeBytesHex(expectedTag, 16) === normalizeBytesHex(normalized.tag, 16),
375
368
  "Encrypted note value integrity tag mismatch.",
376
369
  );
377
370
  const fieldMask = deriveFieldMask({
@@ -9,7 +9,6 @@ import { fetchNpmPackageMetadata } from "@tokamak-private-dapps/common-library/n
9
9
  import {
10
10
  normalizePackageVersionToCompatibleBackendVersion,
11
11
  readTokamakZkEvmCompatibleBackendVersionFromPackageJson,
12
- requireCanonicalCompatibleBackendVersion,
13
12
  requireExactSemverVersion,
14
13
  } from "@tokamak-private-dapps/common-library/proof-backend-versioning";
15
14
  import {