polkadot-cli 0.1.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 +107 -0
  2. package/dist/cli.mjs +745 -0
  3. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # polkadot-cli
2
+
3
+ A command-line tool for querying Polkadot-ecosystem on-chain state. Query storage, look up constants, and inspect chain metadata — all from your terminal.
4
+
5
+ Ships with Polkadot as the default chain. Add any Substrate-based chain by pointing to its RPC endpoint.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g polkadot-cli
11
+ ```
12
+
13
+ This installs the `dot` command globally.
14
+
15
+ ## Usage
16
+
17
+ ### Query storage
18
+
19
+ ```bash
20
+ # Plain storage value
21
+ dot query System.Number
22
+
23
+ # Map entry by key
24
+ dot query System.Account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
25
+
26
+ # All map entries (default limit: 100)
27
+ dot query System.Account --limit 10
28
+
29
+ # Pipe to jq (colors disabled automatically)
30
+ dot query System.Account --limit 5 | jq '.[0].value.data.free'
31
+ ```
32
+
33
+ ### Look up constants
34
+
35
+ ```bash
36
+ dot const Balances.ExistentialDeposit
37
+ dot const System.SS58Prefix --chain kusama
38
+ ```
39
+
40
+ ### Inspect metadata
41
+
42
+ Works offline from cached metadata after the first fetch.
43
+
44
+ ```bash
45
+ # List all pallets
46
+ dot inspect
47
+
48
+ # List a pallet's storage items and constants
49
+ dot inspect System
50
+
51
+ # Detailed type info for a specific item
52
+ dot inspect System.Account
53
+ ```
54
+
55
+ ### Manage chains
56
+
57
+ ```bash
58
+ # Add a chain
59
+ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
60
+ dot chain add westend --light-client
61
+
62
+ # List configured chains
63
+ dot chain list
64
+
65
+ # Set default chain
66
+ dot chain default kusama
67
+
68
+ # Remove a chain
69
+ dot chain remove westend
70
+ ```
71
+
72
+ ### Global options
73
+
74
+ | Flag | Description |
75
+ |------|-------------|
76
+ | `--chain <name>` | Target chain (default from config) |
77
+ | `--rpc <url>` | Override RPC endpoint for this call |
78
+ | `--light-client` | Use Smoldot light client |
79
+ | `--output json` | Raw JSON output (default: pretty) |
80
+ | `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
81
+
82
+ ## Configuration
83
+
84
+ Config and metadata caches live in `~/.polkadot/`:
85
+
86
+ ```
87
+ ~/.polkadot/
88
+ ├── config.json # chains and default chain
89
+ └── chains/
90
+ └── polkadot/
91
+ └── metadata.bin # cached SCALE-encoded metadata
92
+ ```
93
+
94
+ ## Development
95
+
96
+ Requires [Bun](https://bun.sh).
97
+
98
+ ```bash
99
+ bun install
100
+ bun run dev -- query System.Number
101
+ bun run build
102
+ bun test
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT
package/dist/cli.mjs ADDED
@@ -0,0 +1,745 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
+
21
+ // src/cli.ts
22
+ import cac from "cac";
23
+
24
+ // src/config/store.ts
25
+ import { join } from "node:path";
26
+ import { homedir } from "node:os";
27
+ import { mkdir, readFile, writeFile, rm, access } from "node:fs/promises";
28
+
29
+ // src/config/types.ts
30
+ var DEFAULT_CONFIG = {
31
+ defaultChain: "polkadot",
32
+ chains: {
33
+ polkadot: {
34
+ rpc: "wss://rpc.polkadot.io"
35
+ }
36
+ }
37
+ };
38
+
39
+ // src/config/store.ts
40
+ var DOT_DIR = join(homedir(), ".polkadot");
41
+ var CONFIG_PATH = join(DOT_DIR, "config.json");
42
+ var CHAINS_DIR = join(DOT_DIR, "chains");
43
+ function getChainDir(chainName) {
44
+ return join(CHAINS_DIR, chainName);
45
+ }
46
+ function getMetadataPath(chainName) {
47
+ return join(CHAINS_DIR, chainName, "metadata.bin");
48
+ }
49
+ async function ensureDir(dir) {
50
+ await mkdir(dir, { recursive: true });
51
+ }
52
+ async function fileExists(path) {
53
+ try {
54
+ await access(path);
55
+ return true;
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+ async function loadConfig() {
61
+ await ensureDir(DOT_DIR);
62
+ if (await fileExists(CONFIG_PATH)) {
63
+ const data = await readFile(CONFIG_PATH, "utf-8");
64
+ return JSON.parse(data);
65
+ }
66
+ await saveConfig(DEFAULT_CONFIG);
67
+ return DEFAULT_CONFIG;
68
+ }
69
+ async function saveConfig(config) {
70
+ await ensureDir(DOT_DIR);
71
+ await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + `
72
+ `);
73
+ }
74
+ async function loadMetadata(chainName) {
75
+ const path = getMetadataPath(chainName);
76
+ if (await fileExists(path)) {
77
+ return await readFile(path);
78
+ }
79
+ return null;
80
+ }
81
+ async function saveMetadata(chainName, data) {
82
+ const dir = getChainDir(chainName);
83
+ await ensureDir(dir);
84
+ await writeFile(getMetadataPath(chainName), data);
85
+ }
86
+ async function removeChainData(chainName) {
87
+ const dir = getChainDir(chainName);
88
+ await rm(dir, { recursive: true, force: true });
89
+ }
90
+ function resolveChain(config, chainFlag) {
91
+ const name = chainFlag ?? config.defaultChain;
92
+ const chain = config.chains[name];
93
+ if (!chain) {
94
+ const available = Object.keys(config.chains).join(", ");
95
+ throw new Error(`Unknown chain "${name}". Available chains: ${available}`);
96
+ }
97
+ return { name, chain };
98
+ }
99
+
100
+ // src/core/client.ts
101
+ import { createClient } from "polkadot-api";
102
+ import { getWsProvider } from "polkadot-api/ws-provider";
103
+ import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
104
+
105
+ // src/utils/errors.ts
106
+ class CliError extends Error {
107
+ constructor(message) {
108
+ super(message);
109
+ this.name = "CliError";
110
+ }
111
+ }
112
+
113
+ class ConnectionError extends CliError {
114
+ constructor(message) {
115
+ super(message);
116
+ this.name = "ConnectionError";
117
+ }
118
+ }
119
+
120
+ class MetadataError extends CliError {
121
+ constructor(message) {
122
+ super(message);
123
+ this.name = "MetadataError";
124
+ }
125
+ }
126
+
127
+ // src/core/client.ts
128
+ var KNOWN_CHAIN_SPECS = {
129
+ polkadot: "polkadot-api/chains/polkadot",
130
+ kusama: "polkadot-api/chains/ksmcc3",
131
+ westend: "polkadot-api/chains/westend2",
132
+ paseo: "polkadot-api/chains/paseo"
133
+ };
134
+ async function createChainClient(chainName, chainConfig, rpcOverride) {
135
+ const useLight = !rpcOverride && chainConfig.lightClient;
136
+ let provider;
137
+ if (useLight) {
138
+ provider = await createSmoldotProvider(chainName);
139
+ } else {
140
+ const rpc = rpcOverride ?? chainConfig.rpc;
141
+ if (!rpc) {
142
+ throw new ConnectionError(`No RPC endpoint configured for chain "${chainName}". Use --rpc or configure one with: dot chain add ${chainName} --rpc <url>`);
143
+ }
144
+ provider = withPolkadotSdkCompat(getWsProvider(rpc));
145
+ }
146
+ const client = createClient(provider, {
147
+ getMetadata: async () => loadMetadata(chainName),
148
+ setMetadata: async (_codeHash, metadata) => {
149
+ await saveMetadata(chainName, metadata);
150
+ }
151
+ });
152
+ return {
153
+ client,
154
+ destroy: () => client.destroy()
155
+ };
156
+ }
157
+ async function createSmoldotProvider(chainName) {
158
+ const { start } = await import("polkadot-api/smoldot");
159
+ const { getSmProvider } = await import("polkadot-api/sm-provider");
160
+ const specPath = KNOWN_CHAIN_SPECS[chainName];
161
+ if (!specPath) {
162
+ throw new ConnectionError(`Light client is only supported for known chains: ${Object.keys(KNOWN_CHAIN_SPECS).join(", ")}. Use --rpc to connect to "${chainName}" instead.`);
163
+ }
164
+ const { chainSpec } = await import(specPath);
165
+ const smoldot = start();
166
+ const chain = await smoldot.addChain({ chainSpec });
167
+ return getSmProvider(chain);
168
+ }
169
+
170
+ // src/core/metadata.ts
171
+ import {
172
+ decAnyMetadata,
173
+ unifyMetadata
174
+ } from "@polkadot-api/substrate-bindings";
175
+ import { getLookupFn, getDynamicBuilder } from "@polkadot-api/metadata-builders";
176
+ function parseMetadata(raw) {
177
+ const decoded = decAnyMetadata(raw);
178
+ const unified = unifyMetadata(decoded);
179
+ const lookup = getLookupFn(unified);
180
+ const builder = getDynamicBuilder(lookup);
181
+ return { unified, lookup, builder };
182
+ }
183
+ async function fetchMetadataFromChain(clientHandle, chainName) {
184
+ const { client } = clientHandle;
185
+ const hex = await client._request("state_getMetadata", []);
186
+ const bytes = hexToBytes(hex);
187
+ await saveMetadata(chainName, bytes);
188
+ return bytes;
189
+ }
190
+ async function getOrFetchMetadata(chainName, clientHandle) {
191
+ let raw = await loadMetadata(chainName);
192
+ if (!raw) {
193
+ if (!clientHandle) {
194
+ throw new MetadataError(`No cached metadata for chain "${chainName}". Run a command that connects to the chain first, ` + `e.g.: dot chain add ${chainName} --rpc <url>`);
195
+ }
196
+ raw = await fetchMetadataFromChain(clientHandle, chainName);
197
+ }
198
+ return parseMetadata(raw);
199
+ }
200
+ function listPallets(meta) {
201
+ return meta.unified.pallets.map((p) => ({
202
+ name: p.name,
203
+ index: p.index,
204
+ docs: p.docs ?? [],
205
+ storage: (p.storage?.items ?? []).map((s) => ({
206
+ name: s.name,
207
+ docs: s.docs ?? [],
208
+ type: s.type.tag,
209
+ keyTypeId: s.type.tag === "map" ? s.type.value.key : null,
210
+ valueTypeId: s.type.tag === "plain" ? s.type.value : s.type.value.value
211
+ })),
212
+ constants: (p.constants ?? []).map((c) => ({
213
+ name: c.name,
214
+ docs: c.docs ?? [],
215
+ typeId: c.type
216
+ }))
217
+ }));
218
+ }
219
+ function findPallet(meta, palletName) {
220
+ const pallets = listPallets(meta);
221
+ return pallets.find((p) => p.name.toLowerCase() === palletName.toLowerCase());
222
+ }
223
+ function getPalletNames(meta) {
224
+ return meta.unified.pallets.map((p) => p.name);
225
+ }
226
+ function describeType(lookup, typeId) {
227
+ try {
228
+ const entry = lookup(typeId);
229
+ return formatLookupEntry(entry);
230
+ } catch {
231
+ return `type(${typeId})`;
232
+ }
233
+ }
234
+ function formatLookupEntry(entry) {
235
+ switch (entry.type) {
236
+ case "primitive":
237
+ return entry.value;
238
+ case "compact":
239
+ return `Compact<${formatLookupEntry(entry.isBig ? { type: "primitive", value: "u128" } : { type: "primitive", value: "u64" })}>`;
240
+ case "AccountId32":
241
+ return "AccountId32";
242
+ case "bitSequence":
243
+ return "BitSequence";
244
+ case "sequence":
245
+ return `Vec<${formatLookupEntry(entry.value)}>`;
246
+ case "array":
247
+ return `[${formatLookupEntry(entry.value)}; ${entry.len}]`;
248
+ case "tuple":
249
+ return `(${entry.value.map(formatLookupEntry).join(", ")})`;
250
+ case "struct":
251
+ return `{ ${Object.entries(entry.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ")} }`;
252
+ case "option":
253
+ return `Option<${formatLookupEntry(entry.value)}>`;
254
+ case "result":
255
+ return `Result<${formatLookupEntry(entry.value.ok)}, ${formatLookupEntry(entry.value.ko)}>`;
256
+ case "enum": {
257
+ const variants = Object.keys(entry.value);
258
+ if (variants.length <= 4)
259
+ return variants.join(" | ");
260
+ return `enum(${variants.length} variants)`;
261
+ }
262
+ default:
263
+ return "unknown";
264
+ }
265
+ }
266
+ function hexToBytes(hex) {
267
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
268
+ const bytes = new Uint8Array(clean.length / 2);
269
+ for (let i = 0;i < clean.length; i += 2) {
270
+ bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
271
+ }
272
+ return bytes;
273
+ }
274
+
275
+ // src/core/output.ts
276
+ var isTTY = process.stdout.isTTY ?? false;
277
+ var RESET = isTTY ? "\x1B[0m" : "";
278
+ var CYAN = isTTY ? "\x1B[36m" : "";
279
+ var GREEN = isTTY ? "\x1B[32m" : "";
280
+ var YELLOW = isTTY ? "\x1B[33m" : "";
281
+ var MAGENTA = isTTY ? "\x1B[35m" : "";
282
+ var DIM = isTTY ? "\x1B[2m" : "";
283
+ var BOLD = isTTY ? "\x1B[1m" : "";
284
+ function replacer(_key, value) {
285
+ if (typeof value === "bigint")
286
+ return value.toString();
287
+ if (value instanceof Uint8Array)
288
+ return "0x" + Buffer.from(value).toString("hex");
289
+ return value;
290
+ }
291
+ function formatJson(data) {
292
+ return JSON.stringify(data, replacer, 2);
293
+ }
294
+ function formatPretty(data) {
295
+ const json = JSON.stringify(data, replacer, 2);
296
+ if (!json)
297
+ return "undefined";
298
+ return colorizeJson(json);
299
+ }
300
+ function colorizeJson(json) {
301
+ return json.replace(/("(?:\\.|[^"\\])*")\s*:/g, `${CYAN}$1${RESET}:`).replace(/:\s*("(?:\\.|[^"\\])*")/g, (match, str) => match.replace(str, `${GREEN}${str}${RESET}`)).replace(/:\s*(\d+(?:\.\d+)?)/g, (match, num) => match.replace(num, `${YELLOW}${num}${RESET}`)).replace(/:\s*(true|false)/g, (match, bool) => match.replace(bool, `${MAGENTA}${bool}${RESET}`)).replace(/:\s*(null)/g, (match, n) => match.replace(n, `${DIM}${n}${RESET}`));
302
+ }
303
+ function printResult(data, format = "pretty") {
304
+ if (format === "json") {
305
+ console.log(formatJson(data));
306
+ } else {
307
+ console.log(formatPretty(data));
308
+ }
309
+ }
310
+ function printHeading(text) {
311
+ console.log(`
312
+ ${BOLD}${text}${RESET}
313
+ `);
314
+ }
315
+ function printItem(name, description) {
316
+ if (description) {
317
+ console.log(` ${CYAN}${name}${RESET} ${DIM}${description}${RESET}`);
318
+ } else {
319
+ console.log(` ${CYAN}${name}${RESET}`);
320
+ }
321
+ }
322
+ function printDocs(docs) {
323
+ const text = docs.join(`
324
+ `).trim();
325
+ if (text) {
326
+ console.log(` ${DIM}${text}${RESET}`);
327
+ }
328
+ }
329
+
330
+ // src/commands/chain.ts
331
+ var CHAIN_HELP = `
332
+ ${BOLD}Usage:${RESET}
333
+ $ dot chain add <name> --rpc <url> Add a chain via WebSocket RPC
334
+ $ dot chain add <name> --light-client Add a chain via Smoldot light client
335
+ $ dot chain remove <name> Remove a chain
336
+ $ dot chain list List configured chains
337
+ $ dot chain default <name> Set the default chain
338
+
339
+ ${BOLD}Examples:${RESET}
340
+ $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
341
+ $ dot chain add westend --light-client
342
+ $ dot chain default kusama
343
+ $ dot chain list
344
+ $ dot chain remove kusama
345
+ `.trimStart();
346
+ function registerChainCommands(cli) {
347
+ cli.command("chain [action] [name]", "Manage chains (add, remove, list, default)").action(async (action, name, opts) => {
348
+ if (!action) {
349
+ console.log(CHAIN_HELP);
350
+ return;
351
+ }
352
+ switch (action) {
353
+ case "add":
354
+ return chainAdd(name, opts);
355
+ case "remove":
356
+ return chainRemove(name);
357
+ case "list":
358
+ return chainList();
359
+ case "default":
360
+ return chainDefault(name);
361
+ default:
362
+ console.error(`Unknown action "${action}".
363
+ `);
364
+ console.log(CHAIN_HELP);
365
+ process.exit(1);
366
+ }
367
+ });
368
+ }
369
+ async function chainAdd(name, opts) {
370
+ if (!name) {
371
+ console.error(`Chain name is required.
372
+ `);
373
+ console.error("Usage: dot chain add <name> --rpc <url>");
374
+ console.error(" dot chain add <name> --light-client");
375
+ process.exit(1);
376
+ }
377
+ if (!opts.rpc && !opts.lightClient) {
378
+ console.error(`Must provide either --rpc <url> or --light-client.
379
+ `);
380
+ console.error("Usage: dot chain add <name> --rpc <url>");
381
+ console.error(" dot chain add <name> --light-client");
382
+ process.exit(1);
383
+ }
384
+ const config = await loadConfig();
385
+ config.chains[name] = {
386
+ rpc: opts.rpc ?? "",
387
+ ...opts.lightClient ? { lightClient: true } : {}
388
+ };
389
+ await saveConfig(config);
390
+ console.log(`Connecting to ${name}...`);
391
+ const clientHandle = await createChainClient(name, config.chains[name], opts.rpc);
392
+ try {
393
+ console.log("Fetching metadata...");
394
+ await fetchMetadataFromChain(clientHandle, name);
395
+ console.log(`Chain "${name}" added successfully.`);
396
+ } finally {
397
+ clientHandle.destroy();
398
+ }
399
+ }
400
+ async function chainRemove(name) {
401
+ if (!name) {
402
+ console.error("Usage: dot chain remove <name>");
403
+ process.exit(1);
404
+ }
405
+ const config = await loadConfig();
406
+ if (!config.chains[name]) {
407
+ throw new Error(`Chain "${name}" not found.`);
408
+ }
409
+ if (name === "polkadot") {
410
+ throw new Error('Cannot remove the built-in "polkadot" chain.');
411
+ }
412
+ delete config.chains[name];
413
+ if (config.defaultChain === name) {
414
+ config.defaultChain = "polkadot";
415
+ console.log(`Default chain reset to "polkadot".`);
416
+ }
417
+ await saveConfig(config);
418
+ await removeChainData(name);
419
+ console.log(`Chain "${name}" removed.`);
420
+ }
421
+ async function chainList() {
422
+ const config = await loadConfig();
423
+ printHeading("Configured Chains");
424
+ for (const [name, chainConfig] of Object.entries(config.chains)) {
425
+ const isDefault = name === config.defaultChain;
426
+ const marker = isDefault ? ` ${BOLD}(default)${RESET}` : "";
427
+ const provider = chainConfig.lightClient ? `${DIM}light-client${RESET}` : `${DIM}${chainConfig.rpc}${RESET}`;
428
+ console.log(` ${CYAN}${name}${RESET}${marker} ${provider}`);
429
+ }
430
+ console.log();
431
+ }
432
+ async function chainDefault(name) {
433
+ if (!name) {
434
+ console.error("Usage: dot chain default <name>");
435
+ process.exit(1);
436
+ }
437
+ const config = await loadConfig();
438
+ if (!config.chains[name]) {
439
+ const available = Object.keys(config.chains).join(", ");
440
+ throw new Error(`Chain "${name}" not found. Available: ${available}`);
441
+ }
442
+ config.defaultChain = name;
443
+ await saveConfig(config);
444
+ console.log(`Default chain set to "${name}".`);
445
+ }
446
+
447
+ // src/utils/parse-target.ts
448
+ function parseTarget(input) {
449
+ const parts = input.split(".");
450
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
451
+ throw new Error(`Invalid target "${input}". Expected format: Pallet.Item (e.g. System.Account)`);
452
+ }
453
+ return { pallet: parts[0], item: parts[1] };
454
+ }
455
+
456
+ // src/utils/fuzzy-match.ts
457
+ function levenshtein(a, b) {
458
+ const la = a.length;
459
+ const lb = b.length;
460
+ const dp = Array.from({ length: la + 1 }, () => Array(lb + 1).fill(0));
461
+ for (let i = 0;i <= la; i++)
462
+ dp[i][0] = i;
463
+ for (let j = 0;j <= lb; j++)
464
+ dp[0][j] = j;
465
+ for (let i = 1;i <= la; i++) {
466
+ for (let j = 1;j <= lb; j++) {
467
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
468
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
469
+ }
470
+ }
471
+ return dp[la][lb];
472
+ }
473
+ function findClosest(input, candidates, maxDistance = 3) {
474
+ const lower = input.toLowerCase();
475
+ const exact = candidates.find((c) => c.toLowerCase() === lower);
476
+ if (exact)
477
+ return [exact];
478
+ const scored = candidates.map((c) => ({ name: c, dist: levenshtein(lower, c.toLowerCase()) })).filter((s) => s.dist <= maxDistance).sort((a, b) => a.dist - b.dist);
479
+ return scored.slice(0, 3).map((s) => s.name);
480
+ }
481
+ function suggestMessage(kind, input, candidates) {
482
+ const suggestions = findClosest(input, candidates);
483
+ if (suggestions.length === 0) {
484
+ return `Unknown ${kind} "${input}".`;
485
+ }
486
+ if (suggestions.length === 1 && suggestions[0].toLowerCase() === input.toLowerCase()) {
487
+ return suggestions[0];
488
+ }
489
+ return `Unknown ${kind} "${input}". Did you mean: ${suggestions.join(", ")}?`;
490
+ }
491
+
492
+ // src/commands/inspect.ts
493
+ function registerInspectCommand(cli) {
494
+ 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) => {
495
+ const config = await loadConfig();
496
+ const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
497
+ let meta;
498
+ try {
499
+ meta = await getOrFetchMetadata(chainName);
500
+ } catch {
501
+ console.log(`Fetching metadata from ${chainName}...`);
502
+ const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
503
+ try {
504
+ meta = await getOrFetchMetadata(chainName, clientHandle);
505
+ } finally {
506
+ clientHandle.destroy();
507
+ }
508
+ }
509
+ if (!target) {
510
+ const pallets = listPallets(meta);
511
+ printHeading(`Pallets on ${chainName} (${pallets.length})`);
512
+ for (const p of pallets) {
513
+ const counts = [];
514
+ if (p.storage.length)
515
+ counts.push(`${p.storage.length} storage`);
516
+ if (p.constants.length)
517
+ counts.push(`${p.constants.length} constants`);
518
+ printItem(p.name, counts.join(", "));
519
+ }
520
+ console.log();
521
+ return;
522
+ }
523
+ if (!target.includes(".")) {
524
+ const palletNames2 = getPalletNames(meta);
525
+ const pallet2 = findPallet(meta, target);
526
+ if (!pallet2) {
527
+ throw new Error(suggestMessage("pallet", target, palletNames2));
528
+ }
529
+ printHeading(`${pallet2.name} Pallet`);
530
+ if (pallet2.docs.length) {
531
+ printDocs(pallet2.docs);
532
+ console.log();
533
+ }
534
+ if (pallet2.storage.length) {
535
+ console.log(` ${BOLD}Storage Items:${RESET}`);
536
+ for (const s of pallet2.storage) {
537
+ const doc = s.docs[0] ? ` — ${s.docs[0].slice(0, 80)}` : "";
538
+ console.log(` ${CYAN}${s.name}${RESET}${DIM}${doc}${RESET}`);
539
+ }
540
+ console.log();
541
+ }
542
+ if (pallet2.constants.length) {
543
+ console.log(` ${BOLD}Constants:${RESET}`);
544
+ for (const c of pallet2.constants) {
545
+ const doc = c.docs[0] ? ` — ${c.docs[0].slice(0, 80)}` : "";
546
+ console.log(` ${CYAN}${c.name}${RESET}${DIM}${doc}${RESET}`);
547
+ }
548
+ console.log();
549
+ }
550
+ return;
551
+ }
552
+ const { pallet: palletName, item: itemName } = parseTarget(target);
553
+ const palletNames = getPalletNames(meta);
554
+ const pallet = findPallet(meta, palletName);
555
+ if (!pallet) {
556
+ throw new Error(suggestMessage("pallet", palletName, palletNames));
557
+ }
558
+ const storageItem = pallet.storage.find((s) => s.name.toLowerCase() === itemName.toLowerCase());
559
+ if (storageItem) {
560
+ printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
561
+ console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
562
+ console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
563
+ if (storageItem.keyTypeId != null) {
564
+ console.log(` ${BOLD}Key:${RESET} ${describeType(meta.lookup, storageItem.keyTypeId)}`);
565
+ }
566
+ if (storageItem.docs.length) {
567
+ console.log();
568
+ printDocs(storageItem.docs);
569
+ }
570
+ console.log();
571
+ return;
572
+ }
573
+ const constantItem = pallet.constants.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
574
+ if (constantItem) {
575
+ printHeading(`${pallet.name}.${constantItem.name} (Constant)`);
576
+ console.log(` ${BOLD}Type:${RESET} ${describeType(meta.lookup, constantItem.typeId)}`);
577
+ if (constantItem.docs.length) {
578
+ console.log();
579
+ printDocs(constantItem.docs);
580
+ }
581
+ console.log();
582
+ return;
583
+ }
584
+ const allItems = [
585
+ ...pallet.storage.map((s) => s.name),
586
+ ...pallet.constants.map((c) => c.name)
587
+ ];
588
+ throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems));
589
+ });
590
+ }
591
+
592
+ // src/commands/query.ts
593
+ var DEFAULT_LIMIT = 100;
594
+ function registerQueryCommand(cli) {
595
+ cli.command("query [target] [...keys]", "Query on-chain storage (e.g. System.Number, System.Account <addr>)").option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
596
+ default: DEFAULT_LIMIT
597
+ }).action(async (target, keys, opts) => {
598
+ if (!target) {
599
+ console.log("Usage: dot query <Pallet.Item> [...keys] [--chain <name>] [--output json]");
600
+ console.log("");
601
+ console.log("Examples:");
602
+ console.log(" $ dot query System.Number # plain storage value");
603
+ console.log(" $ dot query System.Account 5Grw... # single map entry");
604
+ console.log(" $ dot query System.Account # all entries (limit 100)");
605
+ console.log(" $ dot query System.Account --limit 10 # first 10 entries");
606
+ console.log(" $ dot query System.Account --limit 0 # all entries (no limit)");
607
+ console.log(" $ dot query Assets.Metadata 42 --chain asset-hub");
608
+ return;
609
+ }
610
+ const config = await loadConfig();
611
+ const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
612
+ const { pallet, item } = parseTarget(target);
613
+ const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
614
+ try {
615
+ const meta = await getOrFetchMetadata(chainName, clientHandle);
616
+ const palletNames = getPalletNames(meta);
617
+ const palletInfo = findPallet(meta, pallet);
618
+ if (!palletInfo) {
619
+ throw new Error(suggestMessage("pallet", pallet, palletNames));
620
+ }
621
+ const storageItem = palletInfo.storage.find((s) => s.name.toLowerCase() === item.toLowerCase());
622
+ if (!storageItem) {
623
+ const storageNames = palletInfo.storage.map((s) => s.name);
624
+ throw new Error(suggestMessage(`storage item in ${palletInfo.name}`, item, storageNames));
625
+ }
626
+ const unsafeApi = clientHandle.client.getUnsafeApi();
627
+ const storageApi = unsafeApi.query[palletInfo.name][storageItem.name];
628
+ const parsedKeys = keys.map(parseKeyArg);
629
+ const format = opts.output ?? "pretty";
630
+ if (storageItem.type === "map" && parsedKeys.length === 0) {
631
+ const entries = await storageApi.getEntries();
632
+ const limit = Number(opts.limit);
633
+ const truncated = limit > 0 && entries.length > limit;
634
+ const display = truncated ? entries.slice(0, limit) : entries;
635
+ printResult(display.map((e) => ({
636
+ keys: e.keyArgs,
637
+ value: e.value
638
+ })), format);
639
+ if (truncated) {
640
+ console.error(`
641
+ ${DIM}Showing ${limit} of ${entries.length} entries. Use --limit 0 for all.${RESET}`);
642
+ }
643
+ } else {
644
+ const result = await storageApi.getValue(...parsedKeys);
645
+ printResult(result, format);
646
+ }
647
+ } finally {
648
+ clientHandle.destroy();
649
+ }
650
+ });
651
+ }
652
+ function parseKeyArg(arg) {
653
+ if (/^\d+$/.test(arg))
654
+ return parseInt(arg, 10);
655
+ if (/^\d{16,}$/.test(arg))
656
+ return BigInt(arg);
657
+ if (/^0x[0-9a-fA-F]+$/.test(arg))
658
+ return arg;
659
+ if (arg === "true")
660
+ return true;
661
+ if (arg === "false")
662
+ return false;
663
+ if (arg.startsWith("{") || arg.startsWith("[")) {
664
+ try {
665
+ return JSON.parse(arg);
666
+ } catch {}
667
+ }
668
+ return arg;
669
+ }
670
+
671
+ // src/commands/const.ts
672
+ function registerConstCommand(cli) {
673
+ cli.command("const [target]", "Look up a pallet constant (e.g. Balances.ExistentialDeposit)").action(async (target, opts) => {
674
+ if (!target) {
675
+ console.log("Usage: dot const <Pallet.Constant> [--chain <name>] [--output json]");
676
+ console.log("");
677
+ console.log("Examples:");
678
+ console.log(" $ dot const Balances.ExistentialDeposit");
679
+ console.log(" $ dot const System.SS58Prefix --chain kusama");
680
+ return;
681
+ }
682
+ const config = await loadConfig();
683
+ const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
684
+ const { pallet, item } = parseTarget(target);
685
+ const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
686
+ try {
687
+ const meta = await getOrFetchMetadata(chainName, clientHandle);
688
+ const palletNames = getPalletNames(meta);
689
+ const palletInfo = findPallet(meta, pallet);
690
+ if (!palletInfo) {
691
+ throw new Error(suggestMessage("pallet", pallet, palletNames));
692
+ }
693
+ const constantItem = palletInfo.constants.find((c) => c.name.toLowerCase() === item.toLowerCase());
694
+ if (!constantItem) {
695
+ const constNames = palletInfo.constants.map((c) => c.name);
696
+ throw new Error(suggestMessage(`constant in ${palletInfo.name}`, item, constNames));
697
+ }
698
+ const unsafeApi = clientHandle.client.getUnsafeApi();
699
+ const runtimeToken = await unsafeApi.runtimeToken;
700
+ const result = unsafeApi.constants[palletInfo.name][constantItem.name](runtimeToken);
701
+ printResult(result, opts.output ?? "pretty");
702
+ } finally {
703
+ clientHandle.destroy();
704
+ }
705
+ });
706
+ }
707
+
708
+ // src/utils/errors.ts
709
+ class CliError2 extends Error {
710
+ constructor(message) {
711
+ super(message);
712
+ this.name = "CliError";
713
+ }
714
+ }
715
+
716
+ // src/cli.ts
717
+ var cli = cac("dot");
718
+ cli.option("--chain <name>", "Target chain (default from config)");
719
+ cli.option("--rpc <url>", "Override RPC endpoint for this call");
720
+ cli.option("--light-client", "Use Smoldot light client instead of WebSocket");
721
+ cli.option("--output <format>", "Output format: pretty or json", {
722
+ default: "pretty"
723
+ });
724
+ registerChainCommands(cli);
725
+ registerInspectCommand(cli);
726
+ registerQueryCommand(cli);
727
+ registerConstCommand(cli);
728
+ cli.help();
729
+ cli.version("0.1.0");
730
+ function handleError(err) {
731
+ if (err instanceof CliError2) {
732
+ console.error(`Error: ${err.message}`);
733
+ } else if (err instanceof Error) {
734
+ console.error(`Error: ${err.message}`);
735
+ } else {
736
+ console.error("An unexpected error occurred:", err);
737
+ }
738
+ process.exit(1);
739
+ }
740
+ process.on("unhandledRejection", handleError);
741
+ try {
742
+ cli.parse();
743
+ } catch (err) {
744
+ handleError(err);
745
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "polkadot-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
+ "type": "module",
6
+ "bin": {
7
+ "dot": "./dist/cli.mjs"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "dev": "bun src/cli.ts",
14
+ "build": "bun build src/cli.ts --outfile dist/cli.mjs --target node --packages external && bun run build:shebang",
15
+ "build:shebang": "bun -e \"const f='dist/cli.mjs'; const s=await Bun.file(f).text(); await Bun.write(f, s.replace('#!/usr/bin/env bun', '#!/usr/bin/env node').replace('// @bun\\n',''))\"",
16
+ "prepublishOnly": "bun run build",
17
+ "test": "bun test",
18
+ "changeset": "changeset",
19
+ "version": "changeset version",
20
+ "release": "changeset publish"
21
+ },
22
+ "license": "MIT",
23
+ "keywords": [
24
+ "polkadot",
25
+ "substrate",
26
+ "blockchain",
27
+ "cli",
28
+ "web3"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/peetzweg/polkadot-cli"
33
+ },
34
+ "dependencies": {
35
+ "cac": "^6.7.14",
36
+ "polkadot-api": "^1.23.3",
37
+ "@polkadot-api/metadata-builders": "^0.13.9",
38
+ "@polkadot-api/substrate-bindings": "^0.17.0",
39
+ "@polkadot-api/view-builder": "^0.4.17"
40
+ },
41
+ "devDependencies": {
42
+ "@changesets/cli": "^2.29.4",
43
+ "@types/bun": "latest"
44
+ }
45
+ }