polkadot-cli 1.15.1 → 1.16.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 +50 -1
  2. package/dist/cli.mjs +238 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,6 +20,8 @@ Ships with Polkadot and all system parachains preconfigured with multiple fallba
20
20
  - ✅ Account management — BIP39 mnemonics, derivation paths, env-backed secrets, watch-only, dev accounts
21
21
  - ✅ Named address resolution across all commands
22
22
  - ✅ Runtime API calls — `dot polkadot.apis.Core.version`
23
+ - ✅ Full-metadata dump — `dot metadata <chain>` emits one JSON blob with pallets, runtime APIs, and transaction extensions (or raw SCALE bytes via `--raw`)
24
+ - ✅ Stale-metadata detection — when a tx or query fails because the runtime upgraded, the CLI tells you exactly which `dot chain update` to run
23
25
  - ✅ Chain topology — relay/parachain hierarchy with tree display and auto-detection
24
26
  - ✅ Batteries included — all system parachains and testnets already setup to be used
25
27
  - ✅ File-based commands — run any command from a YAML/JSON file with variable substitution
@@ -577,6 +579,32 @@ The pallet listing view shows type information inline so you can understand item
577
579
 
578
580
  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.
579
581
 
582
+ ### Dump full metadata
583
+
584
+ `dot metadata <chain>` fetches the chain's runtime metadata and prints **everything** as one JSON blob — pallets (with calls, events, errors, storage, constants), runtime APIs, and transaction extensions, headed by a runtime fingerprint (`specVersion`, `transactionVersion`, `codeHash`, etc.). Useful for feeding LLM agents or pipelines that want a single source of truth instead of walking `dot inspect` piecemeal.
585
+
586
+ ```bash
587
+ # Decoded JSON — fetches fresh from the chain
588
+ dot metadata polkadot
589
+
590
+ # SCALE-encoded metadata bytes as a single 0x… hex line (for tools that re-parse)
591
+ dot metadata polkadot --raw
592
+
593
+ # Skip the network round-trip and use cached metadata
594
+ dot metadata polkadot --cached
595
+
596
+ # Override the RPC endpoint
597
+ dot metadata polkadot --rpc wss://rpc.example.com
598
+
599
+ # Slice with jq — list call names in a pallet
600
+ dot metadata polkadot | jq '.pallets[] | select(.name=="Balances") | .calls[].name'
601
+
602
+ # All transaction extension identifiers
603
+ dot metadata polkadot | jq '.transactionExtensions[].identifier'
604
+ ```
605
+
606
+ The decoded JSON is structured-only (no colorization), so it's safe to redirect to a file or pipe into other tools. The default fetches fresh from the RPC and atomically updates the local metadata cache and runtime-fingerprint sidecar — so subsequent commands benefit from the freshest possible metadata.
607
+
580
608
  ### Runtime APIs
581
609
 
582
610
  Browse and call Substrate runtime APIs. These are top-level APIs exposed by the runtime (e.g. `Core`, `AccountNonceApi`, `TransactionPaymentApi`), accessed as `dot <chain>.apis.ApiName.method`.
@@ -848,6 +876,22 @@ dot tx.Balances.transferKeepAlive 5FHneW46... 999999999999999999 --from alice --
848
876
  echo $? # 1
849
877
  ```
850
878
 
879
+ #### Stale metadata detection
880
+
881
+ When a `tx`, `--dry-run`, or `query` fails with an error that smells like stale metadata — a runtime wasm trap, a SCALE codec/decode error, or a fee-estimation panic — the CLI compares your locally cached metadata's runtime fingerprint against the live chain. If it has changed, the CLI appends a one-line suggestion telling you exactly which command to run:
882
+
883
+ ```
884
+ Error: The runtime rejected this transaction in the runtime's validate_transaction step.
885
+ Cause: a runtime invariant failed — typically the call's arguments are out of range, …
886
+
887
+ ⚠ Local metadata for "paseo-people-next" is out of date (spec 1018 → 1020).
888
+ Run: dot chain update paseo-people-next
889
+ ```
890
+
891
+ The fingerprint includes the runtime code hash, so the check also catches local-node restarts where the wasm changed but `specVersion` was kept the same. No automatic refetch happens — the original error still propagates with a non-zero exit code, you just get an actionable suggestion.
892
+
893
+ The check only fires on suspected-stale errors, so the happy path pays no extra RPC. Set `DOT_TRUST_CACHED_METADATA=1` to disable the check entirely (e.g. for CI loops where you've just refreshed manually).
894
+
851
895
  #### Argument parsing errors
852
896
 
853
897
  When a call argument is invalid, the CLI shows a contextual error message with the argument name, the expected type, and a hint:
@@ -1450,11 +1494,16 @@ Config and metadata caches live in `~/.polkadot/` by default:
1450
1494
  ├── update-check.json # cached update check result
1451
1495
  └── chains/
1452
1496
  └── polkadot/
1453
- └── metadata.bin # cached SCALE-encoded metadata
1497
+ ├── metadata.bin # cached SCALE-encoded metadata
1498
+ └── metadata.fingerprint.json # runtime fingerprint (specVersion, codeHash, …) for stale-metadata detection
1454
1499
  ```
1455
1500
 
1456
1501
  > **Warning:** `accounts.json` stores secrets (mnemonics and seeds) in **plain text**. Encrypted-at-rest storage is planned but not yet implemented. Keep appropriate file permissions (`chmod 600 ~/.polkadot/accounts.json`) and do not use this for high-value mainnet accounts.
1457
1502
 
1503
+ ### `DOT_TRUST_CACHED_METADATA` — skip the staleness check
1504
+
1505
+ Set `DOT_TRUST_CACHED_METADATA=1` to disable the post-failure stale-metadata check on `dot tx`, `dot tx --dry-run`, and `dot query`. When set, errors propagate exactly as the runtime / RPC reported them, with no extra `state_getRuntimeVersion` / `state_getStorageHash` round-trip. Useful in CI loops where you've just refreshed metadata manually and don't want the overhead.
1506
+
1458
1507
  ### `DOT_HOME` — redirect the config directory
1459
1508
 
1460
1509
  Set the `DOT_HOME` environment variable to point at a different directory. When set, the CLI reads and writes **everything** (config, accounts, metadata, update cache) under that path — no `.polkadot` suffix is appended.
package/dist/cli.mjs CHANGED
@@ -16,7 +16,31 @@ var __export = (target, all) => {
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
18
  // src/utils/errors.ts
19
- var CliError, ConnectionError, MetadataError;
19
+ function isLikelyStaleMetadataError(err) {
20
+ const msg = err instanceof Error ? err.message : typeof err === "string" ? err : "";
21
+ if (!msg)
22
+ return false;
23
+ return STALE_METADATA_PATTERNS.some((re) => re.test(msg));
24
+ }
25
+ function formatRuntimeError(err) {
26
+ const msg = err instanceof Error ? err.message : String(err);
27
+ if (/wasm trap|wasm `?unreachable`? instruction|Execution aborted due to trap/i.test(msg)) {
28
+ const frames = Array.from(msg.matchAll(/\.wasm!([A-Za-z_]\w+)/g)).map((m) => m[1]);
29
+ const fn = [...frames].reverse().find((name) => !/^(?:__rustc|core::panicking|rust_begin_unwind|panic_fmt)/.test(name));
30
+ const where = fn?.includes("validate_transaction") ? "the runtime's validate_transaction step" : fn ? `runtime function ${fn}` : "the runtime";
31
+ return [
32
+ `The runtime rejected this transaction in ${where}.`,
33
+ " Cause: a runtime invariant failed — typically the call's arguments are out of range, reference an unknown id, or violate a precondition.",
34
+ " Tip: re-check the arguments and the signing account's permissions; --dry-run will surface the same error before signing."
35
+ ].join(`
36
+ `);
37
+ }
38
+ if (/Invalid Transaction/i.test(msg)) {
39
+ return `Transaction rejected as invalid by the runtime: ${msg}`;
40
+ }
41
+ return msg;
42
+ }
43
+ var CliError, ConnectionError, MetadataError, STALE_METADATA_PATTERNS;
20
44
  var init_errors = __esm(() => {
21
45
  CliError = class CliError extends Error {
22
46
  constructor(message) {
@@ -36,8 +60,28 @@ var init_errors = __esm(() => {
36
60
  this.name = "MetadataError";
37
61
  }
38
62
  };
63
+ STALE_METADATA_PATTERNS = [
64
+ /wasm trap/i,
65
+ /wasm `?unreachable`? instruction/i,
66
+ /Execution aborted due to trap/i,
67
+ /codec/i,
68
+ /decod(e|ing)/i,
69
+ /Lookup failed/i,
70
+ /metadata.*mismatch/i
71
+ ];
39
72
  });
40
73
 
74
+ // src/utils/runtime-fingerprint.ts
75
+ function fingerprintsMatch(a, b) {
76
+ return a.codeHash === b.codeHash && a.specVersion === b.specVersion && a.transactionVersion === b.transactionVersion;
77
+ }
78
+ function isRuntimeFingerprint(value) {
79
+ if (!value || typeof value !== "object")
80
+ return false;
81
+ const v = value;
82
+ return typeof v.specName === "string" && typeof v.specVersion === "number" && typeof v.transactionVersion === "number" && typeof v.implName === "string" && typeof v.implVersion === "number" && typeof v.authoringVersion === "number" && typeof v.codeHash === "string" && typeof v.fetchedAt === "string";
83
+ }
84
+
41
85
  // src/config/types.ts
42
86
  function primaryRpc(rpc) {
43
87
  return Array.isArray(rpc) ? rpc[0] : rpc;
@@ -193,6 +237,9 @@ function getChainDir(chainName) {
193
237
  function getMetadataPath(chainName) {
194
238
  return join(getChainDir(chainName), "metadata.bin");
195
239
  }
240
+ function getMetadataFingerprintPath(chainName) {
241
+ return join(getChainDir(chainName), "metadata.fingerprint.json");
242
+ }
196
243
  function getConfigPath() {
197
244
  return join(getConfigDir(), "config.json");
198
245
  }
@@ -238,10 +285,25 @@ async function loadMetadata(chainName) {
238
285
  }
239
286
  return null;
240
287
  }
241
- async function saveMetadata(chainName, data) {
288
+ async function saveMetadata(chainName, data, fingerprint) {
242
289
  const dir = getChainDir(chainName);
243
290
  await ensureDir(dir);
244
291
  await writeFile(getMetadataPath(chainName), data);
292
+ if (fingerprint) {
293
+ await writeFile(getMetadataFingerprintPath(chainName), `${JSON.stringify(fingerprint, null, 2)}
294
+ `);
295
+ }
296
+ }
297
+ async function loadMetadataFingerprint(chainName) {
298
+ const path = getMetadataFingerprintPath(chainName);
299
+ if (!await fileExists(path))
300
+ return null;
301
+ try {
302
+ const parsed = JSON.parse(await readFile(path, "utf-8"));
303
+ return isRuntimeFingerprint(parsed) ? parsed : null;
304
+ } catch {
305
+ return null;
306
+ }
245
307
  }
246
308
  async function removeChainData(chainName) {
247
309
  const dir = getChainDir(chainName);
@@ -809,28 +871,52 @@ function parseMetadata(raw) {
809
871
  const builder = getDynamicBuilder(lookup);
810
872
  return { unified, lookup, builder, version: version2 };
811
873
  }
874
+ async function getRuntimeFingerprint(clientHandle, chainName) {
875
+ const { client } = clientHandle;
876
+ const [version2, codeHash] = await Promise.all([
877
+ withTimeout(client._request("state_getRuntimeVersion", []), chainName),
878
+ withTimeout(client._request("state_getStorageHash", ["0x3a636f6465"]), chainName)
879
+ ]);
880
+ return {
881
+ specName: version2.specName,
882
+ specVersion: version2.specVersion,
883
+ transactionVersion: version2.transactionVersion,
884
+ implName: version2.implName,
885
+ implVersion: version2.implVersion,
886
+ authoringVersion: version2.authoringVersion,
887
+ codeHash,
888
+ fetchedAt: new Date().toISOString()
889
+ };
890
+ }
812
891
  async function fetchMetadataFromChain(clientHandle, chainName) {
813
892
  const { client } = clientHandle;
893
+ let bytes;
814
894
  try {
815
895
  const hex = await withTimeout(client._request("state_call", ["Metadata_metadata_at_version", v15Arg]), chainName);
816
896
  const raw = hexToBytes(hex);
817
897
  const decoded = optionalOpaqueBytes.dec(raw);
818
898
  if (decoded !== undefined) {
819
- const bytes = new Uint8Array(decoded);
820
- await saveMetadata(chainName, bytes);
821
- return bytes;
899
+ bytes = new Uint8Array(decoded);
822
900
  }
823
901
  } catch {}
902
+ if (!bytes) {
903
+ try {
904
+ const hex = await withTimeout(client._request("state_getMetadata", []), chainName);
905
+ bytes = hexToBytes(hex);
906
+ } catch (err) {
907
+ if (err instanceof ConnectionError)
908
+ throw err;
909
+ throw new ConnectionError(`Failed to fetch metadata for "${chainName}": ${err instanceof Error ? err.message : err}. ` + "Check that the RPC endpoint is correct and reachable.");
910
+ }
911
+ }
912
+ let fingerprint;
824
913
  try {
825
- const hex = await withTimeout(client._request("state_getMetadata", []), chainName);
826
- const bytes = hexToBytes(hex);
827
- await saveMetadata(chainName, bytes);
828
- return bytes;
829
- } catch (err) {
830
- if (err instanceof ConnectionError)
831
- throw err;
832
- throw new ConnectionError(`Failed to fetch metadata for "${chainName}": ${err instanceof Error ? err.message : err}. ` + "Check that the RPC endpoint is correct and reachable.");
914
+ fingerprint = await getRuntimeFingerprint(clientHandle, chainName);
915
+ } catch {
916
+ fingerprint = undefined;
833
917
  }
918
+ await saveMetadata(chainName, bytes, fingerprint);
919
+ return bytes;
834
920
  }
835
921
  function withTimeout(promise, chainName) {
836
922
  return Promise.race([
@@ -838,6 +924,33 @@ function withTimeout(promise, chainName) {
838
924
  new Promise((_, reject) => setTimeout(() => reject(new ConnectionError(`Timed out fetching metadata for "${chainName}" after ${METADATA_TIMEOUT_MS / 1000}s. ` + "Check that the RPC endpoint is correct and reachable.")), METADATA_TIMEOUT_MS))
839
925
  ]);
840
926
  }
927
+ async function withStalenessSuggestion(chainName, clientHandle, task) {
928
+ try {
929
+ return await task();
930
+ } catch (err) {
931
+ if (process.env.DOT_TRUST_CACHED_METADATA === "1")
932
+ throw err;
933
+ if (!isLikelyStaleMetadataError(err))
934
+ throw err;
935
+ let live;
936
+ try {
937
+ live = await getRuntimeFingerprint(clientHandle, chainName);
938
+ } catch {
939
+ throw err;
940
+ }
941
+ const cached = await loadMetadataFingerprint(chainName);
942
+ if (!cached)
943
+ throw err;
944
+ if (fingerprintsMatch(cached, live))
945
+ throw err;
946
+ const original = err instanceof Error ? formatRuntimeError(err) : String(err);
947
+ const versionNote = cached.specVersion !== live.specVersion ? `spec ${cached.specVersion} → ${live.specVersion}` : `runtime code hash changed (same spec ${live.specVersion}; likely a node restart with new wasm)`;
948
+ throw new CliError(`${original}
949
+
950
+ ` + `⚠ Local metadata for "${chainName}" is out of date (${versionNote}).
951
+ ` + ` Run: dot chain update ${chainName}`);
952
+ }
953
+ }
841
954
  async function getOrFetchMetadata(chainName, clientHandle) {
842
955
  let raw = await loadMetadata(chainName);
843
956
  if (!raw) {
@@ -2584,7 +2697,7 @@ var init_complete = __esm(() => {
2584
2697
  // src/cli.ts
2585
2698
  import cac from "cac";
2586
2699
  // package.json
2587
- var version = "1.15.1";
2700
+ var version = "1.16.0";
2588
2701
 
2589
2702
  // src/commands/account.ts
2590
2703
  init_accounts_store();
@@ -5081,6 +5194,61 @@ function registerInspectCommand(cli) {
5081
5194
  });
5082
5195
  }
5083
5196
 
5197
+ // src/commands/metadata.ts
5198
+ init_store();
5199
+ init_client();
5200
+ init_metadata();
5201
+ init_output();
5202
+ init_errors();
5203
+ function buildMetadataPayload(chainName, meta, fingerprint) {
5204
+ return {
5205
+ chain: chainName,
5206
+ runtime: { ...fingerprint ?? {}, metadataVersion: meta.version },
5207
+ pallets: listPallets(meta),
5208
+ runtimeApis: listRuntimeApis(meta),
5209
+ transactionExtensions: getSignedExtensions(meta)
5210
+ };
5211
+ }
5212
+ async function handleMetadata(chain, opts) {
5213
+ const config = await loadConfig();
5214
+ const { name: chainName, chain: chainConfig } = resolveChain(config, chain);
5215
+ let rawBytes;
5216
+ if (opts.cached) {
5217
+ rawBytes = await loadMetadata(chainName);
5218
+ if (!rawBytes) {
5219
+ throw new CliError(`No cached metadata for "${chainName}". Run \`dot chain update ${chainName}\` first, or omit --cached to fetch fresh.`);
5220
+ }
5221
+ } else {
5222
+ const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
5223
+ try {
5224
+ await fetchMetadataFromChain(clientHandle, chainName);
5225
+ } finally {
5226
+ clientHandle.destroy();
5227
+ }
5228
+ rawBytes = await loadMetadata(chainName);
5229
+ if (!rawBytes) {
5230
+ throw new CliError(`Failed to load metadata for "${chainName}" after fetch.`);
5231
+ }
5232
+ }
5233
+ if (opts.raw) {
5234
+ await writeStdout(`0x${Buffer.from(rawBytes).toString("hex")}
5235
+ `);
5236
+ return;
5237
+ }
5238
+ const meta = parseMetadata(rawBytes);
5239
+ const fingerprint = await loadMetadataFingerprint(chainName);
5240
+ await writeStdout(`${formatJson(buildMetadataPayload(chainName, meta, fingerprint))}
5241
+ `);
5242
+ }
5243
+ function writeStdout(text) {
5244
+ return new Promise((resolve) => {
5245
+ process.stdout.write(text, () => resolve());
5246
+ });
5247
+ }
5248
+ function registerMetadataCommand(cli) {
5249
+ cli.command("metadata <chain>", "Fetch chain metadata (decoded JSON; --raw for SCALE hex)").option("--raw", "Print SCALE-encoded metadata bytes as hex instead of decoded JSON").option("--cached", "Use cached metadata instead of fetching fresh from the chain").option("--rpc <url>", "Override RPC endpoint(s)").action((chain, opts) => handleMetadata(chain, opts));
5250
+ }
5251
+
5084
5252
  // src/commands/parachain.ts
5085
5253
  init_accounts();
5086
5254
  init_output();
@@ -5276,13 +5444,13 @@ async function handleQuery(target, keys, opts) {
5276
5444
  console.log(`${DIM}Hint: use --dump to fetch all entries${RESET}`);
5277
5445
  return;
5278
5446
  }
5279
- const entries = await storageApi.getEntries(...parsedKeys);
5447
+ const entries = await withStalenessSuggestion(chainName, clientHandle, () => storageApi.getEntries(...parsedKeys));
5280
5448
  printResult(entries.map((e) => ({
5281
5449
  keys: e.keyArgs,
5282
5450
  value: e.value
5283
5451
  })), format);
5284
5452
  } else {
5285
- const result = await storageApi.getValue(...parsedKeys);
5453
+ const result = await withStalenessSuggestion(chainName, clientHandle, () => storageApi.getValue(...parsedKeys));
5286
5454
  printResult(result, format);
5287
5455
  }
5288
5456
  } finally {
@@ -5735,10 +5903,11 @@ async function handleTx(target, args, opts) {
5735
5903
  if (opts.dryRun) {
5736
5904
  const signerAddress = toSs58(signer.publicKey);
5737
5905
  let estimatedFees;
5906
+ let estimationError;
5738
5907
  try {
5739
- estimatedFees = String(await tx.getEstimatedFees(signer?.publicKey, txOptions));
5740
- } catch {
5741
- estimatedFees = undefined;
5908
+ estimatedFees = String(await withStalenessSuggestion(chainName, clientHandle, () => tx.getEstimatedFees(signer?.publicKey, txOptions)));
5909
+ } catch (err) {
5910
+ estimationError = err instanceof Error ? err.message : String(err);
5742
5911
  }
5743
5912
  if (isJsonOutput(opts)) {
5744
5913
  const result2 = {
@@ -5748,6 +5917,8 @@ async function handleTx(target, args, opts) {
5748
5917
  decoded: decodedStr,
5749
5918
  estimatedFees
5750
5919
  };
5920
+ if (estimationError !== undefined)
5921
+ result2.estimationError = estimationError;
5751
5922
  if (nonce !== undefined)
5752
5923
  result2.nonce = nonce;
5753
5924
  if (tip !== undefined)
@@ -5779,6 +5950,12 @@ async function handleTx(target, args, opts) {
5779
5950
  console.log(` ${BOLD}Estimated fees:${RESET} ${estimatedFees}`);
5780
5951
  } else {
5781
5952
  console.log(` ${BOLD}Estimated fees:${RESET} ${YELLOW}unable to estimate${RESET}`);
5953
+ if (estimationError) {
5954
+ const formatted = formatRuntimeError(estimationError).replace(/\n/g, `
5955
+ `);
5956
+ console.log(` ${YELLOW}⚠${RESET} ${formatted}`);
5957
+ console.log(` ${DIM}Submitting this transaction is likely to fail.${RESET}`);
5958
+ }
5782
5959
  }
5783
5960
  return;
5784
5961
  }
@@ -5798,7 +5975,7 @@ async function handleTx(target, args, opts) {
5798
5975
  }
5799
5976
  const observable = clientHandle.client.submitAndWatch(generalTx, at);
5800
5977
  if (isJsonOutput(opts)) {
5801
- const result3 = await watchTransactionJson(observable, waitLevel, { unsigned: true });
5978
+ const result3 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransactionJson(observable, waitLevel, { unsigned: true }));
5802
5979
  const rpcUrl3 = primaryRpc(opts.rpc ?? chainConfig.rpc);
5803
5980
  if (result3.type === "broadcasted") {
5804
5981
  printJsonLine({ event: "broadcasted", txHash: result3.txHash });
@@ -5830,7 +6007,7 @@ async function handleTx(target, args, opts) {
5830
6007
  }
5831
6008
  return;
5832
6009
  }
5833
- const result2 = await watchTransaction(observable, waitLevel, { unsigned: true });
6010
+ const result2 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransaction(observable, waitLevel, { unsigned: true }));
5834
6011
  console.log();
5835
6012
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
5836
6013
  console.log(` ${BOLD}Type:${RESET} unsigned (bare)`);
@@ -5879,7 +6056,7 @@ async function handleTx(target, args, opts) {
5879
6056
  return;
5880
6057
  }
5881
6058
  if (isJsonOutput(opts)) {
5882
- const result2 = await watchTransactionJson(tx.signSubmitAndWatch(signer, txOptions), waitLevel);
6059
+ const result2 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransactionJson(tx.signSubmitAndWatch(signer, txOptions), waitLevel));
5883
6060
  const rpcUrl2 = primaryRpc(opts.rpc ?? chainConfig.rpc);
5884
6061
  if (result2.type === "broadcasted") {
5885
6062
  printJsonLine({ event: "broadcasted", txHash: result2.txHash });
@@ -5910,7 +6087,7 @@ async function handleTx(target, args, opts) {
5910
6087
  }
5911
6088
  return;
5912
6089
  }
5913
- const result = await watchTransaction(tx.signSubmitAndWatch(signer, txOptions), waitLevel);
6090
+ const result = await withStalenessSuggestion(chainName, clientHandle, () => watchTransaction(tx.signSubmitAndWatch(signer, txOptions), waitLevel));
5914
6091
  console.log();
5915
6092
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
5916
6093
  console.log(` ${BOLD}Call:${RESET} ${callHex}`);
@@ -7237,6 +7414,34 @@ class CliError2 extends Error {
7237
7414
  this.name = "CliError";
7238
7415
  }
7239
7416
  }
7417
+ var PAPI_CLEANUP_PATTERNS = [
7418
+ /^Not connected$/,
7419
+ /DisjointError/,
7420
+ /ChainHead.*(aborted|stopped)/i
7421
+ ];
7422
+ function isPapiCleanupError(err) {
7423
+ if (!(err instanceof Error))
7424
+ return false;
7425
+ return PAPI_CLEANUP_PATTERNS.some((re) => re.test(err.message));
7426
+ }
7427
+ function formatRuntimeError2(err) {
7428
+ const msg = err instanceof Error ? err.message : String(err);
7429
+ if (/wasm trap|wasm `?unreachable`? instruction|Execution aborted due to trap/i.test(msg)) {
7430
+ const frames = Array.from(msg.matchAll(/\.wasm!([A-Za-z_]\w+)/g)).map((m) => m[1]);
7431
+ const fn = [...frames].reverse().find((name) => !/^(?:__rustc|core::panicking|rust_begin_unwind|panic_fmt)/.test(name));
7432
+ const where = fn?.includes("validate_transaction") ? "the runtime's validate_transaction step" : fn ? `runtime function ${fn}` : "the runtime";
7433
+ return [
7434
+ `The runtime rejected this transaction in ${where}.`,
7435
+ " Cause: a runtime invariant failed — typically the call's arguments are out of range, reference an unknown id, or violate a precondition.",
7436
+ " Tip: re-check the arguments and the signing account's permissions; --dry-run will surface the same error before signing."
7437
+ ].join(`
7438
+ `);
7439
+ }
7440
+ if (/Invalid Transaction/i.test(msg)) {
7441
+ return `Transaction rejected as invalid by the runtime: ${msg}`;
7442
+ }
7443
+ return msg;
7444
+ }
7240
7445
 
7241
7446
  // src/utils/parse-dot-path.ts
7242
7447
  var CATEGORY_ALIASES = {
@@ -7359,6 +7564,7 @@ if (process.argv[2] === "__complete") {
7359
7564
  console.log(" dot polkadot.const.Balances.ExistentialDeposit");
7360
7565
  console.log(" dot polkadot.events.Balances List events in Balances");
7361
7566
  console.log(" dot polkadot.apis.Core.version Call a runtime API");
7567
+ console.log(" dot metadata polkadot Dump runtime metadata as JSON");
7362
7568
  console.log(" dot polkadot.extensions List transaction extensions");
7363
7569
  console.log(" dot polkadot.extensions.CheckMortality Inspect one extension");
7364
7570
  console.log(" dot query.System.Number --chain polkadot --chain flag form");
@@ -7369,6 +7575,7 @@ if (process.argv[2] === "__complete") {
7369
7575
  console.log();
7370
7576
  console.log("Commands:");
7371
7577
  console.log(" inspect [target] Inspect chain metadata (alias: explore)");
7578
+ console.log(" metadata <chain> Dump runtime metadata as JSON (--raw for SCALE hex)");
7372
7579
  console.log(" chain Manage chain configurations");
7373
7580
  console.log(" account Manage accounts");
7374
7581
  console.log(" hash Hash utilities");
@@ -7395,6 +7602,7 @@ if (process.argv[2] === "__complete") {
7395
7602
  cli.option("--json", "Output as JSON (shorthand for --output json)");
7396
7603
  registerChainCommands(cli);
7397
7604
  registerInspectCommand(cli);
7605
+ registerMetadataCommand(cli);
7398
7606
  registerAccountCommands(cli);
7399
7607
  registerHashCommand(cli);
7400
7608
  registerSignCommand(cli);
@@ -7551,12 +7759,18 @@ if (process.argv[2] === "__complete") {
7551
7759
  if (err instanceof CliError2) {
7552
7760
  console.error(`Error: ${err.message}`);
7553
7761
  } else if (err instanceof Error) {
7554
- console.error(`Error: ${err.message}`);
7762
+ console.error(`Error: ${formatRuntimeError2(err)}`);
7555
7763
  } else {
7556
7764
  console.error("An unexpected error occurred:", err);
7557
7765
  }
7558
7766
  return showUpdateAndExit(1);
7559
7767
  }
7768
+ process.on("unhandledRejection", (reason) => {
7769
+ if (isPapiCleanupError(reason))
7770
+ return;
7771
+ console.error(`Error: ${formatRuntimeError2(reason)}`);
7772
+ process.exit(1);
7773
+ });
7560
7774
  async function main() {
7561
7775
  try {
7562
7776
  cli.parse(process.argv, { run: false });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "1.15.1",
3
+ "version": "1.16.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {