polkadot-cli 1.12.0 → 1.14.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 +271 -56
  2. package/dist/cli.mjs +1770 -353
  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,29 @@ 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);
17
+
18
+ // src/utils/errors.ts
19
+ var CliError, ConnectionError, MetadataError;
20
+ var init_errors = __esm(() => {
21
+ CliError = class CliError extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = "CliError";
25
+ }
26
+ };
27
+ ConnectionError = class ConnectionError extends CliError {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "ConnectionError";
31
+ }
32
+ };
33
+ MetadataError = class MetadataError extends CliError {
34
+ constructor(message) {
35
+ super(message);
36
+ this.name = "MetadataError";
37
+ }
38
+ };
39
+ });
19
40
 
20
41
  // src/config/types.ts
21
42
  function primaryRpc(rpc) {
@@ -24,7 +45,6 @@ function primaryRpc(rpc) {
24
45
  var DEFAULT_CONFIG, BUILTIN_CHAIN_NAMES;
25
46
  var init_types = __esm(() => {
26
47
  DEFAULT_CONFIG = {
27
- defaultChain: "polkadot",
28
48
  chains: {
29
49
  polkadot: {
30
50
  rpc: [
@@ -53,7 +73,9 @@ var init_types = __esm(() => {
53
73
  "wss://statemint-rpc-tn.dwellir.com",
54
74
  "wss://statemint.public.curie.radiumblock.co/ws",
55
75
  "wss://asset-hub-polkadot.rpc.permanence.io"
56
- ]
76
+ ],
77
+ relay: "polkadot",
78
+ parachainId: 1000
57
79
  },
58
80
  "polkadot-bridge-hub": {
59
81
  rpc: [
@@ -65,7 +87,9 @@ var init_types = __esm(() => {
65
87
  "wss://bridgehub-polkadot.api.onfinality.io/public-ws",
66
88
  "wss://polkadot-bridge-hub-rpc-tn.dwellir.com",
67
89
  "wss://bridgehub-polkadot.public.curie.radiumblock.co/ws"
68
- ]
90
+ ],
91
+ relay: "polkadot",
92
+ parachainId: 1002
69
93
  },
70
94
  "polkadot-collectives": {
71
95
  rpc: [
@@ -77,7 +101,9 @@ var init_types = __esm(() => {
77
101
  "wss://collectives.api.onfinality.io/public-ws",
78
102
  "wss://polkadot-collectives-rpc-tn.dwellir.com",
79
103
  "wss://collectives.public.curie.radiumblock.co/ws"
80
- ]
104
+ ],
105
+ relay: "polkadot",
106
+ parachainId: 1001
81
107
  },
82
108
  "polkadot-coretime": {
83
109
  rpc: [
@@ -87,7 +113,9 @@ var init_types = __esm(() => {
87
113
  "wss://coretime-polkadot-rpc.n.dwellir.com",
88
114
  "wss://rpc-coretime-polkadot.luckyfriday.io",
89
115
  "wss://coretime-polkadot.api.onfinality.io/public-ws"
90
- ]
116
+ ],
117
+ relay: "polkadot",
118
+ parachainId: 1005
91
119
  },
92
120
  "polkadot-people": {
93
121
  rpc: [
@@ -97,7 +125,9 @@ var init_types = __esm(() => {
97
125
  "wss://people-polkadot-rpc.n.dwellir.com",
98
126
  "wss://rpc-people-polkadot.luckyfriday.io",
99
127
  "wss://people-polkadot.api.onfinality.io/public-ws"
100
- ]
128
+ ],
129
+ relay: "polkadot",
130
+ parachainId: 1004
101
131
  },
102
132
  paseo: {
103
133
  rpc: [
@@ -113,23 +143,33 @@ var init_types = __esm(() => {
113
143
  "wss://asset-hub-paseo.dotters.network",
114
144
  "wss://asset-hub-paseo-rpc.n.dwellir.com",
115
145
  "wss://sys.turboflakes.io/asset-hub-paseo"
116
- ]
146
+ ],
147
+ relay: "paseo",
148
+ parachainId: 1000
117
149
  },
118
150
  "paseo-bridge-hub": {
119
- rpc: ["wss://bridge-hub-paseo.ibp.network", "wss://bridge-hub-paseo.dotters.network"]
151
+ rpc: ["wss://bridge-hub-paseo.ibp.network", "wss://bridge-hub-paseo.dotters.network"],
152
+ relay: "paseo",
153
+ parachainId: 1002
120
154
  },
121
155
  "paseo-collectives": {
122
- rpc: ["wss://collectives-paseo.ibp.network", "wss://collectives-paseo.dotters.network"]
156
+ rpc: ["wss://collectives-paseo.ibp.network", "wss://collectives-paseo.dotters.network"],
157
+ relay: "paseo",
158
+ parachainId: 1001
123
159
  },
124
160
  "paseo-coretime": {
125
- rpc: ["wss://coretime-paseo.ibp.network", "wss://coretime-paseo.dotters.network"]
161
+ rpc: ["wss://coretime-paseo.ibp.network", "wss://coretime-paseo.dotters.network"],
162
+ relay: "paseo",
163
+ parachainId: 1005
126
164
  },
127
165
  "paseo-people": {
128
166
  rpc: [
129
167
  "wss://people-paseo.ibp.network",
130
168
  "wss://people-paseo.dotters.network",
131
169
  "wss://people-paseo.rpc.amforc.com"
132
- ]
170
+ ],
171
+ relay: "paseo",
172
+ parachainId: 1004
133
173
  }
134
174
  }
135
175
  };
@@ -164,10 +204,16 @@ async function loadConfig() {
164
204
  await ensureDir(DOT_DIR);
165
205
  if (await fileExists(CONFIG_PATH)) {
166
206
  const saved = JSON.parse(await readFile(CONFIG_PATH, "utf-8"));
167
- return {
168
- ...saved,
169
- chains: { ...DEFAULT_CONFIG.chains, ...saved.chains }
170
- };
207
+ const chains = {};
208
+ for (const [name, defaultConfig] of Object.entries(DEFAULT_CONFIG.chains)) {
209
+ chains[name] = saved.chains[name] ? { ...defaultConfig, ...saved.chains[name] } : defaultConfig;
210
+ }
211
+ for (const [name, config] of Object.entries(saved.chains)) {
212
+ if (!(name in DEFAULT_CONFIG.chains)) {
213
+ chains[name] = config;
214
+ }
215
+ }
216
+ return { chains };
171
217
  }
172
218
  await saveConfig(DEFAULT_CONFIG);
173
219
  return DEFAULT_CONFIG;
@@ -199,16 +245,19 @@ function findChainName(config, input) {
199
245
  return Object.keys(config.chains).find((k) => k.toLowerCase() === input.toLowerCase());
200
246
  }
201
247
  function resolveChain(config, chainFlag) {
202
- const input = chainFlag ?? config.defaultChain;
203
- const name = findChainName(config, input);
248
+ const available = Object.keys(config.chains).join(", ");
249
+ if (!chainFlag) {
250
+ throw new CliError(`No chain specified. Pass --chain <name> or use a dotpath prefix (e.g. "dot polkadot.query.System.Number"). Available chains: ${available}`);
251
+ }
252
+ const name = findChainName(config, chainFlag);
204
253
  if (!name) {
205
- const available = Object.keys(config.chains).join(", ");
206
- throw new Error(`Unknown chain "${input}". Available chains: ${available}`);
254
+ throw new CliError(`Unknown chain "${chainFlag}". Available chains: ${available}`);
207
255
  }
208
256
  return { name, chain: config.chains[name] };
209
257
  }
210
258
  var DOT_DIR, CONFIG_PATH, CHAINS_DIR;
211
259
  var init_store = __esm(() => {
260
+ init_errors();
212
261
  init_types();
213
262
  DOT_DIR = join(homedir(), ".polkadot");
214
263
  CONFIG_PATH = join(DOT_DIR, "config.json");
@@ -259,6 +308,42 @@ function isWatchOnly(account) {
259
308
  return account.secret === undefined;
260
309
  }
261
310
 
311
+ // src/utils/fuzzy-match.ts
312
+ function levenshtein(a, b) {
313
+ const la = a.length;
314
+ const lb = b.length;
315
+ const dp = Array.from({ length: la + 1 }, () => Array(lb + 1).fill(0));
316
+ for (let i = 0;i <= la; i++)
317
+ dp[i][0] = i;
318
+ for (let j = 0;j <= lb; j++)
319
+ dp[0][j] = j;
320
+ for (let i = 1;i <= la; i++) {
321
+ for (let j = 1;j <= lb; j++) {
322
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
323
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
324
+ }
325
+ }
326
+ return dp[la][lb];
327
+ }
328
+ function findClosest(input, candidates, maxDistance = 3) {
329
+ const lower = input.toLowerCase();
330
+ const exact = candidates.find((c) => c.toLowerCase() === lower);
331
+ if (exact)
332
+ return [exact];
333
+ const scored = candidates.map((c) => ({ name: c, dist: levenshtein(lower, c.toLowerCase()) })).filter((s) => s.dist <= maxDistance).sort((a, b) => a.dist - b.dist);
334
+ return scored.slice(0, 3).map((s) => s.name);
335
+ }
336
+ function suggestMessage(kind, input, candidates) {
337
+ const suggestions = findClosest(input, candidates);
338
+ if (suggestions.length === 0) {
339
+ return `Unknown ${kind} "${input}".`;
340
+ }
341
+ if (suggestions.length === 1 && suggestions[0]?.toLowerCase() === input.toLowerCase()) {
342
+ return suggestions[0];
343
+ }
344
+ return `Unknown ${kind} "${input}". Did you mean: ${suggestions.join(", ")}?`;
345
+ }
346
+
262
347
  // src/core/accounts.ts
263
348
  import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
264
349
  import {
@@ -369,8 +454,14 @@ async function resolveAccountKeypair(name) {
369
454
  const accountsFile = await loadAccounts();
370
455
  const account = findAccount(accountsFile, name);
371
456
  if (!account) {
372
- const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)];
373
- throw new Error(`Unknown account "${name}". Available accounts: ${available.join(", ")}`);
457
+ const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
458
+ const suggestions = findClosest(name, available);
459
+ const hint = suggestions.length > 0 ? `
460
+ Did you mean: ${suggestions.join(", ")}?` : "";
461
+ const list = available.map((a) => `
462
+ - ${a}`).join("");
463
+ throw new Error(`Unknown account "${name}".${hint}
464
+ Available accounts:${list}`);
374
465
  }
375
466
  if (account.secret === undefined) {
376
467
  throw new Error(`Account "${name}" is watch-only (no secret). Cannot sign. Import with --secret or --env.`);
@@ -390,6 +481,7 @@ var init_accounts = __esm(() => {
390
481
  });
391
482
 
392
483
  // src/utils/binary-display.ts
484
+ import { Binary } from "polkadot-api";
393
485
  function isReadableText(text) {
394
486
  for (let i = 0;i < text.length; i++) {
395
487
  const code = text.charCodeAt(i);
@@ -407,20 +499,17 @@ function isReadableText(text) {
407
499
  return true;
408
500
  }
409
501
  function binaryToDisplay(value) {
410
- const text = value.asText();
411
- return isReadableText(text) ? text : value.asHex();
502
+ const text = Binary.toText(value);
503
+ return isReadableText(text) ? text : Binary.toHex(value);
412
504
  }
505
+ var init_binary_display = () => {};
413
506
 
414
507
  // src/core/output.ts
415
- import { Binary } from "polkadot-api";
416
508
  function replacer(_key, value) {
417
509
  if (typeof value === "bigint")
418
510
  return value.toString();
419
- if (value instanceof Binary) {
420
- return binaryToDisplay(value);
421
- }
422
511
  if (value instanceof Uint8Array)
423
- return `0x${Buffer.from(value).toString("hex")}`;
512
+ return binaryToDisplay(value);
424
513
  return value;
425
514
  }
426
515
  function formatJson(data) {
@@ -442,6 +531,12 @@ function printResult(data, format = "pretty") {
442
531
  console.log(formatPretty(data));
443
532
  }
444
533
  }
534
+ function isJsonOutput(opts) {
535
+ return opts.json === true || opts.output === "json";
536
+ }
537
+ function printJsonLine(data) {
538
+ console.log(JSON.stringify(data, replacer));
539
+ }
445
540
  function printHeading(text) {
446
541
  console.log(`
447
542
  ${BOLD}${text}${RESET}
@@ -500,6 +595,7 @@ function firstSentence(docs) {
500
595
  }
501
596
  var isTTY, RESET, CYAN, GREEN, RED, YELLOW, MAGENTA, DIM, BOLD, CHECK_MARK = "✓", SPINNER_FRAMES;
502
597
  var init_output = __esm(() => {
598
+ init_binary_display();
503
599
  isTTY = process.stdout.isTTY ?? false;
504
600
  RESET = isTTY ? "\x1B[0m" : "";
505
601
  CYAN = isTTY ? "\x1B[36m" : "";
@@ -531,62 +627,35 @@ function deriveBandersnatchMember(mnemonic, context) {
531
627
  }
532
628
  var init_bandersnatch = () => {};
533
629
 
534
- // src/utils/errors.ts
535
- var CliError, ConnectionError, MetadataError;
536
- var init_errors = __esm(() => {
537
- CliError = class CliError extends Error {
538
- constructor(message) {
539
- super(message);
540
- this.name = "CliError";
541
- }
542
- };
543
- ConnectionError = class ConnectionError extends CliError {
544
- constructor(message) {
545
- super(message);
546
- this.name = "ConnectionError";
547
- }
548
- };
549
- MetadataError = class MetadataError extends CliError {
550
- constructor(message) {
551
- super(message);
552
- this.name = "MetadataError";
553
- }
554
- };
555
- });
556
-
557
630
  // src/core/client.ts
558
631
  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;
632
+ import { getWsProvider } from "polkadot-api/ws";
633
+ function suppressProviderNoise() {
634
+ const origError = console.error;
635
+ const origWarn = console.warn;
564
636
  console.error = (...args) => {
565
637
  if (typeof args[0] === "string" && args[0].includes("Unable to connect"))
566
638
  return;
567
- orig(...args);
639
+ origError(...args);
640
+ };
641
+ console.warn = (...args) => {
642
+ if (typeof args[0] === "string" && args[0].includes("ChainHead"))
643
+ return;
644
+ origWarn(...args);
568
645
  };
569
646
  return () => {
570
- console.error = orig;
647
+ console.error = origError;
648
+ console.warn = origWarn;
571
649
  };
572
650
  }
573
651
  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
- }));
652
+ const restoreConsole = suppressProviderNoise();
653
+ const rpc = rpcOverride ?? chainConfig.rpc;
654
+ if (!rpc) {
655
+ restoreConsole();
656
+ throw new ConnectionError(`No RPC endpoint configured for chain "${chainName}". Use --rpc or configure one with: dot chain add ${chainName} --rpc <url>`);
589
657
  }
658
+ const provider = getWsProvider(rpc, { timeout: 1e4 });
590
659
  const client = createClient(provider, {
591
660
  getMetadata: async () => loadMetadata(chainName),
592
661
  setMetadata: async (_codeHash, metadata) => {
@@ -596,51 +665,25 @@ async function createChainClient(chainName, chainConfig, rpcOverride) {
596
665
  return {
597
666
  client,
598
667
  destroy: () => {
599
- client.destroy();
600
- restoreConsole();
668
+ try {
669
+ client.destroy();
670
+ } catch {}
671
+ setTimeout(restoreConsole, 500);
601
672
  }
602
673
  };
603
674
  }
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;
675
+ async function getParachainId(clientHandle) {
676
+ try {
677
+ const unsafeApi = clientHandle.client.getUnsafeApi();
678
+ const parachainId = await unsafeApi.query.ParachainInfo.ParachainId.getValue();
679
+ return typeof parachainId === "number" ? parachainId : null;
680
+ } catch {
681
+ return null;
682
+ }
683
+ }
627
684
  var init_client = __esm(() => {
628
685
  init_store();
629
686
  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
687
  });
645
688
 
646
689
  // src/core/metadata.ts
@@ -915,42 +958,6 @@ var init_metadata = __esm(() => {
915
958
  v15Arg = toHex(u32.enc(15));
916
959
  });
917
960
 
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
961
  // src/core/explorers.ts
955
962
  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
963
 
@@ -976,9 +983,14 @@ async function resolveAccountAddress(input) {
976
983
  return input;
977
984
  }
978
985
  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(", ")}`);
986
+ const available = [...DEV_NAMES, ...stored].sort((a, b) => a.localeCompare(b));
987
+ const suggestions = findClosest(input, available);
988
+ const hint = suggestions.length > 0 ? `
989
+ Did you mean: ${suggestions.join(", ")}?` : "";
990
+ const list = available.map((a) => `
991
+ - ${a}`).join("");
992
+ throw new Error(`Unknown account or address "${input}".${hint}
993
+ Available accounts:${list}`);
982
994
  }
983
995
  var init_resolve_address = __esm(() => {
984
996
  init_accounts_store();
@@ -1006,6 +1018,7 @@ function parseValue(arg) {
1006
1018
  }
1007
1019
 
1008
1020
  // src/commands/tx.ts
1021
+ import { compact as scaleCompact } from "@polkadot-api/substrate-bindings";
1009
1022
  import { getViewBuilder } from "@polkadot-api/view-builder";
1010
1023
  import { Binary as Binary2 } from "polkadot-api";
1011
1024
  import { stringify as stringifyYaml } from "yaml";
@@ -1206,7 +1219,7 @@ async function parseTypedArg(meta, entry, arg) {
1206
1219
  case "enum": {
1207
1220
  if (/^0x[0-9a-fA-F]+$/.test(arg) && meta.lookup.call != null && entry.id === meta.lookup.call) {
1208
1221
  const callCodec = meta.builder.buildDefinition(meta.lookup.call);
1209
- return callCodec.dec(Binary2.fromHex(arg).asBytes());
1222
+ return callCodec.dec(Binary2.fromHex(arg));
1210
1223
  }
1211
1224
  if (arg.startsWith("{")) {
1212
1225
  try {
@@ -1318,6 +1331,7 @@ var init_tx = __esm(() => {
1318
1331
  init_metadata();
1319
1332
  init_output();
1320
1333
  init_resolve_address();
1334
+ init_binary_display();
1321
1335
  init_errors();
1322
1336
  init_focused_inspect();
1323
1337
  PAPI_BUILTIN_EXTENSIONS = new Set([
@@ -1348,6 +1362,13 @@ async function handleApis(target, args, opts) {
1348
1362
  const { name: chainName2, chain: chainConfig2 } = resolveChain(config2, opts.chain);
1349
1363
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
1350
1364
  const apis = listRuntimeApis(meta);
1365
+ if (isJsonOutput(opts)) {
1366
+ console.log(formatJson({
1367
+ chain: chainName2,
1368
+ apis: apis.map((a) => ({ name: a.name, methods: a.methods.length }))
1369
+ }));
1370
+ return;
1371
+ }
1351
1372
  printHeading(`Runtime APIs on ${chainName2} (${apis.length})`);
1352
1373
  for (const api of apis) {
1353
1374
  printItem(api.name, `${api.methods.length} methods`);
@@ -1370,7 +1391,24 @@ async function handleApis(target, args, opts) {
1370
1391
  const meta = await loadMeta(chainName, chainConfig, opts.rpc);
1371
1392
  const api = resolveRuntimeApi(meta, apiName);
1372
1393
  if (api.methods.length === 0) {
1373
- console.log(`No methods in ${api.name}.`);
1394
+ if (isJsonOutput(opts)) {
1395
+ console.log(formatJson({ chain: chainName, api: api.name, methods: [] }));
1396
+ } else {
1397
+ console.log(`No methods in ${api.name}.`);
1398
+ }
1399
+ return;
1400
+ }
1401
+ if (isJsonOutput(opts)) {
1402
+ console.log(formatJson({
1403
+ chain: chainName,
1404
+ api: api.name,
1405
+ methods: api.methods.map((m) => ({
1406
+ name: m.name,
1407
+ args: describeRuntimeApiMethodArgs(meta, m),
1408
+ returns: describeType(meta.lookup, m.output),
1409
+ docs: firstSentence(m.docs)
1410
+ }))
1411
+ }));
1374
1412
  return;
1375
1413
  }
1376
1414
  printHeading(`${api.name} Methods`);
@@ -1401,7 +1439,7 @@ async function handleApis(target, args, opts) {
1401
1439
  const parsedArgs = await parseRuntimeApiArgs(meta, method, effectiveArgs);
1402
1440
  const unsafeApi = clientHandle.client.getUnsafeApi();
1403
1441
  const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
1404
- const format = opts.output ?? "pretty";
1442
+ const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
1405
1443
  printResult(result, format);
1406
1444
  } finally {
1407
1445
  clientHandle.destroy();
@@ -1438,11 +1476,23 @@ var init_apis = __esm(() => {
1438
1476
 
1439
1477
  // src/commands/focused-inspect.ts
1440
1478
  async function loadMeta(chainName, chainConfig, rpcOverride) {
1479
+ if (rpcOverride) {
1480
+ process.stderr.write(`Fetching metadata from ${chainName}...
1481
+ `);
1482
+ const clientHandle = await createChainClient(chainName, chainConfig, rpcOverride);
1483
+ try {
1484
+ const raw = await fetchMetadataFromChain(clientHandle, chainName);
1485
+ return parseMetadata(raw);
1486
+ } finally {
1487
+ clientHandle.destroy();
1488
+ }
1489
+ }
1441
1490
  try {
1442
1491
  return await getOrFetchMetadata(chainName);
1443
1492
  } catch {
1444
- console.error(`Fetching metadata from ${chainName}...`);
1445
- const clientHandle = await createChainClient(chainName, chainConfig, rpcOverride);
1493
+ process.stderr.write(`Fetching metadata from ${chainName}...
1494
+ `);
1495
+ const clientHandle = await createChainClient(chainName, chainConfig);
1446
1496
  try {
1447
1497
  return await getOrFetchMetadata(chainName, clientHandle);
1448
1498
  } finally {
@@ -1465,6 +1515,13 @@ async function handleCalls(target, opts) {
1465
1515
  const meta2 = await loadMeta(chainName2, chainConfig2, opts.rpc);
1466
1516
  const pallets = listPallets(meta2);
1467
1517
  const withCalls = pallets.filter((p) => p.calls.length > 0);
1518
+ if (isJsonOutput(opts)) {
1519
+ console.log(formatJson({
1520
+ chain: chainName2,
1521
+ pallets: withCalls.map((p) => ({ name: p.name, calls: p.calls.length }))
1522
+ }));
1523
+ return;
1524
+ }
1468
1525
  printHeading(`Pallets with calls on ${chainName2} (${withCalls.length})`);
1469
1526
  for (const p of withCalls) {
1470
1527
  printItem(p.name, `${p.calls.length} calls`);
@@ -1481,7 +1538,23 @@ async function handleCalls(target, opts) {
1481
1538
  const pallet = resolvePallet(meta, palletName);
1482
1539
  if (!itemName) {
1483
1540
  if (pallet.calls.length === 0) {
1484
- console.log(`No calls in ${pallet.name}.`);
1541
+ if (isJsonOutput(opts)) {
1542
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, calls: [] }));
1543
+ } else {
1544
+ console.log(`No calls in ${pallet.name}.`);
1545
+ }
1546
+ return;
1547
+ }
1548
+ if (isJsonOutput(opts)) {
1549
+ console.log(formatJson({
1550
+ chain: chainName,
1551
+ pallet: pallet.name,
1552
+ calls: pallet.calls.map((c) => ({
1553
+ name: c.name,
1554
+ args: describeCallArgs(meta, pallet.name, c.name),
1555
+ docs: firstSentence(c.docs)
1556
+ }))
1557
+ }));
1485
1558
  return;
1486
1559
  }
1487
1560
  printHeading(`${pallet.name} Calls`);
@@ -1501,6 +1574,17 @@ async function handleCalls(target, opts) {
1501
1574
  const names = pallet.calls.map((c) => c.name);
1502
1575
  throw new Error(suggestMessage(`call in ${pallet.name}`, itemName, names));
1503
1576
  }
1577
+ if (isJsonOutput(opts)) {
1578
+ console.log(formatJson({
1579
+ chain: chainName,
1580
+ pallet: pallet.name,
1581
+ item: callItem.name,
1582
+ category: "call",
1583
+ args: describeCallArgs(meta, pallet.name, callItem.name),
1584
+ docs: callItem.docs
1585
+ }));
1586
+ return;
1587
+ }
1504
1588
  printHeading(`${pallet.name}.${callItem.name} (Call)`);
1505
1589
  const args = describeCallArgs(meta, pallet.name, callItem.name);
1506
1590
  console.log(` ${BOLD}Args:${RESET} ${args}`);
@@ -1517,6 +1601,13 @@ async function handleEvents(target, opts) {
1517
1601
  const meta2 = await loadMeta(chainName2, chainConfig2, opts.rpc);
1518
1602
  const pallets = listPallets(meta2);
1519
1603
  const withEvents = pallets.filter((p) => p.events.length > 0);
1604
+ if (isJsonOutput(opts)) {
1605
+ console.log(formatJson({
1606
+ chain: chainName2,
1607
+ pallets: withEvents.map((p) => ({ name: p.name, events: p.events.length }))
1608
+ }));
1609
+ return;
1610
+ }
1520
1611
  printHeading(`Pallets with events on ${chainName2} (${withEvents.length})`);
1521
1612
  for (const p of withEvents) {
1522
1613
  printItem(p.name, `${p.events.length} events`);
@@ -1533,7 +1624,23 @@ async function handleEvents(target, opts) {
1533
1624
  const pallet = resolvePallet(meta, palletName);
1534
1625
  if (!itemName) {
1535
1626
  if (pallet.events.length === 0) {
1536
- console.log(`No events in ${pallet.name}.`);
1627
+ if (isJsonOutput(opts)) {
1628
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, events: [] }));
1629
+ } else {
1630
+ console.log(`No events in ${pallet.name}.`);
1631
+ }
1632
+ return;
1633
+ }
1634
+ if (isJsonOutput(opts)) {
1635
+ console.log(formatJson({
1636
+ chain: chainName,
1637
+ pallet: pallet.name,
1638
+ events: pallet.events.map((e) => ({
1639
+ name: e.name,
1640
+ fields: describeEventFields(meta, pallet.name, e.name),
1641
+ docs: firstSentence(e.docs)
1642
+ }))
1643
+ }));
1537
1644
  return;
1538
1645
  }
1539
1646
  printHeading(`${pallet.name} Events`);
@@ -1553,6 +1660,17 @@ async function handleEvents(target, opts) {
1553
1660
  const names = pallet.events.map((e) => e.name);
1554
1661
  throw new Error(suggestMessage(`event in ${pallet.name}`, itemName, names));
1555
1662
  }
1663
+ if (isJsonOutput(opts)) {
1664
+ console.log(formatJson({
1665
+ chain: chainName,
1666
+ pallet: pallet.name,
1667
+ item: eventItem.name,
1668
+ category: "event",
1669
+ fields: describeEventFields(meta, pallet.name, eventItem.name),
1670
+ docs: eventItem.docs
1671
+ }));
1672
+ return;
1673
+ }
1556
1674
  printHeading(`${pallet.name}.${eventItem.name} (Event)`);
1557
1675
  const fields = describeEventFields(meta, pallet.name, eventItem.name);
1558
1676
  console.log(` ${BOLD}Fields:${RESET} ${fields}`);
@@ -1569,6 +1687,13 @@ async function handleErrors(target, opts) {
1569
1687
  const meta2 = await loadMeta(chainName2, chainConfig2, opts.rpc);
1570
1688
  const pallets = listPallets(meta2);
1571
1689
  const withErrors = pallets.filter((p) => p.errors.length > 0);
1690
+ if (isJsonOutput(opts)) {
1691
+ console.log(formatJson({
1692
+ chain: chainName2,
1693
+ pallets: withErrors.map((p) => ({ name: p.name, errors: p.errors.length }))
1694
+ }));
1695
+ return;
1696
+ }
1572
1697
  printHeading(`Pallets with errors on ${chainName2} (${withErrors.length})`);
1573
1698
  for (const p of withErrors) {
1574
1699
  printItem(p.name, `${p.errors.length} errors`);
@@ -1585,7 +1710,19 @@ async function handleErrors(target, opts) {
1585
1710
  const pallet = resolvePallet(meta, palletName);
1586
1711
  if (!itemName) {
1587
1712
  if (pallet.errors.length === 0) {
1588
- console.log(`No errors in ${pallet.name}.`);
1713
+ if (isJsonOutput(opts)) {
1714
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, errors: [] }));
1715
+ } else {
1716
+ console.log(`No errors in ${pallet.name}.`);
1717
+ }
1718
+ return;
1719
+ }
1720
+ if (isJsonOutput(opts)) {
1721
+ console.log(formatJson({
1722
+ chain: chainName,
1723
+ pallet: pallet.name,
1724
+ errors: pallet.errors.map((e) => ({ name: e.name, docs: firstSentence(e.docs) }))
1725
+ }));
1589
1726
  return;
1590
1727
  }
1591
1728
  printHeading(`${pallet.name} Errors`);
@@ -1604,6 +1741,16 @@ async function handleErrors(target, opts) {
1604
1741
  const names = pallet.errors.map((e) => e.name);
1605
1742
  throw new Error(suggestMessage(`error in ${pallet.name}`, itemName, names));
1606
1743
  }
1744
+ if (isJsonOutput(opts)) {
1745
+ console.log(formatJson({
1746
+ chain: chainName,
1747
+ pallet: pallet.name,
1748
+ item: errorItem.name,
1749
+ category: "error",
1750
+ docs: errorItem.docs
1751
+ }));
1752
+ return;
1753
+ }
1607
1754
  printHeading(`${pallet.name}.${errorItem.name} (Error)`);
1608
1755
  if (errorItem.docs.length) {
1609
1756
  printDocs(errorItem.docs);
@@ -1617,6 +1764,13 @@ async function handleStorage(target, opts) {
1617
1764
  const meta2 = await loadMeta(chainName2, chainConfig2, opts.rpc);
1618
1765
  const pallets = listPallets(meta2);
1619
1766
  const withStorage = pallets.filter((p) => p.storage.length > 0);
1767
+ if (isJsonOutput(opts)) {
1768
+ console.log(formatJson({
1769
+ chain: chainName2,
1770
+ pallets: withStorage.map((p) => ({ name: p.name, storage: p.storage.length }))
1771
+ }));
1772
+ return;
1773
+ }
1620
1774
  printHeading(`Pallets with storage on ${chainName2} (${withStorage.length})`);
1621
1775
  for (const p of withStorage) {
1622
1776
  printItem(p.name, `${p.storage.length} storage`);
@@ -1633,7 +1787,23 @@ async function handleStorage(target, opts) {
1633
1787
  const pallet = resolvePallet(meta, palletName);
1634
1788
  if (!itemName) {
1635
1789
  if (pallet.storage.length === 0) {
1636
- console.log(`No storage items in ${pallet.name}.`);
1790
+ if (isJsonOutput(opts)) {
1791
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, storage: [] }));
1792
+ } else {
1793
+ console.log(`No storage items in ${pallet.name}.`);
1794
+ }
1795
+ return;
1796
+ }
1797
+ if (isJsonOutput(opts)) {
1798
+ console.log(formatJson({
1799
+ chain: chainName,
1800
+ pallet: pallet.name,
1801
+ storage: pallet.storage.map((s) => {
1802
+ const valueType = describeType(meta.lookup, s.valueTypeId);
1803
+ const keyType = s.keyTypeId != null ? describeType(meta.lookup, s.keyTypeId) : undefined;
1804
+ return { name: s.name, type: s.type, valueType, keyType, docs: firstSentence(s.docs) };
1805
+ })
1806
+ }));
1637
1807
  return;
1638
1808
  }
1639
1809
  printHeading(`${pallet.name} Storage`);
@@ -1660,6 +1830,21 @@ async function handleStorage(target, opts) {
1660
1830
  const names = pallet.storage.map((s) => s.name);
1661
1831
  throw new Error(suggestMessage(`storage item in ${pallet.name}`, itemName, names));
1662
1832
  }
1833
+ if (isJsonOutput(opts)) {
1834
+ const valueType = describeType(meta.lookup, storageItem.valueTypeId);
1835
+ const keyType = storageItem.keyTypeId != null ? describeType(meta.lookup, storageItem.keyTypeId) : undefined;
1836
+ console.log(formatJson({
1837
+ chain: chainName,
1838
+ pallet: pallet.name,
1839
+ item: storageItem.name,
1840
+ category: "storage",
1841
+ type: storageItem.type,
1842
+ valueType,
1843
+ keyType,
1844
+ docs: storageItem.docs
1845
+ }));
1846
+ return;
1847
+ }
1663
1848
  printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
1664
1849
  console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
1665
1850
  console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
@@ -1998,6 +2183,9 @@ async function generateCompletions(currentWord, precedingWords) {
1998
2183
  if (prevWord === "--wait" || prevWord === "-w") {
1999
2184
  return filterPrefix(["broadcast", "best-block", "best", "finalized"], currentWord);
2000
2185
  }
2186
+ if (prevWord === "--relay") {
2187
+ return filterPrefix(knownChains, currentWord);
2188
+ }
2001
2189
  if (currentWord.startsWith("--")) {
2002
2190
  const activeCategory = detectCategory(precedingWords, knownChains);
2003
2191
  const options = [...GLOBAL_OPTIONS];
@@ -2005,10 +2193,19 @@ async function generateCompletions(currentWord, precedingWords) {
2005
2193
  options.push(...TX_OPTIONS);
2006
2194
  if (activeCategory === "query")
2007
2195
  options.push(...QUERY_OPTIONS);
2196
+ const chainIdx = precedingWords.indexOf("chain");
2197
+ if (chainIdx >= 0 && precedingWords[chainIdx + 1] === "add") {
2198
+ options.push("--relay", "--parachain-id");
2199
+ }
2008
2200
  return filterPrefix(options, currentWord);
2009
2201
  }
2010
2202
  const firstArg = precedingWords.find((w) => !w.startsWith("-"));
2011
2203
  if (firstArg === "chain") {
2204
+ const chainSubIdx = precedingWords.indexOf("chain");
2205
+ const subcommand = precedingWords[chainSubIdx + 1];
2206
+ if (subcommand === "add" && currentWord.startsWith("--")) {
2207
+ return filterPrefix(["--rpc", "--relay", "--parachain-id", ...GLOBAL_OPTIONS], currentWord);
2208
+ }
2012
2209
  return filterPrefix(CHAIN_SUBCOMMANDS, currentWord);
2013
2210
  }
2014
2211
  if (firstArg === "account") {
@@ -2062,7 +2259,9 @@ async function completeDotpath(currentWord, config, knownChains, precedingWords)
2062
2259
  return completeApisCategory(first, numComplete, endsWithDot, completeSegments, currentWord, config, chainFromFlag);
2063
2260
  }
2064
2261
  if (numComplete === 1 && endsWithDot) {
2065
- const chainName = chainFromFlag ?? config.defaultChain;
2262
+ const chainName = chainFromFlag;
2263
+ if (!chainName)
2264
+ return [];
2066
2265
  const pallets = await loadPallets(config, chainName);
2067
2266
  if (!pallets)
2068
2267
  return [];
@@ -2071,7 +2270,9 @@ async function completeDotpath(currentWord, config, knownChains, precedingWords)
2071
2270
  return filterPrefix(candidates, currentWord.slice(0, -1));
2072
2271
  }
2073
2272
  if (numComplete === 1 && !endsWithDot) {
2074
- const chainName = chainFromFlag ?? config.defaultChain;
2273
+ const chainName = chainFromFlag;
2274
+ if (!chainName)
2275
+ return [];
2075
2276
  const pallets = await loadPallets(config, chainName);
2076
2277
  if (!pallets)
2077
2278
  return [];
@@ -2081,7 +2282,9 @@ async function completeDotpath(currentWord, config, knownChains, precedingWords)
2081
2282
  }
2082
2283
  if (numComplete === 2) {
2083
2284
  const palletName2 = completeSegments[1];
2084
- const chainName = chainFromFlag ?? config.defaultChain;
2285
+ const chainName = chainFromFlag;
2286
+ if (!chainName)
2287
+ return [];
2085
2288
  const pallets = await loadPallets(config, chainName);
2086
2289
  if (!pallets)
2087
2290
  return [];
@@ -2143,7 +2346,9 @@ async function completeDotpath(currentWord, config, knownChains, precedingWords)
2143
2346
  return [];
2144
2347
  }
2145
2348
  async function completeApisCategory(prefix, numComplete, endsWithDot, segments, currentWord, config, chainNameOverride) {
2146
- const chainName = chainNameOverride ?? config.defaultChain;
2349
+ const chainName = chainNameOverride;
2350
+ if (!chainName)
2351
+ return [];
2147
2352
  const apis = await loadRuntimeApiNames(config, chainName);
2148
2353
  if (!apis)
2149
2354
  return [];
@@ -2187,7 +2392,7 @@ var init_complete = __esm(() => {
2187
2392
  api: "apis"
2188
2393
  };
2189
2394
  NAMED_COMMANDS = ["chain", "account", "inspect", "hash", "sign", "parachain", "completions"];
2190
- CHAIN_SUBCOMMANDS = ["add", "remove", "update", "list", "default"];
2395
+ CHAIN_SUBCOMMANDS = ["add", "remove", "update", "list"];
2191
2396
  ACCOUNT_SUBCOMMANDS = [
2192
2397
  "add",
2193
2398
  "create",
@@ -2199,9 +2404,10 @@ var init_complete = __esm(() => {
2199
2404
  "delete",
2200
2405
  "inspect"
2201
2406
  ];
2202
- GLOBAL_OPTIONS = ["--chain", "--rpc", "--light-client", "--output", "--help", "--version"];
2407
+ GLOBAL_OPTIONS = ["--chain", "--rpc", "--output", "--help", "--version"];
2203
2408
  TX_OPTIONS = [
2204
2409
  "--from",
2410
+ "--unsigned",
2205
2411
  "--dry-run",
2206
2412
  "--encode",
2207
2413
  "--ext",
@@ -2217,12 +2423,13 @@ var init_complete = __esm(() => {
2217
2423
  // src/cli.ts
2218
2424
  import cac from "cac";
2219
2425
  // package.json
2220
- var version = "1.12.0";
2426
+ var version = "1.14.0";
2221
2427
 
2222
2428
  // src/commands/account.ts
2223
2429
  init_accounts_store();
2224
2430
  init_accounts();
2225
2431
  init_output();
2432
+ import { readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
2226
2433
  var ACCOUNT_HELP = `
2227
2434
  ${BOLD}Usage:${RESET}
2228
2435
  $ dot account add <name> <ss58|hex> Add a watch-only address (no secret)
@@ -2231,6 +2438,8 @@ ${BOLD}Usage:${RESET}
2231
2438
  $ dot account create|new <name> [--path <derivation>] Create a new account
2232
2439
  $ dot account import <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic
2233
2440
  $ dot account import <name> --env <VAR> [--path <derivation>] Import account backed by env variable
2441
+ $ dot account import --file <path> Batch-import accounts from a file
2442
+ $ dot account export [names...] Export accounts to stdout
2234
2443
  $ dot account derive <source> <new-name> --path <derivation> Derive a child account
2235
2444
  $ dot account inspect <input> [--prefix <N>] Inspect an account/address/key
2236
2445
  $ dot account list List all accounts
@@ -2243,6 +2452,13 @@ ${BOLD}Examples:${RESET}
2243
2452
  $ dot account create multi --path //polkadot//0/wallet
2244
2453
  $ dot account import treasury --secret "word1 word2 ... word12"
2245
2454
  $ dot account import ci-signer --env MY_SECRET --path //ci
2455
+ $ dot account import --file team-accounts.json
2456
+ $ dot account import --file accounts.json --dry-run
2457
+ $ dot account import --file accounts.json --overwrite
2458
+ $ dot account export
2459
+ $ dot account export treasury my-validator
2460
+ $ dot account export --include-secrets --file backup.json
2461
+ $ dot account export --watch-only
2246
2462
  $ dot account derive treasury treasury-staking --path //staking
2247
2463
  $ dot account inspect alice
2248
2464
  $ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
@@ -2255,10 +2471,10 @@ ${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
2255
2471
  Hex seed import (0x...) is not supported via CLI.${RESET}
2256
2472
  `.trimStart();
2257
2473
  function registerAccountCommands(cli) {
2258
- 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) => {
2474
+ cli.command("account [action] [...names]", "Manage local accounts (create, import, list, remove, export)").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)").option("--file <path>", "Input/output file for batch import/export").option("--overwrite", "Overwrite existing accounts on batch import").option("--dry-run", "Preview batch import without applying changes").option("--include-secrets", "Include secrets in export (redacted by default)").option("--watch-only", "Export only watch-only accounts").action(async (action, names, opts) => {
2259
2475
  if (!action) {
2260
2476
  if (process.argv[2] === "accounts")
2261
- return accountList();
2477
+ return accountList(opts);
2262
2478
  console.log(ACCOUNT_HELP);
2263
2479
  return;
2264
2480
  }
@@ -2269,16 +2485,20 @@ function registerAccountCommands(cli) {
2269
2485
  case "add":
2270
2486
  if (opts.secret || opts.env)
2271
2487
  return accountImport(names[0], opts);
2272
- return accountAddWatchOnly(names[0], names[1]);
2488
+ return accountAddWatchOnly(names[0], names[1], opts);
2273
2489
  case "import":
2490
+ if (opts.file)
2491
+ return accountBatchImport(opts);
2274
2492
  return accountImport(names[0], opts);
2493
+ case "export":
2494
+ return accountExport(names, opts);
2275
2495
  case "derive":
2276
2496
  return accountDerive(names[0], names[1], opts);
2277
2497
  case "list":
2278
- return accountList();
2498
+ return accountList(opts);
2279
2499
  case "delete":
2280
2500
  case "remove":
2281
- return accountRemove(names);
2501
+ return accountRemove(names, opts);
2282
2502
  case "inspect":
2283
2503
  return accountInspect(names[0], opts);
2284
2504
  default:
@@ -2316,6 +2536,18 @@ async function accountCreate(name, opts) {
2316
2536
  bandersnatch
2317
2537
  });
2318
2538
  await saveAccounts(accountsFile);
2539
+ if (isJsonOutput(opts)) {
2540
+ console.log(formatJson({
2541
+ name,
2542
+ address,
2543
+ publicKey: hexPub,
2544
+ mnemonic,
2545
+ path: path || undefined,
2546
+ bandersnatch
2547
+ }));
2548
+ console.error(`Save this mnemonic phrase! It is the only way to recover this account.`);
2549
+ return;
2550
+ }
2319
2551
  printHeading("Account Created");
2320
2552
  console.log(` ${BOLD}Name:${RESET} ${name}`);
2321
2553
  if (path)
@@ -2364,6 +2596,11 @@ async function accountImport(name, opts) {
2364
2596
  derivationPath: path
2365
2597
  });
2366
2598
  await saveAccounts(accountsFile);
2599
+ if (isJsonOutput(opts)) {
2600
+ const address = publicKey ? toSs58(publicKey) : undefined;
2601
+ console.log(formatJson({ name, address, env: opts.env, path: path || undefined }));
2602
+ return;
2603
+ }
2367
2604
  printHeading("Account Imported");
2368
2605
  console.log(` ${BOLD}Name:${RESET} ${name}`);
2369
2606
  if (path)
@@ -2386,6 +2623,10 @@ async function accountImport(name, opts) {
2386
2623
  derivationPath: path
2387
2624
  });
2388
2625
  await saveAccounts(accountsFile);
2626
+ if (isJsonOutput(opts)) {
2627
+ console.log(formatJson({ name, address, publicKey: hexPub, path: path || undefined }));
2628
+ return;
2629
+ }
2389
2630
  printHeading("Account Imported");
2390
2631
  console.log(` ${BOLD}Name:${RESET} ${name}`);
2391
2632
  if (path)
@@ -2394,7 +2635,7 @@ async function accountImport(name, opts) {
2394
2635
  console.log();
2395
2636
  }
2396
2637
  }
2397
- async function accountAddWatchOnly(name, address) {
2638
+ async function accountAddWatchOnly(name, address, opts = {}) {
2398
2639
  if (!name) {
2399
2640
  console.error(`Account name is required.
2400
2641
  `);
@@ -2431,6 +2672,10 @@ async function accountAddWatchOnly(name, address) {
2431
2672
  derivationPath: ""
2432
2673
  });
2433
2674
  await saveAccounts(accountsFile);
2675
+ if (isJsonOutput(opts)) {
2676
+ console.log(formatJson({ name, address: toSs58(hexPub), watchOnly: true }));
2677
+ return;
2678
+ }
2434
2679
  printHeading("Account Added (watch-only)");
2435
2680
  console.log(` ${BOLD}Name:${RESET} ${name}`);
2436
2681
  console.log(` ${BOLD}Address:${RESET} ${toSs58(hexPub)}`);
@@ -2480,6 +2725,11 @@ async function accountDerive(sourceName, newName, opts) {
2480
2725
  derivationPath: path
2481
2726
  });
2482
2727
  await saveAccounts(accountsFile);
2728
+ if (isJsonOutput(opts)) {
2729
+ const address = publicKey ? toSs58(publicKey) : undefined;
2730
+ console.log(formatJson({ name: newName, source: sourceName, path, address, env: sourceSecret.env }));
2731
+ return;
2732
+ }
2483
2733
  printHeading("Account Derived");
2484
2734
  console.log(` ${BOLD}Name:${RESET} ${newName}`);
2485
2735
  console.log(` ${BOLD}Source:${RESET} ${sourceName}`);
@@ -2502,6 +2752,10 @@ async function accountDerive(sourceName, newName, opts) {
2502
2752
  derivationPath: path
2503
2753
  });
2504
2754
  await saveAccounts(accountsFile);
2755
+ if (isJsonOutput(opts)) {
2756
+ console.log(formatJson({ name: newName, source: sourceName, path, address, publicKey: hexPub }));
2757
+ return;
2758
+ }
2505
2759
  printHeading("Account Derived");
2506
2760
  console.log(` ${BOLD}Name:${RESET} ${newName}`);
2507
2761
  console.log(` ${BOLD}Source:${RESET} ${sourceName}`);
@@ -2510,14 +2764,43 @@ async function accountDerive(sourceName, newName, opts) {
2510
2764
  console.log();
2511
2765
  }
2512
2766
  }
2513
- async function accountList() {
2767
+ async function accountList(opts = {}) {
2768
+ const accountsFile = await loadAccounts();
2769
+ if (isJsonOutput(opts)) {
2770
+ const dev = DEV_NAMES.map((name) => ({
2771
+ name: name.charAt(0).toUpperCase() + name.slice(1),
2772
+ address: getDevAddress(name)
2773
+ }));
2774
+ const stored = accountsFile.accounts.map((account) => {
2775
+ let address;
2776
+ if (isWatchOnly(account)) {
2777
+ address = account.publicKey ? toSs58(account.publicKey) : undefined;
2778
+ } else if (account.secret !== undefined && isEnvSecret(account.secret)) {
2779
+ let pubKey = account.publicKey;
2780
+ if (!pubKey) {
2781
+ pubKey = tryDerivePublicKey(account.secret.env, account.derivationPath) ?? "";
2782
+ }
2783
+ address = pubKey ? toSs58(pubKey) : undefined;
2784
+ } else {
2785
+ address = toSs58(account.publicKey);
2786
+ }
2787
+ return {
2788
+ name: account.name,
2789
+ address,
2790
+ derivationPath: account.derivationPath || undefined,
2791
+ watchOnly: isWatchOnly(account),
2792
+ env: account.secret !== undefined && isEnvSecret(account.secret) ? account.secret.env : undefined
2793
+ };
2794
+ });
2795
+ console.log(formatJson({ dev, stored }));
2796
+ return;
2797
+ }
2514
2798
  printHeading("Dev Accounts");
2515
2799
  for (const name of DEV_NAMES) {
2516
2800
  const display = name.charAt(0).toUpperCase() + name.slice(1);
2517
2801
  const address = getDevAddress(name);
2518
2802
  printItem(display, address);
2519
2803
  }
2520
- const accountsFile = await loadAccounts();
2521
2804
  if (accountsFile.accounts.length > 0) {
2522
2805
  printHeading("Stored Accounts");
2523
2806
  for (const account of accountsFile.accounts) {
@@ -2547,7 +2830,7 @@ async function accountList() {
2547
2830
  }
2548
2831
  console.log();
2549
2832
  }
2550
- async function accountRemove(names) {
2833
+ async function accountRemove(names, opts = {}) {
2551
2834
  if (names.length === 0) {
2552
2835
  console.error(`At least one account name is required.
2553
2836
  `);
@@ -2582,6 +2865,10 @@ async function accountRemove(names) {
2582
2865
  accountsFile.accounts.splice(idx, 1);
2583
2866
  }
2584
2867
  await saveAccounts(accountsFile);
2868
+ if (isJsonOutput(opts)) {
2869
+ console.log(formatJson({ removed: names }));
2870
+ return;
2871
+ }
2585
2872
  for (const name of names) {
2586
2873
  console.log(`Account "${name}" removed.`);
2587
2874
  }
@@ -2637,7 +2924,7 @@ async function accountInspect(input, opts) {
2637
2924
  }
2638
2925
  }
2639
2926
  const ss58 = toSs58(publicKeyHex, prefix);
2640
- if (opts.output === "json") {
2927
+ if (isJsonOutput(opts)) {
2641
2928
  const result = { publicKey: publicKeyHex, ss58, prefix };
2642
2929
  if (name)
2643
2930
  result.name = name;
@@ -2666,6 +2953,162 @@ async function accountInspect(input, opts) {
2666
2953
  console.log();
2667
2954
  }
2668
2955
  }
2956
+ async function readStdin() {
2957
+ const chunks = [];
2958
+ for await (const chunk of process.stdin) {
2959
+ chunks.push(chunk);
2960
+ }
2961
+ return Buffer.concat(chunks).toString("utf-8");
2962
+ }
2963
+ var REDACTED = "<redacted>";
2964
+ async function accountExport(names, opts) {
2965
+ const accountsFile = await loadAccounts();
2966
+ if (opts.includeSecrets) {
2967
+ process.stderr.write(`${YELLOW}Warning: secrets are included in the export.${RESET}
2968
+ `);
2969
+ }
2970
+ let accounts = accountsFile.accounts;
2971
+ if (names.length > 0) {
2972
+ const filtered = [];
2973
+ for (const input of names) {
2974
+ const account = findAccount(accountsFile, input);
2975
+ if (!account) {
2976
+ throw new Error(`Account "${input}" not found.`);
2977
+ }
2978
+ filtered.push(account);
2979
+ }
2980
+ accounts = filtered;
2981
+ }
2982
+ if (opts.watchOnly) {
2983
+ accounts = accounts.filter((a) => isWatchOnly(a));
2984
+ }
2985
+ const exported = accounts.map((account) => {
2986
+ const entry = {
2987
+ name: account.name,
2988
+ publicKey: account.publicKey,
2989
+ derivationPath: account.derivationPath
2990
+ };
2991
+ if (isWatchOnly(account)) {} else if (account.secret !== undefined && isEnvSecret(account.secret)) {
2992
+ entry.secret = account.secret;
2993
+ } else if (opts.includeSecrets) {
2994
+ entry.secret = account.secret;
2995
+ } else {
2996
+ entry.secret = REDACTED;
2997
+ }
2998
+ if (account.bandersnatch && Object.keys(account.bandersnatch).length > 0) {
2999
+ entry.bandersnatch = account.bandersnatch;
3000
+ }
3001
+ return entry;
3002
+ });
3003
+ const exportData = { accounts: exported };
3004
+ const json = `${JSON.stringify(exportData, null, 2)}
3005
+ `;
3006
+ if (opts.file) {
3007
+ await writeFile3(opts.file, json);
3008
+ if (isJsonOutput(opts)) {
3009
+ console.log(formatJson({ action: "exported", file: opts.file, count: exported.length }));
3010
+ } else {
3011
+ console.log(`Exported ${exported.length} account(s) to ${opts.file}`);
3012
+ }
3013
+ } else {
3014
+ process.stdout.write(json);
3015
+ }
3016
+ }
3017
+ async function accountBatchImport(opts) {
3018
+ let raw;
3019
+ if (!opts.file || opts.file === "-") {
3020
+ raw = await readStdin();
3021
+ } else {
3022
+ raw = await readFile3(opts.file, "utf-8");
3023
+ }
3024
+ let importData;
3025
+ try {
3026
+ importData = JSON.parse(raw);
3027
+ } catch {
3028
+ throw new Error("Invalid JSON input.");
3029
+ }
3030
+ if (!Array.isArray(importData.accounts)) {
3031
+ throw new Error('Invalid import format: missing "accounts" array.');
3032
+ }
3033
+ const accountsFile = await loadAccounts();
3034
+ const added = [];
3035
+ const skipped = [];
3036
+ const overwritten = [];
3037
+ for (const entry of importData.accounts) {
3038
+ if (!entry.name) {
3039
+ process.stderr.write(`${YELLOW}Skipped entry with missing name.${RESET}
3040
+ `);
3041
+ continue;
3042
+ }
3043
+ if (isDevAccount(entry.name)) {
3044
+ process.stderr.write(`${YELLOW}Skipped "${entry.name}": built-in dev account.${RESET}
3045
+ `);
3046
+ skipped.push(entry.name);
3047
+ continue;
3048
+ }
3049
+ const existing = findAccount(accountsFile, entry.name);
3050
+ if (existing && !opts.overwrite) {
3051
+ skipped.push(entry.name);
3052
+ process.stderr.write(`${YELLOW}Skipped "${entry.name}": already exists (use --overwrite to replace)${RESET}
3053
+ `);
3054
+ continue;
3055
+ }
3056
+ const stored = {
3057
+ name: entry.name,
3058
+ publicKey: entry.publicKey || "",
3059
+ derivationPath: entry.derivationPath || ""
3060
+ };
3061
+ if (entry.secret === undefined || entry.secret === REDACTED) {} else if (typeof entry.secret === "object" && "env" in entry.secret) {
3062
+ stored.secret = entry.secret;
3063
+ if (!stored.publicKey) {
3064
+ stored.publicKey = tryDerivePublicKey(entry.secret.env, stored.derivationPath) ?? "";
3065
+ }
3066
+ } else if (typeof entry.secret === "string") {
3067
+ stored.secret = entry.secret;
3068
+ try {
3069
+ const { publicKey } = importAccount(entry.secret, stored.derivationPath);
3070
+ stored.publicKey = publicKeyToHex(publicKey);
3071
+ } catch {
3072
+ process.stderr.write(`${YELLOW}Warning: "${entry.name}" has an invalid secret, importing as watch-only.${RESET}
3073
+ `);
3074
+ delete stored.secret;
3075
+ }
3076
+ }
3077
+ if (entry.bandersnatch && Object.keys(entry.bandersnatch).length > 0) {
3078
+ stored.bandersnatch = entry.bandersnatch;
3079
+ }
3080
+ if (existing) {
3081
+ const idx = accountsFile.accounts.findIndex((a) => a.name.toLowerCase() === entry.name.toLowerCase());
3082
+ accountsFile.accounts[idx] = stored;
3083
+ overwritten.push(entry.name);
3084
+ } else {
3085
+ accountsFile.accounts.push(stored);
3086
+ added.push(entry.name);
3087
+ }
3088
+ }
3089
+ if (!opts.dryRun) {
3090
+ await saveAccounts(accountsFile);
3091
+ }
3092
+ if (isJsonOutput(opts)) {
3093
+ console.log(formatJson({
3094
+ action: opts.dryRun ? "dry-run" : "imported",
3095
+ added,
3096
+ overwritten,
3097
+ skipped
3098
+ }));
3099
+ return;
3100
+ }
3101
+ const prefix = opts.dryRun ? "(dry run) " : "";
3102
+ if (added.length > 0)
3103
+ console.log(`${prefix}Added: ${added.join(", ")}`);
3104
+ if (overwritten.length > 0)
3105
+ console.log(`${prefix}Overwritten: ${overwritten.join(", ")}`);
3106
+ if (skipped.length > 0)
3107
+ console.log(`${prefix}Skipped: ${skipped.join(", ")}`);
3108
+ if (added.length === 0 && overwritten.length === 0) {
3109
+ console.log(`${prefix}No accounts imported.`);
3110
+ }
3111
+ }
2669
3112
 
2670
3113
  // src/commands/apis.ts
2671
3114
  init_store();
@@ -2680,6 +3123,13 @@ async function handleApis2(target, args, opts) {
2680
3123
  const { name: chainName2, chain: chainConfig2 } = resolveChain(config2, opts.chain);
2681
3124
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
2682
3125
  const apis = listRuntimeApis(meta);
3126
+ if (isJsonOutput(opts)) {
3127
+ console.log(formatJson({
3128
+ chain: chainName2,
3129
+ apis: apis.map((a) => ({ name: a.name, methods: a.methods.length }))
3130
+ }));
3131
+ return;
3132
+ }
2683
3133
  printHeading(`Runtime APIs on ${chainName2} (${apis.length})`);
2684
3134
  for (const api of apis) {
2685
3135
  printItem(api.name, `${api.methods.length} methods`);
@@ -2702,7 +3152,24 @@ async function handleApis2(target, args, opts) {
2702
3152
  const meta = await loadMeta(chainName, chainConfig, opts.rpc);
2703
3153
  const api = resolveRuntimeApi2(meta, apiName);
2704
3154
  if (api.methods.length === 0) {
2705
- console.log(`No methods in ${api.name}.`);
3155
+ if (isJsonOutput(opts)) {
3156
+ console.log(formatJson({ chain: chainName, api: api.name, methods: [] }));
3157
+ } else {
3158
+ console.log(`No methods in ${api.name}.`);
3159
+ }
3160
+ return;
3161
+ }
3162
+ if (isJsonOutput(opts)) {
3163
+ console.log(formatJson({
3164
+ chain: chainName,
3165
+ api: api.name,
3166
+ methods: api.methods.map((m) => ({
3167
+ name: m.name,
3168
+ args: describeRuntimeApiMethodArgs(meta, m),
3169
+ returns: describeType(meta.lookup, m.output),
3170
+ docs: firstSentence(m.docs)
3171
+ }))
3172
+ }));
2706
3173
  return;
2707
3174
  }
2708
3175
  printHeading(`${api.name} Methods`);
@@ -2733,7 +3200,7 @@ async function handleApis2(target, args, opts) {
2733
3200
  const parsedArgs = await parseRuntimeApiArgs2(meta, method, effectiveArgs);
2734
3201
  const unsafeApi = clientHandle.client.getUnsafeApi();
2735
3202
  const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
2736
- const format = opts.output ?? "pretty";
3203
+ const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
2737
3204
  printResult(result, format);
2738
3205
  } finally {
2739
3206
  clientHandle.destroy();
@@ -2766,46 +3233,54 @@ init_types();
2766
3233
  init_client();
2767
3234
  init_metadata();
2768
3235
  init_output();
3236
+ import { readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2769
3237
  var CHAIN_HELP = `
2770
3238
  ${BOLD}Usage:${RESET}
2771
3239
  $ 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
3240
  $ dot chain remove <name> Remove a chain
2774
- $ dot chain update [name] Re-fetch metadata (default chain if omitted)
3241
+ $ dot chain update <name> Re-fetch metadata for a chain
2775
3242
  $ dot chain update --all Re-fetch metadata for all configured chains
2776
3243
  $ dot chain list List configured chains
2777
- $ dot chain default <name> Set the default chain
3244
+ $ dot chain export [names...] Export chain configuration to stdout
3245
+ $ dot chain import <file> Import chain configuration from a file
2778
3246
 
2779
3247
  ${BOLD}Examples:${RESET}
2780
3248
  $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
2781
3249
  $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io --rpc wss://kusama-rpc.dwellir.com
2782
- $ dot chain add westend --light-client
2783
- $ dot chain default kusama
3250
+ $ dot chain add my-para --rpc wss://rpc.example.com --relay polkadot
3251
+ $ dot chain add my-para --rpc wss://rpc.example.com --relay polkadot --parachain-id 2000
2784
3252
  $ dot chain list
2785
- $ dot chain update
2786
3253
  $ dot chain update kusama
2787
3254
  $ dot chain update --all
2788
3255
  $ dot chain remove kusama
3256
+ $ dot chain export
3257
+ $ dot chain export my-relay my-para --file my-chains.json
3258
+ $ dot chain export --all
3259
+ $ dot chain import my-chains.json
3260
+ $ dot chain import my-chains.json --dry-run
3261
+ $ dot chain import my-chains.json --overwrite
2789
3262
  `.trimStart();
2790
3263
  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) => {
3264
+ cli.command("chain [action] [...names]", "Manage chains (add, remove, update, list, export, import)").alias("chains").option("--all", "Update/export all configured chains").option("--relay <name>", "Parent relay chain for this parachain").option("--parachain-id <id>", "Parachain ID (auto-detected if omitted with --relay)").option("--file <path>", "Output/input file for export/import").option("--overwrite", "Overwrite existing chains on import").option("--dry-run", "Preview import without applying changes").action(async (action, names, opts) => {
2792
3265
  if (!action) {
2793
3266
  if (process.argv[2] === "chains")
2794
- return chainList();
3267
+ return chainList(opts);
2795
3268
  console.log(CHAIN_HELP);
2796
3269
  return;
2797
3270
  }
2798
3271
  switch (action) {
2799
3272
  case "add":
2800
- return chainAdd(name, opts);
3273
+ return chainAdd(names[0], opts);
2801
3274
  case "remove":
2802
- return chainRemove(name);
3275
+ return chainRemove(names[0], opts);
2803
3276
  case "list":
2804
- return chainList();
3277
+ return chainList(opts);
2805
3278
  case "update":
2806
- return chainUpdate(name, opts);
2807
- case "default":
2808
- return chainDefault(name);
3279
+ return chainUpdate(names[0], opts);
3280
+ case "export":
3281
+ return chainExport(names, opts);
3282
+ case "import":
3283
+ return chainImport(names[0], opts);
2809
3284
  default:
2810
3285
  console.error(`Unknown action "${action}".
2811
3286
  `);
@@ -2819,34 +3294,70 @@ async function chainAdd(name, opts) {
2819
3294
  console.error(`Chain name is required.
2820
3295
  `);
2821
3296
  console.error("Usage: dot chain add <name> --rpc <url>");
2822
- console.error(" dot chain add <name> --light-client");
2823
3297
  process.exit(1);
2824
3298
  }
2825
- if (!opts.rpc && !opts.lightClient) {
2826
- console.error(`Must provide either --rpc <url> or --light-client.
3299
+ if (!opts.rpc) {
3300
+ console.error(`Must provide --rpc <url>.
2827
3301
  `);
2828
3302
  console.error("Usage: dot chain add <name> --rpc <url>");
2829
- console.error(" dot chain add <name> --light-client");
2830
3303
  process.exit(1);
2831
3304
  }
2832
- const chainConfig = {
2833
- rpc: opts.rpc ?? "",
2834
- ...opts.lightClient ? { lightClient: true } : {}
2835
- };
2836
- console.error(`Connecting to ${name}...`);
3305
+ const parachainIdRaw = opts.parachainId != null ? Number(opts.parachainId) : undefined;
3306
+ if (parachainIdRaw != null && !opts.relay) {
3307
+ console.error(`Cannot set --parachain-id without --relay.
3308
+ `);
3309
+ console.error("Usage: dot chain add <name> --rpc <url> --relay <relay> --parachain-id <id>");
3310
+ process.exit(1);
3311
+ }
3312
+ const chainConfig = { rpc: opts.rpc };
3313
+ process.stderr.write(`Connecting to ${name}...
3314
+ `);
2837
3315
  const clientHandle = await createChainClient(name, chainConfig, opts.rpc);
2838
3316
  try {
2839
- console.error("Fetching metadata...");
3317
+ process.stderr.write(`Fetching metadata...
3318
+ `);
2840
3319
  await fetchMetadataFromChain(clientHandle, name);
3320
+ if (opts.relay) {
3321
+ const config2 = await loadConfig();
3322
+ const relayResolved = findChainName(config2, opts.relay);
3323
+ if (!relayResolved) {
3324
+ throw new Error(`Relay chain "${opts.relay}" not found. Add it first with: dot chain add ${opts.relay} --rpc <url>`);
3325
+ }
3326
+ chainConfig.relay = relayResolved;
3327
+ if (parachainIdRaw != null) {
3328
+ chainConfig.parachainId = parachainIdRaw;
3329
+ } else {
3330
+ process.stderr.write(`Detecting parachain ID...
3331
+ `);
3332
+ const detected = await getParachainId(clientHandle);
3333
+ if (detected != null) {
3334
+ chainConfig.parachainId = detected;
3335
+ process.stderr.write(`Detected parachain ID: ${detected}
3336
+ `);
3337
+ } else {
3338
+ process.stderr.write(`Could not detect parachain ID. Use --parachain-id to set it manually.
3339
+ `);
3340
+ }
3341
+ }
3342
+ }
2841
3343
  const config = await loadConfig();
2842
3344
  config.chains[name] = chainConfig;
2843
3345
  await saveConfig(config);
2844
- console.log(`Chain "${name}" added successfully.`);
3346
+ const result = { action: "added", chain: name };
3347
+ if (chainConfig.relay)
3348
+ result.relay = chainConfig.relay;
3349
+ if (chainConfig.parachainId != null)
3350
+ result.parachainId = chainConfig.parachainId;
3351
+ if (isJsonOutput(opts)) {
3352
+ console.log(formatJson(result));
3353
+ } else {
3354
+ console.log(`Chain "${name}" added successfully.`);
3355
+ }
2845
3356
  } finally {
2846
3357
  clientHandle.destroy();
2847
3358
  }
2848
3359
  }
2849
- async function chainRemove(name) {
3360
+ async function chainRemove(name, opts = {}) {
2850
3361
  if (!name) {
2851
3362
  console.error("Usage: dot chain remove <name>");
2852
3363
  process.exit(1);
@@ -2859,32 +3370,77 @@ async function chainRemove(name) {
2859
3370
  if (BUILTIN_CHAIN_NAMES.has(resolved)) {
2860
3371
  throw new Error(`Cannot remove the built-in "${resolved}" chain.`);
2861
3372
  }
2862
- delete config.chains[resolved];
2863
- if (config.defaultChain === resolved) {
2864
- config.defaultChain = "polkadot";
2865
- console.log(`Default chain reset to "polkadot".`);
3373
+ const orphans = Object.entries(config.chains).filter(([, c]) => c.relay === resolved).map(([n]) => n);
3374
+ if (orphans.length > 0) {
3375
+ console.error(`Warning: ${orphans.length} chain(s) reference "${resolved}" as their relay: ${orphans.join(", ")}`);
2866
3376
  }
3377
+ delete config.chains[resolved];
2867
3378
  await saveConfig(config);
2868
3379
  await removeChainData(resolved);
2869
- console.log(`Chain "${resolved}" removed.`);
3380
+ if (isJsonOutput(opts)) {
3381
+ console.log(formatJson({ action: "removed", chain: resolved }));
3382
+ } else {
3383
+ console.log(`Chain "${resolved}" removed.`);
3384
+ }
2870
3385
  }
2871
- async function chainList() {
3386
+ async function chainList(opts = {}) {
2872
3387
  const config = await loadConfig();
3388
+ if (isJsonOutput(opts)) {
3389
+ const chains = Object.entries(config.chains).map(([name, chainConfig]) => ({
3390
+ name,
3391
+ rpc: Array.isArray(chainConfig.rpc) ? chainConfig.rpc : [chainConfig.rpc],
3392
+ ...chainConfig.relay && { relay: chainConfig.relay },
3393
+ ...chainConfig.parachainId != null && { parachainId: chainConfig.parachainId }
3394
+ }));
3395
+ console.log(formatJson({ chains }));
3396
+ return;
3397
+ }
2873
3398
  printHeading("Configured Chains");
3399
+ const parachainsByRelay = new Map;
3400
+ const standalone = [];
2874
3401
  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
- }
3402
+ if (chainConfig.relay) {
3403
+ const paras = parachainsByRelay.get(chainConfig.relay) ?? [];
3404
+ paras.push([name, chainConfig]);
3405
+ parachainsByRelay.set(chainConfig.relay, paras);
2885
3406
  }
2886
3407
  }
2887
- console.log();
3408
+ const relayNames = new Set(parachainsByRelay.keys());
3409
+ for (const [name, chainConfig] of Object.entries(config.chains)) {
3410
+ if (relayNames.has(name))
3411
+ continue;
3412
+ if (chainConfig.relay)
3413
+ continue;
3414
+ standalone.push([name, chainConfig]);
3415
+ }
3416
+ for (const relayName of relayNames) {
3417
+ const relayConfig = config.chains[relayName];
3418
+ if (relayConfig) {
3419
+ printChainLine(" ", relayName, relayConfig);
3420
+ }
3421
+ const paras = parachainsByRelay.get(relayName) ?? [];
3422
+ for (let i = 0;i < paras.length; i++) {
3423
+ const [name, chainConfig] = paras[i];
3424
+ const isLast = i === paras.length - 1;
3425
+ const prefix = isLast ? " └─ " : " ├─ ";
3426
+ const idSuffix = chainConfig.parachainId != null ? ` ${DIM}[${chainConfig.parachainId}]${RESET}` : "";
3427
+ printChainLine(prefix, name, chainConfig, idSuffix);
3428
+ }
3429
+ console.log();
3430
+ }
3431
+ for (const [name, chainConfig] of standalone) {
3432
+ printChainLine(" ", name, chainConfig);
3433
+ }
3434
+ if (standalone.length > 0)
3435
+ console.log();
3436
+ }
3437
+ function printChainLine(prefix, name, chainConfig, suffix = "") {
3438
+ const rpcs = Array.isArray(chainConfig.rpc) ? chainConfig.rpc : [chainConfig.rpc];
3439
+ console.log(`${prefix}${CYAN}${name}${RESET}${suffix} ${DIM}${rpcs[0]}${RESET}`);
3440
+ const indent = prefix.replace(/[^\s]/g, " ");
3441
+ for (let i = 1;i < rpcs.length; i++) {
3442
+ console.log(`${indent} ${DIM}${rpcs[i]}${RESET}`);
3443
+ }
2888
3444
  }
2889
3445
  async function chainUpdate(name, opts) {
2890
3446
  const config = await loadConfig();
@@ -2892,20 +3448,31 @@ async function chainUpdate(name, opts) {
2892
3448
  await chainUpdateAll(config);
2893
3449
  return;
2894
3450
  }
3451
+ if (!name) {
3452
+ console.error("Usage: dot chain update <name> | --all");
3453
+ process.exit(1);
3454
+ }
2895
3455
  const { name: chainName, chain: chainConfig } = resolveChain(config, name);
2896
- console.error(`Connecting to ${chainName}...`);
3456
+ process.stderr.write(`Connecting to ${chainName}...
3457
+ `);
2897
3458
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
2898
3459
  try {
2899
- console.error("Fetching metadata...");
3460
+ process.stderr.write(`Fetching metadata...
3461
+ `);
2900
3462
  await fetchMetadataFromChain(clientHandle, chainName);
2901
- console.log(`Metadata for "${chainName}" updated.`);
3463
+ if (isJsonOutput(opts)) {
3464
+ console.log(formatJson({ action: "updated", chain: chainName }));
3465
+ } else {
3466
+ console.log(`Metadata for "${chainName}" updated.`);
3467
+ }
2902
3468
  } finally {
2903
3469
  clientHandle.destroy();
2904
3470
  }
2905
3471
  }
2906
3472
  async function chainUpdateAll(config) {
2907
3473
  const chainNames = Object.keys(config.chains).sort();
2908
- console.error(`Updating metadata for ${chainNames.length} chains...
3474
+ process.stderr.write(`Updating metadata for ${chainNames.length} chains...
3475
+
2909
3476
  `);
2910
3477
  const results = await Promise.allSettled(chainNames.map(async (chainName) => {
2911
3478
  const chainConfig = config.chains[chainName];
@@ -2915,37 +3482,150 @@ async function chainUpdateAll(config) {
2915
3482
  } finally {
2916
3483
  clientHandle.destroy();
2917
3484
  }
2918
- }));
2919
- for (let i = 0;i < chainNames.length; i++) {
2920
- const result = results[i];
2921
- const name = chainNames[i];
2922
- if (result.status === "fulfilled") {
2923
- console.log(` ${GREEN}${CHECK_MARK}${RESET} ${name}`);
3485
+ }));
3486
+ for (let i = 0;i < chainNames.length; i++) {
3487
+ const result = results[i];
3488
+ const name = chainNames[i];
3489
+ if (result.status === "fulfilled") {
3490
+ console.log(` ${GREEN}${CHECK_MARK}${RESET} ${name}`);
3491
+ } else {
3492
+ console.log(` ${RED}✗${RESET} ${name}${DIM} — ${result.reason?.message ?? "unknown error"}${RESET}`);
3493
+ }
3494
+ }
3495
+ const failed = results.filter((r) => r.status === "rejected").length;
3496
+ if (failed > 0) {
3497
+ console.error(`
3498
+ ${failed} of ${chainNames.length} chains failed to update.`);
3499
+ process.exit(1);
3500
+ }
3501
+ }
3502
+ function isBuiltinModified(name, config) {
3503
+ const defaultRpc = DEFAULT_CONFIG.chains[name]?.rpc;
3504
+ const currentRpc = config.chains[name]?.rpc;
3505
+ if (!defaultRpc || !currentRpc)
3506
+ return false;
3507
+ return JSON.stringify(currentRpc) !== JSON.stringify(defaultRpc);
3508
+ }
3509
+ async function readStdin2() {
3510
+ const chunks = [];
3511
+ for await (const chunk of process.stdin) {
3512
+ chunks.push(chunk);
3513
+ }
3514
+ return Buffer.concat(chunks).toString("utf-8");
3515
+ }
3516
+ async function chainExport(names, opts) {
3517
+ const config = await loadConfig();
3518
+ const exportChains = {};
3519
+ if (names.length > 0) {
3520
+ for (const input of names) {
3521
+ const resolved = findChainName(config, input);
3522
+ if (!resolved) {
3523
+ throw new Error(`Chain "${input}" not found.`);
3524
+ }
3525
+ exportChains[resolved] = config.chains[resolved];
3526
+ }
3527
+ } else if (opts.all) {
3528
+ Object.assign(exportChains, config.chains);
3529
+ } else {
3530
+ for (const [name, chainConfig] of Object.entries(config.chains)) {
3531
+ if (!BUILTIN_CHAIN_NAMES.has(name) || isBuiltinModified(name, config)) {
3532
+ exportChains[name] = chainConfig;
3533
+ }
3534
+ }
3535
+ }
3536
+ const exportData = {
3537
+ chains: exportChains
3538
+ };
3539
+ const json = `${JSON.stringify(exportData, null, 2)}
3540
+ `;
3541
+ if (opts.file) {
3542
+ await writeFile4(opts.file, json);
3543
+ if (isJsonOutput(opts)) {
3544
+ console.log(formatJson({
3545
+ action: "exported",
3546
+ file: opts.file,
3547
+ count: Object.keys(exportChains).length
3548
+ }));
3549
+ } else {
3550
+ console.log(`Exported ${Object.keys(exportChains).length} chain(s) to ${opts.file}`);
3551
+ }
3552
+ } else {
3553
+ process.stdout.write(json);
3554
+ }
3555
+ }
3556
+ async function chainImport(filePath, opts) {
3557
+ const inputPath = filePath ?? opts.file;
3558
+ let raw;
3559
+ if (inputPath && inputPath !== "-") {
3560
+ raw = await readFile4(inputPath, "utf-8");
3561
+ } else {
3562
+ raw = await readStdin2();
3563
+ }
3564
+ let importData;
3565
+ try {
3566
+ importData = JSON.parse(raw);
3567
+ } catch {
3568
+ throw new Error("Invalid JSON input.");
3569
+ }
3570
+ if (!importData.chains || typeof importData.chains !== "object") {
3571
+ throw new Error('Invalid import format: missing "chains" object.');
3572
+ }
3573
+ const config = await loadConfig();
3574
+ const added = [];
3575
+ const skipped = [];
3576
+ const overwritten = [];
3577
+ const warnings = [];
3578
+ for (const [name, chainConfig] of Object.entries(importData.chains)) {
3579
+ const existing = findChainName(config, name);
3580
+ if (existing && !opts.overwrite) {
3581
+ skipped.push(existing);
3582
+ process.stderr.write(`${YELLOW}Skipped "${existing}": already exists (use --overwrite to replace)${RESET}
3583
+ `);
3584
+ continue;
3585
+ }
3586
+ if (chainConfig.relay) {
3587
+ const relayInImport = Object.keys(importData.chains).some((n) => n.toLowerCase() === chainConfig.relay.toLowerCase());
3588
+ const relayInConfig = findChainName(config, chainConfig.relay);
3589
+ if (!relayInImport && !relayInConfig) {
3590
+ warnings.push(`Chain "${name}" references relay "${chainConfig.relay}" which does not exist.`);
3591
+ process.stderr.write(`${YELLOW}Warning: "${name}" references relay "${chainConfig.relay}" which does not exist.${RESET}
3592
+ `);
3593
+ }
3594
+ }
3595
+ if (existing) {
3596
+ overwritten.push(existing);
3597
+ config.chains[existing] = chainConfig;
2924
3598
  } else {
2925
- console.log(` ${RED}✗${RESET} ${name}${DIM} — ${result.reason?.message ?? "unknown error"}${RESET}`);
3599
+ added.push(name);
3600
+ config.chains[name] = chainConfig;
2926
3601
  }
2927
3602
  }
2928
- const failed = results.filter((r) => r.status === "rejected").length;
2929
- if (failed > 0) {
2930
- console.error(`
2931
- ${failed} of ${chainNames.length} chains failed to update.`);
2932
- process.exit(1);
3603
+ if (!opts.dryRun) {
3604
+ await saveConfig(config);
2933
3605
  }
2934
- }
2935
- async function chainDefault(name) {
2936
- if (!name) {
2937
- console.error("Usage: dot chain default <name>");
2938
- process.exit(1);
3606
+ if (isJsonOutput(opts)) {
3607
+ console.log(formatJson({
3608
+ action: opts.dryRun ? "dry-run" : "imported",
3609
+ added,
3610
+ overwritten,
3611
+ skipped,
3612
+ warnings
3613
+ }));
3614
+ return;
2939
3615
  }
2940
- const config = await loadConfig();
2941
- const resolved = findChainName(config, name);
2942
- if (!resolved) {
2943
- const available = Object.keys(config.chains).join(", ");
2944
- throw new Error(`Chain "${name}" not found. Available: ${available}`);
3616
+ const prefix = opts.dryRun ? "(dry run) " : "";
3617
+ if (added.length > 0)
3618
+ console.log(`${prefix}Added: ${added.join(", ")}`);
3619
+ if (overwritten.length > 0)
3620
+ console.log(`${prefix}Overwritten: ${overwritten.join(", ")}`);
3621
+ if (skipped.length > 0)
3622
+ console.log(`${prefix}Skipped: ${skipped.join(", ")}`);
3623
+ if (added.length === 0 && overwritten.length === 0) {
3624
+ console.log(`${prefix}No chains imported.`);
3625
+ } else if (!opts.dryRun) {
3626
+ console.error(`
3627
+ Run "dot chain update --all" to fetch metadata for imported chains.`);
2945
3628
  }
2946
- config.defaultChain = resolved;
2947
- await saveConfig(config);
2948
- console.log(`Default chain set to "${resolved}".`);
2949
3629
  }
2950
3630
 
2951
3631
  // src/commands/completions.ts
@@ -3045,6 +3725,13 @@ async function handleConst(target, opts) {
3045
3725
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
3046
3726
  const pallets = listPallets(meta);
3047
3727
  const withConsts = pallets.filter((p) => p.constants.length > 0);
3728
+ if (isJsonOutput(opts)) {
3729
+ console.log(formatJson({
3730
+ chain: chainName2,
3731
+ pallets: withConsts.map((p) => ({ name: p.name, constants: p.constants.length }))
3732
+ }));
3733
+ return;
3734
+ }
3048
3735
  printHeading(`Pallets with constants on ${chainName2} (${withConsts.length})`);
3049
3736
  for (const p of withConsts) {
3050
3737
  printItem(p.name, `${p.constants.length} constants`);
@@ -3065,7 +3752,23 @@ async function handleConst(target, opts) {
3065
3752
  throw new Error(suggestMessage("pallet", pallet, palletNames));
3066
3753
  }
3067
3754
  if (palletInfo.constants.length === 0) {
3068
- console.log(`No constants in ${palletInfo.name}.`);
3755
+ if (isJsonOutput(opts)) {
3756
+ console.log(formatJson({ chain: chainName, pallet: palletInfo.name, constants: [] }));
3757
+ } else {
3758
+ console.log(`No constants in ${palletInfo.name}.`);
3759
+ }
3760
+ return;
3761
+ }
3762
+ if (isJsonOutput(opts)) {
3763
+ console.log(formatJson({
3764
+ chain: chainName,
3765
+ pallet: palletInfo.name,
3766
+ constants: palletInfo.constants.map((c) => ({
3767
+ name: c.name,
3768
+ type: describeType(meta.lookup, c.typeId),
3769
+ docs: firstSentence(c.docs)
3770
+ }))
3771
+ }));
3069
3772
  return;
3070
3773
  }
3071
3774
  printHeading(`${palletInfo.name} Constants`);
@@ -3094,9 +3797,9 @@ async function handleConst(target, opts) {
3094
3797
  throw new Error(suggestMessage(`constant in ${palletInfo.name}`, item, constNames));
3095
3798
  }
3096
3799
  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";
3800
+ const staticApis = await unsafeApi.getStaticApis();
3801
+ const result = staticApis.constants[palletInfo.name][constantItem.name];
3802
+ const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
3100
3803
  printResult(result, format);
3101
3804
  } finally {
3102
3805
  clientHandle.destroy();
@@ -3109,11 +3812,23 @@ init_client();
3109
3812
  init_metadata();
3110
3813
  init_output();
3111
3814
  async function loadMeta2(chainName, chainConfig, rpcOverride) {
3815
+ if (rpcOverride) {
3816
+ process.stderr.write(`Fetching metadata from ${chainName}...
3817
+ `);
3818
+ const clientHandle = await createChainClient(chainName, chainConfig, rpcOverride);
3819
+ try {
3820
+ const raw = await fetchMetadataFromChain(clientHandle, chainName);
3821
+ return parseMetadata(raw);
3822
+ } finally {
3823
+ clientHandle.destroy();
3824
+ }
3825
+ }
3112
3826
  try {
3113
3827
  return await getOrFetchMetadata(chainName);
3114
3828
  } catch {
3115
- console.error(`Fetching metadata from ${chainName}...`);
3116
- const clientHandle = await createChainClient(chainName, chainConfig, rpcOverride);
3829
+ process.stderr.write(`Fetching metadata from ${chainName}...
3830
+ `);
3831
+ const clientHandle = await createChainClient(chainName, chainConfig);
3117
3832
  try {
3118
3833
  return await getOrFetchMetadata(chainName, clientHandle);
3119
3834
  } finally {
@@ -3136,6 +3851,13 @@ async function handleCalls2(target, opts) {
3136
3851
  const meta2 = await loadMeta2(chainName2, chainConfig2, opts.rpc);
3137
3852
  const pallets = listPallets(meta2);
3138
3853
  const withCalls = pallets.filter((p) => p.calls.length > 0);
3854
+ if (isJsonOutput(opts)) {
3855
+ console.log(formatJson({
3856
+ chain: chainName2,
3857
+ pallets: withCalls.map((p) => ({ name: p.name, calls: p.calls.length }))
3858
+ }));
3859
+ return;
3860
+ }
3139
3861
  printHeading(`Pallets with calls on ${chainName2} (${withCalls.length})`);
3140
3862
  for (const p of withCalls) {
3141
3863
  printItem(p.name, `${p.calls.length} calls`);
@@ -3152,7 +3874,23 @@ async function handleCalls2(target, opts) {
3152
3874
  const pallet = resolvePallet2(meta, palletName);
3153
3875
  if (!itemName) {
3154
3876
  if (pallet.calls.length === 0) {
3155
- console.log(`No calls in ${pallet.name}.`);
3877
+ if (isJsonOutput(opts)) {
3878
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, calls: [] }));
3879
+ } else {
3880
+ console.log(`No calls in ${pallet.name}.`);
3881
+ }
3882
+ return;
3883
+ }
3884
+ if (isJsonOutput(opts)) {
3885
+ console.log(formatJson({
3886
+ chain: chainName,
3887
+ pallet: pallet.name,
3888
+ calls: pallet.calls.map((c) => ({
3889
+ name: c.name,
3890
+ args: describeCallArgs(meta, pallet.name, c.name),
3891
+ docs: firstSentence(c.docs)
3892
+ }))
3893
+ }));
3156
3894
  return;
3157
3895
  }
3158
3896
  printHeading(`${pallet.name} Calls`);
@@ -3172,6 +3910,17 @@ async function handleCalls2(target, opts) {
3172
3910
  const names = pallet.calls.map((c) => c.name);
3173
3911
  throw new Error(suggestMessage(`call in ${pallet.name}`, itemName, names));
3174
3912
  }
3913
+ if (isJsonOutput(opts)) {
3914
+ console.log(formatJson({
3915
+ chain: chainName,
3916
+ pallet: pallet.name,
3917
+ item: callItem.name,
3918
+ category: "call",
3919
+ args: describeCallArgs(meta, pallet.name, callItem.name),
3920
+ docs: callItem.docs
3921
+ }));
3922
+ return;
3923
+ }
3175
3924
  printHeading(`${pallet.name}.${callItem.name} (Call)`);
3176
3925
  const args = describeCallArgs(meta, pallet.name, callItem.name);
3177
3926
  console.log(` ${BOLD}Args:${RESET} ${args}`);
@@ -3188,6 +3937,13 @@ async function handleEvents2(target, opts) {
3188
3937
  const meta2 = await loadMeta2(chainName2, chainConfig2, opts.rpc);
3189
3938
  const pallets = listPallets(meta2);
3190
3939
  const withEvents = pallets.filter((p) => p.events.length > 0);
3940
+ if (isJsonOutput(opts)) {
3941
+ console.log(formatJson({
3942
+ chain: chainName2,
3943
+ pallets: withEvents.map((p) => ({ name: p.name, events: p.events.length }))
3944
+ }));
3945
+ return;
3946
+ }
3191
3947
  printHeading(`Pallets with events on ${chainName2} (${withEvents.length})`);
3192
3948
  for (const p of withEvents) {
3193
3949
  printItem(p.name, `${p.events.length} events`);
@@ -3204,7 +3960,23 @@ async function handleEvents2(target, opts) {
3204
3960
  const pallet = resolvePallet2(meta, palletName);
3205
3961
  if (!itemName) {
3206
3962
  if (pallet.events.length === 0) {
3207
- console.log(`No events in ${pallet.name}.`);
3963
+ if (isJsonOutput(opts)) {
3964
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, events: [] }));
3965
+ } else {
3966
+ console.log(`No events in ${pallet.name}.`);
3967
+ }
3968
+ return;
3969
+ }
3970
+ if (isJsonOutput(opts)) {
3971
+ console.log(formatJson({
3972
+ chain: chainName,
3973
+ pallet: pallet.name,
3974
+ events: pallet.events.map((e) => ({
3975
+ name: e.name,
3976
+ fields: describeEventFields(meta, pallet.name, e.name),
3977
+ docs: firstSentence(e.docs)
3978
+ }))
3979
+ }));
3208
3980
  return;
3209
3981
  }
3210
3982
  printHeading(`${pallet.name} Events`);
@@ -3224,6 +3996,17 @@ async function handleEvents2(target, opts) {
3224
3996
  const names = pallet.events.map((e) => e.name);
3225
3997
  throw new Error(suggestMessage(`event in ${pallet.name}`, itemName, names));
3226
3998
  }
3999
+ if (isJsonOutput(opts)) {
4000
+ console.log(formatJson({
4001
+ chain: chainName,
4002
+ pallet: pallet.name,
4003
+ item: eventItem.name,
4004
+ category: "event",
4005
+ fields: describeEventFields(meta, pallet.name, eventItem.name),
4006
+ docs: eventItem.docs
4007
+ }));
4008
+ return;
4009
+ }
3227
4010
  printHeading(`${pallet.name}.${eventItem.name} (Event)`);
3228
4011
  const fields = describeEventFields(meta, pallet.name, eventItem.name);
3229
4012
  console.log(` ${BOLD}Fields:${RESET} ${fields}`);
@@ -3240,6 +4023,13 @@ async function handleErrors2(target, opts) {
3240
4023
  const meta2 = await loadMeta2(chainName2, chainConfig2, opts.rpc);
3241
4024
  const pallets = listPallets(meta2);
3242
4025
  const withErrors = pallets.filter((p) => p.errors.length > 0);
4026
+ if (isJsonOutput(opts)) {
4027
+ console.log(formatJson({
4028
+ chain: chainName2,
4029
+ pallets: withErrors.map((p) => ({ name: p.name, errors: p.errors.length }))
4030
+ }));
4031
+ return;
4032
+ }
3243
4033
  printHeading(`Pallets with errors on ${chainName2} (${withErrors.length})`);
3244
4034
  for (const p of withErrors) {
3245
4035
  printItem(p.name, `${p.errors.length} errors`);
@@ -3256,7 +4046,19 @@ async function handleErrors2(target, opts) {
3256
4046
  const pallet = resolvePallet2(meta, palletName);
3257
4047
  if (!itemName) {
3258
4048
  if (pallet.errors.length === 0) {
3259
- console.log(`No errors in ${pallet.name}.`);
4049
+ if (isJsonOutput(opts)) {
4050
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, errors: [] }));
4051
+ } else {
4052
+ console.log(`No errors in ${pallet.name}.`);
4053
+ }
4054
+ return;
4055
+ }
4056
+ if (isJsonOutput(opts)) {
4057
+ console.log(formatJson({
4058
+ chain: chainName,
4059
+ pallet: pallet.name,
4060
+ errors: pallet.errors.map((e) => ({ name: e.name, docs: firstSentence(e.docs) }))
4061
+ }));
3260
4062
  return;
3261
4063
  }
3262
4064
  printHeading(`${pallet.name} Errors`);
@@ -3275,6 +4077,16 @@ async function handleErrors2(target, opts) {
3275
4077
  const names = pallet.errors.map((e) => e.name);
3276
4078
  throw new Error(suggestMessage(`error in ${pallet.name}`, itemName, names));
3277
4079
  }
4080
+ if (isJsonOutput(opts)) {
4081
+ console.log(formatJson({
4082
+ chain: chainName,
4083
+ pallet: pallet.name,
4084
+ item: errorItem.name,
4085
+ category: "error",
4086
+ docs: errorItem.docs
4087
+ }));
4088
+ return;
4089
+ }
3278
4090
  printHeading(`${pallet.name}.${errorItem.name} (Error)`);
3279
4091
  if (errorItem.docs.length) {
3280
4092
  printDocs(errorItem.docs);
@@ -3288,6 +4100,13 @@ async function handleStorage2(target, opts) {
3288
4100
  const meta2 = await loadMeta2(chainName2, chainConfig2, opts.rpc);
3289
4101
  const pallets = listPallets(meta2);
3290
4102
  const withStorage = pallets.filter((p) => p.storage.length > 0);
4103
+ if (isJsonOutput(opts)) {
4104
+ console.log(formatJson({
4105
+ chain: chainName2,
4106
+ pallets: withStorage.map((p) => ({ name: p.name, storage: p.storage.length }))
4107
+ }));
4108
+ return;
4109
+ }
3291
4110
  printHeading(`Pallets with storage on ${chainName2} (${withStorage.length})`);
3292
4111
  for (const p of withStorage) {
3293
4112
  printItem(p.name, `${p.storage.length} storage`);
@@ -3304,7 +4123,23 @@ async function handleStorage2(target, opts) {
3304
4123
  const pallet = resolvePallet2(meta, palletName);
3305
4124
  if (!itemName) {
3306
4125
  if (pallet.storage.length === 0) {
3307
- console.log(`No storage items in ${pallet.name}.`);
4126
+ if (isJsonOutput(opts)) {
4127
+ console.log(formatJson({ chain: chainName, pallet: pallet.name, storage: [] }));
4128
+ } else {
4129
+ console.log(`No storage items in ${pallet.name}.`);
4130
+ }
4131
+ return;
4132
+ }
4133
+ if (isJsonOutput(opts)) {
4134
+ console.log(formatJson({
4135
+ chain: chainName,
4136
+ pallet: pallet.name,
4137
+ storage: pallet.storage.map((s) => {
4138
+ const valueType = describeType(meta.lookup, s.valueTypeId);
4139
+ const keyType = s.keyTypeId != null ? describeType(meta.lookup, s.keyTypeId) : undefined;
4140
+ return { name: s.name, type: s.type, valueType, keyType, docs: firstSentence(s.docs) };
4141
+ })
4142
+ }));
3308
4143
  return;
3309
4144
  }
3310
4145
  printHeading(`${pallet.name} Storage`);
@@ -3331,6 +4166,21 @@ async function handleStorage2(target, opts) {
3331
4166
  const names = pallet.storage.map((s) => s.name);
3332
4167
  throw new Error(suggestMessage(`storage item in ${pallet.name}`, itemName, names));
3333
4168
  }
4169
+ if (isJsonOutput(opts)) {
4170
+ const valueType = describeType(meta.lookup, storageItem.valueTypeId);
4171
+ const keyType = storageItem.keyTypeId != null ? describeType(meta.lookup, storageItem.keyTypeId) : undefined;
4172
+ console.log(formatJson({
4173
+ chain: chainName,
4174
+ pallet: pallet.name,
4175
+ item: storageItem.name,
4176
+ category: "storage",
4177
+ type: storageItem.type,
4178
+ valueType,
4179
+ keyType,
4180
+ docs: storageItem.docs
4181
+ }));
4182
+ return;
4183
+ }
3334
4184
  printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
3335
4185
  console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
3336
4186
  console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
@@ -3521,7 +4371,7 @@ async function showItemHelp2(category, target, opts) {
3521
4371
  init_hash();
3522
4372
  init_output();
3523
4373
  init_errors();
3524
- import { readFile as readFile3 } from "node:fs/promises";
4374
+ import { readFile as readFile5 } from "node:fs/promises";
3525
4375
  async function resolveInput(data, opts) {
3526
4376
  const sources = [data !== undefined, !!opts.file, !!opts.stdin].filter(Boolean).length;
3527
4377
  if (sources > 1) {
@@ -3531,7 +4381,7 @@ async function resolveInput(data, opts) {
3531
4381
  throw new CliError("No input provided. Pass data as argument, or use --file or --stdin");
3532
4382
  }
3533
4383
  if (opts.file) {
3534
- const buf = await readFile3(opts.file);
4384
+ const buf = await readFile5(opts.file);
3535
4385
  return new Uint8Array(buf);
3536
4386
  }
3537
4387
  if (opts.stdin) {
@@ -3574,8 +4424,7 @@ function registerHashCommand(cli) {
3574
4424
  const input = await resolveInput(data, opts);
3575
4425
  const hash = computeHash(algorithm, input);
3576
4426
  const hexHash = toHex2(hash);
3577
- const format = opts.output ?? "pretty";
3578
- if (format === "json") {
4427
+ if (isJsonOutput(opts)) {
3579
4428
  printResult({
3580
4429
  algorithm,
3581
4430
  input: data ?? (opts.file ? `file:${opts.file}` : "stdin"),
@@ -3658,19 +4507,46 @@ function registerInspectCommand(cli) {
3658
4507
  }
3659
4508
  const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
3660
4509
  let meta;
3661
- try {
3662
- meta = await getOrFetchMetadata(chainName);
3663
- } catch {
3664
- console.error(`Fetching metadata from ${chainName}...`);
4510
+ if (opts.rpc) {
4511
+ process.stderr.write(`Fetching metadata from ${chainName}...
4512
+ `);
3665
4513
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
3666
4514
  try {
3667
- meta = await getOrFetchMetadata(chainName, clientHandle);
4515
+ const raw = await fetchMetadataFromChain(clientHandle, chainName);
4516
+ meta = parseMetadata(raw);
3668
4517
  } finally {
3669
4518
  clientHandle.destroy();
3670
4519
  }
4520
+ } else {
4521
+ try {
4522
+ meta = await getOrFetchMetadata(chainName);
4523
+ } catch {
4524
+ process.stderr.write(`Fetching metadata from ${chainName}...
4525
+ `);
4526
+ const clientHandle = await createChainClient(chainName, chainConfig);
4527
+ try {
4528
+ meta = await getOrFetchMetadata(chainName, clientHandle);
4529
+ } finally {
4530
+ clientHandle.destroy();
4531
+ }
4532
+ }
3671
4533
  }
3672
4534
  if (!target) {
3673
4535
  const pallets = listPallets(meta);
4536
+ if (isJsonOutput(opts)) {
4537
+ console.log(formatJson({
4538
+ chain: chainName,
4539
+ pallets: pallets.map((p) => ({
4540
+ name: p.name,
4541
+ storage: p.storage.length,
4542
+ constants: p.constants.length,
4543
+ calls: p.calls.length,
4544
+ events: p.events.length,
4545
+ errors: p.errors.length
4546
+ }))
4547
+ }));
4548
+ return;
4549
+ }
3674
4550
  printHeading(`Pallets on ${chainName} (${pallets.length})`);
3675
4551
  for (const p of pallets) {
3676
4552
  const counts = [];
@@ -3695,6 +4571,44 @@ function registerInspectCommand(cli) {
3695
4571
  if (!pallet2) {
3696
4572
  throw new Error(suggestMessage("pallet", palletName, palletNames2));
3697
4573
  }
4574
+ if (isJsonOutput(opts)) {
4575
+ console.log(formatJson({
4576
+ chain: chainName,
4577
+ pallet: pallet2.name,
4578
+ docs: pallet2.docs,
4579
+ storage: pallet2.storage.map((s) => {
4580
+ const valueType = describeType(meta.lookup, s.valueTypeId);
4581
+ const keyType = s.keyTypeId != null ? describeType(meta.lookup, s.keyTypeId) : undefined;
4582
+ return {
4583
+ name: s.name,
4584
+ type: s.type,
4585
+ valueType,
4586
+ keyType,
4587
+ docs: firstSentence(s.docs)
4588
+ };
4589
+ }),
4590
+ constants: pallet2.constants.map((c) => ({
4591
+ name: c.name,
4592
+ type: describeType(meta.lookup, c.typeId),
4593
+ docs: firstSentence(c.docs)
4594
+ })),
4595
+ calls: pallet2.calls.map((c) => ({
4596
+ name: c.name,
4597
+ args: describeCallArgs(meta, pallet2.name, c.name),
4598
+ docs: firstSentence(c.docs)
4599
+ })),
4600
+ events: pallet2.events.map((e) => ({
4601
+ name: e.name,
4602
+ fields: describeEventFields(meta, pallet2.name, e.name),
4603
+ docs: firstSentence(e.docs)
4604
+ })),
4605
+ errors: pallet2.errors.map((e) => ({
4606
+ name: e.name,
4607
+ docs: firstSentence(e.docs)
4608
+ }))
4609
+ }));
4610
+ return;
4611
+ }
3698
4612
  printHeading(`${pallet2.name} Pallet`);
3699
4613
  if (pallet2.docs.length) {
3700
4614
  printDocs(pallet2.docs);
@@ -3773,6 +4687,79 @@ function registerInspectCommand(cli) {
3773
4687
  if (!pallet) {
3774
4688
  throw new Error(suggestMessage("pallet", palletName, palletNames));
3775
4689
  }
4690
+ if (isJsonOutput(opts)) {
4691
+ const si = pallet.storage.find((s) => s.name.toLowerCase() === itemName.toLowerCase());
4692
+ if (si) {
4693
+ const valueType = describeType(meta.lookup, si.valueTypeId);
4694
+ const keyType = si.keyTypeId != null ? describeType(meta.lookup, si.keyTypeId) : undefined;
4695
+ console.log(formatJson({
4696
+ chain: chainName,
4697
+ pallet: pallet.name,
4698
+ item: si.name,
4699
+ category: "storage",
4700
+ type: si.type,
4701
+ valueType,
4702
+ keyType,
4703
+ docs: si.docs
4704
+ }));
4705
+ return;
4706
+ }
4707
+ const ci = pallet.constants.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
4708
+ if (ci) {
4709
+ console.log(formatJson({
4710
+ chain: chainName,
4711
+ pallet: pallet.name,
4712
+ item: ci.name,
4713
+ category: "constant",
4714
+ type: describeType(meta.lookup, ci.typeId),
4715
+ docs: ci.docs
4716
+ }));
4717
+ return;
4718
+ }
4719
+ const ca = pallet.calls.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
4720
+ if (ca) {
4721
+ console.log(formatJson({
4722
+ chain: chainName,
4723
+ pallet: pallet.name,
4724
+ item: ca.name,
4725
+ category: "call",
4726
+ args: describeCallArgs(meta, pallet.name, ca.name),
4727
+ docs: ca.docs
4728
+ }));
4729
+ return;
4730
+ }
4731
+ const ev = pallet.events.find((e) => e.name.toLowerCase() === itemName.toLowerCase());
4732
+ if (ev) {
4733
+ console.log(formatJson({
4734
+ chain: chainName,
4735
+ pallet: pallet.name,
4736
+ item: ev.name,
4737
+ category: "event",
4738
+ fields: describeEventFields(meta, pallet.name, ev.name),
4739
+ docs: ev.docs
4740
+ }));
4741
+ return;
4742
+ }
4743
+ const er = pallet.errors.find((e) => e.name.toLowerCase() === itemName.toLowerCase());
4744
+ if (er) {
4745
+ console.log(formatJson({
4746
+ chain: chainName,
4747
+ pallet: pallet.name,
4748
+ item: er.name,
4749
+ category: "error",
4750
+ docs: er.docs
4751
+ }));
4752
+ return;
4753
+ }
4754
+ const allItems2 = [
4755
+ ...pallet.storage.map((s) => s.name),
4756
+ ...pallet.constants.map((c) => c.name),
4757
+ ...pallet.calls.map((c) => c.name),
4758
+ ...pallet.events.map((e) => e.name),
4759
+ ...pallet.errors.map((e) => e.name)
4760
+ ];
4761
+ throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems2));
4762
+ }
3776
4763
  const storageItem = pallet.storage.find((s) => s.name.toLowerCase() === itemName.toLowerCase());
3777
4764
  if (storageItem) {
3778
4765
  printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
@@ -3906,8 +4893,7 @@ function registerParachainCommand(cli) {
3906
4893
  throw new CliError(`Invalid prefix "${opts.prefix}". Must be a non-negative integer.`);
3907
4894
  }
3908
4895
  const types = opts.type ? [validateType(opts.type)] : SOVEREIGN_ACCOUNT_TYPES;
3909
- const format = opts.output ?? "pretty";
3910
- if (format === "json") {
4896
+ if (isJsonOutput(opts)) {
3911
4897
  const result = { paraId, prefix };
3912
4898
  for (const type of types) {
3913
4899
  const accountId = deriveSovereignAccount(paraId, type);
@@ -3948,6 +4934,13 @@ async function handleQuery(target, keys, opts) {
3948
4934
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
3949
4935
  const pallets = listPallets(meta);
3950
4936
  const withStorage = pallets.filter((p) => p.storage.length > 0);
4937
+ if (isJsonOutput(opts)) {
4938
+ console.log(formatJson({
4939
+ chain: chainName2,
4940
+ pallets: withStorage.map((p) => ({ name: p.name, storage: p.storage.length }))
4941
+ }));
4942
+ return;
4943
+ }
3951
4944
  printHeading(`Pallets with storage on ${chainName2} (${withStorage.length})`);
3952
4945
  for (const p of withStorage) {
3953
4946
  printItem(p.name, `${p.storage.length} storage items`);
@@ -3962,7 +4955,23 @@ async function handleQuery(target, keys, opts) {
3962
4955
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
3963
4956
  const pallet2 = resolvePallet(meta, palletName(target));
3964
4957
  if (pallet2.storage.length === 0) {
3965
- console.log(`No storage items in ${pallet2.name}.`);
4958
+ if (isJsonOutput(opts)) {
4959
+ console.log(formatJson({ chain: chainName2, pallet: pallet2.name, storage: [] }));
4960
+ } else {
4961
+ console.log(`No storage items in ${pallet2.name}.`);
4962
+ }
4963
+ return;
4964
+ }
4965
+ if (isJsonOutput(opts)) {
4966
+ console.log(formatJson({
4967
+ chain: chainName2,
4968
+ pallet: pallet2.name,
4969
+ storage: pallet2.storage.map((s) => {
4970
+ const valueType = describeType(meta.lookup, s.valueTypeId);
4971
+ const keyType = s.keyTypeId != null ? describeType(meta.lookup, s.keyTypeId) : undefined;
4972
+ return { name: s.name, type: s.type, valueType, keyType, docs: firstSentence(s.docs) };
4973
+ })
4974
+ }));
3966
4975
  return;
3967
4976
  }
3968
4977
  printHeading(`${pallet2.name} Storage`);
@@ -4007,7 +5016,7 @@ async function handleQuery(target, keys, opts) {
4007
5016
  typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
4008
5017
  ];
4009
5018
  const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, effectiveKeys);
4010
- const format = opts.output ?? "pretty";
5019
+ const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
4011
5020
  const expectedLen = storageItem.type === "map" && storageItem.keyTypeId != null ? meta.builder.buildStorage(palletInfo.name, storageItem.name).len : 0;
4012
5021
  if (storageItem.type === "map" && parsedKeys.length < expectedLen) {
4013
5022
  if (parsedKeys.length === 0 && !opts.dump) {
@@ -4074,7 +5083,7 @@ init_accounts();
4074
5083
  init_hash();
4075
5084
  init_output();
4076
5085
  init_errors();
4077
- import { readFile as readFile4 } from "node:fs/promises";
5086
+ import { readFile as readFile6 } from "node:fs/promises";
4078
5087
  var SUPPORTED_TYPES = ["sr25519"];
4079
5088
  function isSupportedType(type) {
4080
5089
  return SUPPORTED_TYPES.includes(type.toLowerCase());
@@ -4094,7 +5103,7 @@ async function resolveInput2(data, opts) {
4094
5103
  throw new CliError("No input provided. Pass data as argument, or use --file or --stdin");
4095
5104
  }
4096
5105
  if (opts.file) {
4097
- const buf = await readFile4(opts.file);
5106
+ const buf = await readFile6(opts.file);
4098
5107
  return new Uint8Array(buf);
4099
5108
  }
4100
5109
  if (opts.stdin) {
@@ -4152,8 +5161,7 @@ function registerSignCommand(cli) {
4152
5161
  signature: hexSignature,
4153
5162
  enum: enumValue
4154
5163
  };
4155
- const format = opts.output ?? "pretty";
4156
- if (format === "json") {
5164
+ if (isJsonOutput(opts)) {
4157
5165
  printResult(result, "json");
4158
5166
  } else {
4159
5167
  console.log(` ${BOLD}Type:${RESET} ${result.type}`);
@@ -4172,8 +5180,10 @@ init_client();
4172
5180
  init_metadata();
4173
5181
  init_output();
4174
5182
  init_resolve_address();
5183
+ init_binary_display();
4175
5184
  init_errors();
4176
5185
  init_focused_inspect();
5186
+ import { compact as scaleCompact2 } from "@polkadot-api/substrate-bindings";
4177
5187
  import { getViewBuilder as getViewBuilder2 } from "@polkadot-api/view-builder";
4178
5188
  import { Binary as Binary3 } from "polkadot-api";
4179
5189
  import { stringify as stringifyYaml2 } from "yaml";
@@ -4226,14 +5236,31 @@ function parseMortalityOption(raw) {
4226
5236
  }
4227
5237
  return { mortal: true, period: n };
4228
5238
  }
5239
+ function parseAssetOption(raw) {
5240
+ if (raw === undefined)
5241
+ return;
5242
+ try {
5243
+ const parsed = JSON.parse(raw);
5244
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
5245
+ throw new Error("must be a JSON object");
5246
+ }
5247
+ return parsed;
5248
+ } catch (err) {
5249
+ throw new CliError(`Invalid --asset value: ${err.message}
5250
+ ` + `Expected an XCM location, e.g. '{"parents":0,"interior":{"type":"X2","value":[{"type":"PalletInstance","value":50},{"type":"GeneralIndex","value":"3"}]}}'`);
5251
+ }
5252
+ }
4229
5253
  function parseAtOption(raw) {
4230
5254
  if (raw === undefined)
4231
5255
  return;
4232
- if (raw === "best" || raw === "finalized")
4233
- return raw;
5256
+ if (raw === "finalized")
5257
+ return;
5258
+ if (raw === "best") {
5259
+ throw new CliError('"best" is no longer supported for --at in papi v2. Omit --at for finalized, or pass a specific block hash.');
5260
+ }
4234
5261
  if (/^0x[0-9a-fA-F]{64}$/.test(raw))
4235
5262
  return raw;
4236
- throw new CliError(`Invalid --at value "${raw}". Use "best", "finalized", or a 0x-prefixed 32-byte block hash.`);
5263
+ throw new CliError(`Invalid --at value "${raw}". Use a 0x-prefixed 32-byte block hash, or omit for finalized.`);
4237
5264
  }
4238
5265
  async function handleTx(target, args, opts) {
4239
5266
  if (!target) {
@@ -4242,6 +5269,13 @@ async function handleTx(target, args, opts) {
4242
5269
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
4243
5270
  const pallets = listPallets(meta);
4244
5271
  const withCalls = pallets.filter((p) => p.calls.length > 0);
5272
+ if (isJsonOutput(opts)) {
5273
+ console.log(formatJson({
5274
+ chain: chainName2,
5275
+ pallets: withCalls.map((p) => ({ name: p.name, calls: p.calls.length }))
5276
+ }));
5277
+ return;
5278
+ }
4245
5279
  printHeading(`Pallets with calls on ${chainName2} (${withCalls.length})`);
4246
5280
  for (const p of withCalls) {
4247
5281
  printItem(p.name, `${p.calls.length} calls`);
@@ -4256,7 +5290,23 @@ async function handleTx(target, args, opts) {
4256
5290
  const meta = await loadMeta(chainName2, chainConfig2, opts.rpc);
4257
5291
  const pallet2 = resolvePallet(meta, target);
4258
5292
  if (pallet2.calls.length === 0) {
4259
- console.log(`No calls in ${pallet2.name}.`);
5293
+ if (isJsonOutput(opts)) {
5294
+ console.log(formatJson({ chain: chainName2, pallet: pallet2.name, calls: [] }));
5295
+ } else {
5296
+ console.log(`No calls in ${pallet2.name}.`);
5297
+ }
5298
+ return;
5299
+ }
5300
+ if (isJsonOutput(opts)) {
5301
+ console.log(formatJson({
5302
+ chain: chainName2,
5303
+ pallet: pallet2.name,
5304
+ calls: pallet2.calls.map((c) => ({
5305
+ name: c.name,
5306
+ args: describeCallArgs(meta, pallet2.name, c.name),
5307
+ docs: firstSentence(c.docs)
5308
+ }))
5309
+ }));
4260
5310
  return;
4261
5311
  }
4262
5312
  printHeading(`${pallet2.name} Calls`);
@@ -4271,9 +5321,9 @@ async function handleTx(target, args, opts) {
4271
5321
  console.log();
4272
5322
  return;
4273
5323
  }
4274
- if (!opts.from && !opts.encode && !opts.yaml && !opts.json) {
5324
+ if (!opts.from && !opts.unsigned && !opts.encode && !opts.toYaml && !opts.toJson) {
4275
5325
  if (isRawCall) {
4276
- throw new Error("--from is required (or use --encode to output hex without signing)");
5326
+ throw new Error("--from is required (or use --unsigned for bare tx, --encode for hex without signing)");
4277
5327
  }
4278
5328
  await showItemHelp("tx", target, opts);
4279
5329
  return;
@@ -4284,14 +5334,26 @@ async function handleTx(target, args, opts) {
4284
5334
  if (opts.encode && isRawCall) {
4285
5335
  throw new Error("--encode cannot be used with raw call hex (already encoded)");
4286
5336
  }
4287
- if ((opts.yaml || opts.json) && opts.encode) {
4288
- throw new Error("--yaml/--json and --encode are mutually exclusive");
5337
+ if ((opts.toYaml || opts.toJson) && opts.encode) {
5338
+ throw new Error("--to-yaml/--to-json and --encode are mutually exclusive");
5339
+ }
5340
+ if ((opts.toYaml || opts.toJson) && opts.dryRun) {
5341
+ throw new Error("--to-yaml/--to-json and --dry-run are mutually exclusive");
4289
5342
  }
4290
- if ((opts.yaml || opts.json) && opts.dryRun) {
4291
- throw new Error("--yaml/--json and --dry-run are mutually exclusive");
5343
+ if (opts.toYaml && opts.toJson) {
5344
+ throw new Error("--to-yaml and --to-json are mutually exclusive");
4292
5345
  }
4293
- if (opts.yaml && opts.json) {
4294
- throw new Error("--yaml and --json are mutually exclusive");
5346
+ if (opts.unsigned && opts.from) {
5347
+ throw new Error("--unsigned and --from are mutually exclusive");
5348
+ }
5349
+ if (opts.unsigned && opts.nonce) {
5350
+ throw new Error("--unsigned does not support --nonce");
5351
+ }
5352
+ if (opts.unsigned && opts.tip) {
5353
+ throw new Error("--unsigned does not support --tip");
5354
+ }
5355
+ if (opts.unsigned && opts.mortality) {
5356
+ throw new Error("--unsigned does not support --mortality");
4295
5357
  }
4296
5358
  const config = await loadConfig();
4297
5359
  const effectiveChain = opts.chain;
@@ -4303,10 +5365,10 @@ async function handleTx(target, args, opts) {
4303
5365
  callName = target.slice(dotIdx + 1);
4304
5366
  }
4305
5367
  const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
4306
- const decodeOnly = opts.encode || opts.yaml || opts.json;
4307
- const signer = decodeOnly ? undefined : await resolveAccountSigner(opts.from);
5368
+ const decodeOnly = opts.encode || opts.toYaml || opts.toJson;
5369
+ const signer = decodeOnly || opts.unsigned ? undefined : await resolveAccountSigner(opts.from);
4308
5370
  let clientHandle;
4309
- if (!decodeOnly) {
5371
+ if (!decodeOnly || opts.unsigned) {
4310
5372
  clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
4311
5373
  }
4312
5374
  try {
@@ -4325,11 +5387,18 @@ async function handleTx(target, args, opts) {
4325
5387
  let txOptions;
4326
5388
  const nonce = parseNonceOption(opts.nonce);
4327
5389
  const tip = parseTipOption(opts.tip);
5390
+ const asset = parseAssetOption(opts.asset);
4328
5391
  const mortality = parseMortalityOption(opts.mortality);
4329
5392
  const at = parseAtOption(opts.at);
4330
- if (!decodeOnly) {
5393
+ if (!decodeOnly || opts.unsigned) {
4331
5394
  const userExtOverrides = parseExtOption(opts.ext);
4332
- const customSignedExtensions = buildCustomSignedExtensions(meta, userExtOverrides);
5395
+ const skipBuiltins = asset !== undefined ? new Set([...PAPI_BUILTIN_EXTENSIONS2].filter((e) => e !== "ChargeAssetTxPayment")) : PAPI_BUILTIN_EXTENSIONS2;
5396
+ if (asset !== undefined) {
5397
+ userExtOverrides.ChargeAssetTxPayment ??= {
5398
+ value: { tip: tip ?? 0n, asset_id: asset }
5399
+ };
5400
+ }
5401
+ const customSignedExtensions = buildCustomSignedExtensions(meta, userExtOverrides, skipBuiltins);
4333
5402
  const built = {};
4334
5403
  if (Object.keys(customSignedExtensions).length > 0)
4335
5404
  built.customSignedExtensions = customSignedExtensions;
@@ -4352,9 +5421,9 @@ async function handleTx(target, args, opts) {
4352
5421
  ` + "Usage: dot tx 0x<call_hex> --from <account>");
4353
5422
  }
4354
5423
  callHex = target;
4355
- if (opts.yaml || opts.json) {
5424
+ if (opts.toYaml || opts.toJson) {
4356
5425
  const fileObj = decodeCallToFileFormat(meta, callHex, chainName);
4357
- outputFileFormat(fileObj, !!opts.yaml);
5426
+ outputFileFormat(fileObj, !!opts.toYaml);
4358
5427
  return;
4359
5428
  }
4360
5429
  const callBinary = Binary3.fromHex(target);
@@ -4372,26 +5441,75 @@ async function handleTx(target, args, opts) {
4372
5441
  }
4373
5442
  const effectiveArgs = opts.parsedArgs !== undefined ? fileArgsToStrings(opts.parsedArgs) : args;
4374
5443
  const callData = await parseCallArgs(meta, palletInfo.name, callInfo.name, effectiveArgs);
4375
- if (opts.encode || opts.yaml || opts.json) {
5444
+ if (opts.encode && !opts.unsigned || opts.toYaml || opts.toJson) {
4376
5445
  const { codec, location } = meta.builder.buildCall(palletInfo.name, callInfo.name);
4377
5446
  const encodedArgs = codec.enc(callData);
4378
5447
  const fullCall = new Uint8Array([location[0], location[1], ...encodedArgs]);
4379
- const hex = Binary3.fromBytes(fullCall).asHex();
5448
+ const hex = Binary3.toHex(fullCall);
4380
5449
  if (opts.encode) {
4381
- console.log(hex);
5450
+ if (isJsonOutput(opts)) {
5451
+ console.log(formatJson({ callHex: hex }));
5452
+ } else {
5453
+ console.log(hex);
5454
+ }
4382
5455
  return;
4383
5456
  }
4384
5457
  const fileObj = decodeCallToFileFormat(meta, hex, chainName);
4385
- outputFileFormat(fileObj, !!opts.yaml);
5458
+ outputFileFormat(fileObj, !!opts.toYaml);
4386
5459
  return;
4387
5460
  }
4388
5461
  tx = unsafeApi.tx[palletInfo.name][callInfo.name](callData);
4389
5462
  const encodedCall = await tx.getEncodedData();
4390
- callHex = encodedCall.asHex();
5463
+ callHex = Binary3.toHex(encodedCall);
4391
5464
  }
4392
5465
  const decodedStr = decodeCall(meta, callHex);
5466
+ if (opts.dryRun && opts.unsigned) {
5467
+ if (isJsonOutput(opts)) {
5468
+ console.log(formatJson({
5469
+ chain: chainName,
5470
+ unsigned: true,
5471
+ callHex,
5472
+ decoded: decodedStr,
5473
+ estimatedFees: null
5474
+ }));
5475
+ return;
5476
+ }
5477
+ console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
5478
+ console.log(` ${BOLD}Type:${RESET} unsigned (bare)`);
5479
+ console.log(` ${BOLD}Call:${RESET} ${callHex}`);
5480
+ console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
5481
+ console.log(` ${BOLD}Fees:${RESET} ${DIM}N/A (unsigned transaction)${RESET}`);
5482
+ return;
5483
+ }
4393
5484
  if (opts.dryRun) {
4394
5485
  const signerAddress = toSs58(signer.publicKey);
5486
+ let estimatedFees;
5487
+ try {
5488
+ estimatedFees = String(await tx.getEstimatedFees(signer?.publicKey, txOptions));
5489
+ } catch {
5490
+ estimatedFees = undefined;
5491
+ }
5492
+ if (isJsonOutput(opts)) {
5493
+ const result2 = {
5494
+ chain: chainName,
5495
+ from: { name: opts.from, address: signerAddress },
5496
+ callHex,
5497
+ decoded: decodedStr,
5498
+ estimatedFees
5499
+ };
5500
+ if (nonce !== undefined)
5501
+ result2.nonce = nonce;
5502
+ if (tip !== undefined)
5503
+ result2.tip = String(tip);
5504
+ if (asset !== undefined)
5505
+ result2.asset = asset;
5506
+ if (mortality !== undefined)
5507
+ result2.mortality = mortality.mortal ? `mortal (period ${mortality.period})` : "immortal";
5508
+ if (at !== undefined)
5509
+ result2.at = at;
5510
+ console.log(formatJson(result2));
5511
+ return;
5512
+ }
4395
5513
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
4396
5514
  console.log(` ${BOLD}From:${RESET} ${opts.from} (${signerAddress})`);
4397
5515
  console.log(` ${BOLD}Call:${RESET} ${callHex}`);
@@ -4400,20 +5518,147 @@ async function handleTx(target, args, opts) {
4400
5518
  console.log(` ${BOLD}Nonce:${RESET} ${nonce}`);
4401
5519
  if (tip !== undefined)
4402
5520
  console.log(` ${BOLD}Tip:${RESET} ${tip}`);
5521
+ if (asset !== undefined)
5522
+ console.log(` ${BOLD}Asset:${RESET} ${JSON.stringify(asset)}`);
4403
5523
  if (mortality !== undefined)
4404
5524
  console.log(` ${BOLD}Mortality:${RESET} ${mortality.mortal ? `mortal (period ${mortality.period})` : "immortal"}`);
4405
5525
  if (at !== undefined)
4406
5526
  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) {
5527
+ if (estimatedFees !== undefined) {
5528
+ console.log(` ${BOLD}Estimated fees:${RESET} ${estimatedFees}`);
5529
+ } else {
4411
5530
  console.log(` ${BOLD}Estimated fees:${RESET} ${YELLOW}unable to estimate${RESET}`);
4412
- console.log(` ${DIM}${err.message ?? err}${RESET}`);
4413
5531
  }
4414
5532
  return;
4415
5533
  }
4416
5534
  const waitLevel = parseWaitLevel(opts.wait);
5535
+ if (opts.unsigned) {
5536
+ const callDataBytes = await tx.getEncodedData();
5537
+ const userExtOverrides = parseExtOption(opts.ext);
5538
+ const generalTx = buildGeneralTx(meta, callDataBytes, userExtOverrides);
5539
+ if (opts.encode) {
5540
+ const hex = Binary3.toHex(generalTx);
5541
+ if (isJsonOutput(opts)) {
5542
+ console.log(formatJson({ generalTxHex: hex }));
5543
+ } else {
5544
+ console.log(hex);
5545
+ }
5546
+ return;
5547
+ }
5548
+ const observable = clientHandle.client.submitAndWatch(generalTx, at);
5549
+ if (isJsonOutput(opts)) {
5550
+ const result3 = await watchTransactionJson(observable, waitLevel, { unsigned: true });
5551
+ const rpcUrl3 = primaryRpc(opts.rpc ?? chainConfig.rpc);
5552
+ if (result3.type === "broadcasted") {
5553
+ printJsonLine({ event: "broadcasted", txHash: result3.txHash });
5554
+ return;
5555
+ }
5556
+ const blockHash = result3.block.hash;
5557
+ const explorer = {};
5558
+ if (rpcUrl3) {
5559
+ explorer.polkadotjs = pjsAppsLink(rpcUrl3, blockHash);
5560
+ explorer.papi = papiLink(rpcUrl3, blockHash);
5561
+ }
5562
+ printJsonLine({
5563
+ event: result3.type === "finalized" ? "finalized" : "bestBlock",
5564
+ unsigned: true,
5565
+ blockNumber: result3.block.number,
5566
+ blockHash,
5567
+ txHash: result3.txHash,
5568
+ ok: result3.ok,
5569
+ events: result3.events?.map((e) => ({
5570
+ pallet: e.type,
5571
+ name: e.value?.type,
5572
+ fields: e.value?.value
5573
+ })),
5574
+ dispatchError: result3.ok ? null : formatDispatchError(result3.dispatchError),
5575
+ explorer
5576
+ });
5577
+ if (!result3.ok) {
5578
+ throw new CliError(`Transaction dispatch error: ${formatDispatchError(result3.dispatchError)}`);
5579
+ }
5580
+ return;
5581
+ }
5582
+ const result2 = await watchTransaction(observable, waitLevel, { unsigned: true });
5583
+ console.log();
5584
+ console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
5585
+ console.log(` ${BOLD}Type:${RESET} unsigned (bare)`);
5586
+ console.log(` ${BOLD}Call:${RESET} ${callHex}`);
5587
+ console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
5588
+ console.log(` ${BOLD}Tx:${RESET} ${result2.txHash}`);
5589
+ if (result2.type === "broadcasted") {
5590
+ console.log(` ${BOLD}Status:${RESET} ${GREEN}broadcasted${RESET}`);
5591
+ console.log(` ${DIM}Note: tx was broadcast but not yet included in a block${RESET}`);
5592
+ console.log();
5593
+ return;
5594
+ }
5595
+ let dispatchErrorMsg2;
5596
+ if (result2.ok) {
5597
+ const hint = result2.type === "txBestBlocksState" ? ` ${DIM}(best block, not yet finalized)${RESET}` : "";
5598
+ console.log(` ${BOLD}Status:${RESET} ${GREEN}ok${RESET}${hint}`);
5599
+ } else {
5600
+ dispatchErrorMsg2 = formatDispatchError(result2.dispatchError);
5601
+ console.log(` ${BOLD}Status:${RESET} ${RED}dispatch error${RESET}`);
5602
+ console.log(` ${BOLD}Error:${RESET} ${dispatchErrorMsg2}`);
5603
+ }
5604
+ if (result2.events && result2.events.length > 0) {
5605
+ console.log(` ${BOLD}Events:${RESET}`);
5606
+ for (const event of result2.events) {
5607
+ const name = `${CYAN}${event.type}${RESET}.${CYAN}${event.value?.type ?? ""}${RESET}`;
5608
+ const payload = event.value?.value;
5609
+ if (payload && typeof payload === "object") {
5610
+ const fields = Object.entries(payload).map(([k, v]) => `${k}: ${formatEventValue(v)}`).join(", ");
5611
+ console.log(` ${name} { ${fields} }`);
5612
+ } else {
5613
+ console.log(` ${name}`);
5614
+ }
5615
+ }
5616
+ }
5617
+ const rpcUrl2 = primaryRpc(opts.rpc ?? chainConfig.rpc);
5618
+ if (rpcUrl2) {
5619
+ const blockHash = result2.block.hash;
5620
+ console.log(` ${BOLD}Block:${RESET} #${result2.block.number} (${blockHash})`);
5621
+ console.log(` ${DIM}${pjsAppsLink(rpcUrl2, blockHash)}${RESET}`);
5622
+ console.log(` ${DIM}${papiLink(rpcUrl2, blockHash)}${RESET}`);
5623
+ }
5624
+ console.log();
5625
+ if (!result2.ok) {
5626
+ throw new CliError(`Transaction dispatch error: ${dispatchErrorMsg2}`);
5627
+ }
5628
+ return;
5629
+ }
5630
+ if (isJsonOutput(opts)) {
5631
+ const result2 = await watchTransactionJson(tx.signSubmitAndWatch(signer, txOptions), waitLevel);
5632
+ const rpcUrl2 = primaryRpc(opts.rpc ?? chainConfig.rpc);
5633
+ if (result2.type === "broadcasted") {
5634
+ printJsonLine({ event: "broadcasted", txHash: result2.txHash });
5635
+ return;
5636
+ }
5637
+ const blockHash = result2.block.hash;
5638
+ const explorer = {};
5639
+ if (rpcUrl2) {
5640
+ explorer.polkadotjs = pjsAppsLink(rpcUrl2, blockHash);
5641
+ explorer.papi = papiLink(rpcUrl2, blockHash);
5642
+ }
5643
+ printJsonLine({
5644
+ event: result2.type === "finalized" ? "finalized" : "bestBlock",
5645
+ blockNumber: result2.block.number,
5646
+ blockHash,
5647
+ txHash: result2.txHash,
5648
+ ok: result2.ok,
5649
+ events: result2.events?.map((e) => ({
5650
+ pallet: e.type,
5651
+ name: e.value?.type,
5652
+ fields: e.value?.value
5653
+ })),
5654
+ dispatchError: result2.ok ? null : formatDispatchError(result2.dispatchError),
5655
+ explorer
5656
+ });
5657
+ if (!result2.ok) {
5658
+ throw new CliError(`Transaction dispatch error: ${formatDispatchError(result2.dispatchError)}`);
5659
+ }
5660
+ return;
5661
+ }
4417
5662
  const result = await watchTransaction(tx.signSubmitAndWatch(signer, txOptions), waitLevel);
4418
5663
  console.log();
4419
5664
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
@@ -4423,6 +5668,8 @@ async function handleTx(target, args, opts) {
4423
5668
  console.log(` ${BOLD}Nonce:${RESET} ${nonce}`);
4424
5669
  if (tip !== undefined)
4425
5670
  console.log(` ${BOLD}Tip:${RESET} ${tip}`);
5671
+ if (asset !== undefined)
5672
+ console.log(` ${BOLD}Asset:${RESET} ${JSON.stringify(asset)}`);
4426
5673
  if (mortality !== undefined)
4427
5674
  console.log(` ${BOLD}Mortality:${RESET} ${mortality.mortal ? `mortal (period ${mortality.period})` : "immortal"}`);
4428
5675
  if (at !== undefined)
@@ -4508,7 +5755,7 @@ function decodeCallFallback(meta, callHex) {
4508
5755
  if (callTypeId == null)
4509
5756
  throw new Error("No RuntimeCall type ID");
4510
5757
  const codec = meta.builder.buildDefinition(callTypeId);
4511
- const decoded = codec.dec(Binary3.fromHex(callHex).asBytes());
5758
+ const decoded = codec.dec(Binary3.fromHex(callHex));
4512
5759
  const palletName2 = decoded.type;
4513
5760
  const call = decoded.value;
4514
5761
  const callName = call.type;
@@ -4524,7 +5771,7 @@ function decodeCallToFileFormat(meta, callHex, chainName) {
4524
5771
  if (callTypeId == null)
4525
5772
  throw new Error("No RuntimeCall type ID in metadata");
4526
5773
  const codec = meta.builder.buildDefinition(callTypeId);
4527
- const decoded = codec.dec(Binary3.fromHex(callHex).asBytes());
5774
+ const decoded = codec.dec(Binary3.fromHex(callHex));
4528
5775
  const palletName2 = decoded.type;
4529
5776
  const call = decoded.value;
4530
5777
  const callName = call.type;
@@ -4541,8 +5788,8 @@ function decodeCallToFileFormat(meta, callHex, chainName) {
4541
5788
  function sanitizeForSerialization(value) {
4542
5789
  if (value === undefined || value === null)
4543
5790
  return null;
4544
- if (value instanceof Binary3)
4545
- return value.asHex();
5791
+ if (value instanceof Uint8Array)
5792
+ return Binary3.toHex(value);
4546
5793
  if (typeof value === "bigint") {
4547
5794
  if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) {
4548
5795
  return Number(value);
@@ -4574,8 +5821,8 @@ function outputFileFormat(obj, asYaml) {
4574
5821
  function formatRawDecoded(value) {
4575
5822
  if (value === undefined || value === null)
4576
5823
  return "null";
4577
- if (value instanceof Binary3)
4578
- return value.asHex();
5824
+ if (value instanceof Uint8Array)
5825
+ return Binary3.toHex(value);
4579
5826
  if (typeof value === "bigint")
4580
5827
  return value.toString();
4581
5828
  if (typeof value === "string")
@@ -4696,7 +5943,7 @@ function formatEventValue(v) {
4696
5943
  return v.toString();
4697
5944
  if (v === null || v === undefined)
4698
5945
  return "null";
4699
- if (v instanceof Binary3) {
5946
+ if (v instanceof Uint8Array) {
4700
5947
  return binaryToDisplay(v);
4701
5948
  }
4702
5949
  return JSON.stringify(v, (_k, val) => typeof val === "bigint" ? val.toString() : val);
@@ -4970,7 +6217,7 @@ async function parseTypedArg2(meta, entry, arg) {
4970
6217
  case "enum": {
4971
6218
  if (/^0x[0-9a-fA-F]+$/.test(arg) && meta.lookup.call != null && entry.id === meta.lookup.call) {
4972
6219
  const callCodec = meta.builder.buildDefinition(meta.lookup.call);
4973
- return callCodec.dec(Binary3.fromHex(arg).asBytes());
6220
+ return callCodec.dec(Binary3.fromHex(arg));
4974
6221
  }
4975
6222
  if (arg.startsWith("{")) {
4976
6223
  try {
@@ -5104,11 +6351,11 @@ function parseExtOption(ext) {
5104
6351
  }
5105
6352
  }
5106
6353
  var NO_DEFAULT2 = Symbol("no-default");
5107
- function buildCustomSignedExtensions(meta, userOverrides) {
6354
+ function buildCustomSignedExtensions(meta, userOverrides, builtins = PAPI_BUILTIN_EXTENSIONS2) {
5108
6355
  const result = {};
5109
6356
  const extensions = getSignedExtensions(meta);
5110
6357
  for (const ext of extensions) {
5111
- if (PAPI_BUILTIN_EXTENSIONS2.has(ext.identifier))
6358
+ if (builtins.has(ext.identifier))
5112
6359
  continue;
5113
6360
  if (ext.identifier in userOverrides) {
5114
6361
  result[ext.identifier] = userOverrides[ext.identifier];
@@ -5140,20 +6387,114 @@ function autoDefaultForType(entry) {
5140
6387
  }
5141
6388
  return NO_DEFAULT2;
5142
6389
  }
5143
- function watchTransaction(observable, level) {
6390
+ function unsignedDefaultForType(identifier, entry) {
6391
+ switch (identifier) {
6392
+ case "CheckMortality":
6393
+ return { type: "Immortal" };
6394
+ case "CheckNonce":
6395
+ return 0;
6396
+ case "ChargeTransactionPayment":
6397
+ return 0n;
6398
+ case "ChargeAssetTxPayment":
6399
+ return { tip: 0n, asset_id: undefined };
6400
+ }
6401
+ const auto = autoDefaultForType(entry);
6402
+ if (auto !== NO_DEFAULT2)
6403
+ return auto;
6404
+ if (entry.type === "primitive") {
6405
+ switch (entry.value) {
6406
+ case "bool":
6407
+ return false;
6408
+ case "u8":
6409
+ case "u16":
6410
+ case "u32":
6411
+ case "i8":
6412
+ case "i16":
6413
+ case "i32":
6414
+ return 0;
6415
+ case "u64":
6416
+ case "u128":
6417
+ case "u256":
6418
+ case "i64":
6419
+ case "i128":
6420
+ case "i256":
6421
+ return 0n;
6422
+ case "str":
6423
+ case "char":
6424
+ return "";
6425
+ }
6426
+ }
6427
+ if (entry.type === "compact")
6428
+ return 0;
6429
+ if (entry.type === "enum") {
6430
+ const variants = entry.value;
6431
+ if ("Immortal" in variants)
6432
+ return { type: "Immortal" };
6433
+ for (const [name, variant] of Object.entries(variants)) {
6434
+ if (variant.type === "void")
6435
+ return { type: name };
6436
+ }
6437
+ }
6438
+ return NO_DEFAULT2;
6439
+ }
6440
+ function buildGeneralTx(meta, callData, userExtOverrides) {
6441
+ const extensions = getSignedExtensions(meta);
6442
+ const extBytes = [];
6443
+ for (const ext of extensions) {
6444
+ const valueEntry = meta.lookup(ext.type);
6445
+ if (valueEntry.type === "void")
6446
+ continue;
6447
+ let value;
6448
+ if (ext.identifier in userExtOverrides) {
6449
+ const override = userExtOverrides[ext.identifier];
6450
+ value = override.value !== undefined ? override.value : override;
6451
+ } else {
6452
+ value = unsignedDefaultForType(ext.identifier, valueEntry);
6453
+ if (value === NO_DEFAULT2) {
6454
+ throw new CliError(`Cannot determine default unsigned value for extension "${ext.identifier}" ` + `(type: ${valueEntry.type}). Provide it via --ext '{"${ext.identifier}":{"value":...}}'`);
6455
+ }
6456
+ }
6457
+ const codec = meta.builder.buildDefinition(ext.type);
6458
+ extBytes.push(codec.enc(value));
6459
+ }
6460
+ const extVersion = new Uint8Array([0]);
6461
+ const versionByte = new Uint8Array([69]);
6462
+ let payloadLen = 1 + 1;
6463
+ for (const b of extBytes)
6464
+ payloadLen += b.length;
6465
+ payloadLen += callData.length;
6466
+ const lengthPrefix = scaleCompact2.enc(payloadLen);
6467
+ const total = new Uint8Array(lengthPrefix.length + payloadLen);
6468
+ let offset = 0;
6469
+ total.set(lengthPrefix, offset);
6470
+ offset += lengthPrefix.length;
6471
+ total.set(versionByte, offset);
6472
+ offset += 1;
6473
+ total.set(extVersion, offset);
6474
+ offset += 1;
6475
+ for (const b of extBytes) {
6476
+ total.set(b, offset);
6477
+ offset += b.length;
6478
+ }
6479
+ total.set(callData, offset);
6480
+ return total;
6481
+ }
6482
+ function watchTransaction(observable, level, options) {
5144
6483
  const spinner = new Spinner;
5145
6484
  return new Promise((resolve, reject) => {
5146
6485
  let settled = false;
5147
- spinner.start("Signing...");
6486
+ spinner.start(options?.unsigned ? "Submitting..." : "Signing...");
5148
6487
  const subscription = observable.subscribe({
5149
6488
  next(event) {
5150
6489
  if (settled)
5151
6490
  return;
5152
6491
  switch (event.type) {
5153
6492
  case "signed":
5154
- spinner.succeed("Signed");
5155
- console.log(` ${BOLD}Tx:${RESET} ${event.txHash}`);
5156
- spinner.start("Broadcasting...");
6493
+ if (!options?.unsigned) {
6494
+ spinner.succeed("Signed");
6495
+ console.log(` ${BOLD}Tx:${RESET} ${event.txHash}`);
6496
+ spinner.start("Broadcasting...");
6497
+ }
5157
6498
  break;
5158
6499
  case "broadcasted":
5159
6500
  if (level === "broadcast") {
@@ -5197,6 +6538,51 @@ function watchTransaction(observable, level) {
5197
6538
  });
5198
6539
  });
5199
6540
  }
6541
+ function watchTransactionJson(observable, level, options) {
6542
+ return new Promise((resolve, reject) => {
6543
+ let settled = false;
6544
+ const subscription = observable.subscribe({
6545
+ next(event) {
6546
+ if (settled)
6547
+ return;
6548
+ switch (event.type) {
6549
+ case "signed":
6550
+ if (!options?.unsigned) {
6551
+ printJsonLine({ event: "signed", txHash: event.txHash });
6552
+ }
6553
+ break;
6554
+ case "broadcasted":
6555
+ printJsonLine({ event: "broadcasted", txHash: event.txHash });
6556
+ if (level === "broadcast") {
6557
+ settled = true;
6558
+ subscription.unsubscribe();
6559
+ resolve(event);
6560
+ }
6561
+ break;
6562
+ case "txBestBlocksState":
6563
+ if (event.found) {
6564
+ printJsonLine({ event: "bestBlock", blockNumber: event.block.number, found: true });
6565
+ if (level === "best-block") {
6566
+ settled = true;
6567
+ subscription.unsubscribe();
6568
+ resolve(event);
6569
+ }
6570
+ }
6571
+ break;
6572
+ case "finalized":
6573
+ settled = true;
6574
+ resolve(event);
6575
+ break;
6576
+ }
6577
+ },
6578
+ error(err) {
6579
+ if (settled)
6580
+ return;
6581
+ reject(err);
6582
+ }
6583
+ });
6584
+ });
6585
+ }
5200
6586
 
5201
6587
  // src/commands/verifiable.ts
5202
6588
  init_accounts_store();
@@ -5253,7 +6639,7 @@ async function deriveVerifiable(account, opts) {
5253
6639
  await saveAccounts(accountsFile);
5254
6640
  }
5255
6641
  }
5256
- if (opts.output === "json") {
6642
+ if (isJsonOutput(opts)) {
5257
6643
  const result = {
5258
6644
  account,
5259
6645
  memberKey: memberKeyHex
@@ -5277,8 +6663,14 @@ async function resolveMnemonic(account) {
5277
6663
  const accountsFile = await loadAccounts();
5278
6664
  const stored = findAccount(accountsFile, account);
5279
6665
  if (!stored) {
5280
- const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)];
5281
- throw new Error(`Unknown account "${account}". Available accounts: ${available.join(", ")}`);
6666
+ const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
6667
+ const suggestions = findClosest(account, available);
6668
+ const hint = suggestions.length > 0 ? `
6669
+ Did you mean: ${suggestions.join(", ")}?` : "";
6670
+ const list = available.map((a) => `
6671
+ - ${a}`).join("");
6672
+ throw new Error(`Unknown account "${account}".${hint}
6673
+ Available accounts:${list}`);
5282
6674
  }
5283
6675
  if (isWatchOnly(stored)) {
5284
6676
  throw new Error(`Account "${account}" is watch-only (no secret). Cannot derive Bandersnatch key.`);
@@ -5291,8 +6683,9 @@ async function resolveMnemonic(account) {
5291
6683
  }
5292
6684
 
5293
6685
  // src/config/store.ts
6686
+ init_errors();
5294
6687
  init_types();
5295
- import { access as access3, mkdir as mkdir3, readFile as readFile5, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
6688
+ import { access as access3, mkdir as mkdir3, readFile as readFile7, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
5296
6689
  import { homedir as homedir2 } from "node:os";
5297
6690
  import { join as join3 } from "node:path";
5298
6691
  var DOT_DIR2 = join3(homedir2(), ".polkadot");
@@ -5312,24 +6705,30 @@ async function fileExists3(path) {
5312
6705
  async function loadConfig2() {
5313
6706
  await ensureDir3(DOT_DIR2);
5314
6707
  if (await fileExists3(CONFIG_PATH2)) {
5315
- const saved = JSON.parse(await readFile5(CONFIG_PATH2, "utf-8"));
5316
- return {
5317
- ...saved,
5318
- chains: { ...DEFAULT_CONFIG.chains, ...saved.chains }
5319
- };
6708
+ const saved = JSON.parse(await readFile7(CONFIG_PATH2, "utf-8"));
6709
+ const chains = {};
6710
+ for (const [name, defaultConfig] of Object.entries(DEFAULT_CONFIG.chains)) {
6711
+ chains[name] = saved.chains[name] ? { ...defaultConfig, ...saved.chains[name] } : defaultConfig;
6712
+ }
6713
+ for (const [name, config] of Object.entries(saved.chains)) {
6714
+ if (!(name in DEFAULT_CONFIG.chains)) {
6715
+ chains[name] = config;
6716
+ }
6717
+ }
6718
+ return { chains };
5320
6719
  }
5321
6720
  await saveConfig2(DEFAULT_CONFIG);
5322
6721
  return DEFAULT_CONFIG;
5323
6722
  }
5324
6723
  async function saveConfig2(config) {
5325
6724
  await ensureDir3(DOT_DIR2);
5326
- await writeFile3(CONFIG_PATH2, `${JSON.stringify(config, null, 2)}
6725
+ await writeFile5(CONFIG_PATH2, `${JSON.stringify(config, null, 2)}
5327
6726
  `);
5328
6727
  }
5329
6728
 
5330
6729
  // src/core/file-loader.ts
5331
6730
  init_errors();
5332
- import { access as access4, readFile as readFile6 } from "node:fs/promises";
6731
+ import { access as access4, readFile as readFile8 } from "node:fs/promises";
5333
6732
  import { parse as parseYaml } from "yaml";
5334
6733
  var CATEGORIES = ["tx", "query", "const", "apis"];
5335
6734
  var FILE_EXTENSIONS = [".json", ".yaml", ".yml"];
@@ -5394,7 +6793,7 @@ async function loadCommandFile(filePath, cliVars) {
5394
6793
  } catch {
5395
6794
  throw new CliError(`File not found: ${filePath}`);
5396
6795
  }
5397
- const rawText = await readFile6(filePath, "utf-8");
6796
+ const rawText = await readFile8(filePath, "utf-8");
5398
6797
  if (!rawText.trim()) {
5399
6798
  throw new CliError(`File is empty: ${filePath}`);
5400
6799
  }
@@ -5427,6 +6826,7 @@ async function loadCommandFile(filePath, cliVars) {
5427
6826
  }
5428
6827
  const doc = parsed;
5429
6828
  const chain = doc.chain != null ? String(doc.chain) : undefined;
6829
+ const unsigned = doc.unsigned === true ? true : undefined;
5430
6830
  const foundCategories = CATEGORIES.filter((c) => (c in doc));
5431
6831
  if (foundCategories.length === 0) {
5432
6832
  throw new CliError(`File "${filePath}" must contain exactly one category key: ${CATEGORIES.join(", ")}. None found.`);
@@ -5457,14 +6857,15 @@ async function loadCommandFile(filePath, cliVars) {
5457
6857
  category,
5458
6858
  pallet,
5459
6859
  item,
5460
- args: args ?? undefined
6860
+ args: args ?? undefined,
6861
+ unsigned
5461
6862
  };
5462
6863
  }
5463
6864
 
5464
6865
  // src/core/update-notifier.ts
5465
6866
  init_store();
5466
6867
  import { readFileSync } from "node:fs";
5467
- import { mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
6868
+ import { mkdir as mkdir4, writeFile as writeFile6 } from "node:fs/promises";
5468
6869
  import { join as join4 } from "node:path";
5469
6870
  var CACHE_FILE = "update-check.json";
5470
6871
  var STALE_MS = 24 * 60 * 60 * 1000;
@@ -5534,7 +6935,7 @@ function readCache() {
5534
6935
  async function writeCache(cache) {
5535
6936
  try {
5536
6937
  await mkdir4(getConfigDir(), { recursive: true });
5537
- await writeFile4(getCachePath(), `${JSON.stringify(cache)}
6938
+ await writeFile6(getCachePath(), `${JSON.stringify(cache)}
5538
6939
  `);
5539
6940
  } catch {}
5540
6941
  }
@@ -5702,13 +7103,15 @@ if (process.argv[2] === "__complete") {
5702
7103
  console.log(" dot query.System.Account <addr> Query a storage item");
5703
7104
  console.log(" dot query.System List storage items in System");
5704
7105
  console.log(" dot tx.System.remark 0xdead --from alice");
7106
+ console.log(" dot tx.System.remark 0xdead --unsigned Submit unsigned/bare tx");
5705
7107
  console.log(" dot const.Balances.ExistentialDeposit");
5706
7108
  console.log(" dot events.Balances List events in Balances");
5707
7109
  console.log(" dot apis.Core.version Call a runtime API");
5708
7110
  console.log(" dot polkadot.query.System.Number With chain prefix");
5709
7111
  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");
7112
+ console.log(" dot tx.0x1f0003... --to-yaml Decode hex call to YAML");
7113
+ console.log(" dot tx.System.remark 0xdead --to-json Encode & output as JSON file format");
7114
+ console.log(" dot query.System.Number --json Output as JSON");
5712
7115
  console.log();
5713
7116
  console.log("Commands:");
5714
7117
  console.log(" inspect [target] Inspect chain metadata (alias: explore)");
@@ -5721,21 +7124,21 @@ if (process.argv[2] === "__complete") {
5721
7124
  console.log(" completions <sh> Generate shell completions (zsh, bash, fish)");
5722
7125
  console.log();
5723
7126
  console.log("Global options:");
5724
- console.log(" --chain <name> Target chain (default from config)");
7127
+ console.log(" --chain <name> Target chain (required)");
5725
7128
  console.log(" --rpc <url> Override RPC endpoint");
5726
- console.log(" --light-client Use Smoldot light client");
7129
+ console.log(" --json Output as JSON");
5727
7130
  console.log(" --output <format> Output format: pretty or json");
5728
7131
  console.log(" --help, -h Display this message");
5729
7132
  console.log(" --version Show version");
5730
7133
  };
5731
7134
  startBackgroundCheck(version);
5732
7135
  const cli = cac("dot");
5733
- cli.option("--chain <name>", "Target chain (default from config)");
7136
+ cli.option("--chain <name>", "Target chain (required)");
5734
7137
  cli.option("--rpc <url>", "Override RPC endpoint for this call");
5735
- cli.option("--light-client", "Use Smoldot light client instead of WebSocket");
5736
7138
  cli.option("--output <format>", "Output format: pretty or json", {
5737
7139
  default: "pretty"
5738
7140
  });
7141
+ cli.option("--json", "Output as JSON (shorthand for --output json)");
5739
7142
  registerChainCommands(cli);
5740
7143
  registerInspectCommand(cli);
5741
7144
  registerAccountCommands(cli);
@@ -5744,9 +7147,9 @@ if (process.argv[2] === "__complete") {
5744
7147
  registerParachainCommand(cli);
5745
7148
  registerCompletionsCommand(cli);
5746
7149
  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)", {
7150
+ 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("--asset <json>", "Pay fees in an alternative asset (XCM location JSON, for tx)").option("-w, --wait <level>", "Resolve at: broadcast, best-block (or best), finalized (for tx)", {
5748
7151
  default: "finalized"
5749
- }).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) => {
7152
+ }).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)').option("--unsigned", "Submit as unsigned/bare transaction (no signer required, for tx)").action(async (dotpath, args, opts) => {
5750
7153
  if (!dotpath) {
5751
7154
  printHelp();
5752
7155
  return;
@@ -5755,18 +7158,25 @@ if (process.argv[2] === "__complete") {
5755
7158
  const cliVars = collectVarFlags(process.argv);
5756
7159
  const cmd = await loadCommandFile(dotpath, cliVars);
5757
7160
  const effectiveChain2 = opts.chain ?? cmd.chain;
5758
- const handlerOpts2 = { chain: effectiveChain2, rpc: opts.rpc, output: opts.output };
7161
+ const handlerOpts2 = {
7162
+ chain: effectiveChain2,
7163
+ rpc: opts.rpc,
7164
+ output: opts.output,
7165
+ json: opts.json
7166
+ };
5759
7167
  const target2 = `${cmd.pallet}.${cmd.item}`;
5760
7168
  switch (cmd.category) {
5761
7169
  case "tx":
5762
7170
  await handleTx(target2, args, {
5763
7171
  ...handlerOpts2,
5764
7172
  from: opts.from,
7173
+ unsigned: opts.unsigned ?? cmd.unsigned,
5765
7174
  dryRun: opts.dryRun,
5766
7175
  encode: opts.encode,
5767
- yaml: opts.yaml,
5768
- json: opts.json,
7176
+ toYaml: opts.toYaml,
7177
+ toJson: opts.toJson,
5769
7178
  ext: opts.ext,
7179
+ asset: opts.asset,
5770
7180
  wait: opts.wait,
5771
7181
  nonce: opts.nonce,
5772
7182
  tip: opts.tip,
@@ -5812,7 +7222,12 @@ if (process.argv[2] === "__complete") {
5812
7222
  throw new CliError2(`Chain specified both as prefix ("${parsed.chain}") and as --chain flag ("${opts.chain}"). Use one or the other.`);
5813
7223
  }
5814
7224
  const effectiveChain = parsed.chain ?? opts.chain;
5815
- const handlerOpts = { chain: effectiveChain, rpc: opts.rpc, output: opts.output };
7225
+ const handlerOpts = {
7226
+ chain: effectiveChain,
7227
+ rpc: opts.rpc,
7228
+ output: opts.output,
7229
+ json: opts.json
7230
+ };
5816
7231
  const target = parsed.pallet ? parsed.item ? `${parsed.pallet}.${parsed.item}` : parsed.pallet : undefined;
5817
7232
  if (cli.options.help && parsed.pallet && parsed.item) {
5818
7233
  await showItemHelp2(parsed.category, target, handlerOpts);
@@ -5826,11 +7241,13 @@ if (process.argv[2] === "__complete") {
5826
7241
  const txOpts = {
5827
7242
  ...handlerOpts,
5828
7243
  from: opts.from,
7244
+ unsigned: opts.unsigned,
5829
7245
  dryRun: opts.dryRun,
5830
7246
  encode: opts.encode,
5831
- yaml: opts.yaml,
5832
- json: opts.json,
7247
+ toYaml: opts.toYaml,
7248
+ toJson: opts.toJson,
5833
7249
  ext: opts.ext,
7250
+ asset: opts.asset,
5834
7251
  wait: opts.wait,
5835
7252
  nonce: opts.nonce,
5836
7253
  tip: opts.tip,