polkadot-cli 1.15.0 → 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 +246 -26
- package/package.json +2 -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) {
|
|
@@ -1251,7 +1364,10 @@ function normalizeValue(lookup, entry, value) {
|
|
|
1251
1364
|
innerResolved = innerResolved.value;
|
|
1252
1365
|
}
|
|
1253
1366
|
if (innerResolved.type === "primitive" && innerResolved.value === "u8" && typeof value === "string") {
|
|
1254
|
-
|
|
1367
|
+
const isHex = /^0x[0-9a-fA-F]*$/.test(value);
|
|
1368
|
+
if (resolved.type === "array" && isHex)
|
|
1369
|
+
return value;
|
|
1370
|
+
if (isHex)
|
|
1255
1371
|
return Binary2.fromHex(value);
|
|
1256
1372
|
return Binary2.fromText(value);
|
|
1257
1373
|
}
|
|
@@ -2581,7 +2697,7 @@ var init_complete = __esm(() => {
|
|
|
2581
2697
|
// src/cli.ts
|
|
2582
2698
|
import cac from "cac";
|
|
2583
2699
|
// package.json
|
|
2584
|
-
var version = "1.
|
|
2700
|
+
var version = "1.16.0";
|
|
2585
2701
|
|
|
2586
2702
|
// src/commands/account.ts
|
|
2587
2703
|
init_accounts_store();
|
|
@@ -5078,6 +5194,61 @@ function registerInspectCommand(cli) {
|
|
|
5078
5194
|
});
|
|
5079
5195
|
}
|
|
5080
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
|
+
|
|
5081
5252
|
// src/commands/parachain.ts
|
|
5082
5253
|
init_accounts();
|
|
5083
5254
|
init_output();
|
|
@@ -5273,13 +5444,13 @@ async function handleQuery(target, keys, opts) {
|
|
|
5273
5444
|
console.log(`${DIM}Hint: use --dump to fetch all entries${RESET}`);
|
|
5274
5445
|
return;
|
|
5275
5446
|
}
|
|
5276
|
-
const entries = await storageApi.getEntries(...parsedKeys);
|
|
5447
|
+
const entries = await withStalenessSuggestion(chainName, clientHandle, () => storageApi.getEntries(...parsedKeys));
|
|
5277
5448
|
printResult(entries.map((e) => ({
|
|
5278
5449
|
keys: e.keyArgs,
|
|
5279
5450
|
value: e.value
|
|
5280
5451
|
})), format);
|
|
5281
5452
|
} else {
|
|
5282
|
-
const result = await storageApi.getValue(...parsedKeys);
|
|
5453
|
+
const result = await withStalenessSuggestion(chainName, clientHandle, () => storageApi.getValue(...parsedKeys));
|
|
5283
5454
|
printResult(result, format);
|
|
5284
5455
|
}
|
|
5285
5456
|
} finally {
|
|
@@ -5732,10 +5903,11 @@ async function handleTx(target, args, opts) {
|
|
|
5732
5903
|
if (opts.dryRun) {
|
|
5733
5904
|
const signerAddress = toSs58(signer.publicKey);
|
|
5734
5905
|
let estimatedFees;
|
|
5906
|
+
let estimationError;
|
|
5735
5907
|
try {
|
|
5736
|
-
estimatedFees = String(await tx.getEstimatedFees(signer?.publicKey, txOptions));
|
|
5737
|
-
} catch {
|
|
5738
|
-
|
|
5908
|
+
estimatedFees = String(await withStalenessSuggestion(chainName, clientHandle, () => tx.getEstimatedFees(signer?.publicKey, txOptions)));
|
|
5909
|
+
} catch (err) {
|
|
5910
|
+
estimationError = err instanceof Error ? err.message : String(err);
|
|
5739
5911
|
}
|
|
5740
5912
|
if (isJsonOutput(opts)) {
|
|
5741
5913
|
const result2 = {
|
|
@@ -5745,6 +5917,8 @@ async function handleTx(target, args, opts) {
|
|
|
5745
5917
|
decoded: decodedStr,
|
|
5746
5918
|
estimatedFees
|
|
5747
5919
|
};
|
|
5920
|
+
if (estimationError !== undefined)
|
|
5921
|
+
result2.estimationError = estimationError;
|
|
5748
5922
|
if (nonce !== undefined)
|
|
5749
5923
|
result2.nonce = nonce;
|
|
5750
5924
|
if (tip !== undefined)
|
|
@@ -5776,6 +5950,12 @@ async function handleTx(target, args, opts) {
|
|
|
5776
5950
|
console.log(` ${BOLD}Estimated fees:${RESET} ${estimatedFees}`);
|
|
5777
5951
|
} else {
|
|
5778
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
|
+
}
|
|
5779
5959
|
}
|
|
5780
5960
|
return;
|
|
5781
5961
|
}
|
|
@@ -5795,7 +5975,7 @@ async function handleTx(target, args, opts) {
|
|
|
5795
5975
|
}
|
|
5796
5976
|
const observable = clientHandle.client.submitAndWatch(generalTx, at);
|
|
5797
5977
|
if (isJsonOutput(opts)) {
|
|
5798
|
-
const result3 = await watchTransactionJson(observable, waitLevel, { unsigned: true });
|
|
5978
|
+
const result3 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransactionJson(observable, waitLevel, { unsigned: true }));
|
|
5799
5979
|
const rpcUrl3 = primaryRpc(opts.rpc ?? chainConfig.rpc);
|
|
5800
5980
|
if (result3.type === "broadcasted") {
|
|
5801
5981
|
printJsonLine({ event: "broadcasted", txHash: result3.txHash });
|
|
@@ -5827,7 +6007,7 @@ async function handleTx(target, args, opts) {
|
|
|
5827
6007
|
}
|
|
5828
6008
|
return;
|
|
5829
6009
|
}
|
|
5830
|
-
const result2 = await watchTransaction(observable, waitLevel, { unsigned: true });
|
|
6010
|
+
const result2 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransaction(observable, waitLevel, { unsigned: true }));
|
|
5831
6011
|
console.log();
|
|
5832
6012
|
console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
|
|
5833
6013
|
console.log(` ${BOLD}Type:${RESET} unsigned (bare)`);
|
|
@@ -5876,7 +6056,7 @@ async function handleTx(target, args, opts) {
|
|
|
5876
6056
|
return;
|
|
5877
6057
|
}
|
|
5878
6058
|
if (isJsonOutput(opts)) {
|
|
5879
|
-
const result2 = await watchTransactionJson(tx.signSubmitAndWatch(signer, txOptions), waitLevel);
|
|
6059
|
+
const result2 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransactionJson(tx.signSubmitAndWatch(signer, txOptions), waitLevel));
|
|
5880
6060
|
const rpcUrl2 = primaryRpc(opts.rpc ?? chainConfig.rpc);
|
|
5881
6061
|
if (result2.type === "broadcasted") {
|
|
5882
6062
|
printJsonLine({ event: "broadcasted", txHash: result2.txHash });
|
|
@@ -5907,7 +6087,7 @@ async function handleTx(target, args, opts) {
|
|
|
5907
6087
|
}
|
|
5908
6088
|
return;
|
|
5909
6089
|
}
|
|
5910
|
-
const result = await watchTransaction(tx.signSubmitAndWatch(signer, txOptions), waitLevel);
|
|
6090
|
+
const result = await withStalenessSuggestion(chainName, clientHandle, () => watchTransaction(tx.signSubmitAndWatch(signer, txOptions), waitLevel));
|
|
5911
6091
|
console.log();
|
|
5912
6092
|
console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
|
|
5913
6093
|
console.log(` ${BOLD}Call:${RESET} ${callHex}`);
|
|
@@ -6342,7 +6522,10 @@ function normalizeValue2(lookup, entry, value) {
|
|
|
6342
6522
|
innerResolved = innerResolved.value;
|
|
6343
6523
|
}
|
|
6344
6524
|
if (innerResolved.type === "primitive" && innerResolved.value === "u8" && typeof value === "string") {
|
|
6345
|
-
|
|
6525
|
+
const isHex = /^0x[0-9a-fA-F]*$/.test(value);
|
|
6526
|
+
if (resolved.type === "array" && isHex)
|
|
6527
|
+
return value;
|
|
6528
|
+
if (isHex)
|
|
6346
6529
|
return Binary3.fromHex(value);
|
|
6347
6530
|
return Binary3.fromText(value);
|
|
6348
6531
|
}
|
|
@@ -7231,6 +7414,34 @@ class CliError2 extends Error {
|
|
|
7231
7414
|
this.name = "CliError";
|
|
7232
7415
|
}
|
|
7233
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
|
+
}
|
|
7234
7445
|
|
|
7235
7446
|
// src/utils/parse-dot-path.ts
|
|
7236
7447
|
var CATEGORY_ALIASES = {
|
|
@@ -7353,6 +7564,7 @@ if (process.argv[2] === "__complete") {
|
|
|
7353
7564
|
console.log(" dot polkadot.const.Balances.ExistentialDeposit");
|
|
7354
7565
|
console.log(" dot polkadot.events.Balances List events in Balances");
|
|
7355
7566
|
console.log(" dot polkadot.apis.Core.version Call a runtime API");
|
|
7567
|
+
console.log(" dot metadata polkadot Dump runtime metadata as JSON");
|
|
7356
7568
|
console.log(" dot polkadot.extensions List transaction extensions");
|
|
7357
7569
|
console.log(" dot polkadot.extensions.CheckMortality Inspect one extension");
|
|
7358
7570
|
console.log(" dot query.System.Number --chain polkadot --chain flag form");
|
|
@@ -7363,6 +7575,7 @@ if (process.argv[2] === "__complete") {
|
|
|
7363
7575
|
console.log();
|
|
7364
7576
|
console.log("Commands:");
|
|
7365
7577
|
console.log(" inspect [target] Inspect chain metadata (alias: explore)");
|
|
7578
|
+
console.log(" metadata <chain> Dump runtime metadata as JSON (--raw for SCALE hex)");
|
|
7366
7579
|
console.log(" chain Manage chain configurations");
|
|
7367
7580
|
console.log(" account Manage accounts");
|
|
7368
7581
|
console.log(" hash Hash utilities");
|
|
@@ -7389,6 +7602,7 @@ if (process.argv[2] === "__complete") {
|
|
|
7389
7602
|
cli.option("--json", "Output as JSON (shorthand for --output json)");
|
|
7390
7603
|
registerChainCommands(cli);
|
|
7391
7604
|
registerInspectCommand(cli);
|
|
7605
|
+
registerMetadataCommand(cli);
|
|
7392
7606
|
registerAccountCommands(cli);
|
|
7393
7607
|
registerHashCommand(cli);
|
|
7394
7608
|
registerSignCommand(cli);
|
|
@@ -7545,12 +7759,18 @@ if (process.argv[2] === "__complete") {
|
|
|
7545
7759
|
if (err instanceof CliError2) {
|
|
7546
7760
|
console.error(`Error: ${err.message}`);
|
|
7547
7761
|
} else if (err instanceof Error) {
|
|
7548
|
-
console.error(`Error: ${err
|
|
7762
|
+
console.error(`Error: ${formatRuntimeError2(err)}`);
|
|
7549
7763
|
} else {
|
|
7550
7764
|
console.error("An unexpected error occurred:", err);
|
|
7551
7765
|
}
|
|
7552
7766
|
return showUpdateAndExit(1);
|
|
7553
7767
|
}
|
|
7768
|
+
process.on("unhandledRejection", (reason) => {
|
|
7769
|
+
if (isPapiCleanupError(reason))
|
|
7770
|
+
return;
|
|
7771
|
+
console.error(`Error: ${formatRuntimeError2(reason)}`);
|
|
7772
|
+
process.exit(1);
|
|
7773
|
+
});
|
|
7554
7774
|
async function main() {
|
|
7555
7775
|
try {
|
|
7556
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.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "CLI tool for querying Polkadot-ecosystem on-chain state",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@biomejs/biome": "^2.4.5",
|
|
54
54
|
"@changesets/cli": "^2.29.4",
|
|
55
|
+
"@polkadot-api/metadata-compatibility": "^0.6.1",
|
|
55
56
|
"@types/bun": "^1.3.9",
|
|
56
57
|
"husky": "^9.1.7"
|
|
57
58
|
}
|