polkadot-cli 0.12.0 → 0.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 +123 -5
  2. package/dist/cli.mjs +711 -34
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -103,6 +103,45 @@ MY_SECRET="word1 word2 ..." dot tx System.remark 0xdead --from ci-signer
103
103
  # Remove one or more accounts
104
104
  dot account remove my-validator
105
105
  dot account delete my-validator stale-key
106
+
107
+ # Inspect an account — show public key and SS58 address
108
+ dot account inspect alice
109
+ dot account alice # shorthand (same as inspect)
110
+ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
111
+ dot account inspect 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
112
+ dot account inspect alice --prefix 0 # Polkadot mainnet prefix
113
+ dot account inspect alice --output json # JSON output
114
+ ```
115
+
116
+ #### Inspect accounts
117
+
118
+ Convert between SS58 addresses, hex public keys, and account names. Accepts any of:
119
+
120
+ - **Dev account name** (`alice`, `bob`, etc.) — resolves to public key and SS58
121
+ - **Stored account name** — looks up the public key from the accounts file
122
+ - **SS58 address** — decodes to the underlying public key
123
+ - **Hex public key** (`0x` + 64 hex chars) — encodes to SS58
124
+
125
+ ```bash
126
+ dot account inspect alice
127
+ dot account alice # shorthand — unknown subcommands fall through to inspect
128
+
129
+ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
130
+ dot account inspect 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
131
+ ```
132
+
133
+ Use `--prefix` to encode the SS58 address with a specific network prefix (default: 42):
134
+
135
+ ```bash
136
+ dot account inspect alice --prefix 0 # Polkadot mainnet (prefix 0, starts with '1')
137
+ dot account inspect alice --prefix 2 # Kusama (prefix 2)
138
+ ```
139
+
140
+ JSON output:
141
+
142
+ ```bash
143
+ dot account inspect alice --output json
144
+ # {"publicKey":"0xd435...a27d","ss58":"5Grw...utQY","prefix":42,"name":"Alice"}
106
145
  ```
107
146
 
108
147
  #### Env-var-backed accounts
@@ -181,7 +220,7 @@ dot query System.Account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
181
220
  # All map entries (default limit: 100)
182
221
  dot query System.Account --limit 10
183
222
 
184
- # Pipe to jq — stdout is clean JSON, no extra text
223
+ # Pipe-safe — stdout is clean data, progress messages go to stderr
185
224
  dot query System.Account --limit 5 | jq '.[0].value.data.free'
186
225
  dot query System.Number --output json | jq '.+1'
187
226
 
@@ -210,7 +249,7 @@ dot const Balances.ExistentialDeposit
210
249
  dot const System.SS58Prefix --chain kusama
211
250
  dot const kusama.Balances.ExistentialDeposit
212
251
 
213
- # Pipe to jq — stdout is clean JSON, no extra text
252
+ # Pipe-safe — stdout is clean JSON, progress messages go to stderr
214
253
  dot const Balances.ExistentialDeposit --output json | jq
215
254
  ```
216
255
 
@@ -219,20 +258,70 @@ dot const Balances.ExistentialDeposit --output json | jq
219
258
  Works offline from cached metadata after the first fetch.
220
259
 
221
260
  ```bash
222
- # List all pallets
261
+ # List all pallets (shows storage, constants, calls, events, and errors counts)
223
262
  dot inspect
224
263
 
225
- # List a pallet's storage items and constants
264
+ # List a pallet's storage items, constants, calls, events, and errors
226
265
  dot inspect System
227
266
 
228
- # Detailed type info for a specific item
267
+ # Detailed type info for a specific storage item or constant
229
268
  dot inspect System.Account
230
269
 
270
+ # Call detail — shows argument signature and docs
271
+ dot inspect Balances.transfer_allow_death
272
+
273
+ # Event detail — shows field signature and docs
274
+ dot inspect Balances.Transfer
275
+
276
+ # Error detail — shows docs
277
+ dot inspect Balances.InsufficientBalance
278
+
231
279
  # Inspect a specific chain using chain prefix
232
280
  dot inspect kusama.System
233
281
  dot inspect kusama.System.Account
234
282
  ```
235
283
 
284
+ The pallet listing view shows type information inline so you can understand item shapes at a glance:
285
+
286
+ - **Storage**: key/value types with `[map]` tag for map items (e.g. `Account: AccountId32 → { nonce: u32, ... } [map]`)
287
+ - **Constants**: the constant's type (e.g. `ExistentialDeposit: u128`)
288
+ - **Calls**: full argument signature (e.g. `transfer_allow_death(dest: enum(5 variants), value: Compact<u128>)`)
289
+ - **Events**: field signature (e.g. `Transfer(from: AccountId32, to: AccountId32, amount: u128)`)
290
+ - **Errors**: name and documentation (e.g. `InsufficientBalance`)
291
+
292
+ Documentation from the runtime metadata is shown on an indented line below each item. The detail view (`dot inspect Balances.transfer_allow_death`) shows the full argument signature and complete documentation text. Use call inspection to discover argument names, types, and docs before constructing `dot tx` commands.
293
+
294
+ ### Focused commands
295
+
296
+ Browse specific metadata categories directly without using `dot inspect`:
297
+
298
+ ```bash
299
+ # List all pallets
300
+ dot pallets
301
+
302
+ # List pallet calls with argument signatures
303
+ dot calls Balances
304
+ dot calls Balances.transfer_allow_death # call detail
305
+
306
+ # List pallet events with field signatures
307
+ dot events Balances
308
+ dot events Balances.Transfer # event detail
309
+
310
+ # List pallet errors
311
+ dot errors Balances
312
+ dot errors Balances.InsufficientBalance # error detail
313
+
314
+ # List pallet storage items with types
315
+ dot storage System
316
+ dot storage System.Account # storage detail
317
+
318
+ # List pallet constants (dual-purpose — also works as value lookup)
319
+ dot const Balances # list constants
320
+ dot const Balances.ExistentialDeposit # look up value
321
+ ```
322
+
323
+ Each command supports `--chain <name>`, `--rpc <url>`, and chain prefix syntax. Singular and plural forms are interchangeable (e.g. `dot call` = `dot calls`, `dot event` = `dot events`).
324
+
236
325
  ### Submit extrinsics
237
326
 
238
327
  Build, sign, and submit transactions. Pass a `Pallet.Call` with arguments, or a raw SCALE-encoded call hex (e.g. from a multisig proposal or governance). Both forms display a decoded human-readable representation of the call.
@@ -299,6 +388,12 @@ Both dry-run and submission display the encoded call hex and a decoded human-rea
299
388
  Status: ok
300
389
  ```
301
390
 
391
+ Complex calls (e.g. XCM teleports) that the primary decoder cannot handle are automatically decoded via a fallback path:
392
+
393
+ ```
394
+ Decode: PolkadotXcm.limited_teleport_assets { dest: V3 { parents: 1, interior: X1(Parachain(5140)) }, beneficiary: V3 { ... }, assets: V3 [...], fee_asset_item: 0, weight_limit: Unlimited }
395
+ ```
396
+
302
397
  #### Exit codes
303
398
 
304
399
  The CLI exits with code **1** when a finalized transaction has a dispatch error (e.g. insufficient balance, bad origin). The full transaction output (events, explorer links) is still printed before the error so you can debug the failure. Module errors are formatted as `PalletName.ErrorVariant` (e.g. `Balances.InsufficientBalance`).
@@ -310,6 +405,18 @@ dot tx Balances.transferKeepAlive 5FHneW46... 999999999999999999 --from alice
310
405
  echo $? # 1
311
406
  ```
312
407
 
408
+ #### Argument parsing errors
409
+
410
+ When a call argument is invalid, the CLI shows a contextual error message with the argument name, the expected type, and a hint:
411
+
412
+ ```bash
413
+ dot tx Balances.transferKeepAlive 5GrwvaEF... abc --encode
414
+ # Error: Invalid value for argument 'value' (expected Compact<u128>): "abc"
415
+ # Hint: Compact<u128>
416
+ ```
417
+
418
+ For struct-based calls, the error identifies the specific field that failed. For tuple-based calls, it shows the argument index. The original parse error is preserved as the `cause` for programmatic access.
419
+
313
420
  #### Custom signed extensions
314
421
 
315
422
  Chains with non-standard signed extensions (e.g. `people-preview`) are auto-handled:
@@ -369,6 +476,17 @@ dot hash --help # same as `dot hash` — shows algorithms and examples
369
476
  | `--output json` | Raw JSON output (default: pretty) |
370
477
  | `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
371
478
 
479
+ ### Pipe-safe output
480
+
481
+ All commands follow Unix conventions: **data goes to stdout, progress goes to stderr**. This means you can safely pipe `--output json` into `jq` or other tools without progress messages ("Fetching metadata...", spinner output, "Connecting...") corrupting the data stream:
482
+
483
+ ```bash
484
+ dot const System.SS58Prefix --output json | jq '.+1'
485
+ dot query System.Number --output json | jq
486
+ ```
487
+
488
+ In an interactive terminal, both streams render together so you see progress and results normally.
489
+
372
490
  ## How it compares
373
491
 
374
492
  | | polkadot-cli | @polkadot/api-cli | subxt-cli | Pop CLI |
package/dist/cli.mjs CHANGED
@@ -5,7 +5,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
5
  // src/cli.ts
6
6
  import cac from "cac";
7
7
  // package.json
8
- var version = "0.12.0";
8
+ var version = "0.14.0";
9
9
 
10
10
  // src/config/accounts-store.ts
11
11
  import { access as access2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
@@ -247,6 +247,7 @@ import {
247
247
  generateMnemonic,
248
248
  mnemonicToEntropy,
249
249
  ss58Address,
250
+ ss58Decode,
250
251
  validateMnemonic
251
252
  } from "@polkadot-labs/hdkd-helpers";
252
253
  import { getPolkadotSigner } from "polkadot-api/signer";
@@ -314,6 +315,13 @@ function toSs58(publicKey, prefix = 42) {
314
315
  }
315
316
  return ss58Address(publicKey, prefix);
316
317
  }
318
+ function fromSs58(address) {
319
+ const [payload] = ss58Decode(address);
320
+ return payload;
321
+ }
322
+ function isHexPublicKey(input) {
323
+ return /^0x[0-9a-fA-F]{64}$/.test(input);
324
+ }
317
325
  function resolveSecret(secret) {
318
326
  if (isEnvSecret(secret)) {
319
327
  const value = process.env[secret.env];
@@ -421,7 +429,7 @@ class Spinner {
421
429
  start(msg) {
422
430
  this.stop();
423
431
  if (!isTTY) {
424
- console.log(msg);
432
+ console.error(msg);
425
433
  return;
426
434
  }
427
435
  process.stdout.write(`${SPINNER_FRAMES[0]} ${msg}`);
@@ -441,9 +449,16 @@ class Spinner {
441
449
  }
442
450
  succeed(msg) {
443
451
  this.stop();
444
- console.log(`${GREEN}${CHECK_MARK}${RESET} ${msg}`);
452
+ console.error(`${GREEN}${CHECK_MARK}${RESET} ${msg}`);
445
453
  }
446
454
  }
455
+ function firstSentence(docs) {
456
+ const text = docs.join(" ").trim();
457
+ if (!text)
458
+ return "";
459
+ const match = text.match(/^.*?(?<![ei]\.g|[ei]\.[eg])(?<!\betc)[.!?](?:\s|$)/);
460
+ return match ? match[0].trim() : text;
461
+ }
447
462
 
448
463
  // src/commands/account.ts
449
464
  var ACCOUNT_HELP = `
@@ -452,6 +467,7 @@ ${BOLD}Usage:${RESET}
452
467
  $ dot account import|add <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic
453
468
  $ dot account import|add <name> --env <VAR> [--path <derivation>] Import account backed by env variable
454
469
  $ dot account derive <source> <new-name> --path <derivation> Derive a child account
470
+ $ dot account inspect <input> [--prefix <N>] Inspect an account/address/key
455
471
  $ dot account list List all accounts
456
472
  $ dot account remove|delete <name> [name2] ... Remove stored account(s)
457
473
 
@@ -462,6 +478,9 @@ ${BOLD}Examples:${RESET}
462
478
  $ dot account import treasury --secret "word1 word2 ... word12"
463
479
  $ dot account import ci-signer --env MY_SECRET --path //ci
464
480
  $ dot account derive treasury treasury-staking --path //staking
481
+ $ dot account inspect alice
482
+ $ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
483
+ $ dot account inspect 0xd435...a27d --prefix 0
465
484
  $ dot account list
466
485
  $ dot account remove my-validator stale-key
467
486
 
@@ -470,7 +489,7 @@ ${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
470
489
  Hex seed import (0x...) is not supported via CLI.${RESET}
471
490
  `.trimStart();
472
491
  function registerAccountCommands(cli) {
473
- 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)").action(async (action, names, opts) => {
492
+ 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) => {
474
493
  if (!action) {
475
494
  if (process.argv[2] === "accounts")
476
495
  return accountList();
@@ -491,11 +510,10 @@ function registerAccountCommands(cli) {
491
510
  case "delete":
492
511
  case "remove":
493
512
  return accountRemove(names);
513
+ case "inspect":
514
+ return accountInspect(names[0], opts);
494
515
  default:
495
- console.error(`Unknown action "${action}".
496
- `);
497
- console.log(ACCOUNT_HELP);
498
- process.exit(1);
516
+ return accountInspect(action, opts);
499
517
  }
500
518
  });
501
519
  }
@@ -743,6 +761,70 @@ async function accountRemove(names) {
743
761
  console.log(`Account "${name}" removed.`);
744
762
  }
745
763
  }
764
+ async function accountInspect(input, opts) {
765
+ if (!input) {
766
+ console.error(`Input is required.
767
+ `);
768
+ console.error("Usage: dot account inspect <name|ss58-address|0x-public-key> [--prefix <N>]");
769
+ process.exit(1);
770
+ }
771
+ const prefix = opts.prefix != null ? Number(opts.prefix) : 42;
772
+ if (Number.isNaN(prefix) || prefix < 0) {
773
+ console.error(`Invalid prefix "${opts.prefix}". Must be a non-negative integer.`);
774
+ process.exit(1);
775
+ }
776
+ let name;
777
+ let publicKeyHex;
778
+ if (isDevAccount(input)) {
779
+ name = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
780
+ const devAddr = getDevAddress(input);
781
+ publicKeyHex = publicKeyToHex(fromSs58(devAddr));
782
+ } else {
783
+ const accountsFile = await loadAccounts();
784
+ const account = findAccount(accountsFile, input);
785
+ if (account) {
786
+ name = account.name;
787
+ if (account.publicKey) {
788
+ publicKeyHex = account.publicKey;
789
+ } else if (isEnvSecret(account.secret)) {
790
+ const derived = tryDerivePublicKey(account.secret.env, account.derivationPath);
791
+ if (!derived) {
792
+ console.error(`Cannot derive public key for "${account.name}": $${account.secret.env} is not set.`);
793
+ process.exit(1);
794
+ }
795
+ publicKeyHex = derived;
796
+ } else {
797
+ console.error(`Account "${account.name}" has no public key.`);
798
+ process.exit(1);
799
+ }
800
+ } else if (isHexPublicKey(input)) {
801
+ publicKeyHex = input;
802
+ } else {
803
+ try {
804
+ const decoded = fromSs58(input);
805
+ publicKeyHex = publicKeyToHex(decoded);
806
+ } catch {
807
+ console.error(`Cannot identify "${input}" as an account name, SS58 address, or hex public key.`);
808
+ process.exit(1);
809
+ }
810
+ }
811
+ }
812
+ const ss58 = toSs58(publicKeyHex, prefix);
813
+ if (opts.output === "json") {
814
+ const result = { publicKey: publicKeyHex, ss58, prefix };
815
+ if (name)
816
+ result.name = name;
817
+ console.log(formatJson(result));
818
+ } else {
819
+ printHeading("Account Info");
820
+ if (name)
821
+ console.log(` ${BOLD}Name:${RESET} ${name}`);
822
+ console.log(` ${BOLD}Public Key:${RESET} ${publicKeyHex}`);
823
+ console.log(` ${BOLD}SS58:${RESET} ${ss58}`);
824
+ console.log(` ${BOLD}Prefix:${RESET} ${prefix}`);
825
+ console.log();
826
+ }
827
+ }
746
828
 
747
829
  // src/core/client.ts
748
830
  import { createClient } from "polkadot-api";
@@ -902,26 +984,28 @@ function listPallets(meta) {
902
984
  docs: c.docs ?? [],
903
985
  typeId: c.type
904
986
  })),
905
- calls: extractCalls(meta, p.calls)
987
+ calls: extractEnumVariants(meta, p.calls),
988
+ events: extractEnumVariants(meta, p.events),
989
+ errors: extractEnumVariants(meta, p.errors).map(({ name, docs }) => ({ name, docs }))
906
990
  }));
907
991
  }
908
- function extractCalls(meta, callsRef) {
909
- if (!callsRef)
992
+ function extractEnumVariants(meta, ref) {
993
+ if (!ref)
910
994
  return [];
911
995
  try {
912
- const entry = meta.lookup(callsRef.type);
996
+ const entry = meta.lookup(ref.type);
913
997
  if (entry.type !== "enum")
914
998
  return [];
915
999
  return Object.entries(entry.value).map(([name, variant]) => ({
916
1000
  name,
917
- docs: variant.docs ?? [],
918
- typeId: resolveCallTypeId(variant)
1001
+ docs: entry.innerDocs?.[name] ?? [],
1002
+ typeId: resolveVariantTypeId(variant)
919
1003
  }));
920
1004
  } catch {
921
1005
  return [];
922
1006
  }
923
1007
  }
924
- function resolveCallTypeId(variant) {
1008
+ function resolveVariantTypeId(variant) {
925
1009
  if (variant.type === "lookupEntry")
926
1010
  return variant.value?.id ?? null;
927
1011
  if (variant.type === "struct")
@@ -984,6 +1068,78 @@ function formatLookupEntry(entry) {
984
1068
  return "unknown";
985
1069
  }
986
1070
  }
1071
+ function describeCallArgs(meta, palletName, callName) {
1072
+ try {
1073
+ const palletMeta = meta.unified.pallets.find((p) => p.name === palletName);
1074
+ if (!palletMeta?.calls)
1075
+ return "";
1076
+ const callsEntry = meta.lookup(palletMeta.calls.type);
1077
+ if (callsEntry.type !== "enum")
1078
+ return "";
1079
+ const variant = callsEntry.value[callName];
1080
+ if (!variant)
1081
+ return "";
1082
+ if (variant.type === "void")
1083
+ return "()";
1084
+ if (variant.type === "struct") {
1085
+ const fields = Object.entries(variant.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
1086
+ return `(${fields})`;
1087
+ }
1088
+ if (variant.type === "lookupEntry") {
1089
+ const inner = variant.value;
1090
+ if (inner.type === "void")
1091
+ return "()";
1092
+ if (inner.type === "struct") {
1093
+ const fields = Object.entries(inner.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
1094
+ return `(${fields})`;
1095
+ }
1096
+ return `(${formatLookupEntry(inner)})`;
1097
+ }
1098
+ if (variant.type === "tuple") {
1099
+ const types = variant.value.map(formatLookupEntry).join(", ");
1100
+ return `(${types})`;
1101
+ }
1102
+ return "";
1103
+ } catch {
1104
+ return "";
1105
+ }
1106
+ }
1107
+ function describeEventFields(meta, palletName, eventName) {
1108
+ try {
1109
+ const palletMeta = meta.unified.pallets.find((p) => p.name === palletName);
1110
+ if (!palletMeta?.events)
1111
+ return "";
1112
+ const eventsEntry = meta.lookup(palletMeta.events.type);
1113
+ if (eventsEntry.type !== "enum")
1114
+ return "";
1115
+ const variant = eventsEntry.value[eventName];
1116
+ if (!variant)
1117
+ return "";
1118
+ if (variant.type === "void")
1119
+ return "()";
1120
+ if (variant.type === "struct") {
1121
+ const fields = Object.entries(variant.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
1122
+ return `(${fields})`;
1123
+ }
1124
+ if (variant.type === "lookupEntry") {
1125
+ const inner = variant.value;
1126
+ if (inner.type === "void")
1127
+ return "()";
1128
+ if (inner.type === "struct") {
1129
+ const fields = Object.entries(inner.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
1130
+ return `(${fields})`;
1131
+ }
1132
+ return `(${formatLookupEntry(inner)})`;
1133
+ }
1134
+ if (variant.type === "tuple") {
1135
+ const types = variant.value.map(formatLookupEntry).join(", ");
1136
+ return `(${types})`;
1137
+ }
1138
+ return "";
1139
+ } catch {
1140
+ return "";
1141
+ }
1142
+ }
987
1143
  function hexToBytes(hex) {
988
1144
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
989
1145
  const bytes = new Uint8Array(clean.length / 2);
@@ -1059,10 +1215,10 @@ async function chainAdd(name, opts) {
1059
1215
  rpc: opts.rpc ?? "",
1060
1216
  ...opts.lightClient ? { lightClient: true } : {}
1061
1217
  };
1062
- console.log(`Connecting to ${name}...`);
1218
+ console.error(`Connecting to ${name}...`);
1063
1219
  const clientHandle = await createChainClient(name, chainConfig, opts.rpc);
1064
1220
  try {
1065
- console.log("Fetching metadata...");
1221
+ console.error("Fetching metadata...");
1066
1222
  await fetchMetadataFromChain(clientHandle, name);
1067
1223
  const config = await loadConfig();
1068
1224
  config.chains[name] = chainConfig;
@@ -1115,10 +1271,10 @@ async function chainList() {
1115
1271
  async function chainUpdate(name, opts) {
1116
1272
  const config = await loadConfig();
1117
1273
  const { name: chainName, chain: chainConfig } = resolveChain(config, name);
1118
- console.log(`Connecting to ${chainName}...`);
1274
+ console.error(`Connecting to ${chainName}...`);
1119
1275
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
1120
1276
  try {
1121
- console.log("Fetching metadata...");
1277
+ console.error("Fetching metadata...");
1122
1278
  await fetchMetadataFromChain(clientHandle, chainName);
1123
1279
  console.log(`Metadata for "${chainName}" updated.`);
1124
1280
  } finally {
@@ -1228,23 +1384,58 @@ function resolveTargetChain(target, chainFlag) {
1228
1384
 
1229
1385
  // src/commands/const.ts
1230
1386
  function registerConstCommand(cli) {
1231
- cli.command("const [target]", "Look up a pallet constant (e.g. Balances.ExistentialDeposit)").action(async (target, opts) => {
1387
+ cli.command("const [target]", "Look up or list pallet constants (e.g. Balances.ExistentialDeposit)").alias("consts").alias("constants").action(async (target, opts) => {
1232
1388
  if (!target) {
1233
- console.log("Usage: dot const <[Chain.]Pallet.Constant> [--chain <name>] [--output json]");
1389
+ console.log("Usage: dot const <[Chain.]Pallet[.Constant]> [--chain <name>] [--output json]");
1234
1390
  console.log("");
1235
1391
  console.log("Examples:");
1236
- console.log(" $ dot const Balances.ExistentialDeposit");
1392
+ console.log(" $ dot const Balances # list constants");
1393
+ console.log(" $ dot const Balances.ExistentialDeposit # look up value");
1237
1394
  console.log(" $ dot const System.SS58Prefix --chain kusama");
1238
1395
  console.log(" $ dot const kusama.Balances.ExistentialDeposit # chain prefix");
1239
1396
  return;
1240
1397
  }
1241
1398
  const config = await loadConfig();
1242
1399
  const knownChains = Object.keys(config.chains);
1243
- const parsed = parseTarget(target, { knownChains });
1400
+ const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
1244
1401
  const effectiveChain = resolveTargetChain(parsed, opts.chain);
1245
1402
  const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1246
1403
  const pallet = parsed.pallet;
1247
1404
  const item = parsed.item;
1405
+ if (!item) {
1406
+ let meta;
1407
+ try {
1408
+ meta = await getOrFetchMetadata(chainName);
1409
+ } catch {
1410
+ console.error(`Fetching metadata from ${chainName}...`);
1411
+ const clientHandle2 = await createChainClient(chainName, chainConfig, opts.rpc);
1412
+ try {
1413
+ meta = await getOrFetchMetadata(chainName, clientHandle2);
1414
+ } finally {
1415
+ clientHandle2.destroy();
1416
+ }
1417
+ }
1418
+ const palletNames = getPalletNames(meta);
1419
+ const palletInfo = findPallet(meta, pallet);
1420
+ if (!palletInfo) {
1421
+ throw new Error(suggestMessage("pallet", pallet, palletNames));
1422
+ }
1423
+ if (palletInfo.constants.length === 0) {
1424
+ console.log(`No constants in ${palletInfo.name}.`);
1425
+ return;
1426
+ }
1427
+ printHeading(`${palletInfo.name} Constants`);
1428
+ for (const c of palletInfo.constants) {
1429
+ const typeStr = describeType(meta.lookup, c.typeId);
1430
+ console.log(` ${CYAN}${c.name}${RESET}${DIM}: ${typeStr}${RESET}`);
1431
+ const summary = firstSentence(c.docs);
1432
+ if (summary) {
1433
+ console.log(` ${DIM}${summary}${RESET}`);
1434
+ }
1435
+ }
1436
+ console.log();
1437
+ return;
1438
+ }
1248
1439
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
1249
1440
  try {
1250
1441
  const meta = await getOrFetchMetadata(chainName, clientHandle);
@@ -1269,6 +1460,279 @@ function registerConstCommand(cli) {
1269
1460
  });
1270
1461
  }
1271
1462
 
1463
+ // src/commands/focused-inspect.ts
1464
+ async function loadMeta(chainName, chainConfig, rpcOverride) {
1465
+ try {
1466
+ return await getOrFetchMetadata(chainName);
1467
+ } catch {
1468
+ console.error(`Fetching metadata from ${chainName}...`);
1469
+ const clientHandle = await createChainClient(chainName, chainConfig, rpcOverride);
1470
+ try {
1471
+ return await getOrFetchMetadata(chainName, clientHandle);
1472
+ } finally {
1473
+ clientHandle.destroy();
1474
+ }
1475
+ }
1476
+ }
1477
+ function resolvePallet(meta, palletName) {
1478
+ const palletNames = getPalletNames(meta);
1479
+ const pallet = findPallet(meta, palletName);
1480
+ if (!pallet) {
1481
+ throw new Error(suggestMessage("pallet", palletName, palletNames));
1482
+ }
1483
+ return pallet;
1484
+ }
1485
+ function registerFocusedInspectCommands(cli) {
1486
+ cli.command("call [target]", "List or inspect pallet calls").alias("calls").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
1487
+ if (!target) {
1488
+ console.log("Usage: dot calls <[Chain.]Pallet[.Call]> [--chain <name>]");
1489
+ console.log("");
1490
+ console.log("Examples:");
1491
+ console.log(" $ dot calls Balances");
1492
+ console.log(" $ dot calls Balances.transfer_allow_death");
1493
+ return;
1494
+ }
1495
+ const config = await loadConfig();
1496
+ const knownChains = Object.keys(config.chains);
1497
+ const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
1498
+ const effectiveChain = resolveTargetChain(parsed, opts.chain);
1499
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1500
+ const meta = await loadMeta(chainName, chainConfig, opts.rpc);
1501
+ const pallet = resolvePallet(meta, parsed.pallet);
1502
+ if (!parsed.item) {
1503
+ if (pallet.calls.length === 0) {
1504
+ console.log(`No calls in ${pallet.name}.`);
1505
+ return;
1506
+ }
1507
+ printHeading(`${pallet.name} Calls`);
1508
+ for (const c of pallet.calls) {
1509
+ const args2 = describeCallArgs(meta, pallet.name, c.name);
1510
+ console.log(` ${CYAN}${c.name}${RESET}${DIM}${args2}${RESET}`);
1511
+ const summary = firstSentence(c.docs);
1512
+ if (summary) {
1513
+ console.log(` ${DIM}${summary}${RESET}`);
1514
+ }
1515
+ }
1516
+ console.log();
1517
+ return;
1518
+ }
1519
+ const callItem = pallet.calls.find((c) => c.name.toLowerCase() === parsed.item.toLowerCase());
1520
+ if (!callItem) {
1521
+ const names = pallet.calls.map((c) => c.name);
1522
+ throw new Error(suggestMessage(`call in ${pallet.name}`, parsed.item, names));
1523
+ }
1524
+ printHeading(`${pallet.name}.${callItem.name} (Call)`);
1525
+ const args = describeCallArgs(meta, pallet.name, callItem.name);
1526
+ console.log(` ${BOLD}Args:${RESET} ${args}`);
1527
+ if (callItem.docs.length) {
1528
+ console.log();
1529
+ printDocs(callItem.docs);
1530
+ }
1531
+ console.log();
1532
+ });
1533
+ cli.command("event [target]", "List or inspect pallet events").alias("events").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
1534
+ if (!target) {
1535
+ console.log("Usage: dot events <[Chain.]Pallet[.Event]> [--chain <name>]");
1536
+ console.log("");
1537
+ console.log("Examples:");
1538
+ console.log(" $ dot events Balances");
1539
+ console.log(" $ dot events Balances.Transfer");
1540
+ return;
1541
+ }
1542
+ const config = await loadConfig();
1543
+ const knownChains = Object.keys(config.chains);
1544
+ const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
1545
+ const effectiveChain = resolveTargetChain(parsed, opts.chain);
1546
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1547
+ const meta = await loadMeta(chainName, chainConfig, opts.rpc);
1548
+ const pallet = resolvePallet(meta, parsed.pallet);
1549
+ if (!parsed.item) {
1550
+ if (pallet.events.length === 0) {
1551
+ console.log(`No events in ${pallet.name}.`);
1552
+ return;
1553
+ }
1554
+ printHeading(`${pallet.name} Events`);
1555
+ for (const e of pallet.events) {
1556
+ const fields2 = describeEventFields(meta, pallet.name, e.name);
1557
+ console.log(` ${CYAN}${e.name}${RESET}${DIM}${fields2}${RESET}`);
1558
+ const summary = firstSentence(e.docs);
1559
+ if (summary) {
1560
+ console.log(` ${DIM}${summary}${RESET}`);
1561
+ }
1562
+ }
1563
+ console.log();
1564
+ return;
1565
+ }
1566
+ const eventItem = pallet.events.find((e) => e.name.toLowerCase() === parsed.item.toLowerCase());
1567
+ if (!eventItem) {
1568
+ const names = pallet.events.map((e) => e.name);
1569
+ throw new Error(suggestMessage(`event in ${pallet.name}`, parsed.item, names));
1570
+ }
1571
+ printHeading(`${pallet.name}.${eventItem.name} (Event)`);
1572
+ const fields = describeEventFields(meta, pallet.name, eventItem.name);
1573
+ console.log(` ${BOLD}Fields:${RESET} ${fields}`);
1574
+ if (eventItem.docs.length) {
1575
+ console.log();
1576
+ printDocs(eventItem.docs);
1577
+ }
1578
+ console.log();
1579
+ });
1580
+ cli.command("error [target]", "List or inspect pallet errors").alias("errors").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
1581
+ if (!target) {
1582
+ console.log("Usage: dot errors <[Chain.]Pallet[.Error]> [--chain <name>]");
1583
+ console.log("");
1584
+ console.log("Examples:");
1585
+ console.log(" $ dot errors Balances");
1586
+ console.log(" $ dot errors Balances.InsufficientBalance");
1587
+ return;
1588
+ }
1589
+ const config = await loadConfig();
1590
+ const knownChains = Object.keys(config.chains);
1591
+ const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
1592
+ const effectiveChain = resolveTargetChain(parsed, opts.chain);
1593
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1594
+ const meta = await loadMeta(chainName, chainConfig, opts.rpc);
1595
+ const pallet = resolvePallet(meta, parsed.pallet);
1596
+ if (!parsed.item) {
1597
+ if (pallet.errors.length === 0) {
1598
+ console.log(`No errors in ${pallet.name}.`);
1599
+ return;
1600
+ }
1601
+ printHeading(`${pallet.name} Errors`);
1602
+ for (const e of pallet.errors) {
1603
+ console.log(` ${CYAN}${e.name}${RESET}`);
1604
+ const summary = firstSentence(e.docs);
1605
+ if (summary) {
1606
+ console.log(` ${DIM}${summary}${RESET}`);
1607
+ }
1608
+ }
1609
+ console.log();
1610
+ return;
1611
+ }
1612
+ const errorItem = pallet.errors.find((e) => e.name.toLowerCase() === parsed.item.toLowerCase());
1613
+ if (!errorItem) {
1614
+ const names = pallet.errors.map((e) => e.name);
1615
+ throw new Error(suggestMessage(`error in ${pallet.name}`, parsed.item, names));
1616
+ }
1617
+ printHeading(`${pallet.name}.${errorItem.name} (Error)`);
1618
+ if (errorItem.docs.length) {
1619
+ printDocs(errorItem.docs);
1620
+ }
1621
+ console.log();
1622
+ });
1623
+ cli.command("storage [target]", "List or inspect pallet storage items").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
1624
+ if (!target) {
1625
+ console.log("Usage: dot storage <[Chain.]Pallet[.Item]> [--chain <name>]");
1626
+ console.log("");
1627
+ console.log("Examples:");
1628
+ console.log(" $ dot storage System");
1629
+ console.log(" $ dot storage System.Account");
1630
+ return;
1631
+ }
1632
+ const config = await loadConfig();
1633
+ const knownChains = Object.keys(config.chains);
1634
+ const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
1635
+ const effectiveChain = resolveTargetChain(parsed, opts.chain);
1636
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1637
+ const meta = await loadMeta(chainName, chainConfig, opts.rpc);
1638
+ const pallet = resolvePallet(meta, parsed.pallet);
1639
+ if (!parsed.item) {
1640
+ if (pallet.storage.length === 0) {
1641
+ console.log(`No storage items in ${pallet.name}.`);
1642
+ return;
1643
+ }
1644
+ printHeading(`${pallet.name} Storage`);
1645
+ for (const s of pallet.storage) {
1646
+ const valueType = describeType(meta.lookup, s.valueTypeId);
1647
+ let typeSuffix;
1648
+ if (s.keyTypeId != null) {
1649
+ const keyType = describeType(meta.lookup, s.keyTypeId);
1650
+ typeSuffix = `: ${keyType} → ${valueType} [map]`;
1651
+ } else {
1652
+ typeSuffix = `: ${valueType}`;
1653
+ }
1654
+ console.log(` ${CYAN}${s.name}${RESET}${DIM}${typeSuffix}${RESET}`);
1655
+ const summary = firstSentence(s.docs);
1656
+ if (summary) {
1657
+ console.log(` ${DIM}${summary}${RESET}`);
1658
+ }
1659
+ }
1660
+ console.log();
1661
+ return;
1662
+ }
1663
+ const storageItem = pallet.storage.find((s) => s.name.toLowerCase() === parsed.item.toLowerCase());
1664
+ if (!storageItem) {
1665
+ const names = pallet.storage.map((s) => s.name);
1666
+ throw new Error(suggestMessage(`storage item in ${pallet.name}`, parsed.item, names));
1667
+ }
1668
+ printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
1669
+ console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
1670
+ console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
1671
+ if (storageItem.keyTypeId != null) {
1672
+ console.log(` ${BOLD}Key:${RESET} ${describeType(meta.lookup, storageItem.keyTypeId)}`);
1673
+ }
1674
+ if (storageItem.docs.length) {
1675
+ console.log();
1676
+ printDocs(storageItem.docs);
1677
+ }
1678
+ console.log();
1679
+ });
1680
+ cli.command("pallet [target]", "List all pallets or inspect a specific pallet").alias("pallets").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
1681
+ const config = await loadConfig();
1682
+ const knownChains = Object.keys(config.chains);
1683
+ let effectiveChain = opts.chain;
1684
+ let palletName;
1685
+ if (target) {
1686
+ const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
1687
+ effectiveChain = resolveTargetChain(parsed, opts.chain);
1688
+ palletName = parsed.pallet;
1689
+ }
1690
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1691
+ const meta = await loadMeta(chainName, chainConfig, opts.rpc);
1692
+ if (!palletName) {
1693
+ const pallets = listPallets(meta);
1694
+ printHeading(`Pallets on ${chainName} (${pallets.length})`);
1695
+ for (const p of pallets) {
1696
+ const counts2 = [];
1697
+ if (p.storage.length)
1698
+ counts2.push(`${p.storage.length} storage`);
1699
+ if (p.constants.length)
1700
+ counts2.push(`${p.constants.length} constants`);
1701
+ if (p.calls.length)
1702
+ counts2.push(`${p.calls.length} calls`);
1703
+ if (p.events.length)
1704
+ counts2.push(`${p.events.length} events`);
1705
+ if (p.errors.length)
1706
+ counts2.push(`${p.errors.length} errors`);
1707
+ printItem(p.name, counts2.join(", "));
1708
+ }
1709
+ console.log();
1710
+ return;
1711
+ }
1712
+ const pallet = resolvePallet(meta, palletName);
1713
+ printHeading(`${pallet.name} Pallet`);
1714
+ if (pallet.docs.length) {
1715
+ printDocs(pallet.docs);
1716
+ console.log();
1717
+ }
1718
+ const counts = [];
1719
+ if (pallet.storage.length)
1720
+ counts.push(`${pallet.storage.length} storage`);
1721
+ if (pallet.constants.length)
1722
+ counts.push(`${pallet.constants.length} constants`);
1723
+ if (pallet.calls.length)
1724
+ counts.push(`${pallet.calls.length} calls`);
1725
+ if (pallet.events.length)
1726
+ counts.push(`${pallet.events.length} events`);
1727
+ if (pallet.errors.length)
1728
+ counts.push(`${pallet.errors.length} errors`);
1729
+ if (counts.length) {
1730
+ console.log(` ${counts.join(", ")}`);
1731
+ }
1732
+ console.log();
1733
+ });
1734
+ }
1735
+
1272
1736
  // src/core/hash.ts
1273
1737
  import { blake2b } from "@noble/hashes/blake2.js";
1274
1738
  import { sha256 } from "@noble/hashes/sha2.js";
@@ -1402,7 +1866,7 @@ function registerHashCommand(cli) {
1402
1866
 
1403
1867
  // src/commands/inspect.ts
1404
1868
  function registerInspectCommand(cli) {
1405
- cli.command("inspect [target]", "Inspect chain metadata (pallets, storage, constants)").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
1869
+ cli.command("inspect [target]", "Inspect chain metadata (pallets, storage, constants, calls, events, errors)").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
1406
1870
  const config = await loadConfig();
1407
1871
  const knownChains = Object.keys(config.chains);
1408
1872
  let effectiveChain = opts.chain;
@@ -1419,7 +1883,7 @@ function registerInspectCommand(cli) {
1419
1883
  try {
1420
1884
  meta = await getOrFetchMetadata(chainName);
1421
1885
  } catch {
1422
- console.log(`Fetching metadata from ${chainName}...`);
1886
+ console.error(`Fetching metadata from ${chainName}...`);
1423
1887
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
1424
1888
  try {
1425
1889
  meta = await getOrFetchMetadata(chainName, clientHandle);
@@ -1436,6 +1900,12 @@ function registerInspectCommand(cli) {
1436
1900
  counts.push(`${p.storage.length} storage`);
1437
1901
  if (p.constants.length)
1438
1902
  counts.push(`${p.constants.length} constants`);
1903
+ if (p.calls.length)
1904
+ counts.push(`${p.calls.length} calls`);
1905
+ if (p.events.length)
1906
+ counts.push(`${p.events.length} events`);
1907
+ if (p.errors.length)
1908
+ counts.push(`${p.errors.length} errors`);
1439
1909
  printItem(p.name, counts.join(", "));
1440
1910
  }
1441
1911
  console.log();
@@ -1455,16 +1925,66 @@ function registerInspectCommand(cli) {
1455
1925
  if (pallet2.storage.length) {
1456
1926
  console.log(` ${BOLD}Storage Items:${RESET}`);
1457
1927
  for (const s of pallet2.storage) {
1458
- const doc = s.docs[0] ? ` — ${s.docs[0].slice(0, 80)}` : "";
1459
- console.log(` ${CYAN}${s.name}${RESET}${DIM}${doc}${RESET}`);
1928
+ const valueType = describeType(meta.lookup, s.valueTypeId);
1929
+ let typeSuffix;
1930
+ if (s.keyTypeId != null) {
1931
+ const keyType = describeType(meta.lookup, s.keyTypeId);
1932
+ typeSuffix = `: ${keyType} → ${valueType} [map]`;
1933
+ } else {
1934
+ typeSuffix = `: ${valueType}`;
1935
+ }
1936
+ console.log(` ${CYAN}${s.name}${RESET}${DIM}${typeSuffix}${RESET}`);
1937
+ const summary = firstSentence(s.docs);
1938
+ if (summary) {
1939
+ console.log(` ${DIM}${summary}${RESET}`);
1940
+ }
1460
1941
  }
1461
1942
  console.log();
1462
1943
  }
1463
1944
  if (pallet2.constants.length) {
1464
1945
  console.log(` ${BOLD}Constants:${RESET}`);
1465
1946
  for (const c of pallet2.constants) {
1466
- const doc = c.docs[0] ? ` — ${c.docs[0].slice(0, 80)}` : "";
1467
- console.log(` ${CYAN}${c.name}${RESET}${DIM}${doc}${RESET}`);
1947
+ const typeStr = describeType(meta.lookup, c.typeId);
1948
+ console.log(` ${CYAN}${c.name}${RESET}${DIM}: ${typeStr}${RESET}`);
1949
+ const summary = firstSentence(c.docs);
1950
+ if (summary) {
1951
+ console.log(` ${DIM}${summary}${RESET}`);
1952
+ }
1953
+ }
1954
+ console.log();
1955
+ }
1956
+ if (pallet2.calls.length) {
1957
+ console.log(` ${BOLD}Calls:${RESET}`);
1958
+ for (const c of pallet2.calls) {
1959
+ const args = describeCallArgs(meta, pallet2.name, c.name);
1960
+ console.log(` ${CYAN}${c.name}${RESET}${DIM}${args}${RESET}`);
1961
+ const summary = firstSentence(c.docs);
1962
+ if (summary) {
1963
+ console.log(` ${DIM}${summary}${RESET}`);
1964
+ }
1965
+ }
1966
+ console.log();
1967
+ }
1968
+ if (pallet2.events.length) {
1969
+ console.log(` ${BOLD}Events:${RESET}`);
1970
+ for (const e of pallet2.events) {
1971
+ const fields = describeEventFields(meta, pallet2.name, e.name);
1972
+ console.log(` ${CYAN}${e.name}${RESET}${DIM}${fields}${RESET}`);
1973
+ const summary = firstSentence(e.docs);
1974
+ if (summary) {
1975
+ console.log(` ${DIM}${summary}${RESET}`);
1976
+ }
1977
+ }
1978
+ console.log();
1979
+ }
1980
+ if (pallet2.errors.length) {
1981
+ console.log(` ${BOLD}Errors:${RESET}`);
1982
+ for (const e of pallet2.errors) {
1983
+ console.log(` ${CYAN}${e.name}${RESET}`);
1984
+ const summary = firstSentence(e.docs);
1985
+ if (summary) {
1986
+ console.log(` ${DIM}${summary}${RESET}`);
1987
+ }
1468
1988
  }
1469
1989
  console.log();
1470
1990
  }
@@ -1501,9 +2021,45 @@ function registerInspectCommand(cli) {
1501
2021
  console.log();
1502
2022
  return;
1503
2023
  }
2024
+ const callItem = pallet.calls.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
2025
+ if (callItem) {
2026
+ printHeading(`${pallet.name}.${callItem.name} (Call)`);
2027
+ const args = describeCallArgs(meta, pallet.name, callItem.name);
2028
+ console.log(` ${BOLD}Args:${RESET} ${args}`);
2029
+ if (callItem.docs.length) {
2030
+ console.log();
2031
+ printDocs(callItem.docs);
2032
+ }
2033
+ console.log();
2034
+ return;
2035
+ }
2036
+ const eventItem = pallet.events.find((e) => e.name.toLowerCase() === itemName.toLowerCase());
2037
+ if (eventItem) {
2038
+ printHeading(`${pallet.name}.${eventItem.name} (Event)`);
2039
+ const fields = describeEventFields(meta, pallet.name, eventItem.name);
2040
+ console.log(` ${BOLD}Fields:${RESET} ${fields}`);
2041
+ if (eventItem.docs.length) {
2042
+ console.log();
2043
+ printDocs(eventItem.docs);
2044
+ }
2045
+ console.log();
2046
+ return;
2047
+ }
2048
+ const errorItem = pallet.errors.find((e) => e.name.toLowerCase() === itemName.toLowerCase());
2049
+ if (errorItem) {
2050
+ printHeading(`${pallet.name}.${errorItem.name} (Error)`);
2051
+ if (errorItem.docs.length) {
2052
+ printDocs(errorItem.docs);
2053
+ }
2054
+ console.log();
2055
+ return;
2056
+ }
1504
2057
  const allItems = [
1505
2058
  ...pallet.storage.map((s) => s.name),
1506
- ...pallet.constants.map((c) => c.name)
2059
+ ...pallet.constants.map((c) => c.name),
2060
+ ...pallet.calls.map((c) => c.name),
2061
+ ...pallet.events.map((e) => e.name),
2062
+ ...pallet.errors.map((e) => e.name)
1507
2063
  ];
1508
2064
  throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems));
1509
2065
  });
@@ -1549,10 +2105,36 @@ function parseStructArgs(meta, fields, args, callLabel) {
1549
2105
  for (let i = 0;i < fieldNames.length; i++) {
1550
2106
  const name = fieldNames[i];
1551
2107
  const entry = fields[name];
1552
- result[name] = parseTypedArg(meta, entry, args[i]);
2108
+ try {
2109
+ result[name] = parseTypedArg(meta, entry, args[i]);
2110
+ } catch (err) {
2111
+ const typeDesc = describeType(meta.lookup, entry.id);
2112
+ throw new Error(`Invalid value for argument '${name}' (expected ${typeDesc}): ${JSON.stringify(args[i])}
2113
+ ` + ` Hint: ${typeHint(entry, meta)}`, { cause: err });
2114
+ }
1553
2115
  }
1554
2116
  return result;
1555
2117
  }
2118
+ function typeHint(entry, meta) {
2119
+ const resolved = entry.type === "lookupEntry" ? entry.value : entry;
2120
+ switch (resolved.type) {
2121
+ case "enum": {
2122
+ const variants = Object.keys(resolved.value);
2123
+ if (variants.length <= 6)
2124
+ return `a variant: ${variants.join(" | ")}`;
2125
+ return `one of ${variants.length} variants (e.g. ${variants.slice(0, 3).join(", ")})`;
2126
+ }
2127
+ case "struct":
2128
+ return `a JSON object with fields: ${Object.keys(resolved.value).join(", ")}`;
2129
+ case "tuple":
2130
+ return "a JSON array";
2131
+ case "sequence":
2132
+ case "array":
2133
+ return "a JSON array or hex-encoded bytes";
2134
+ default:
2135
+ return describeType(meta.lookup, entry.id);
2136
+ }
2137
+ }
1556
2138
  function normalizeValue(lookup, entry, value) {
1557
2139
  let resolved = entry;
1558
2140
  while (resolved.type === "lookupEntry") {
@@ -2084,10 +2666,66 @@ function decodeCall(meta, callHex) {
2084
2666
  const callName = decoded.call.value.name;
2085
2667
  const argsStr = formatDecodedArgs(decoded.args.value);
2086
2668
  return `${palletName}.${callName}${argsStr}`;
2669
+ } catch {}
2670
+ try {
2671
+ return decodeCallFallback(meta, callHex);
2087
2672
  } catch {
2088
2673
  return "(unable to decode)";
2089
2674
  }
2090
2675
  }
2676
+ function decodeCallFallback(meta, callHex) {
2677
+ const callTypeId = meta.lookup.call;
2678
+ if (callTypeId == null)
2679
+ throw new Error("No RuntimeCall type ID");
2680
+ const codec = meta.builder.buildDefinition(callTypeId);
2681
+ const decoded = codec.dec(Binary3.fromHex(callHex).asBytes());
2682
+ const palletName = decoded.type;
2683
+ const call = decoded.value;
2684
+ const callName = call.type;
2685
+ const args = call.value;
2686
+ if (args === undefined || args === null) {
2687
+ return `${palletName}.${callName}`;
2688
+ }
2689
+ const argsStr = formatRawDecoded(args);
2690
+ return `${palletName}.${callName} ${argsStr}`;
2691
+ }
2692
+ function formatRawDecoded(value) {
2693
+ if (value === undefined || value === null)
2694
+ return "null";
2695
+ if (value instanceof Binary3)
2696
+ return value.asHex();
2697
+ if (typeof value === "bigint")
2698
+ return value.toString();
2699
+ if (typeof value === "string")
2700
+ return value;
2701
+ if (typeof value === "number")
2702
+ return value.toString();
2703
+ if (typeof value === "boolean")
2704
+ return value.toString();
2705
+ if (Array.isArray(value)) {
2706
+ if (value.length === 0)
2707
+ return "[]";
2708
+ return `[${value.map(formatRawDecoded).join(", ")}]`;
2709
+ }
2710
+ if (typeof value === "object") {
2711
+ const obj = value;
2712
+ if ("type" in obj && typeof obj.type === "string") {
2713
+ const inner = obj.value;
2714
+ if (inner === undefined || inner === null)
2715
+ return obj.type;
2716
+ const innerStr = formatRawDecoded(inner);
2717
+ if (innerStr.startsWith("{"))
2718
+ return `${obj.type} ${innerStr}`;
2719
+ return `${obj.type}(${innerStr})`;
2720
+ }
2721
+ const entries = Object.entries(obj);
2722
+ if (entries.length === 0)
2723
+ return "{}";
2724
+ const fields = entries.map(([k, v]) => `${k}: ${formatRawDecoded(v)}`).join(", ");
2725
+ return `{ ${fields} }`;
2726
+ }
2727
+ return String(value);
2728
+ }
2091
2729
  function formatDecodedArgs(decoded) {
2092
2730
  return formatDecoded(decoded);
2093
2731
  }
@@ -2211,14 +2849,26 @@ function parseCallArgs(meta, palletName, callName, args) {
2211
2849
  if (args.length !== 1) {
2212
2850
  throw new Error(`${palletName}.${callName} takes 1 argument (${describeType(meta.lookup, inner.id)}), but ${args.length} provided.`);
2213
2851
  }
2214
- return parseTypedArg2(meta, inner, args[0]);
2852
+ try {
2853
+ return parseTypedArg2(meta, inner, args[0]);
2854
+ } catch (err) {
2855
+ const typeDesc = describeType(meta.lookup, inner.id);
2856
+ throw new Error(`Invalid value for argument 0 (expected ${typeDesc}): ${JSON.stringify(args[0])}`, { cause: err });
2857
+ }
2215
2858
  }
2216
2859
  if (variant.type === "tuple") {
2217
2860
  const entries = variant.value;
2218
2861
  if (args.length !== entries.length) {
2219
2862
  throw new Error(`${palletName}.${callName} takes ${entries.length} arguments, but ${args.length} provided.`);
2220
2863
  }
2221
- return entries.map((entry, i) => parseTypedArg2(meta, entry, args[i]));
2864
+ return entries.map((entry, i) => {
2865
+ try {
2866
+ return parseTypedArg2(meta, entry, args[i]);
2867
+ } catch (err) {
2868
+ const typeDesc = describeType(meta.lookup, entry.id);
2869
+ throw new Error(`Invalid value for argument ${i} (expected ${typeDesc}): ${JSON.stringify(args[i])}`, { cause: err });
2870
+ }
2871
+ });
2222
2872
  }
2223
2873
  return args.length === 0 ? undefined : args.map(parseValue);
2224
2874
  }
@@ -2233,10 +2883,36 @@ function parseStructArgs2(meta, fields, args, callLabel) {
2233
2883
  for (let i = 0;i < fieldNames.length; i++) {
2234
2884
  const name = fieldNames[i];
2235
2885
  const entry = fields[name];
2236
- result[name] = parseTypedArg2(meta, entry, args[i]);
2886
+ try {
2887
+ result[name] = parseTypedArg2(meta, entry, args[i]);
2888
+ } catch (err) {
2889
+ const typeDesc = describeType(meta.lookup, entry.id);
2890
+ throw new Error(`Invalid value for argument '${name}' (expected ${typeDesc}): ${JSON.stringify(args[i])}
2891
+ ` + ` Hint: ${typeHint2(entry, meta)}`, { cause: err });
2892
+ }
2237
2893
  }
2238
2894
  return result;
2239
2895
  }
2896
+ function typeHint2(entry, meta) {
2897
+ const resolved = entry.type === "lookupEntry" ? entry.value : entry;
2898
+ switch (resolved.type) {
2899
+ case "enum": {
2900
+ const variants = Object.keys(resolved.value);
2901
+ if (variants.length <= 6)
2902
+ return `a variant: ${variants.join(" | ")}`;
2903
+ return `one of ${variants.length} variants (e.g. ${variants.slice(0, 3).join(", ")})`;
2904
+ }
2905
+ case "struct":
2906
+ return `a JSON object with fields: ${Object.keys(resolved.value).join(", ")}`;
2907
+ case "tuple":
2908
+ return "a JSON array";
2909
+ case "sequence":
2910
+ case "array":
2911
+ return "a JSON array or hex-encoded bytes";
2912
+ default:
2913
+ return describeType(meta.lookup, entry.id);
2914
+ }
2915
+ }
2240
2916
  function normalizeValue2(lookup, entry, value) {
2241
2917
  let resolved = entry;
2242
2918
  while (resolved.type === "lookupEntry") {
@@ -2716,6 +3392,7 @@ registerChainCommands(cli);
2716
3392
  registerInspectCommand(cli);
2717
3393
  registerQueryCommand(cli);
2718
3394
  registerConstCommand(cli);
3395
+ registerFocusedInspectCommands(cli);
2719
3396
  registerAccountCommands(cli);
2720
3397
  registerTxCommand(cli);
2721
3398
  registerHashCommand(cli);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {