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.
- package/README.md +50 -1
- package/dist/cli.mjs +238 -24
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 });
|