polkadot-cli 1.12.0 → 1.13.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.
Files changed (3) hide show
  1. package/README.md +113 -45
  2. package/dist/cli.mjs +1084 -253
  3. package/package.json +8 -10
package/dist/cli.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
2
  var __defProp = Object.defineProperty;
4
3
  var __returnValue = (v) => v;
5
4
  function __exportSetter(name, newValue) {
@@ -15,7 +14,6 @@ var __export = (target, all) => {
15
14
  });
16
15
  };
17
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
17
 
20
18
  // src/config/types.ts
21
19
  function primaryRpc(rpc) {
@@ -53,7 +51,9 @@ var init_types = __esm(() => {
53
51
  "wss://statemint-rpc-tn.dwellir.com",
54
52
  "wss://statemint.public.curie.radiumblock.co/ws",
55
53
  "wss://asset-hub-polkadot.rpc.permanence.io"
56
- ]
54
+ ],
55
+ relay: "polkadot",
56
+ parachainId: 1000
57
57
  },
58
58
  "polkadot-bridge-hub": {
59
59
  rpc: [
@@ -65,7 +65,9 @@ var init_types = __esm(() => {
65
65
  "wss://bridgehub-polkadot.api.onfinality.io/public-ws",
66
66
  "wss://polkadot-bridge-hub-rpc-tn.dwellir.com",
67
67
  "wss://bridgehub-polkadot.public.curie.radiumblock.co/ws"
68
- ]
68
+ ],
69
+ relay: "polkadot",
70
+ parachainId: 1002
69
71
  },
70
72
  "polkadot-collectives": {
71
73
  rpc: [
@@ -77,7 +79,9 @@ var init_types = __esm(() => {
77
79
  "wss://collectives.api.onfinality.io/public-ws",
78
80
  "wss://polkadot-collectives-rpc-tn.dwellir.com",
79
81
  "wss://collectives.public.curie.radiumblock.co/ws"
80
- ]
82
+ ],
83
+ relay: "polkadot",
84
+ parachainId: 1001
81
85
  },
82
86
  "polkadot-coretime": {
83
87
  rpc: [
@@ -87,7 +91,9 @@ var init_types = __esm(() => {
87
91
  "wss://coretime-polkadot-rpc.n.dwellir.com",
88
92
  "wss://rpc-coretime-polkadot.luckyfriday.io",
89
93
  "wss://coretime-polkadot.api.onfinality.io/public-ws"
90
- ]
94
+ ],
95
+ relay: "polkadot",
96
+ parachainId: 1005
91
97
  },
92
98
  "polkadot-people": {
93
99
  rpc: [
@@ -97,7 +103,9 @@ var init_types = __esm(() => {
97
103
  "wss://people-polkadot-rpc.n.dwellir.com",
98
104
  "wss://rpc-people-polkadot.luckyfriday.io",
99
105
  "wss://people-polkadot.api.onfinality.io/public-ws"
100
- ]
106
+ ],
107
+ relay: "polkadot",
108
+ parachainId: 1004
101
109
  },
102
110
  paseo: {
103
111
  rpc: [
@@ -113,23 +121,33 @@ var init_types = __esm(() => {
113
121
  "wss://asset-hub-paseo.dotters.network",
114
122
  "wss://asset-hub-paseo-rpc.n.dwellir.com",
115
123
  "wss://sys.turboflakes.io/asset-hub-paseo"
116
- ]
124
+ ],
125
+ relay: "paseo",
126
+ parachainId: 1000
117
127
  },
118
128
  "paseo-bridge-hub": {
119
- rpc: ["wss://bridge-hub-paseo.ibp.network", "wss://bridge-hub-paseo.dotters.network"]
129
+ rpc: ["wss://bridge-hub-paseo.ibp.network", "wss://bridge-hub-paseo.dotters.network"],
130
+ relay: "paseo",
131
+ parachainId: 1002
120
132
  },
121
133
  "paseo-collectives": {
122
- rpc: ["wss://collectives-paseo.ibp.network", "wss://collectives-paseo.dotters.network"]
134
+ rpc: ["wss://collectives-paseo.ibp.network", "wss://collectives-paseo.dotters.network"],
135
+ relay: "paseo",
136
+ parachainId: 1001
123
137
  },
124
138
  "paseo-coretime": {
125
- rpc: ["wss://coretime-paseo.ibp.network", "wss://coretime-paseo.dotters.network"]
139
+ rpc: ["wss://coretime-paseo.ibp.network", "wss://coretime-paseo.dotters.network"],
140
+ relay: "paseo",
141
+ parachainId: 1005
126
142
  },
127
143
  "paseo-people": {
128
144
  rpc: [
129
145
  "wss://people-paseo.ibp.network",
130
146
  "wss://people-paseo.dotters.network",
131
147
  "wss://people-paseo.rpc.amforc.com"
132
- ]
148
+ ],
149
+ relay: "paseo",
150
+ parachainId: 1004
133
151
  }
134
152
  }
135
153
  };
@@ -164,10 +182,16 @@ async function loadConfig() {
164
182
  await ensureDir(DOT_DIR);
165
183
  if (await fileExists(CONFIG_PATH)) {
166
184
  const saved = JSON.parse(await readFile(CONFIG_PATH, "utf-8"));
167
- return {
168
- ...saved,
169
- chains: { ...DEFAULT_CONFIG.chains, ...saved.chains }
170
- };
185
+ const chains = {};
186
+ for (const [name, defaultConfig] of Object.entries(DEFAULT_CONFIG.chains)) {
187
+ chains[name] = saved.chains[name] ? { ...defaultConfig, ...saved.chains[name] } : defaultConfig;
188
+ }
189
+ for (const [name, config] of Object.entries(saved.chains)) {
190
+ if (!(name in DEFAULT_CONFIG.chains)) {
191
+ chains[name] = config;
192
+ }
193
+ }
194
+ return { ...saved, chains };
171
195
  }
172
196
  await saveConfig(DEFAULT_CONFIG);
173
197
  return DEFAULT_CONFIG;
@@ -259,6 +283,42 @@ function isWatchOnly(account) {
259
283
  return account.secret === undefined;
260
284
  }
261
285
 
286
+ // src/utils/fuzzy-match.ts
287
+ function levenshtein(a, b) {
288
+ const la = a.length;
289
+ const lb = b.length;
290
+ const dp = Array.from({ length: la + 1 }, () => Array(lb + 1).fill(0));
291
+ for (let i = 0;i <= la; i++)
292
+ dp[i][0] = i;
293
+ for (let j = 0;j <= lb; j++)
294
+ dp[0][j] = j;
295
+ for (let i = 1;i <= la; i++) {
296
+ for (let j = 1;j <= lb; j++) {
297
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
298
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
299
+ }
300
+ }
301
+ return dp[la][lb];
302
+ }
303
+ function findClosest(input, candidates, maxDistance = 3) {
304
+ const lower = input.toLowerCase();
305
+ const exact = candidates.find((c) => c.toLowerCase() === lower);
306
+ if (exact)
307
+ return [exact];
308
+ const scored = candidates.map((c) => ({ name: c, dist: levenshtein(lower, c.toLowerCase()) })).filter((s) => s.dist <= maxDistance).sort((a, b) => a.dist - b.dist);
309
+ return scored.slice(0, 3).map((s) => s.name);
310
+ }
311
+ function suggestMessage(kind, input, candidates) {
312
+ const suggestions = findClosest(input, candidates);
313
+ if (suggestions.length === 0) {
314
+ return `Unknown ${kind} "${input}".`;
315
+ }
316
+ if (suggestions.length === 1 && suggestions[0]?.toLowerCase() === input.toLowerCase()) {
317
+ return suggestions[0];
318
+ }
319
+ return `Unknown ${kind} "${input}". Did you mean: ${suggestions.join(", ")}?`;
320
+ }
321
+
262
322
  // src/core/accounts.ts
263
323
  import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
264
324
  import {
@@ -369,8 +429,14 @@ async function resolveAccountKeypair(name) {
369
429
  const accountsFile = await loadAccounts();
370
430
  const account = findAccount(accountsFile, name);
371
431
  if (!account) {
372
- const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)];
373
- throw new Error(`Unknown account "${name}". Available accounts: ${available.join(", ")}`);
432
+ const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
433
+ const suggestions = findClosest(name, available);
434
+ const hint = suggestions.length > 0 ? `
435
+ Did you mean: ${suggestions.join(", ")}?` : "";
436
+ const list = available.map((a) => `
437
+ - ${a}`).join("");
438
+ throw new Error(`Unknown account "${name}".${hint}
439
+ Available accounts:${list}`);
374
440
  }
375
441
  if (account.secret === undefined) {
376
442
  throw new Error(`Account "${name}" is watch-only (no secret). Cannot sign. Import with --secret or --env.`);
@@ -390,6 +456,7 @@ var init_accounts = __esm(() => {
390
456
  });
391
457
 
392
458
  // src/utils/binary-display.ts
459
+ import { Binary } from "polkadot-api";
393
460
  function isReadableText(text) {
394
461
  for (let i = 0;i < text.length; i++) {
395
462
  const code = text.charCodeAt(i);
@@ -407,20 +474,17 @@ function isReadableText(text) {
407
474
  return true;
408
475
  }
409
476
  function binaryToDisplay(value) {
410
- const text = value.asText();
411
- return isReadableText(text) ? text : value.asHex();
477
+ const text = Binary.toText(value);
478
+ return isReadableText(text) ? text : Binary.toHex(value);
412
479
  }
480
+ var init_binary_display = () => {};
413
481
 
414
482
  // src/core/output.ts
415
- import { Binary } from "polkadot-api";
416
483
  function replacer(_key, value) {
417
484
  if (typeof value === "bigint")
418
485
  return value.toString();
419
- if (value instanceof Binary) {
420
- return binaryToDisplay(value);
421
- }
422
486
  if (value instanceof Uint8Array)
423
- return `0x${Buffer.from(value).toString("hex")}`;
487
+ return binaryToDisplay(value);
424
488
  return value;
425
489
  }
426
490
  function formatJson(data) {
@@ -442,6 +506,12 @@ function printResult(data, format = "pretty") {
442
506
  console.log(formatPretty(data));
443
507
  }
444
508
  }
509
+ function isJsonOutput(opts) {
510
+ return opts.json === true || opts.output === "json";
511
+ }
512
+ function printJsonLine(data) {
513
+ console.log(JSON.stringify(data, replacer));
514
+ }
445
515
  function printHeading(text) {
446
516
  console.log(`
447
517
  ${BOLD}${text}${RESET}
@@ -500,6 +570,7 @@ function firstSentence(docs) {
500
570
  }
501
571
  var isTTY, RESET, CYAN, GREEN, RED, YELLOW, MAGENTA, DIM, BOLD, CHECK_MARK = "✓", SPINNER_FRAMES;
502
572
  var init_output = __esm(() => {
573
+ init_binary_display();
503
574
  isTTY = process.stdout.isTTY ?? false;
504
575
  RESET = isTTY ? "\x1B[0m" : "";
505
576
  CYAN = isTTY ? "\x1B[36m" : "";
@@ -556,37 +627,33 @@ var init_errors = __esm(() => {
556
627
 
557
628
  // src/core/client.ts
558
629
  import { createClient } from "polkadot-api";
559
- import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
560
- import { getWsProvider } from "polkadot-api/ws-provider";
561
- import { WebSocket } from "ws";
562
- function suppressWsNoise() {
563
- const orig = console.error;
630
+ import { getWsProvider } from "polkadot-api/ws";
631
+ function suppressProviderNoise() {
632
+ const origError = console.error;
633
+ const origWarn = console.warn;
564
634
  console.error = (...args) => {
565
635
  if (typeof args[0] === "string" && args[0].includes("Unable to connect"))
566
636
  return;
567
- orig(...args);
637
+ origError(...args);
638
+ };
639
+ console.warn = (...args) => {
640
+ if (typeof args[0] === "string" && args[0].includes("ChainHead"))
641
+ return;
642
+ origWarn(...args);
568
643
  };
569
644
  return () => {
570
- console.error = orig;
645
+ console.error = origError;
646
+ console.warn = origWarn;
571
647
  };
572
648
  }
573
649
  async function createChainClient(chainName, chainConfig, rpcOverride) {
574
- const useLight = !rpcOverride && chainConfig.lightClient;
575
- const restoreConsole = suppressWsNoise();
576
- let provider;
577
- if (useLight) {
578
- provider = await createSmoldotProvider(chainName);
579
- } else {
580
- const rpc = rpcOverride ?? chainConfig.rpc;
581
- if (!rpc) {
582
- restoreConsole();
583
- throw new ConnectionError(`No RPC endpoint configured for chain "${chainName}". Use --rpc or configure one with: dot chain add ${chainName} --rpc <url>`);
584
- }
585
- provider = withPolkadotSdkCompat(getWsProvider(rpc, {
586
- timeout: 1e4,
587
- websocketClass: WebSocket
588
- }));
650
+ const restoreConsole = suppressProviderNoise();
651
+ const rpc = rpcOverride ?? chainConfig.rpc;
652
+ if (!rpc) {
653
+ restoreConsole();
654
+ throw new ConnectionError(`No RPC endpoint configured for chain "${chainName}". Use --rpc or configure one with: dot chain add ${chainName} --rpc <url>`);
589
655
  }
656
+ const provider = getWsProvider(rpc, { timeout: 1e4 });
590
657
  const client = createClient(provider, {
591
658
  getMetadata: async () => loadMetadata(chainName),
592
659
  setMetadata: async (_codeHash, metadata) => {
@@ -596,51 +663,25 @@ async function createChainClient(chainName, chainConfig, rpcOverride) {
596
663
  return {
597
664
  client,
598
665
  destroy: () => {
599
- client.destroy();
600
- restoreConsole();
666
+ try {
667
+ client.destroy();
668
+ } catch {}
669
+ setTimeout(restoreConsole, 500);
601
670
  }
602
671
  };
603
672
  }
604
- async function createSmoldotProvider(chainName) {
605
- const { start } = await import("polkadot-api/smoldot");
606
- const { getSmProvider } = await import("polkadot-api/sm-provider");
607
- const entry = KNOWN_CHAIN_SPECS[chainName];
608
- if (!entry) {
609
- throw new ConnectionError(`Light client is only supported for known chains: ${Object.keys(KNOWN_CHAIN_SPECS).join(", ")}. Use --rpc to connect to "${chainName}" instead.`);
610
- }
611
- const { chainSpec } = await import(entry.spec);
612
- const smoldot = start();
613
- if (entry.relay) {
614
- const relayEntry = KNOWN_CHAIN_SPECS[entry.relay];
615
- if (!relayEntry) {
616
- throw new ConnectionError(`Relay chain "${entry.relay}" not found in known chain specs.`);
617
- }
618
- const { chainSpec: relaySpec } = await import(relayEntry.spec);
619
- const relayChain = await smoldot.addChain({ chainSpec: relaySpec, disableJsonRpc: true });
620
- const chain2 = await smoldot.addChain({ chainSpec, potentialRelayChains: [relayChain] });
621
- return getSmProvider(chain2);
622
- }
623
- const chain = await smoldot.addChain({ chainSpec });
624
- return getSmProvider(chain);
625
- }
626
- var KNOWN_CHAIN_SPECS;
673
+ async function getParachainId(clientHandle) {
674
+ try {
675
+ const unsafeApi = clientHandle.client.getUnsafeApi();
676
+ const parachainId = await unsafeApi.query.ParachainInfo.ParachainId.getValue();
677
+ return typeof parachainId === "number" ? parachainId : null;
678
+ } catch {
679
+ return null;
680
+ }
681
+ }
627
682
  var init_client = __esm(() => {
628
683
  init_store();
629
684
  init_errors();
630
- KNOWN_CHAIN_SPECS = {
631
- polkadot: { spec: "polkadot-api/chains/polkadot" },
632
- kusama: { spec: "polkadot-api/chains/ksmcc3" },
633
- westend: { spec: "polkadot-api/chains/westend2" },
634
- paseo: { spec: "polkadot-api/chains/paseo" },
635
- "polkadot-asset-hub": { spec: "polkadot-api/chains/polkadot_asset_hub", relay: "polkadot" },
636
- "polkadot-bridge-hub": { spec: "polkadot-api/chains/polkadot_bridge_hub", relay: "polkadot" },
637
- "polkadot-collectives": { spec: "polkadot-api/chains/polkadot_collectives", relay: "polkadot" },
638
- "polkadot-coretime": { spec: "polkadot-api/chains/polkadot_coretime", relay: "polkadot" },
639
- "polkadot-people": { spec: "polkadot-api/chains/polkadot_people", relay: "polkadot" },
640
- "paseo-asset-hub": { spec: "polkadot-api/chains/paseo_asset_hub", relay: "paseo" },
641
- "paseo-coretime": { spec: "polkadot-api/chains/paseo_coretime", relay: "paseo" },
642
- "paseo-people": { spec: "polkadot-api/chains/paseo_people", relay: "paseo" }
643
- };
644
685
  });
645
686
 
646
687
  // src/core/metadata.ts
@@ -915,42 +956,6 @@ var init_metadata = __esm(() => {
915
956
  v15Arg = toHex(u32.enc(15));
916
957
  });
917
958
 
918
- // src/utils/fuzzy-match.ts
919
- function levenshtein(a, b) {
920
- const la = a.length;
921
- const lb = b.length;
922
- const dp = Array.from({ length: la + 1 }, () => Array(lb + 1).fill(0));
923
- for (let i = 0;i <= la; i++)
924
- dp[i][0] = i;
925
- for (let j = 0;j <= lb; j++)
926
- dp[0][j] = j;
927
- for (let i = 1;i <= la; i++) {
928
- for (let j = 1;j <= lb; j++) {
929
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
930
- dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
931
- }
932
- }
933
- return dp[la][lb];
934
- }
935
- function findClosest(input, candidates, maxDistance = 3) {
936
- const lower = input.toLowerCase();
937
- const exact = candidates.find((c) => c.toLowerCase() === lower);
938
- if (exact)
939
- return [exact];
940
- const scored = candidates.map((c) => ({ name: c, dist: levenshtein(lower, c.toLowerCase()) })).filter((s) => s.dist <= maxDistance).sort((a, b) => a.dist - b.dist);
941
- return scored.slice(0, 3).map((s) => s.name);
942
- }
943
- function suggestMessage(kind, input, candidates) {
944
- const suggestions = findClosest(input, candidates);
945
- if (suggestions.length === 0) {
946
- return `Unknown ${kind} "${input}".`;
947
- }
948
- if (suggestions.length === 1 && suggestions[0]?.toLowerCase() === input.toLowerCase()) {
949
- return suggestions[0];
950
- }
951
- return `Unknown ${kind} "${input}". Did you mean: ${suggestions.join(", ")}?`;
952
- }
953
-
954
959
  // src/core/explorers.ts
955
960
  var pjsAppsLink = (rpc, hash) => `https://polkadot.js.org/apps/?rpc=${encodeURIComponent(rpc)}#/explorer/query/${hash}`, papiLink = (rpc, hash) => `https://dev.papi.how/explorer/${hash}#networkId=custom&endpoint=${encodeURIComponent(rpc)}`;
956
961
 
@@ -976,9 +981,14 @@ async function resolveAccountAddress(input) {
976
981
  return input;
977
982
  }
978
983
  const stored = accountsFile.accounts.map((a) => a.name);
979
- const available = [...DEV_NAMES, ...stored];
980
- throw new Error(`Unknown account or address "${input}".
981
- Available accounts: ${available.join(", ")}`);
984
+ const available = [...DEV_NAMES, ...stored].sort((a, b) => a.localeCompare(b));
985
+ const suggestions = findClosest(input, available);
986
+ const hint = suggestions.length > 0 ? `
987
+ Did you mean: ${suggestions.join(", ")}?` : "";
988
+ const list = available.map((a) => `
989
+ - ${a}`).join("");
990
+ throw new Error(`Unknown account or address "${input}".${hint}
991
+ Available accounts:${list}`);
982
992
  }
983
993
  var init_resolve_address = __esm(() => {
984
994
  init_accounts_store();
@@ -1206,7 +1216,7 @@ async function parseTypedArg(meta, entry, arg) {
1206
1216
  case "enum": {
1207
1217
  if (/^0x[0-9a-fA-F]+$/.test(arg) && meta.lookup.call != null && entry.id === meta.lookup.call) {
1208
1218
  const callCodec = meta.builder.buildDefinition(meta.lookup.call);
1209
- return callCodec.dec(Binary2.fromHex(arg).asBytes());
1219
+ return callCodec.dec(Binary2.fromHex(arg));
1210
1220
  }
1211
1221
  if (arg.startsWith("{")) {
1212
1222
  try {
@@ -1318,6 +1328,7 @@ var init_tx = __esm(() => {
1318
1328
  init_metadata();
1319
1329
  init_output();
1320
1330
  init_resolve_address();
1331
+ init_binary_display();
1321
1332
  init_errors();
1322
1333
  init_focused_inspect();
1323
1334
  PAPI_BUILTIN_EXTENSIONS = new Set([
@@ -1348,6 +1359,13 @@ async function handleApis(target, args, opts) {
1348
1359
  const { name: chainName2, chain: chainConfig2 } = resolveChain(config2, opts.chain);
1349
1360
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
1350
1361
  const apis = listRuntimeApis(meta);
1362
+ if (isJsonOutput(opts)) {
1363
+ console.log(formatJson({
1364
+ chain: chainName2,
1365
+ apis: apis.map((a) => ({ name: a.name, methods: a.methods.length }))
1366
+ }));
1367
+ return;
1368
+ }
1351
1369
  printHeading(`Runtime APIs on ${chainName2} (${apis.length})`);
1352
1370
  for (const api of apis) {
1353
1371
  printItem(api.name, `${api.methods.length} methods`);
@@ -1370,7 +1388,24 @@ async function handleApis(target, args, opts) {
1370
1388
  const meta = await loadMeta(chainName, chainConfig, opts.rpc);
1371
1389
  const api = resolveRuntimeApi(meta, apiName);
1372
1390
  if (api.methods.length === 0) {
1373
- console.log(`No methods in ${api.name}.`);
1391
+ if (isJsonOutput(opts)) {
1392
+ console.log(formatJson({ chain: chainName, api: api.name, methods: [] }));
1393
+ } else {
1394
+ console.log(`No methods in ${api.name}.`);
1395
+ }
1396
+ return;
1397
+ }
1398
+ if (isJsonOutput(opts)) {
1399
+ console.log(formatJson({
1400
+ chain: chainName,
1401
+ api: api.name,
1402
+ methods: api.methods.map((m) => ({
1403
+ name: m.name,
1404
+ args: describeRuntimeApiMethodArgs(meta, m),
1405
+ returns: describeType(meta.lookup, m.output),
1406
+ docs: firstSentence(m.docs)
1407
+ }))
1408
+ }));
1374
1409
  return;
1375
1410
  }
1376
1411
  printHeading(`${api.name} Methods`);
@@ -1401,7 +1436,7 @@ async function handleApis(target, args, opts) {
1401
1436
  const parsedArgs = await parseRuntimeApiArgs(meta, method, effectiveArgs);
1402
1437
  const unsafeApi = clientHandle.client.getUnsafeApi();
1403
1438
  const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
1404
- const format = opts.output ?? "pretty";
1439
+ const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
1405
1440
  printResult(result, format);
1406
1441
  } finally {
1407
1442
  clientHandle.destroy();
@@ -1441,7 +1476,8 @@ async function loadMeta(chainName, chainConfig, rpcOverride) {
1441
1476
  try {
1442
1477
  return await getOrFetchMetadata(chainName);
1443
1478
  } catch {
1444
- console.error(`Fetching metadata from ${chainName}...`);
1479
+ process.stderr.write(`Fetching metadata from ${chainName}...
1480
+ `);
1445
1481
  const clientHandle = await createChainClient(chainName, chainConfig, rpcOverride);
1446
1482
  try {
1447
1483
  return await getOrFetchMetadata(chainName, clientHandle);
@@ -1465,6 +1501,13 @@ async function handleCalls(target, opts) {
1465
1501
  const meta2 = await loadMeta(chainName2, chainConfig2, opts.rpc);
1466
1502
  const pallets = listPallets(meta2);
1467
1503
  const withCalls = pallets.filter((p) => p.calls.length > 0);
1504
+ if (isJsonOutput(opts)) {
1505
+ console.log(formatJson({
1506
+ chain: chainName2,
1507
+ pallets: withCalls.map((p) => ({ name: p.name, calls: p.calls.length }))
1508
+ }));
1509
+ return;
1510
+ }
1468
1511
  printHeading(`Pallets with calls on ${chainName2} (${withCalls.length})`);
1469
1512
  for (const p of withCalls) {
1470
1513
  printItem(p.name, `${p.calls.length} calls`);
@@ -1481,7 +1524,23 @@ async function handleCalls(target, opts) {
1481
1524
  const pallet = resolvePallet(meta, palletName);
1482
1525
  if (!itemName) {
1483
1526
  if (pallet.calls.length === 0) {
1484
- console.log(`No calls in ${pallet.name}.`);
1527
+ if (isJsonOutput(opts)) {
1528
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, calls: [] }));
1529
+ } else {
1530
+ console.log(`No calls in ${pallet.name}.`);
1531
+ }
1532
+ return;
1533
+ }
1534
+ if (isJsonOutput(opts)) {
1535
+ console.log(formatJson({
1536
+ chain: chainName,
1537
+ pallet: pallet.name,
1538
+ calls: pallet.calls.map((c) => ({
1539
+ name: c.name,
1540
+ args: describeCallArgs(meta, pallet.name, c.name),
1541
+ docs: firstSentence(c.docs)
1542
+ }))
1543
+ }));
1485
1544
  return;
1486
1545
  }
1487
1546
  printHeading(`${pallet.name} Calls`);
@@ -1501,6 +1560,17 @@ async function handleCalls(target, opts) {
1501
1560
  const names = pallet.calls.map((c) => c.name);
1502
1561
  throw new Error(suggestMessage(`call in ${pallet.name}`, itemName, names));
1503
1562
  }
1563
+ if (isJsonOutput(opts)) {
1564
+ console.log(formatJson({
1565
+ chain: chainName,
1566
+ pallet: pallet.name,
1567
+ item: callItem.name,
1568
+ category: "call",
1569
+ args: describeCallArgs(meta, pallet.name, callItem.name),
1570
+ docs: callItem.docs
1571
+ }));
1572
+ return;
1573
+ }
1504
1574
  printHeading(`${pallet.name}.${callItem.name} (Call)`);
1505
1575
  const args = describeCallArgs(meta, pallet.name, callItem.name);
1506
1576
  console.log(` ${BOLD}Args:${RESET} ${args}`);
@@ -1517,6 +1587,13 @@ async function handleEvents(target, opts) {
1517
1587
  const meta2 = await loadMeta(chainName2, chainConfig2, opts.rpc);
1518
1588
  const pallets = listPallets(meta2);
1519
1589
  const withEvents = pallets.filter((p) => p.events.length > 0);
1590
+ if (isJsonOutput(opts)) {
1591
+ console.log(formatJson({
1592
+ chain: chainName2,
1593
+ pallets: withEvents.map((p) => ({ name: p.name, events: p.events.length }))
1594
+ }));
1595
+ return;
1596
+ }
1520
1597
  printHeading(`Pallets with events on ${chainName2} (${withEvents.length})`);
1521
1598
  for (const p of withEvents) {
1522
1599
  printItem(p.name, `${p.events.length} events`);
@@ -1533,7 +1610,23 @@ async function handleEvents(target, opts) {
1533
1610
  const pallet = resolvePallet(meta, palletName);
1534
1611
  if (!itemName) {
1535
1612
  if (pallet.events.length === 0) {
1536
- console.log(`No events in ${pallet.name}.`);
1613
+ if (isJsonOutput(opts)) {
1614
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, events: [] }));
1615
+ } else {
1616
+ console.log(`No events in ${pallet.name}.`);
1617
+ }
1618
+ return;
1619
+ }
1620
+ if (isJsonOutput(opts)) {
1621
+ console.log(formatJson({
1622
+ chain: chainName,
1623
+ pallet: pallet.name,
1624
+ events: pallet.events.map((e) => ({
1625
+ name: e.name,
1626
+ fields: describeEventFields(meta, pallet.name, e.name),
1627
+ docs: firstSentence(e.docs)
1628
+ }))
1629
+ }));
1537
1630
  return;
1538
1631
  }
1539
1632
  printHeading(`${pallet.name} Events`);
@@ -1553,6 +1646,17 @@ async function handleEvents(target, opts) {
1553
1646
  const names = pallet.events.map((e) => e.name);
1554
1647
  throw new Error(suggestMessage(`event in ${pallet.name}`, itemName, names));
1555
1648
  }
1649
+ if (isJsonOutput(opts)) {
1650
+ console.log(formatJson({
1651
+ chain: chainName,
1652
+ pallet: pallet.name,
1653
+ item: eventItem.name,
1654
+ category: "event",
1655
+ fields: describeEventFields(meta, pallet.name, eventItem.name),
1656
+ docs: eventItem.docs
1657
+ }));
1658
+ return;
1659
+ }
1556
1660
  printHeading(`${pallet.name}.${eventItem.name} (Event)`);
1557
1661
  const fields = describeEventFields(meta, pallet.name, eventItem.name);
1558
1662
  console.log(` ${BOLD}Fields:${RESET} ${fields}`);
@@ -1569,6 +1673,13 @@ async function handleErrors(target, opts) {
1569
1673
  const meta2 = await loadMeta(chainName2, chainConfig2, opts.rpc);
1570
1674
  const pallets = listPallets(meta2);
1571
1675
  const withErrors = pallets.filter((p) => p.errors.length > 0);
1676
+ if (isJsonOutput(opts)) {
1677
+ console.log(formatJson({
1678
+ chain: chainName2,
1679
+ pallets: withErrors.map((p) => ({ name: p.name, errors: p.errors.length }))
1680
+ }));
1681
+ return;
1682
+ }
1572
1683
  printHeading(`Pallets with errors on ${chainName2} (${withErrors.length})`);
1573
1684
  for (const p of withErrors) {
1574
1685
  printItem(p.name, `${p.errors.length} errors`);
@@ -1585,7 +1696,19 @@ async function handleErrors(target, opts) {
1585
1696
  const pallet = resolvePallet(meta, palletName);
1586
1697
  if (!itemName) {
1587
1698
  if (pallet.errors.length === 0) {
1588
- console.log(`No errors in ${pallet.name}.`);
1699
+ if (isJsonOutput(opts)) {
1700
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, errors: [] }));
1701
+ } else {
1702
+ console.log(`No errors in ${pallet.name}.`);
1703
+ }
1704
+ return;
1705
+ }
1706
+ if (isJsonOutput(opts)) {
1707
+ console.log(formatJson({
1708
+ chain: chainName,
1709
+ pallet: pallet.name,
1710
+ errors: pallet.errors.map((e) => ({ name: e.name, docs: firstSentence(e.docs) }))
1711
+ }));
1589
1712
  return;
1590
1713
  }
1591
1714
  printHeading(`${pallet.name} Errors`);
@@ -1604,6 +1727,16 @@ async function handleErrors(target, opts) {
1604
1727
  const names = pallet.errors.map((e) => e.name);
1605
1728
  throw new Error(suggestMessage(`error in ${pallet.name}`, itemName, names));
1606
1729
  }
1730
+ if (isJsonOutput(opts)) {
1731
+ console.log(formatJson({
1732
+ chain: chainName,
1733
+ pallet: pallet.name,
1734
+ item: errorItem.name,
1735
+ category: "error",
1736
+ docs: errorItem.docs
1737
+ }));
1738
+ return;
1739
+ }
1607
1740
  printHeading(`${pallet.name}.${errorItem.name} (Error)`);
1608
1741
  if (errorItem.docs.length) {
1609
1742
  printDocs(errorItem.docs);
@@ -1617,6 +1750,13 @@ async function handleStorage(target, opts) {
1617
1750
  const meta2 = await loadMeta(chainName2, chainConfig2, opts.rpc);
1618
1751
  const pallets = listPallets(meta2);
1619
1752
  const withStorage = pallets.filter((p) => p.storage.length > 0);
1753
+ if (isJsonOutput(opts)) {
1754
+ console.log(formatJson({
1755
+ chain: chainName2,
1756
+ pallets: withStorage.map((p) => ({ name: p.name, storage: p.storage.length }))
1757
+ }));
1758
+ return;
1759
+ }
1620
1760
  printHeading(`Pallets with storage on ${chainName2} (${withStorage.length})`);
1621
1761
  for (const p of withStorage) {
1622
1762
  printItem(p.name, `${p.storage.length} storage`);
@@ -1633,7 +1773,23 @@ async function handleStorage(target, opts) {
1633
1773
  const pallet = resolvePallet(meta, palletName);
1634
1774
  if (!itemName) {
1635
1775
  if (pallet.storage.length === 0) {
1636
- console.log(`No storage items in ${pallet.name}.`);
1776
+ if (isJsonOutput(opts)) {
1777
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, storage: [] }));
1778
+ } else {
1779
+ console.log(`No storage items in ${pallet.name}.`);
1780
+ }
1781
+ return;
1782
+ }
1783
+ if (isJsonOutput(opts)) {
1784
+ console.log(formatJson({
1785
+ chain: chainName,
1786
+ pallet: pallet.name,
1787
+ storage: pallet.storage.map((s) => {
1788
+ const valueType = describeType(meta.lookup, s.valueTypeId);
1789
+ const keyType = s.keyTypeId != null ? describeType(meta.lookup, s.keyTypeId) : undefined;
1790
+ return { name: s.name, type: s.type, valueType, keyType, docs: firstSentence(s.docs) };
1791
+ })
1792
+ }));
1637
1793
  return;
1638
1794
  }
1639
1795
  printHeading(`${pallet.name} Storage`);
@@ -1660,6 +1816,21 @@ async function handleStorage(target, opts) {
1660
1816
  const names = pallet.storage.map((s) => s.name);
1661
1817
  throw new Error(suggestMessage(`storage item in ${pallet.name}`, itemName, names));
1662
1818
  }
1819
+ if (isJsonOutput(opts)) {
1820
+ const valueType = describeType(meta.lookup, storageItem.valueTypeId);
1821
+ const keyType = storageItem.keyTypeId != null ? describeType(meta.lookup, storageItem.keyTypeId) : undefined;
1822
+ console.log(formatJson({
1823
+ chain: chainName,
1824
+ pallet: pallet.name,
1825
+ item: storageItem.name,
1826
+ category: "storage",
1827
+ type: storageItem.type,
1828
+ valueType,
1829
+ keyType,
1830
+ docs: storageItem.docs
1831
+ }));
1832
+ return;
1833
+ }
1663
1834
  printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
1664
1835
  console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
1665
1836
  console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
@@ -1998,6 +2169,9 @@ async function generateCompletions(currentWord, precedingWords) {
1998
2169
  if (prevWord === "--wait" || prevWord === "-w") {
1999
2170
  return filterPrefix(["broadcast", "best-block", "best", "finalized"], currentWord);
2000
2171
  }
2172
+ if (prevWord === "--relay") {
2173
+ return filterPrefix(knownChains, currentWord);
2174
+ }
2001
2175
  if (currentWord.startsWith("--")) {
2002
2176
  const activeCategory = detectCategory(precedingWords, knownChains);
2003
2177
  const options = [...GLOBAL_OPTIONS];
@@ -2005,10 +2179,19 @@ async function generateCompletions(currentWord, precedingWords) {
2005
2179
  options.push(...TX_OPTIONS);
2006
2180
  if (activeCategory === "query")
2007
2181
  options.push(...QUERY_OPTIONS);
2182
+ const chainIdx = precedingWords.indexOf("chain");
2183
+ if (chainIdx >= 0 && precedingWords[chainIdx + 1] === "add") {
2184
+ options.push("--relay", "--parachain-id");
2185
+ }
2008
2186
  return filterPrefix(options, currentWord);
2009
2187
  }
2010
2188
  const firstArg = precedingWords.find((w) => !w.startsWith("-"));
2011
2189
  if (firstArg === "chain") {
2190
+ const chainSubIdx = precedingWords.indexOf("chain");
2191
+ const subcommand = precedingWords[chainSubIdx + 1];
2192
+ if (subcommand === "add" && currentWord.startsWith("--")) {
2193
+ return filterPrefix(["--rpc", "--relay", "--parachain-id", ...GLOBAL_OPTIONS], currentWord);
2194
+ }
2012
2195
  return filterPrefix(CHAIN_SUBCOMMANDS, currentWord);
2013
2196
  }
2014
2197
  if (firstArg === "account") {
@@ -2199,7 +2382,7 @@ var init_complete = __esm(() => {
2199
2382
  "delete",
2200
2383
  "inspect"
2201
2384
  ];
2202
- GLOBAL_OPTIONS = ["--chain", "--rpc", "--light-client", "--output", "--help", "--version"];
2385
+ GLOBAL_OPTIONS = ["--chain", "--rpc", "--output", "--help", "--version"];
2203
2386
  TX_OPTIONS = [
2204
2387
  "--from",
2205
2388
  "--dry-run",
@@ -2217,7 +2400,7 @@ var init_complete = __esm(() => {
2217
2400
  // src/cli.ts
2218
2401
  import cac from "cac";
2219
2402
  // package.json
2220
- var version = "1.12.0";
2403
+ var version = "1.13.0";
2221
2404
 
2222
2405
  // src/commands/account.ts
2223
2406
  init_accounts_store();
@@ -2258,7 +2441,7 @@ function registerAccountCommands(cli) {
2258
2441
  cli.command("account [action] [...names]", "Manage local accounts (create, import, list, remove)").alias("accounts").option("--secret <value>", "Secret key (mnemonic or hex seed) for import").option("--env <varName>", "Environment variable name holding the secret").option("--path <derivation>", "Derivation path (e.g. //staking, //polkadot//0/wallet)").option("--prefix <number>", "SS58 prefix for address encoding (default: 42)").action(async (action, names, opts) => {
2259
2442
  if (!action) {
2260
2443
  if (process.argv[2] === "accounts")
2261
- return accountList();
2444
+ return accountList(opts);
2262
2445
  console.log(ACCOUNT_HELP);
2263
2446
  return;
2264
2447
  }
@@ -2269,16 +2452,16 @@ function registerAccountCommands(cli) {
2269
2452
  case "add":
2270
2453
  if (opts.secret || opts.env)
2271
2454
  return accountImport(names[0], opts);
2272
- return accountAddWatchOnly(names[0], names[1]);
2455
+ return accountAddWatchOnly(names[0], names[1], opts);
2273
2456
  case "import":
2274
2457
  return accountImport(names[0], opts);
2275
2458
  case "derive":
2276
2459
  return accountDerive(names[0], names[1], opts);
2277
2460
  case "list":
2278
- return accountList();
2461
+ return accountList(opts);
2279
2462
  case "delete":
2280
2463
  case "remove":
2281
- return accountRemove(names);
2464
+ return accountRemove(names, opts);
2282
2465
  case "inspect":
2283
2466
  return accountInspect(names[0], opts);
2284
2467
  default:
@@ -2316,6 +2499,18 @@ async function accountCreate(name, opts) {
2316
2499
  bandersnatch
2317
2500
  });
2318
2501
  await saveAccounts(accountsFile);
2502
+ if (isJsonOutput(opts)) {
2503
+ console.log(formatJson({
2504
+ name,
2505
+ address,
2506
+ publicKey: hexPub,
2507
+ mnemonic,
2508
+ path: path || undefined,
2509
+ bandersnatch
2510
+ }));
2511
+ console.error(`Save this mnemonic phrase! It is the only way to recover this account.`);
2512
+ return;
2513
+ }
2319
2514
  printHeading("Account Created");
2320
2515
  console.log(` ${BOLD}Name:${RESET} ${name}`);
2321
2516
  if (path)
@@ -2364,6 +2559,11 @@ async function accountImport(name, opts) {
2364
2559
  derivationPath: path
2365
2560
  });
2366
2561
  await saveAccounts(accountsFile);
2562
+ if (isJsonOutput(opts)) {
2563
+ const address = publicKey ? toSs58(publicKey) : undefined;
2564
+ console.log(formatJson({ name, address, env: opts.env, path: path || undefined }));
2565
+ return;
2566
+ }
2367
2567
  printHeading("Account Imported");
2368
2568
  console.log(` ${BOLD}Name:${RESET} ${name}`);
2369
2569
  if (path)
@@ -2386,6 +2586,10 @@ async function accountImport(name, opts) {
2386
2586
  derivationPath: path
2387
2587
  });
2388
2588
  await saveAccounts(accountsFile);
2589
+ if (isJsonOutput(opts)) {
2590
+ console.log(formatJson({ name, address, publicKey: hexPub, path: path || undefined }));
2591
+ return;
2592
+ }
2389
2593
  printHeading("Account Imported");
2390
2594
  console.log(` ${BOLD}Name:${RESET} ${name}`);
2391
2595
  if (path)
@@ -2394,7 +2598,7 @@ async function accountImport(name, opts) {
2394
2598
  console.log();
2395
2599
  }
2396
2600
  }
2397
- async function accountAddWatchOnly(name, address) {
2601
+ async function accountAddWatchOnly(name, address, opts = {}) {
2398
2602
  if (!name) {
2399
2603
  console.error(`Account name is required.
2400
2604
  `);
@@ -2431,6 +2635,10 @@ async function accountAddWatchOnly(name, address) {
2431
2635
  derivationPath: ""
2432
2636
  });
2433
2637
  await saveAccounts(accountsFile);
2638
+ if (isJsonOutput(opts)) {
2639
+ console.log(formatJson({ name, address: toSs58(hexPub), watchOnly: true }));
2640
+ return;
2641
+ }
2434
2642
  printHeading("Account Added (watch-only)");
2435
2643
  console.log(` ${BOLD}Name:${RESET} ${name}`);
2436
2644
  console.log(` ${BOLD}Address:${RESET} ${toSs58(hexPub)}`);
@@ -2480,6 +2688,11 @@ async function accountDerive(sourceName, newName, opts) {
2480
2688
  derivationPath: path
2481
2689
  });
2482
2690
  await saveAccounts(accountsFile);
2691
+ if (isJsonOutput(opts)) {
2692
+ const address = publicKey ? toSs58(publicKey) : undefined;
2693
+ console.log(formatJson({ name: newName, source: sourceName, path, address, env: sourceSecret.env }));
2694
+ return;
2695
+ }
2483
2696
  printHeading("Account Derived");
2484
2697
  console.log(` ${BOLD}Name:${RESET} ${newName}`);
2485
2698
  console.log(` ${BOLD}Source:${RESET} ${sourceName}`);
@@ -2502,6 +2715,10 @@ async function accountDerive(sourceName, newName, opts) {
2502
2715
  derivationPath: path
2503
2716
  });
2504
2717
  await saveAccounts(accountsFile);
2718
+ if (isJsonOutput(opts)) {
2719
+ console.log(formatJson({ name: newName, source: sourceName, path, address, publicKey: hexPub }));
2720
+ return;
2721
+ }
2505
2722
  printHeading("Account Derived");
2506
2723
  console.log(` ${BOLD}Name:${RESET} ${newName}`);
2507
2724
  console.log(` ${BOLD}Source:${RESET} ${sourceName}`);
@@ -2510,14 +2727,43 @@ async function accountDerive(sourceName, newName, opts) {
2510
2727
  console.log();
2511
2728
  }
2512
2729
  }
2513
- async function accountList() {
2730
+ async function accountList(opts = {}) {
2731
+ const accountsFile = await loadAccounts();
2732
+ if (isJsonOutput(opts)) {
2733
+ const dev = DEV_NAMES.map((name) => ({
2734
+ name: name.charAt(0).toUpperCase() + name.slice(1),
2735
+ address: getDevAddress(name)
2736
+ }));
2737
+ const stored = accountsFile.accounts.map((account) => {
2738
+ let address;
2739
+ if (isWatchOnly(account)) {
2740
+ address = account.publicKey ? toSs58(account.publicKey) : undefined;
2741
+ } else if (account.secret !== undefined && isEnvSecret(account.secret)) {
2742
+ let pubKey = account.publicKey;
2743
+ if (!pubKey) {
2744
+ pubKey = tryDerivePublicKey(account.secret.env, account.derivationPath) ?? "";
2745
+ }
2746
+ address = pubKey ? toSs58(pubKey) : undefined;
2747
+ } else {
2748
+ address = toSs58(account.publicKey);
2749
+ }
2750
+ return {
2751
+ name: account.name,
2752
+ address,
2753
+ derivationPath: account.derivationPath || undefined,
2754
+ watchOnly: isWatchOnly(account),
2755
+ env: account.secret !== undefined && isEnvSecret(account.secret) ? account.secret.env : undefined
2756
+ };
2757
+ });
2758
+ console.log(formatJson({ dev, stored }));
2759
+ return;
2760
+ }
2514
2761
  printHeading("Dev Accounts");
2515
2762
  for (const name of DEV_NAMES) {
2516
2763
  const display = name.charAt(0).toUpperCase() + name.slice(1);
2517
2764
  const address = getDevAddress(name);
2518
2765
  printItem(display, address);
2519
2766
  }
2520
- const accountsFile = await loadAccounts();
2521
2767
  if (accountsFile.accounts.length > 0) {
2522
2768
  printHeading("Stored Accounts");
2523
2769
  for (const account of accountsFile.accounts) {
@@ -2547,7 +2793,7 @@ async function accountList() {
2547
2793
  }
2548
2794
  console.log();
2549
2795
  }
2550
- async function accountRemove(names) {
2796
+ async function accountRemove(names, opts = {}) {
2551
2797
  if (names.length === 0) {
2552
2798
  console.error(`At least one account name is required.
2553
2799
  `);
@@ -2582,6 +2828,10 @@ async function accountRemove(names) {
2582
2828
  accountsFile.accounts.splice(idx, 1);
2583
2829
  }
2584
2830
  await saveAccounts(accountsFile);
2831
+ if (isJsonOutput(opts)) {
2832
+ console.log(formatJson({ removed: names }));
2833
+ return;
2834
+ }
2585
2835
  for (const name of names) {
2586
2836
  console.log(`Account "${name}" removed.`);
2587
2837
  }
@@ -2637,7 +2887,7 @@ async function accountInspect(input, opts) {
2637
2887
  }
2638
2888
  }
2639
2889
  const ss58 = toSs58(publicKeyHex, prefix);
2640
- if (opts.output === "json") {
2890
+ if (isJsonOutput(opts)) {
2641
2891
  const result = { publicKey: publicKeyHex, ss58, prefix };
2642
2892
  if (name)
2643
2893
  result.name = name;
@@ -2680,6 +2930,13 @@ async function handleApis2(target, args, opts) {
2680
2930
  const { name: chainName2, chain: chainConfig2 } = resolveChain(config2, opts.chain);
2681
2931
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
2682
2932
  const apis = listRuntimeApis(meta);
2933
+ if (isJsonOutput(opts)) {
2934
+ console.log(formatJson({
2935
+ chain: chainName2,
2936
+ apis: apis.map((a) => ({ name: a.name, methods: a.methods.length }))
2937
+ }));
2938
+ return;
2939
+ }
2683
2940
  printHeading(`Runtime APIs on ${chainName2} (${apis.length})`);
2684
2941
  for (const api of apis) {
2685
2942
  printItem(api.name, `${api.methods.length} methods`);
@@ -2702,7 +2959,24 @@ async function handleApis2(target, args, opts) {
2702
2959
  const meta = await loadMeta(chainName, chainConfig, opts.rpc);
2703
2960
  const api = resolveRuntimeApi2(meta, apiName);
2704
2961
  if (api.methods.length === 0) {
2705
- console.log(`No methods in ${api.name}.`);
2962
+ if (isJsonOutput(opts)) {
2963
+ console.log(formatJson({ chain: chainName, api: api.name, methods: [] }));
2964
+ } else {
2965
+ console.log(`No methods in ${api.name}.`);
2966
+ }
2967
+ return;
2968
+ }
2969
+ if (isJsonOutput(opts)) {
2970
+ console.log(formatJson({
2971
+ chain: chainName,
2972
+ api: api.name,
2973
+ methods: api.methods.map((m) => ({
2974
+ name: m.name,
2975
+ args: describeRuntimeApiMethodArgs(meta, m),
2976
+ returns: describeType(meta.lookup, m.output),
2977
+ docs: firstSentence(m.docs)
2978
+ }))
2979
+ }));
2706
2980
  return;
2707
2981
  }
2708
2982
  printHeading(`${api.name} Methods`);
@@ -2733,7 +3007,7 @@ async function handleApis2(target, args, opts) {
2733
3007
  const parsedArgs = await parseRuntimeApiArgs2(meta, method, effectiveArgs);
2734
3008
  const unsafeApi = clientHandle.client.getUnsafeApi();
2735
3009
  const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
2736
- const format = opts.output ?? "pretty";
3010
+ const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
2737
3011
  printResult(result, format);
2738
3012
  } finally {
2739
3013
  clientHandle.destroy();
@@ -2769,7 +3043,6 @@ init_output();
2769
3043
  var CHAIN_HELP = `
2770
3044
  ${BOLD}Usage:${RESET}
2771
3045
  $ dot chain add <name> --rpc <url> Add a chain via WebSocket RPC
2772
- $ dot chain add <name> --light-client Add a chain via Smoldot light client
2773
3046
  $ dot chain remove <name> Remove a chain
2774
3047
  $ dot chain update [name] Re-fetch metadata (default chain if omitted)
2775
3048
  $ dot chain update --all Re-fetch metadata for all configured chains
@@ -2779,7 +3052,8 @@ ${BOLD}Usage:${RESET}
2779
3052
  ${BOLD}Examples:${RESET}
2780
3053
  $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
2781
3054
  $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io --rpc wss://kusama-rpc.dwellir.com
2782
- $ dot chain add westend --light-client
3055
+ $ dot chain add my-para --rpc wss://rpc.example.com --relay polkadot
3056
+ $ dot chain add my-para --rpc wss://rpc.example.com --relay polkadot --parachain-id 2000
2783
3057
  $ dot chain default kusama
2784
3058
  $ dot chain list
2785
3059
  $ dot chain update
@@ -2788,10 +3062,10 @@ ${BOLD}Examples:${RESET}
2788
3062
  $ dot chain remove kusama
2789
3063
  `.trimStart();
2790
3064
  function registerChainCommands(cli) {
2791
- cli.command("chain [action] [name]", "Manage chains (add, remove, update, list, default)").alias("chains").option("--all", "Update all configured chains").action(async (action, name, opts) => {
3065
+ cli.command("chain [action] [name]", "Manage chains (add, remove, update, list, default)").alias("chains").option("--all", "Update all configured chains").option("--relay <name>", "Parent relay chain for this parachain").option("--parachain-id <id>", "Parachain ID (auto-detected if omitted with --relay)").action(async (action, name, opts) => {
2792
3066
  if (!action) {
2793
3067
  if (process.argv[2] === "chains")
2794
- return chainList();
3068
+ return chainList(opts);
2795
3069
  console.log(CHAIN_HELP);
2796
3070
  return;
2797
3071
  }
@@ -2799,13 +3073,13 @@ function registerChainCommands(cli) {
2799
3073
  case "add":
2800
3074
  return chainAdd(name, opts);
2801
3075
  case "remove":
2802
- return chainRemove(name);
3076
+ return chainRemove(name, opts);
2803
3077
  case "list":
2804
- return chainList();
3078
+ return chainList(opts);
2805
3079
  case "update":
2806
3080
  return chainUpdate(name, opts);
2807
3081
  case "default":
2808
- return chainDefault(name);
3082
+ return chainDefault(name, opts);
2809
3083
  default:
2810
3084
  console.error(`Unknown action "${action}".
2811
3085
  `);
@@ -2819,34 +3093,70 @@ async function chainAdd(name, opts) {
2819
3093
  console.error(`Chain name is required.
2820
3094
  `);
2821
3095
  console.error("Usage: dot chain add <name> --rpc <url>");
2822
- console.error(" dot chain add <name> --light-client");
2823
3096
  process.exit(1);
2824
3097
  }
2825
- if (!opts.rpc && !opts.lightClient) {
2826
- console.error(`Must provide either --rpc <url> or --light-client.
3098
+ if (!opts.rpc) {
3099
+ console.error(`Must provide --rpc <url>.
2827
3100
  `);
2828
3101
  console.error("Usage: dot chain add <name> --rpc <url>");
2829
- console.error(" dot chain add <name> --light-client");
2830
3102
  process.exit(1);
2831
3103
  }
2832
- const chainConfig = {
2833
- rpc: opts.rpc ?? "",
2834
- ...opts.lightClient ? { lightClient: true } : {}
2835
- };
2836
- console.error(`Connecting to ${name}...`);
3104
+ const parachainIdRaw = opts.parachainId != null ? Number(opts.parachainId) : undefined;
3105
+ if (parachainIdRaw != null && !opts.relay) {
3106
+ console.error(`Cannot set --parachain-id without --relay.
3107
+ `);
3108
+ console.error("Usage: dot chain add <name> --rpc <url> --relay <relay> --parachain-id <id>");
3109
+ process.exit(1);
3110
+ }
3111
+ const chainConfig = { rpc: opts.rpc };
3112
+ process.stderr.write(`Connecting to ${name}...
3113
+ `);
2837
3114
  const clientHandle = await createChainClient(name, chainConfig, opts.rpc);
2838
3115
  try {
2839
- console.error("Fetching metadata...");
3116
+ process.stderr.write(`Fetching metadata...
3117
+ `);
2840
3118
  await fetchMetadataFromChain(clientHandle, name);
3119
+ if (opts.relay) {
3120
+ const config2 = await loadConfig();
3121
+ const relayResolved = findChainName(config2, opts.relay);
3122
+ if (!relayResolved) {
3123
+ throw new Error(`Relay chain "${opts.relay}" not found. Add it first with: dot chain add ${opts.relay} --rpc <url>`);
3124
+ }
3125
+ chainConfig.relay = relayResolved;
3126
+ if (parachainIdRaw != null) {
3127
+ chainConfig.parachainId = parachainIdRaw;
3128
+ } else {
3129
+ process.stderr.write(`Detecting parachain ID...
3130
+ `);
3131
+ const detected = await getParachainId(clientHandle);
3132
+ if (detected != null) {
3133
+ chainConfig.parachainId = detected;
3134
+ process.stderr.write(`Detected parachain ID: ${detected}
3135
+ `);
3136
+ } else {
3137
+ process.stderr.write(`Could not detect parachain ID. Use --parachain-id to set it manually.
3138
+ `);
3139
+ }
3140
+ }
3141
+ }
2841
3142
  const config = await loadConfig();
2842
3143
  config.chains[name] = chainConfig;
2843
3144
  await saveConfig(config);
2844
- console.log(`Chain "${name}" added successfully.`);
3145
+ const result = { action: "added", chain: name };
3146
+ if (chainConfig.relay)
3147
+ result.relay = chainConfig.relay;
3148
+ if (chainConfig.parachainId != null)
3149
+ result.parachainId = chainConfig.parachainId;
3150
+ if (isJsonOutput(opts)) {
3151
+ console.log(formatJson(result));
3152
+ } else {
3153
+ console.log(`Chain "${name}" added successfully.`);
3154
+ }
2845
3155
  } finally {
2846
3156
  clientHandle.destroy();
2847
3157
  }
2848
3158
  }
2849
- async function chainRemove(name) {
3159
+ async function chainRemove(name, opts = {}) {
2850
3160
  if (!name) {
2851
3161
  console.error("Usage: dot chain remove <name>");
2852
3162
  process.exit(1);
@@ -2859,32 +3169,85 @@ async function chainRemove(name) {
2859
3169
  if (BUILTIN_CHAIN_NAMES.has(resolved)) {
2860
3170
  throw new Error(`Cannot remove the built-in "${resolved}" chain.`);
2861
3171
  }
3172
+ const orphans = Object.entries(config.chains).filter(([, c]) => c.relay === resolved).map(([n]) => n);
3173
+ if (orphans.length > 0) {
3174
+ console.error(`Warning: ${orphans.length} chain(s) reference "${resolved}" as their relay: ${orphans.join(", ")}`);
3175
+ }
2862
3176
  delete config.chains[resolved];
2863
3177
  if (config.defaultChain === resolved) {
2864
3178
  config.defaultChain = "polkadot";
2865
- console.log(`Default chain reset to "polkadot".`);
3179
+ if (!isJsonOutput(opts))
3180
+ console.log(`Default chain reset to "polkadot".`);
2866
3181
  }
2867
3182
  await saveConfig(config);
2868
3183
  await removeChainData(resolved);
2869
- console.log(`Chain "${resolved}" removed.`);
3184
+ if (isJsonOutput(opts)) {
3185
+ console.log(formatJson({ action: "removed", chain: resolved }));
3186
+ } else {
3187
+ console.log(`Chain "${resolved}" removed.`);
3188
+ }
2870
3189
  }
2871
- async function chainList() {
3190
+ async function chainList(opts = {}) {
2872
3191
  const config = await loadConfig();
3192
+ if (isJsonOutput(opts)) {
3193
+ const chains = Object.entries(config.chains).map(([name, chainConfig]) => ({
3194
+ name,
3195
+ default: name === config.defaultChain,
3196
+ rpc: Array.isArray(chainConfig.rpc) ? chainConfig.rpc : [chainConfig.rpc],
3197
+ ...chainConfig.relay && { relay: chainConfig.relay },
3198
+ ...chainConfig.parachainId != null && { parachainId: chainConfig.parachainId }
3199
+ }));
3200
+ console.log(formatJson({ chains }));
3201
+ return;
3202
+ }
2873
3203
  printHeading("Configured Chains");
3204
+ const parachainsByRelay = new Map;
3205
+ const standalone = [];
2874
3206
  for (const [name, chainConfig] of Object.entries(config.chains)) {
2875
- const isDefault = name === config.defaultChain;
2876
- const marker = isDefault ? ` ${BOLD}(default)${RESET}` : "";
2877
- if (chainConfig.lightClient) {
2878
- console.log(` ${CYAN}${name}${RESET}${marker} ${DIM}light-client${RESET}`);
2879
- } else {
2880
- const rpcs = Array.isArray(chainConfig.rpc) ? chainConfig.rpc : [chainConfig.rpc];
2881
- console.log(` ${CYAN}${name}${RESET}${marker} ${DIM}${rpcs[0]}${RESET}`);
2882
- for (let i = 1;i < rpcs.length; i++) {
2883
- console.log(` ${DIM}${rpcs[i]}${RESET}`);
2884
- }
3207
+ if (chainConfig.relay) {
3208
+ const paras = parachainsByRelay.get(chainConfig.relay) ?? [];
3209
+ paras.push([name, chainConfig]);
3210
+ parachainsByRelay.set(chainConfig.relay, paras);
2885
3211
  }
2886
3212
  }
2887
- console.log();
3213
+ const relayNames = new Set(parachainsByRelay.keys());
3214
+ for (const [name, chainConfig] of Object.entries(config.chains)) {
3215
+ if (relayNames.has(name))
3216
+ continue;
3217
+ if (chainConfig.relay)
3218
+ continue;
3219
+ standalone.push([name, chainConfig]);
3220
+ }
3221
+ for (const relayName of relayNames) {
3222
+ const relayConfig = config.chains[relayName];
3223
+ if (relayConfig) {
3224
+ printChainLine(" ", relayName, relayConfig, config.defaultChain);
3225
+ }
3226
+ const paras = parachainsByRelay.get(relayName) ?? [];
3227
+ for (let i = 0;i < paras.length; i++) {
3228
+ const [name, chainConfig] = paras[i];
3229
+ const isLast = i === paras.length - 1;
3230
+ const prefix = isLast ? " └─ " : " ├─ ";
3231
+ const idSuffix = chainConfig.parachainId != null ? ` ${DIM}[${chainConfig.parachainId}]${RESET}` : "";
3232
+ printChainLine(prefix, name, chainConfig, config.defaultChain, idSuffix);
3233
+ }
3234
+ console.log();
3235
+ }
3236
+ for (const [name, chainConfig] of standalone) {
3237
+ printChainLine(" ", name, chainConfig, config.defaultChain);
3238
+ }
3239
+ if (standalone.length > 0)
3240
+ console.log();
3241
+ }
3242
+ function printChainLine(prefix, name, chainConfig, defaultChain, suffix = "") {
3243
+ const isDefault = name === defaultChain;
3244
+ const marker = isDefault ? ` ${BOLD}(default)${RESET}` : "";
3245
+ const rpcs = Array.isArray(chainConfig.rpc) ? chainConfig.rpc : [chainConfig.rpc];
3246
+ console.log(`${prefix}${CYAN}${name}${RESET}${suffix}${marker} ${DIM}${rpcs[0]}${RESET}`);
3247
+ const indent = prefix.replace(/[^\s]/g, " ");
3248
+ for (let i = 1;i < rpcs.length; i++) {
3249
+ console.log(`${indent} ${DIM}${rpcs[i]}${RESET}`);
3250
+ }
2888
3251
  }
2889
3252
  async function chainUpdate(name, opts) {
2890
3253
  const config = await loadConfig();
@@ -2893,19 +3256,26 @@ async function chainUpdate(name, opts) {
2893
3256
  return;
2894
3257
  }
2895
3258
  const { name: chainName, chain: chainConfig } = resolveChain(config, name);
2896
- console.error(`Connecting to ${chainName}...`);
3259
+ process.stderr.write(`Connecting to ${chainName}...
3260
+ `);
2897
3261
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
2898
3262
  try {
2899
- console.error("Fetching metadata...");
3263
+ process.stderr.write(`Fetching metadata...
3264
+ `);
2900
3265
  await fetchMetadataFromChain(clientHandle, chainName);
2901
- console.log(`Metadata for "${chainName}" updated.`);
3266
+ if (isJsonOutput(opts)) {
3267
+ console.log(formatJson({ action: "updated", chain: chainName }));
3268
+ } else {
3269
+ console.log(`Metadata for "${chainName}" updated.`);
3270
+ }
2902
3271
  } finally {
2903
3272
  clientHandle.destroy();
2904
3273
  }
2905
3274
  }
2906
3275
  async function chainUpdateAll(config) {
2907
3276
  const chainNames = Object.keys(config.chains).sort();
2908
- console.error(`Updating metadata for ${chainNames.length} chains...
3277
+ process.stderr.write(`Updating metadata for ${chainNames.length} chains...
3278
+
2909
3279
  `);
2910
3280
  const results = await Promise.allSettled(chainNames.map(async (chainName) => {
2911
3281
  const chainConfig = config.chains[chainName];
@@ -2932,7 +3302,7 @@ ${failed} of ${chainNames.length} chains failed to update.`);
2932
3302
  process.exit(1);
2933
3303
  }
2934
3304
  }
2935
- async function chainDefault(name) {
3305
+ async function chainDefault(name, opts = {}) {
2936
3306
  if (!name) {
2937
3307
  console.error("Usage: dot chain default <name>");
2938
3308
  process.exit(1);
@@ -2945,7 +3315,11 @@ async function chainDefault(name) {
2945
3315
  }
2946
3316
  config.defaultChain = resolved;
2947
3317
  await saveConfig(config);
2948
- console.log(`Default chain set to "${resolved}".`);
3318
+ if (isJsonOutput(opts)) {
3319
+ console.log(formatJson({ action: "default", chain: resolved }));
3320
+ } else {
3321
+ console.log(`Default chain set to "${resolved}".`);
3322
+ }
2949
3323
  }
2950
3324
 
2951
3325
  // src/commands/completions.ts
@@ -3045,6 +3419,13 @@ async function handleConst(target, opts) {
3045
3419
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
3046
3420
  const pallets = listPallets(meta);
3047
3421
  const withConsts = pallets.filter((p) => p.constants.length > 0);
3422
+ if (isJsonOutput(opts)) {
3423
+ console.log(formatJson({
3424
+ chain: chainName2,
3425
+ pallets: withConsts.map((p) => ({ name: p.name, constants: p.constants.length }))
3426
+ }));
3427
+ return;
3428
+ }
3048
3429
  printHeading(`Pallets with constants on ${chainName2} (${withConsts.length})`);
3049
3430
  for (const p of withConsts) {
3050
3431
  printItem(p.name, `${p.constants.length} constants`);
@@ -3065,7 +3446,23 @@ async function handleConst(target, opts) {
3065
3446
  throw new Error(suggestMessage("pallet", pallet, palletNames));
3066
3447
  }
3067
3448
  if (palletInfo.constants.length === 0) {
3068
- console.log(`No constants in ${palletInfo.name}.`);
3449
+ if (isJsonOutput(opts)) {
3450
+ console.log(formatJson({ chain: chainName, pallet: palletInfo.name, constants: [] }));
3451
+ } else {
3452
+ console.log(`No constants in ${palletInfo.name}.`);
3453
+ }
3454
+ return;
3455
+ }
3456
+ if (isJsonOutput(opts)) {
3457
+ console.log(formatJson({
3458
+ chain: chainName,
3459
+ pallet: palletInfo.name,
3460
+ constants: palletInfo.constants.map((c) => ({
3461
+ name: c.name,
3462
+ type: describeType(meta.lookup, c.typeId),
3463
+ docs: firstSentence(c.docs)
3464
+ }))
3465
+ }));
3069
3466
  return;
3070
3467
  }
3071
3468
  printHeading(`${palletInfo.name} Constants`);
@@ -3094,9 +3491,9 @@ async function handleConst(target, opts) {
3094
3491
  throw new Error(suggestMessage(`constant in ${palletInfo.name}`, item, constNames));
3095
3492
  }
3096
3493
  const unsafeApi = clientHandle.client.getUnsafeApi();
3097
- const runtimeToken = await unsafeApi.runtimeToken;
3098
- const result = unsafeApi.constants[palletInfo.name][constantItem.name](runtimeToken);
3099
- const format = opts.output ?? "pretty";
3494
+ const staticApis = await unsafeApi.getStaticApis();
3495
+ const result = staticApis.constants[palletInfo.name][constantItem.name];
3496
+ const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
3100
3497
  printResult(result, format);
3101
3498
  } finally {
3102
3499
  clientHandle.destroy();
@@ -3112,7 +3509,8 @@ async function loadMeta2(chainName, chainConfig, rpcOverride) {
3112
3509
  try {
3113
3510
  return await getOrFetchMetadata(chainName);
3114
3511
  } catch {
3115
- console.error(`Fetching metadata from ${chainName}...`);
3512
+ process.stderr.write(`Fetching metadata from ${chainName}...
3513
+ `);
3116
3514
  const clientHandle = await createChainClient(chainName, chainConfig, rpcOverride);
3117
3515
  try {
3118
3516
  return await getOrFetchMetadata(chainName, clientHandle);
@@ -3136,6 +3534,13 @@ async function handleCalls2(target, opts) {
3136
3534
  const meta2 = await loadMeta2(chainName2, chainConfig2, opts.rpc);
3137
3535
  const pallets = listPallets(meta2);
3138
3536
  const withCalls = pallets.filter((p) => p.calls.length > 0);
3537
+ if (isJsonOutput(opts)) {
3538
+ console.log(formatJson({
3539
+ chain: chainName2,
3540
+ pallets: withCalls.map((p) => ({ name: p.name, calls: p.calls.length }))
3541
+ }));
3542
+ return;
3543
+ }
3139
3544
  printHeading(`Pallets with calls on ${chainName2} (${withCalls.length})`);
3140
3545
  for (const p of withCalls) {
3141
3546
  printItem(p.name, `${p.calls.length} calls`);
@@ -3152,7 +3557,23 @@ async function handleCalls2(target, opts) {
3152
3557
  const pallet = resolvePallet2(meta, palletName);
3153
3558
  if (!itemName) {
3154
3559
  if (pallet.calls.length === 0) {
3155
- console.log(`No calls in ${pallet.name}.`);
3560
+ if (isJsonOutput(opts)) {
3561
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, calls: [] }));
3562
+ } else {
3563
+ console.log(`No calls in ${pallet.name}.`);
3564
+ }
3565
+ return;
3566
+ }
3567
+ if (isJsonOutput(opts)) {
3568
+ console.log(formatJson({
3569
+ chain: chainName,
3570
+ pallet: pallet.name,
3571
+ calls: pallet.calls.map((c) => ({
3572
+ name: c.name,
3573
+ args: describeCallArgs(meta, pallet.name, c.name),
3574
+ docs: firstSentence(c.docs)
3575
+ }))
3576
+ }));
3156
3577
  return;
3157
3578
  }
3158
3579
  printHeading(`${pallet.name} Calls`);
@@ -3172,6 +3593,17 @@ async function handleCalls2(target, opts) {
3172
3593
  const names = pallet.calls.map((c) => c.name);
3173
3594
  throw new Error(suggestMessage(`call in ${pallet.name}`, itemName, names));
3174
3595
  }
3596
+ if (isJsonOutput(opts)) {
3597
+ console.log(formatJson({
3598
+ chain: chainName,
3599
+ pallet: pallet.name,
3600
+ item: callItem.name,
3601
+ category: "call",
3602
+ args: describeCallArgs(meta, pallet.name, callItem.name),
3603
+ docs: callItem.docs
3604
+ }));
3605
+ return;
3606
+ }
3175
3607
  printHeading(`${pallet.name}.${callItem.name} (Call)`);
3176
3608
  const args = describeCallArgs(meta, pallet.name, callItem.name);
3177
3609
  console.log(` ${BOLD}Args:${RESET} ${args}`);
@@ -3188,6 +3620,13 @@ async function handleEvents2(target, opts) {
3188
3620
  const meta2 = await loadMeta2(chainName2, chainConfig2, opts.rpc);
3189
3621
  const pallets = listPallets(meta2);
3190
3622
  const withEvents = pallets.filter((p) => p.events.length > 0);
3623
+ if (isJsonOutput(opts)) {
3624
+ console.log(formatJson({
3625
+ chain: chainName2,
3626
+ pallets: withEvents.map((p) => ({ name: p.name, events: p.events.length }))
3627
+ }));
3628
+ return;
3629
+ }
3191
3630
  printHeading(`Pallets with events on ${chainName2} (${withEvents.length})`);
3192
3631
  for (const p of withEvents) {
3193
3632
  printItem(p.name, `${p.events.length} events`);
@@ -3204,7 +3643,23 @@ async function handleEvents2(target, opts) {
3204
3643
  const pallet = resolvePallet2(meta, palletName);
3205
3644
  if (!itemName) {
3206
3645
  if (pallet.events.length === 0) {
3207
- console.log(`No events in ${pallet.name}.`);
3646
+ if (isJsonOutput(opts)) {
3647
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, events: [] }));
3648
+ } else {
3649
+ console.log(`No events in ${pallet.name}.`);
3650
+ }
3651
+ return;
3652
+ }
3653
+ if (isJsonOutput(opts)) {
3654
+ console.log(formatJson({
3655
+ chain: chainName,
3656
+ pallet: pallet.name,
3657
+ events: pallet.events.map((e) => ({
3658
+ name: e.name,
3659
+ fields: describeEventFields(meta, pallet.name, e.name),
3660
+ docs: firstSentence(e.docs)
3661
+ }))
3662
+ }));
3208
3663
  return;
3209
3664
  }
3210
3665
  printHeading(`${pallet.name} Events`);
@@ -3224,6 +3679,17 @@ async function handleEvents2(target, opts) {
3224
3679
  const names = pallet.events.map((e) => e.name);
3225
3680
  throw new Error(suggestMessage(`event in ${pallet.name}`, itemName, names));
3226
3681
  }
3682
+ if (isJsonOutput(opts)) {
3683
+ console.log(formatJson({
3684
+ chain: chainName,
3685
+ pallet: pallet.name,
3686
+ item: eventItem.name,
3687
+ category: "event",
3688
+ fields: describeEventFields(meta, pallet.name, eventItem.name),
3689
+ docs: eventItem.docs
3690
+ }));
3691
+ return;
3692
+ }
3227
3693
  printHeading(`${pallet.name}.${eventItem.name} (Event)`);
3228
3694
  const fields = describeEventFields(meta, pallet.name, eventItem.name);
3229
3695
  console.log(` ${BOLD}Fields:${RESET} ${fields}`);
@@ -3240,6 +3706,13 @@ async function handleErrors2(target, opts) {
3240
3706
  const meta2 = await loadMeta2(chainName2, chainConfig2, opts.rpc);
3241
3707
  const pallets = listPallets(meta2);
3242
3708
  const withErrors = pallets.filter((p) => p.errors.length > 0);
3709
+ if (isJsonOutput(opts)) {
3710
+ console.log(formatJson({
3711
+ chain: chainName2,
3712
+ pallets: withErrors.map((p) => ({ name: p.name, errors: p.errors.length }))
3713
+ }));
3714
+ return;
3715
+ }
3243
3716
  printHeading(`Pallets with errors on ${chainName2} (${withErrors.length})`);
3244
3717
  for (const p of withErrors) {
3245
3718
  printItem(p.name, `${p.errors.length} errors`);
@@ -3256,7 +3729,19 @@ async function handleErrors2(target, opts) {
3256
3729
  const pallet = resolvePallet2(meta, palletName);
3257
3730
  if (!itemName) {
3258
3731
  if (pallet.errors.length === 0) {
3259
- console.log(`No errors in ${pallet.name}.`);
3732
+ if (isJsonOutput(opts)) {
3733
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, errors: [] }));
3734
+ } else {
3735
+ console.log(`No errors in ${pallet.name}.`);
3736
+ }
3737
+ return;
3738
+ }
3739
+ if (isJsonOutput(opts)) {
3740
+ console.log(formatJson({
3741
+ chain: chainName,
3742
+ pallet: pallet.name,
3743
+ errors: pallet.errors.map((e) => ({ name: e.name, docs: firstSentence(e.docs) }))
3744
+ }));
3260
3745
  return;
3261
3746
  }
3262
3747
  printHeading(`${pallet.name} Errors`);
@@ -3275,6 +3760,16 @@ async function handleErrors2(target, opts) {
3275
3760
  const names = pallet.errors.map((e) => e.name);
3276
3761
  throw new Error(suggestMessage(`error in ${pallet.name}`, itemName, names));
3277
3762
  }
3763
+ if (isJsonOutput(opts)) {
3764
+ console.log(formatJson({
3765
+ chain: chainName,
3766
+ pallet: pallet.name,
3767
+ item: errorItem.name,
3768
+ category: "error",
3769
+ docs: errorItem.docs
3770
+ }));
3771
+ return;
3772
+ }
3278
3773
  printHeading(`${pallet.name}.${errorItem.name} (Error)`);
3279
3774
  if (errorItem.docs.length) {
3280
3775
  printDocs(errorItem.docs);
@@ -3288,6 +3783,13 @@ async function handleStorage2(target, opts) {
3288
3783
  const meta2 = await loadMeta2(chainName2, chainConfig2, opts.rpc);
3289
3784
  const pallets = listPallets(meta2);
3290
3785
  const withStorage = pallets.filter((p) => p.storage.length > 0);
3786
+ if (isJsonOutput(opts)) {
3787
+ console.log(formatJson({
3788
+ chain: chainName2,
3789
+ pallets: withStorage.map((p) => ({ name: p.name, storage: p.storage.length }))
3790
+ }));
3791
+ return;
3792
+ }
3291
3793
  printHeading(`Pallets with storage on ${chainName2} (${withStorage.length})`);
3292
3794
  for (const p of withStorage) {
3293
3795
  printItem(p.name, `${p.storage.length} storage`);
@@ -3304,7 +3806,23 @@ async function handleStorage2(target, opts) {
3304
3806
  const pallet = resolvePallet2(meta, palletName);
3305
3807
  if (!itemName) {
3306
3808
  if (pallet.storage.length === 0) {
3307
- console.log(`No storage items in ${pallet.name}.`);
3809
+ if (isJsonOutput(opts)) {
3810
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, storage: [] }));
3811
+ } else {
3812
+ console.log(`No storage items in ${pallet.name}.`);
3813
+ }
3814
+ return;
3815
+ }
3816
+ if (isJsonOutput(opts)) {
3817
+ console.log(formatJson({
3818
+ chain: chainName,
3819
+ pallet: pallet.name,
3820
+ storage: pallet.storage.map((s) => {
3821
+ const valueType = describeType(meta.lookup, s.valueTypeId);
3822
+ const keyType = s.keyTypeId != null ? describeType(meta.lookup, s.keyTypeId) : undefined;
3823
+ return { name: s.name, type: s.type, valueType, keyType, docs: firstSentence(s.docs) };
3824
+ })
3825
+ }));
3308
3826
  return;
3309
3827
  }
3310
3828
  printHeading(`${pallet.name} Storage`);
@@ -3331,6 +3849,21 @@ async function handleStorage2(target, opts) {
3331
3849
  const names = pallet.storage.map((s) => s.name);
3332
3850
  throw new Error(suggestMessage(`storage item in ${pallet.name}`, itemName, names));
3333
3851
  }
3852
+ if (isJsonOutput(opts)) {
3853
+ const valueType = describeType(meta.lookup, storageItem.valueTypeId);
3854
+ const keyType = storageItem.keyTypeId != null ? describeType(meta.lookup, storageItem.keyTypeId) : undefined;
3855
+ console.log(formatJson({
3856
+ chain: chainName,
3857
+ pallet: pallet.name,
3858
+ item: storageItem.name,
3859
+ category: "storage",
3860
+ type: storageItem.type,
3861
+ valueType,
3862
+ keyType,
3863
+ docs: storageItem.docs
3864
+ }));
3865
+ return;
3866
+ }
3334
3867
  printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
3335
3868
  console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
3336
3869
  console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
@@ -3574,8 +4107,7 @@ function registerHashCommand(cli) {
3574
4107
  const input = await resolveInput(data, opts);
3575
4108
  const hash = computeHash(algorithm, input);
3576
4109
  const hexHash = toHex2(hash);
3577
- const format = opts.output ?? "pretty";
3578
- if (format === "json") {
4110
+ if (isJsonOutput(opts)) {
3579
4111
  printResult({
3580
4112
  algorithm,
3581
4113
  input: data ?? (opts.file ? `file:${opts.file}` : "stdin"),
@@ -3661,7 +4193,8 @@ function registerInspectCommand(cli) {
3661
4193
  try {
3662
4194
  meta = await getOrFetchMetadata(chainName);
3663
4195
  } catch {
3664
- console.error(`Fetching metadata from ${chainName}...`);
4196
+ process.stderr.write(`Fetching metadata from ${chainName}...
4197
+ `);
3665
4198
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
3666
4199
  try {
3667
4200
  meta = await getOrFetchMetadata(chainName, clientHandle);
@@ -3671,6 +4204,20 @@ function registerInspectCommand(cli) {
3671
4204
  }
3672
4205
  if (!target) {
3673
4206
  const pallets = listPallets(meta);
4207
+ if (isJsonOutput(opts)) {
4208
+ console.log(formatJson({
4209
+ chain: chainName,
4210
+ pallets: pallets.map((p) => ({
4211
+ name: p.name,
4212
+ storage: p.storage.length,
4213
+ constants: p.constants.length,
4214
+ calls: p.calls.length,
4215
+ events: p.events.length,
4216
+ errors: p.errors.length
4217
+ }))
4218
+ }));
4219
+ return;
4220
+ }
3674
4221
  printHeading(`Pallets on ${chainName} (${pallets.length})`);
3675
4222
  for (const p of pallets) {
3676
4223
  const counts = [];
@@ -3695,6 +4242,44 @@ function registerInspectCommand(cli) {
3695
4242
  if (!pallet2) {
3696
4243
  throw new Error(suggestMessage("pallet", palletName, palletNames2));
3697
4244
  }
4245
+ if (isJsonOutput(opts)) {
4246
+ console.log(formatJson({
4247
+ chain: chainName,
4248
+ pallet: pallet2.name,
4249
+ docs: pallet2.docs,
4250
+ storage: pallet2.storage.map((s) => {
4251
+ const valueType = describeType(meta.lookup, s.valueTypeId);
4252
+ const keyType = s.keyTypeId != null ? describeType(meta.lookup, s.keyTypeId) : undefined;
4253
+ return {
4254
+ name: s.name,
4255
+ type: s.type,
4256
+ valueType,
4257
+ keyType,
4258
+ docs: firstSentence(s.docs)
4259
+ };
4260
+ }),
4261
+ constants: pallet2.constants.map((c) => ({
4262
+ name: c.name,
4263
+ type: describeType(meta.lookup, c.typeId),
4264
+ docs: firstSentence(c.docs)
4265
+ })),
4266
+ calls: pallet2.calls.map((c) => ({
4267
+ name: c.name,
4268
+ args: describeCallArgs(meta, pallet2.name, c.name),
4269
+ docs: firstSentence(c.docs)
4270
+ })),
4271
+ events: pallet2.events.map((e) => ({
4272
+ name: e.name,
4273
+ fields: describeEventFields(meta, pallet2.name, e.name),
4274
+ docs: firstSentence(e.docs)
4275
+ })),
4276
+ errors: pallet2.errors.map((e) => ({
4277
+ name: e.name,
4278
+ docs: firstSentence(e.docs)
4279
+ }))
4280
+ }));
4281
+ return;
4282
+ }
3698
4283
  printHeading(`${pallet2.name} Pallet`);
3699
4284
  if (pallet2.docs.length) {
3700
4285
  printDocs(pallet2.docs);
@@ -3773,6 +4358,79 @@ function registerInspectCommand(cli) {
3773
4358
  if (!pallet) {
3774
4359
  throw new Error(suggestMessage("pallet", palletName, palletNames));
3775
4360
  }
4361
+ if (isJsonOutput(opts)) {
4362
+ const si = pallet.storage.find((s) => s.name.toLowerCase() === itemName.toLowerCase());
4363
+ if (si) {
4364
+ const valueType = describeType(meta.lookup, si.valueTypeId);
4365
+ const keyType = si.keyTypeId != null ? describeType(meta.lookup, si.keyTypeId) : undefined;
4366
+ console.log(formatJson({
4367
+ chain: chainName,
4368
+ pallet: pallet.name,
4369
+ item: si.name,
4370
+ category: "storage",
4371
+ type: si.type,
4372
+ valueType,
4373
+ keyType,
4374
+ docs: si.docs
4375
+ }));
4376
+ return;
4377
+ }
4378
+ const ci = pallet.constants.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
4379
+ if (ci) {
4380
+ console.log(formatJson({
4381
+ chain: chainName,
4382
+ pallet: pallet.name,
4383
+ item: ci.name,
4384
+ category: "constant",
4385
+ type: describeType(meta.lookup, ci.typeId),
4386
+ docs: ci.docs
4387
+ }));
4388
+ return;
4389
+ }
4390
+ const ca = pallet.calls.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
4391
+ if (ca) {
4392
+ console.log(formatJson({
4393
+ chain: chainName,
4394
+ pallet: pallet.name,
4395
+ item: ca.name,
4396
+ category: "call",
4397
+ args: describeCallArgs(meta, pallet.name, ca.name),
4398
+ docs: ca.docs
4399
+ }));
4400
+ return;
4401
+ }
4402
+ const ev = pallet.events.find((e) => e.name.toLowerCase() === itemName.toLowerCase());
4403
+ if (ev) {
4404
+ console.log(formatJson({
4405
+ chain: chainName,
4406
+ pallet: pallet.name,
4407
+ item: ev.name,
4408
+ category: "event",
4409
+ fields: describeEventFields(meta, pallet.name, ev.name),
4410
+ docs: ev.docs
4411
+ }));
4412
+ return;
4413
+ }
4414
+ const er = pallet.errors.find((e) => e.name.toLowerCase() === itemName.toLowerCase());
4415
+ if (er) {
4416
+ console.log(formatJson({
4417
+ chain: chainName,
4418
+ pallet: pallet.name,
4419
+ item: er.name,
4420
+ category: "error",
4421
+ docs: er.docs
4422
+ }));
4423
+ return;
4424
+ }
4425
+ const allItems2 = [
4426
+ ...pallet.storage.map((s) => s.name),
4427
+ ...pallet.constants.map((c) => c.name),
4428
+ ...pallet.calls.map((c) => c.name),
4429
+ ...pallet.events.map((e) => e.name),
4430
+ ...pallet.errors.map((e) => e.name)
4431
+ ];
4432
+ throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems2));
4433
+ }
3776
4434
  const storageItem = pallet.storage.find((s) => s.name.toLowerCase() === itemName.toLowerCase());
3777
4435
  if (storageItem) {
3778
4436
  printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
@@ -3906,8 +4564,7 @@ function registerParachainCommand(cli) {
3906
4564
  throw new CliError(`Invalid prefix "${opts.prefix}". Must be a non-negative integer.`);
3907
4565
  }
3908
4566
  const types = opts.type ? [validateType(opts.type)] : SOVEREIGN_ACCOUNT_TYPES;
3909
- const format = opts.output ?? "pretty";
3910
- if (format === "json") {
4567
+ if (isJsonOutput(opts)) {
3911
4568
  const result = { paraId, prefix };
3912
4569
  for (const type of types) {
3913
4570
  const accountId = deriveSovereignAccount(paraId, type);
@@ -3948,6 +4605,13 @@ async function handleQuery(target, keys, opts) {
3948
4605
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
3949
4606
  const pallets = listPallets(meta);
3950
4607
  const withStorage = pallets.filter((p) => p.storage.length > 0);
4608
+ if (isJsonOutput(opts)) {
4609
+ console.log(formatJson({
4610
+ chain: chainName2,
4611
+ pallets: withStorage.map((p) => ({ name: p.name, storage: p.storage.length }))
4612
+ }));
4613
+ return;
4614
+ }
3951
4615
  printHeading(`Pallets with storage on ${chainName2} (${withStorage.length})`);
3952
4616
  for (const p of withStorage) {
3953
4617
  printItem(p.name, `${p.storage.length} storage items`);
@@ -3962,7 +4626,23 @@ async function handleQuery(target, keys, opts) {
3962
4626
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
3963
4627
  const pallet2 = resolvePallet(meta, palletName(target));
3964
4628
  if (pallet2.storage.length === 0) {
3965
- console.log(`No storage items in ${pallet2.name}.`);
4629
+ if (isJsonOutput(opts)) {
4630
+ console.log(formatJson({ chain: chainName2, pallet: pallet2.name, storage: [] }));
4631
+ } else {
4632
+ console.log(`No storage items in ${pallet2.name}.`);
4633
+ }
4634
+ return;
4635
+ }
4636
+ if (isJsonOutput(opts)) {
4637
+ console.log(formatJson({
4638
+ chain: chainName2,
4639
+ pallet: pallet2.name,
4640
+ storage: pallet2.storage.map((s) => {
4641
+ const valueType = describeType(meta.lookup, s.valueTypeId);
4642
+ const keyType = s.keyTypeId != null ? describeType(meta.lookup, s.keyTypeId) : undefined;
4643
+ return { name: s.name, type: s.type, valueType, keyType, docs: firstSentence(s.docs) };
4644
+ })
4645
+ }));
3966
4646
  return;
3967
4647
  }
3968
4648
  printHeading(`${pallet2.name} Storage`);
@@ -4007,7 +4687,7 @@ async function handleQuery(target, keys, opts) {
4007
4687
  typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
4008
4688
  ];
4009
4689
  const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, effectiveKeys);
4010
- const format = opts.output ?? "pretty";
4690
+ const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
4011
4691
  const expectedLen = storageItem.type === "map" && storageItem.keyTypeId != null ? meta.builder.buildStorage(palletInfo.name, storageItem.name).len : 0;
4012
4692
  if (storageItem.type === "map" && parsedKeys.length < expectedLen) {
4013
4693
  if (parsedKeys.length === 0 && !opts.dump) {
@@ -4152,8 +4832,7 @@ function registerSignCommand(cli) {
4152
4832
  signature: hexSignature,
4153
4833
  enum: enumValue
4154
4834
  };
4155
- const format = opts.output ?? "pretty";
4156
- if (format === "json") {
4835
+ if (isJsonOutput(opts)) {
4157
4836
  printResult(result, "json");
4158
4837
  } else {
4159
4838
  console.log(` ${BOLD}Type:${RESET} ${result.type}`);
@@ -4172,6 +4851,7 @@ init_client();
4172
4851
  init_metadata();
4173
4852
  init_output();
4174
4853
  init_resolve_address();
4854
+ init_binary_display();
4175
4855
  init_errors();
4176
4856
  init_focused_inspect();
4177
4857
  import { getViewBuilder as getViewBuilder2 } from "@polkadot-api/view-builder";
@@ -4229,11 +4909,14 @@ function parseMortalityOption(raw) {
4229
4909
  function parseAtOption(raw) {
4230
4910
  if (raw === undefined)
4231
4911
  return;
4232
- if (raw === "best" || raw === "finalized")
4233
- return raw;
4912
+ if (raw === "finalized")
4913
+ return;
4914
+ if (raw === "best") {
4915
+ throw new CliError('"best" is no longer supported for --at in papi v2. Omit --at for finalized, or pass a specific block hash.');
4916
+ }
4234
4917
  if (/^0x[0-9a-fA-F]{64}$/.test(raw))
4235
4918
  return raw;
4236
- throw new CliError(`Invalid --at value "${raw}". Use "best", "finalized", or a 0x-prefixed 32-byte block hash.`);
4919
+ throw new CliError(`Invalid --at value "${raw}". Use a 0x-prefixed 32-byte block hash, or omit for finalized.`);
4237
4920
  }
4238
4921
  async function handleTx(target, args, opts) {
4239
4922
  if (!target) {
@@ -4242,6 +4925,13 @@ async function handleTx(target, args, opts) {
4242
4925
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
4243
4926
  const pallets = listPallets(meta);
4244
4927
  const withCalls = pallets.filter((p) => p.calls.length > 0);
4928
+ if (isJsonOutput(opts)) {
4929
+ console.log(formatJson({
4930
+ chain: chainName2,
4931
+ pallets: withCalls.map((p) => ({ name: p.name, calls: p.calls.length }))
4932
+ }));
4933
+ return;
4934
+ }
4245
4935
  printHeading(`Pallets with calls on ${chainName2} (${withCalls.length})`);
4246
4936
  for (const p of withCalls) {
4247
4937
  printItem(p.name, `${p.calls.length} calls`);
@@ -4256,7 +4946,23 @@ async function handleTx(target, args, opts) {
4256
4946
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
4257
4947
  const pallet2 = resolvePallet(meta, target);
4258
4948
  if (pallet2.calls.length === 0) {
4259
- console.log(`No calls in ${pallet2.name}.`);
4949
+ if (isJsonOutput(opts)) {
4950
+ console.log(formatJson({ chain: chainName2, pallet: pallet2.name, calls: [] }));
4951
+ } else {
4952
+ console.log(`No calls in ${pallet2.name}.`);
4953
+ }
4954
+ return;
4955
+ }
4956
+ if (isJsonOutput(opts)) {
4957
+ console.log(formatJson({
4958
+ chain: chainName2,
4959
+ pallet: pallet2.name,
4960
+ calls: pallet2.calls.map((c) => ({
4961
+ name: c.name,
4962
+ args: describeCallArgs(meta, pallet2.name, c.name),
4963
+ docs: firstSentence(c.docs)
4964
+ }))
4965
+ }));
4260
4966
  return;
4261
4967
  }
4262
4968
  printHeading(`${pallet2.name} Calls`);
@@ -4271,7 +4977,7 @@ async function handleTx(target, args, opts) {
4271
4977
  console.log();
4272
4978
  return;
4273
4979
  }
4274
- if (!opts.from && !opts.encode && !opts.yaml && !opts.json) {
4980
+ if (!opts.from && !opts.encode && !opts.toYaml && !opts.toJson) {
4275
4981
  if (isRawCall) {
4276
4982
  throw new Error("--from is required (or use --encode to output hex without signing)");
4277
4983
  }
@@ -4284,14 +4990,14 @@ async function handleTx(target, args, opts) {
4284
4990
  if (opts.encode && isRawCall) {
4285
4991
  throw new Error("--encode cannot be used with raw call hex (already encoded)");
4286
4992
  }
4287
- if ((opts.yaml || opts.json) && opts.encode) {
4288
- throw new Error("--yaml/--json and --encode are mutually exclusive");
4993
+ if ((opts.toYaml || opts.toJson) && opts.encode) {
4994
+ throw new Error("--to-yaml/--to-json and --encode are mutually exclusive");
4289
4995
  }
4290
- if ((opts.yaml || opts.json) && opts.dryRun) {
4291
- throw new Error("--yaml/--json and --dry-run are mutually exclusive");
4996
+ if ((opts.toYaml || opts.toJson) && opts.dryRun) {
4997
+ throw new Error("--to-yaml/--to-json and --dry-run are mutually exclusive");
4292
4998
  }
4293
- if (opts.yaml && opts.json) {
4294
- throw new Error("--yaml and --json are mutually exclusive");
4999
+ if (opts.toYaml && opts.toJson) {
5000
+ throw new Error("--to-yaml and --to-json are mutually exclusive");
4295
5001
  }
4296
5002
  const config = await loadConfig();
4297
5003
  const effectiveChain = opts.chain;
@@ -4303,7 +5009,7 @@ async function handleTx(target, args, opts) {
4303
5009
  callName = target.slice(dotIdx + 1);
4304
5010
  }
4305
5011
  const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
4306
- const decodeOnly = opts.encode || opts.yaml || opts.json;
5012
+ const decodeOnly = opts.encode || opts.toYaml || opts.toJson;
4307
5013
  const signer = decodeOnly ? undefined : await resolveAccountSigner(opts.from);
4308
5014
  let clientHandle;
4309
5015
  if (!decodeOnly) {
@@ -4352,9 +5058,9 @@ async function handleTx(target, args, opts) {
4352
5058
  ` + "Usage: dot tx 0x<call_hex> --from <account>");
4353
5059
  }
4354
5060
  callHex = target;
4355
- if (opts.yaml || opts.json) {
5061
+ if (opts.toYaml || opts.toJson) {
4356
5062
  const fileObj = decodeCallToFileFormat(meta, callHex, chainName);
4357
- outputFileFormat(fileObj, !!opts.yaml);
5063
+ outputFileFormat(fileObj, !!opts.toYaml);
4358
5064
  return;
4359
5065
  }
4360
5066
  const callBinary = Binary3.fromHex(target);
@@ -4372,26 +5078,55 @@ async function handleTx(target, args, opts) {
4372
5078
  }
4373
5079
  const effectiveArgs = opts.parsedArgs !== undefined ? fileArgsToStrings(opts.parsedArgs) : args;
4374
5080
  const callData = await parseCallArgs(meta, palletInfo.name, callInfo.name, effectiveArgs);
4375
- if (opts.encode || opts.yaml || opts.json) {
5081
+ if (opts.encode || opts.toYaml || opts.toJson) {
4376
5082
  const { codec, location } = meta.builder.buildCall(palletInfo.name, callInfo.name);
4377
5083
  const encodedArgs = codec.enc(callData);
4378
5084
  const fullCall = new Uint8Array([location[0], location[1], ...encodedArgs]);
4379
- const hex = Binary3.fromBytes(fullCall).asHex();
5085
+ const hex = Binary3.toHex(fullCall);
4380
5086
  if (opts.encode) {
4381
- console.log(hex);
5087
+ if (isJsonOutput(opts)) {
5088
+ console.log(formatJson({ callHex: hex }));
5089
+ } else {
5090
+ console.log(hex);
5091
+ }
4382
5092
  return;
4383
5093
  }
4384
5094
  const fileObj = decodeCallToFileFormat(meta, hex, chainName);
4385
- outputFileFormat(fileObj, !!opts.yaml);
5095
+ outputFileFormat(fileObj, !!opts.toYaml);
4386
5096
  return;
4387
5097
  }
4388
5098
  tx = unsafeApi.tx[palletInfo.name][callInfo.name](callData);
4389
5099
  const encodedCall = await tx.getEncodedData();
4390
- callHex = encodedCall.asHex();
5100
+ callHex = Binary3.toHex(encodedCall);
4391
5101
  }
4392
5102
  const decodedStr = decodeCall(meta, callHex);
4393
5103
  if (opts.dryRun) {
4394
5104
  const signerAddress = toSs58(signer.publicKey);
5105
+ let estimatedFees;
5106
+ try {
5107
+ estimatedFees = String(await tx.getEstimatedFees(signer?.publicKey, txOptions));
5108
+ } catch {
5109
+ estimatedFees = undefined;
5110
+ }
5111
+ if (isJsonOutput(opts)) {
5112
+ const result2 = {
5113
+ chain: chainName,
5114
+ from: { name: opts.from, address: signerAddress },
5115
+ callHex,
5116
+ decoded: decodedStr,
5117
+ estimatedFees
5118
+ };
5119
+ if (nonce !== undefined)
5120
+ result2.nonce = nonce;
5121
+ if (tip !== undefined)
5122
+ result2.tip = String(tip);
5123
+ if (mortality !== undefined)
5124
+ result2.mortality = mortality.mortal ? `mortal (period ${mortality.period})` : "immortal";
5125
+ if (at !== undefined)
5126
+ result2.at = at;
5127
+ console.log(formatJson(result2));
5128
+ return;
5129
+ }
4395
5130
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
4396
5131
  console.log(` ${BOLD}From:${RESET} ${opts.from} (${signerAddress})`);
4397
5132
  console.log(` ${BOLD}Call:${RESET} ${callHex}`);
@@ -4404,16 +5139,46 @@ async function handleTx(target, args, opts) {
4404
5139
  console.log(` ${BOLD}Mortality:${RESET} ${mortality.mortal ? `mortal (period ${mortality.period})` : "immortal"}`);
4405
5140
  if (at !== undefined)
4406
5141
  console.log(` ${BOLD}At:${RESET} ${at}`);
4407
- try {
4408
- const fees = await tx.getEstimatedFees(signer?.publicKey, txOptions);
4409
- console.log(` ${BOLD}Estimated fees:${RESET} ${fees}`);
4410
- } catch (err) {
5142
+ if (estimatedFees !== undefined) {
5143
+ console.log(` ${BOLD}Estimated fees:${RESET} ${estimatedFees}`);
5144
+ } else {
4411
5145
  console.log(` ${BOLD}Estimated fees:${RESET} ${YELLOW}unable to estimate${RESET}`);
4412
- console.log(` ${DIM}${err.message ?? err}${RESET}`);
4413
5146
  }
4414
5147
  return;
4415
5148
  }
4416
5149
  const waitLevel = parseWaitLevel(opts.wait);
5150
+ if (isJsonOutput(opts)) {
5151
+ const result2 = await watchTransactionJson(tx.signSubmitAndWatch(signer, txOptions), waitLevel);
5152
+ const rpcUrl2 = primaryRpc(opts.rpc ?? chainConfig.rpc);
5153
+ if (result2.type === "broadcasted") {
5154
+ printJsonLine({ event: "broadcasted", txHash: result2.txHash });
5155
+ return;
5156
+ }
5157
+ const blockHash = result2.block.hash;
5158
+ const explorer = {};
5159
+ if (rpcUrl2) {
5160
+ explorer.polkadotjs = pjsAppsLink(rpcUrl2, blockHash);
5161
+ explorer.papi = papiLink(rpcUrl2, blockHash);
5162
+ }
5163
+ printJsonLine({
5164
+ event: result2.type === "finalized" ? "finalized" : "bestBlock",
5165
+ blockNumber: result2.block.number,
5166
+ blockHash,
5167
+ txHash: result2.txHash,
5168
+ ok: result2.ok,
5169
+ events: result2.events?.map((e) => ({
5170
+ pallet: e.type,
5171
+ name: e.value?.type,
5172
+ fields: e.value?.value
5173
+ })),
5174
+ dispatchError: result2.ok ? null : formatDispatchError(result2.dispatchError),
5175
+ explorer
5176
+ });
5177
+ if (!result2.ok) {
5178
+ throw new CliError(`Transaction dispatch error: ${formatDispatchError(result2.dispatchError)}`);
5179
+ }
5180
+ return;
5181
+ }
4417
5182
  const result = await watchTransaction(tx.signSubmitAndWatch(signer, txOptions), waitLevel);
4418
5183
  console.log();
4419
5184
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
@@ -4508,7 +5273,7 @@ function decodeCallFallback(meta, callHex) {
4508
5273
  if (callTypeId == null)
4509
5274
  throw new Error("No RuntimeCall type ID");
4510
5275
  const codec = meta.builder.buildDefinition(callTypeId);
4511
- const decoded = codec.dec(Binary3.fromHex(callHex).asBytes());
5276
+ const decoded = codec.dec(Binary3.fromHex(callHex));
4512
5277
  const palletName2 = decoded.type;
4513
5278
  const call = decoded.value;
4514
5279
  const callName = call.type;
@@ -4524,7 +5289,7 @@ function decodeCallToFileFormat(meta, callHex, chainName) {
4524
5289
  if (callTypeId == null)
4525
5290
  throw new Error("No RuntimeCall type ID in metadata");
4526
5291
  const codec = meta.builder.buildDefinition(callTypeId);
4527
- const decoded = codec.dec(Binary3.fromHex(callHex).asBytes());
5292
+ const decoded = codec.dec(Binary3.fromHex(callHex));
4528
5293
  const palletName2 = decoded.type;
4529
5294
  const call = decoded.value;
4530
5295
  const callName = call.type;
@@ -4541,8 +5306,8 @@ function decodeCallToFileFormat(meta, callHex, chainName) {
4541
5306
  function sanitizeForSerialization(value) {
4542
5307
  if (value === undefined || value === null)
4543
5308
  return null;
4544
- if (value instanceof Binary3)
4545
- return value.asHex();
5309
+ if (value instanceof Uint8Array)
5310
+ return Binary3.toHex(value);
4546
5311
  if (typeof value === "bigint") {
4547
5312
  if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) {
4548
5313
  return Number(value);
@@ -4574,8 +5339,8 @@ function outputFileFormat(obj, asYaml) {
4574
5339
  function formatRawDecoded(value) {
4575
5340
  if (value === undefined || value === null)
4576
5341
  return "null";
4577
- if (value instanceof Binary3)
4578
- return value.asHex();
5342
+ if (value instanceof Uint8Array)
5343
+ return Binary3.toHex(value);
4579
5344
  if (typeof value === "bigint")
4580
5345
  return value.toString();
4581
5346
  if (typeof value === "string")
@@ -4696,7 +5461,7 @@ function formatEventValue(v) {
4696
5461
  return v.toString();
4697
5462
  if (v === null || v === undefined)
4698
5463
  return "null";
4699
- if (v instanceof Binary3) {
5464
+ if (v instanceof Uint8Array) {
4700
5465
  return binaryToDisplay(v);
4701
5466
  }
4702
5467
  return JSON.stringify(v, (_k, val) => typeof val === "bigint" ? val.toString() : val);
@@ -4970,7 +5735,7 @@ async function parseTypedArg2(meta, entry, arg) {
4970
5735
  case "enum": {
4971
5736
  if (/^0x[0-9a-fA-F]+$/.test(arg) && meta.lookup.call != null && entry.id === meta.lookup.call) {
4972
5737
  const callCodec = meta.builder.buildDefinition(meta.lookup.call);
4973
- return callCodec.dec(Binary3.fromHex(arg).asBytes());
5738
+ return callCodec.dec(Binary3.fromHex(arg));
4974
5739
  }
4975
5740
  if (arg.startsWith("{")) {
4976
5741
  try {
@@ -5197,6 +5962,49 @@ function watchTransaction(observable, level) {
5197
5962
  });
5198
5963
  });
5199
5964
  }
5965
+ function watchTransactionJson(observable, level) {
5966
+ return new Promise((resolve, reject) => {
5967
+ let settled = false;
5968
+ const subscription = observable.subscribe({
5969
+ next(event) {
5970
+ if (settled)
5971
+ return;
5972
+ switch (event.type) {
5973
+ case "signed":
5974
+ printJsonLine({ event: "signed", txHash: event.txHash });
5975
+ break;
5976
+ case "broadcasted":
5977
+ printJsonLine({ event: "broadcasted", txHash: event.txHash });
5978
+ if (level === "broadcast") {
5979
+ settled = true;
5980
+ subscription.unsubscribe();
5981
+ resolve(event);
5982
+ }
5983
+ break;
5984
+ case "txBestBlocksState":
5985
+ if (event.found) {
5986
+ printJsonLine({ event: "bestBlock", blockNumber: event.block.number, found: true });
5987
+ if (level === "best-block") {
5988
+ settled = true;
5989
+ subscription.unsubscribe();
5990
+ resolve(event);
5991
+ }
5992
+ }
5993
+ break;
5994
+ case "finalized":
5995
+ settled = true;
5996
+ resolve(event);
5997
+ break;
5998
+ }
5999
+ },
6000
+ error(err) {
6001
+ if (settled)
6002
+ return;
6003
+ reject(err);
6004
+ }
6005
+ });
6006
+ });
6007
+ }
5200
6008
 
5201
6009
  // src/commands/verifiable.ts
5202
6010
  init_accounts_store();
@@ -5253,7 +6061,7 @@ async function deriveVerifiable(account, opts) {
5253
6061
  await saveAccounts(accountsFile);
5254
6062
  }
5255
6063
  }
5256
- if (opts.output === "json") {
6064
+ if (isJsonOutput(opts)) {
5257
6065
  const result = {
5258
6066
  account,
5259
6067
  memberKey: memberKeyHex
@@ -5277,8 +6085,14 @@ async function resolveMnemonic(account) {
5277
6085
  const accountsFile = await loadAccounts();
5278
6086
  const stored = findAccount(accountsFile, account);
5279
6087
  if (!stored) {
5280
- const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)];
5281
- throw new Error(`Unknown account "${account}". Available accounts: ${available.join(", ")}`);
6088
+ const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
6089
+ const suggestions = findClosest(account, available);
6090
+ const hint = suggestions.length > 0 ? `
6091
+ Did you mean: ${suggestions.join(", ")}?` : "";
6092
+ const list = available.map((a) => `
6093
+ - ${a}`).join("");
6094
+ throw new Error(`Unknown account "${account}".${hint}
6095
+ Available accounts:${list}`);
5282
6096
  }
5283
6097
  if (isWatchOnly(stored)) {
5284
6098
  throw new Error(`Account "${account}" is watch-only (no secret). Cannot derive Bandersnatch key.`);
@@ -5313,10 +6127,16 @@ async function loadConfig2() {
5313
6127
  await ensureDir3(DOT_DIR2);
5314
6128
  if (await fileExists3(CONFIG_PATH2)) {
5315
6129
  const saved = JSON.parse(await readFile5(CONFIG_PATH2, "utf-8"));
5316
- return {
5317
- ...saved,
5318
- chains: { ...DEFAULT_CONFIG.chains, ...saved.chains }
5319
- };
6130
+ const chains = {};
6131
+ for (const [name, defaultConfig] of Object.entries(DEFAULT_CONFIG.chains)) {
6132
+ chains[name] = saved.chains[name] ? { ...defaultConfig, ...saved.chains[name] } : defaultConfig;
6133
+ }
6134
+ for (const [name, config] of Object.entries(saved.chains)) {
6135
+ if (!(name in DEFAULT_CONFIG.chains)) {
6136
+ chains[name] = config;
6137
+ }
6138
+ }
6139
+ return { ...saved, chains };
5320
6140
  }
5321
6141
  await saveConfig2(DEFAULT_CONFIG);
5322
6142
  return DEFAULT_CONFIG;
@@ -5707,8 +6527,9 @@ if (process.argv[2] === "__complete") {
5707
6527
  console.log(" dot apis.Core.version Call a runtime API");
5708
6528
  console.log(" dot polkadot.query.System.Number With chain prefix");
5709
6529
  console.log(" dot ./transfer.yaml --from alice Run from file");
5710
- console.log(" dot tx.0x1f0003... --yaml Decode hex call to YAML");
5711
- console.log(" dot tx.System.remark 0xdead --json Encode & output as JSON file format");
6530
+ console.log(" dot tx.0x1f0003... --to-yaml Decode hex call to YAML");
6531
+ console.log(" dot tx.System.remark 0xdead --to-json Encode & output as JSON file format");
6532
+ console.log(" dot query.System.Number --json Output as JSON");
5712
6533
  console.log();
5713
6534
  console.log("Commands:");
5714
6535
  console.log(" inspect [target] Inspect chain metadata (alias: explore)");
@@ -5723,7 +6544,7 @@ if (process.argv[2] === "__complete") {
5723
6544
  console.log("Global options:");
5724
6545
  console.log(" --chain <name> Target chain (default from config)");
5725
6546
  console.log(" --rpc <url> Override RPC endpoint");
5726
- console.log(" --light-client Use Smoldot light client");
6547
+ console.log(" --json Output as JSON");
5727
6548
  console.log(" --output <format> Output format: pretty or json");
5728
6549
  console.log(" --help, -h Display this message");
5729
6550
  console.log(" --version Show version");
@@ -5732,10 +6553,10 @@ if (process.argv[2] === "__complete") {
5732
6553
  const cli = cac("dot");
5733
6554
  cli.option("--chain <name>", "Target chain (default from config)");
5734
6555
  cli.option("--rpc <url>", "Override RPC endpoint for this call");
5735
- cli.option("--light-client", "Use Smoldot light client instead of WebSocket");
5736
6556
  cli.option("--output <format>", "Output format: pretty or json", {
5737
6557
  default: "pretty"
5738
6558
  });
6559
+ cli.option("--json", "Output as JSON (shorthand for --output json)");
5739
6560
  registerChainCommands(cli);
5740
6561
  registerInspectCommand(cli);
5741
6562
  registerAccountCommands(cli);
@@ -5744,7 +6565,7 @@ if (process.argv[2] === "__complete") {
5744
6565
  registerParachainCommand(cli);
5745
6566
  registerCompletionsCommand(cli);
5746
6567
  registerVerifiableCommands(cli);
5747
- cli.command("[dotpath] [...args]").option("--from <name>", "Account to sign with (for tx)").option("--dry-run", "Estimate fees without submitting (for tx)").option("--encode", "Encode call to hex without signing (for tx)").option("--yaml", "Decode call to YAML file format (for tx)").option("--json", "Decode call to JSON file format (for tx)").option("--ext <json>", "Custom signed extension values as JSON (for tx)").option("-w, --wait <level>", "Resolve at: broadcast, best-block (or best), finalized (for tx)", {
6568
+ cli.command("[dotpath] [...args]").option("--from <name>", "Account to sign with (for tx)").option("--dry-run", "Estimate fees without submitting (for tx)").option("--encode", "Encode call to hex without signing (for tx)").option("--to-yaml", "Decode call to YAML file format (for tx)").option("--to-json", "Decode call to JSON file format (for tx)").option("--ext <json>", "Custom signed extension values as JSON (for tx)").option("-w, --wait <level>", "Resolve at: broadcast, best-block (or best), finalized (for tx)", {
5748
6569
  default: "finalized"
5749
6570
  }).option("--dump", "Dump all entries of a storage map (without specifying a key)").option("--var <kv>", "Template variable for file input (KEY=VALUE, repeatable)").option("--nonce <n>", "Custom nonce for manual tx sequencing (for tx)").option("--tip <amount>", "Tip to prioritize transaction (for tx)").option("--mortality <spec>", '"immortal" or period number (for tx)').option("--at <block>", 'Block hash, "best", or "finalized" to validate against (for tx)').action(async (dotpath, args, opts) => {
5750
6571
  if (!dotpath) {
@@ -5755,7 +6576,12 @@ if (process.argv[2] === "__complete") {
5755
6576
  const cliVars = collectVarFlags(process.argv);
5756
6577
  const cmd = await loadCommandFile(dotpath, cliVars);
5757
6578
  const effectiveChain2 = opts.chain ?? cmd.chain;
5758
- const handlerOpts2 = { chain: effectiveChain2, rpc: opts.rpc, output: opts.output };
6579
+ const handlerOpts2 = {
6580
+ chain: effectiveChain2,
6581
+ rpc: opts.rpc,
6582
+ output: opts.output,
6583
+ json: opts.json
6584
+ };
5759
6585
  const target2 = `${cmd.pallet}.${cmd.item}`;
5760
6586
  switch (cmd.category) {
5761
6587
  case "tx":
@@ -5764,8 +6590,8 @@ if (process.argv[2] === "__complete") {
5764
6590
  from: opts.from,
5765
6591
  dryRun: opts.dryRun,
5766
6592
  encode: opts.encode,
5767
- yaml: opts.yaml,
5768
- json: opts.json,
6593
+ toYaml: opts.toYaml,
6594
+ toJson: opts.toJson,
5769
6595
  ext: opts.ext,
5770
6596
  wait: opts.wait,
5771
6597
  nonce: opts.nonce,
@@ -5812,7 +6638,12 @@ if (process.argv[2] === "__complete") {
5812
6638
  throw new CliError2(`Chain specified both as prefix ("${parsed.chain}") and as --chain flag ("${opts.chain}"). Use one or the other.`);
5813
6639
  }
5814
6640
  const effectiveChain = parsed.chain ?? opts.chain;
5815
- const handlerOpts = { chain: effectiveChain, rpc: opts.rpc, output: opts.output };
6641
+ const handlerOpts = {
6642
+ chain: effectiveChain,
6643
+ rpc: opts.rpc,
6644
+ output: opts.output,
6645
+ json: opts.json
6646
+ };
5816
6647
  const target = parsed.pallet ? parsed.item ? `${parsed.pallet}.${parsed.item}` : parsed.pallet : undefined;
5817
6648
  if (cli.options.help && parsed.pallet && parsed.item) {
5818
6649
  await showItemHelp2(parsed.category, target, handlerOpts);
@@ -5828,8 +6659,8 @@ if (process.argv[2] === "__complete") {
5828
6659
  from: opts.from,
5829
6660
  dryRun: opts.dryRun,
5830
6661
  encode: opts.encode,
5831
- yaml: opts.yaml,
5832
- json: opts.json,
6662
+ toYaml: opts.toYaml,
6663
+ toJson: opts.toJson,
5833
6664
  ext: opts.ext,
5834
6665
  wait: opts.wait,
5835
6666
  nonce: opts.nonce,