polkadot-cli 0.7.0 → 0.8.1

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 +29 -1
  2. package/dist/cli.mjs +89 -26
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![codecov](https://codecov.io/gh/peetzweg/polkadot-cli/branch/main/graph/badge.svg)](https://codecov.io/gh/peetzweg/polkadot-cli)
2
+
1
3
  # polkadot-cli
2
4
 
3
5
  A command-line tool for interacting with Polkadot-ecosystem chains. Manage chains and accounts, query storage, look up constants, inspect metadata, submit extrinsics, and compute hashes — all from your terminal.
@@ -66,6 +68,20 @@ dot account remove my-validator
66
68
 
67
69
  **Known limitation:** Hex seed import (`--secret 0x...`) does not work from the command line. The CLI argument parser (`cac`) interprets `0x`-prefixed values as JavaScript numbers, which loses precision for 32-byte seeds. Use a BIP39 mnemonic instead. If you need to import a raw seed programmatically, write it directly to `~/.polkadot/accounts.json`.
68
70
 
71
+ ### Chain prefix
72
+
73
+ Instead of the `--chain` flag, you can prefix any target with the chain name using dot notation:
74
+
75
+ ```bash
76
+ dot query kusama.System.Account 5GrwvaEF...
77
+ dot const kusama.Balances.ExistentialDeposit
78
+ dot tx kusama.Balances.transferKeepAlive 5FHneW46... 1000000000000 --from alice
79
+ dot inspect kusama.System
80
+ dot inspect kusama.System.Account
81
+ ```
82
+
83
+ The `--chain` flag and default chain still work as before. If both a chain prefix and `--chain` flag are provided, the CLI errors.
84
+
69
85
  ### Query storage
70
86
 
71
87
  ```bash
@@ -78,8 +94,12 @@ dot query System.Account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
78
94
  # All map entries (default limit: 100)
79
95
  dot query System.Account --limit 10
80
96
 
81
- # Pipe to jq (colors disabled automatically)
97
+ # Pipe to jq stdout is clean JSON, no extra text
82
98
  dot query System.Account --limit 5 | jq '.[0].value.data.free'
99
+ dot query System.Number --output json | jq '.+1'
100
+
101
+ # Query a specific chain using chain prefix
102
+ dot query kusama.System.Account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
83
103
  ```
84
104
 
85
105
  ### Look up constants
@@ -87,6 +107,10 @@ dot query System.Account --limit 5 | jq '.[0].value.data.free'
87
107
  ```bash
88
108
  dot const Balances.ExistentialDeposit
89
109
  dot const System.SS58Prefix --chain kusama
110
+ dot const kusama.Balances.ExistentialDeposit
111
+
112
+ # Pipe to jq — stdout is clean JSON, no extra text
113
+ dot const Balances.ExistentialDeposit --output json | jq
90
114
  ```
91
115
 
92
116
  ### Inspect metadata
@@ -102,6 +126,10 @@ dot inspect System
102
126
 
103
127
  # Detailed type info for a specific item
104
128
  dot inspect System.Account
129
+
130
+ # Inspect a specific chain using chain prefix
131
+ dot inspect kusama.System
132
+ dot inspect kusama.System.Account
105
133
  ```
106
134
 
107
135
  ### Submit extrinsics
package/dist/cli.mjs CHANGED
@@ -5,7 +5,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
5
  // src/cli.ts
6
6
  import cac from "cac";
7
7
  // package.json
8
- var version = "0.7.0";
8
+ var version = "0.8.1";
9
9
 
10
10
  // src/config/accounts-store.ts
11
11
  import { access as access2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
@@ -848,28 +848,73 @@ function suggestMessage(kind, input, candidates) {
848
848
  }
849
849
 
850
850
  // src/utils/parse-target.ts
851
- function parseTarget(input) {
851
+ function parseTarget(input, options) {
852
852
  const parts = input.split(".");
853
- if (parts.length !== 2 || !parts[0] || !parts[1]) {
854
- throw new Error(`Invalid target "${input}". Expected format: Pallet.Item (e.g. System.Account)`);
853
+ if (options?.allowPalletOnly) {
854
+ switch (parts.length) {
855
+ case 1:
856
+ if (!parts[0]) {
857
+ throw new Error(`Invalid target "${input}". Expected format: Pallet or Pallet.Item (e.g. System or System.Account)`);
858
+ }
859
+ return { pallet: parts[0] };
860
+ case 2:
861
+ if (!parts[0] || !parts[1]) {
862
+ throw new Error(`Invalid target "${input}". Expected format: Pallet.Item or Chain.Pallet (e.g. System.Account or kusama.System)`);
863
+ }
864
+ if (options.knownChains?.some((c) => c.toLowerCase() === parts[0].toLowerCase())) {
865
+ return { chain: parts[0], pallet: parts[1] };
866
+ }
867
+ return { pallet: parts[0], item: parts[1] };
868
+ case 3:
869
+ if (!parts[0] || !parts[1] || !parts[2]) {
870
+ throw new Error(`Invalid target "${input}". Expected format: Chain.Pallet.Item (e.g. kusama.System.Account)`);
871
+ }
872
+ return { chain: parts[0], pallet: parts[1], item: parts[2] };
873
+ default:
874
+ throw new Error(`Invalid target "${input}". Expected format: Pallet, Pallet.Item, or Chain.Pallet.Item`);
875
+ }
876
+ }
877
+ switch (parts.length) {
878
+ case 2:
879
+ if (!parts[0] || !parts[1]) {
880
+ throw new Error(`Invalid target "${input}". Expected format: Pallet.Item (e.g. System.Account)`);
881
+ }
882
+ return { pallet: parts[0], item: parts[1] };
883
+ case 3:
884
+ if (!parts[0] || !parts[1] || !parts[2]) {
885
+ throw new Error(`Invalid target "${input}". Expected format: Chain.Pallet.Item (e.g. kusama.System.Account)`);
886
+ }
887
+ return { chain: parts[0], pallet: parts[1], item: parts[2] };
888
+ default:
889
+ throw new Error(`Invalid target "${input}". Expected format: Pallet.Item (e.g. System.Account)`);
890
+ }
891
+ }
892
+ function resolveTargetChain(target, chainFlag) {
893
+ if (target.chain && chainFlag) {
894
+ throw new Error(`Chain specified both as prefix ("${target.chain}") and as --chain flag ("${chainFlag}"). Use one or the other.`);
855
895
  }
856
- return { pallet: parts[0], item: parts[1] };
896
+ return target.chain ?? chainFlag;
857
897
  }
858
898
 
859
899
  // src/commands/const.ts
860
900
  function registerConstCommand(cli) {
861
901
  cli.command("const [target]", "Look up a pallet constant (e.g. Balances.ExistentialDeposit)").action(async (target, opts) => {
862
902
  if (!target) {
863
- console.log("Usage: dot const <Pallet.Constant> [--chain <name>] [--output json]");
903
+ console.log("Usage: dot const <[Chain.]Pallet.Constant> [--chain <name>] [--output json]");
864
904
  console.log("");
865
905
  console.log("Examples:");
866
906
  console.log(" $ dot const Balances.ExistentialDeposit");
867
907
  console.log(" $ dot const System.SS58Prefix --chain kusama");
908
+ console.log(" $ dot const kusama.Balances.ExistentialDeposit # chain prefix");
868
909
  return;
869
910
  }
870
911
  const config = await loadConfig();
871
- const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
872
- const { pallet, item } = parseTarget(target);
912
+ const knownChains = Object.keys(config.chains);
913
+ const parsed = parseTarget(target, { knownChains });
914
+ const effectiveChain = resolveTargetChain(parsed, opts.chain);
915
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
916
+ const pallet = parsed.pallet;
917
+ const item = parsed.item;
873
918
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
874
919
  try {
875
920
  const meta = await getOrFetchMetadata(chainName, clientHandle);
@@ -886,7 +931,8 @@ function registerConstCommand(cli) {
886
931
  const unsafeApi = clientHandle.client.getUnsafeApi();
887
932
  const runtimeToken = await unsafeApi.runtimeToken;
888
933
  const result = unsafeApi.constants[palletInfo.name][constantItem.name](runtimeToken);
889
- printResult(result, opts.output ?? "pretty");
934
+ const format = opts.output ?? "pretty";
935
+ printResult(result, format);
890
936
  } finally {
891
937
  clientHandle.destroy();
892
938
  }
@@ -1028,7 +1074,17 @@ function registerHashCommand(cli) {
1028
1074
  function registerInspectCommand(cli) {
1029
1075
  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) => {
1030
1076
  const config = await loadConfig();
1031
- const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
1077
+ const knownChains = Object.keys(config.chains);
1078
+ let effectiveChain = opts.chain;
1079
+ let palletName;
1080
+ let itemName;
1081
+ if (target) {
1082
+ const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
1083
+ effectiveChain = resolveTargetChain(parsed, opts.chain);
1084
+ palletName = parsed.pallet;
1085
+ itemName = parsed.item;
1086
+ }
1087
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1032
1088
  let meta;
1033
1089
  try {
1034
1090
  meta = await getOrFetchMetadata(chainName);
@@ -1055,11 +1111,11 @@ function registerInspectCommand(cli) {
1055
1111
  console.log();
1056
1112
  return;
1057
1113
  }
1058
- if (!target.includes(".")) {
1114
+ if (!itemName) {
1059
1115
  const palletNames2 = getPalletNames(meta);
1060
- const pallet2 = findPallet(meta, target);
1116
+ const pallet2 = findPallet(meta, palletName);
1061
1117
  if (!pallet2) {
1062
- throw new Error(suggestMessage("pallet", target, palletNames2));
1118
+ throw new Error(suggestMessage("pallet", palletName, palletNames2));
1063
1119
  }
1064
1120
  printHeading(`${pallet2.name} Pallet`);
1065
1121
  if (pallet2.docs.length) {
@@ -1084,7 +1140,6 @@ function registerInspectCommand(cli) {
1084
1140
  }
1085
1141
  return;
1086
1142
  }
1087
- const { pallet: palletName, item: itemName } = parseTarget(target);
1088
1143
  const palletNames = getPalletNames(meta);
1089
1144
  const pallet = findPallet(meta, palletName);
1090
1145
  if (!pallet) {
@@ -1399,7 +1454,7 @@ function registerQueryCommand(cli) {
1399
1454
  default: DEFAULT_LIMIT
1400
1455
  }).action(async (target, keys, opts) => {
1401
1456
  if (!target) {
1402
- console.log("Usage: dot query <Pallet.Item> [...keys] [--chain <name>] [--output json]");
1457
+ console.log("Usage: dot query <[Chain.]Pallet.Item> [...keys] [--chain <name>] [--output json]");
1403
1458
  console.log("");
1404
1459
  console.log("Examples:");
1405
1460
  console.log(" $ dot query System.Number # plain storage value");
@@ -1408,11 +1463,16 @@ function registerQueryCommand(cli) {
1408
1463
  console.log(" $ dot query System.Account --limit 10 # first 10 entries");
1409
1464
  console.log(" $ dot query System.Account --limit 0 # all entries (no limit)");
1410
1465
  console.log(" $ dot query Assets.Metadata 42 --chain asset-hub");
1466
+ console.log(" $ dot query kusama.System.Account 5Grw... # chain prefix");
1411
1467
  return;
1412
1468
  }
1413
1469
  const config = await loadConfig();
1414
- const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
1415
- const { pallet, item } = parseTarget(target);
1470
+ const knownChains = Object.keys(config.chains);
1471
+ const parsed = parseTarget(target, { knownChains });
1472
+ const effectiveChain = resolveTargetChain(parsed, opts.chain);
1473
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1474
+ const pallet = parsed.pallet;
1475
+ const item = parsed.item;
1416
1476
  const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
1417
1477
  try {
1418
1478
  const meta = await getOrFetchMetadata(chainName, clientHandle);
@@ -1430,12 +1490,6 @@ function registerQueryCommand(cli) {
1430
1490
  const storageApi = unsafeApi.query[palletInfo.name][storageItem.name];
1431
1491
  const parsedKeys = parseStorageKeys(meta, palletInfo.name, storageItem, keys);
1432
1492
  const format = opts.output ?? "pretty";
1433
- if (format === "json") {
1434
- console.error(`chain: ${chainName}`);
1435
- } else {
1436
- console.log(`${DIM}chain: ${chainName}${RESET}
1437
- `);
1438
- }
1439
1493
  if (storageItem.type === "map" && parsedKeys.length === 0) {
1440
1494
  const entries = await storageApi.getEntries();
1441
1495
  const limit = Number(opts.limit);
@@ -1501,13 +1555,14 @@ import { Binary as Binary2 } from "polkadot-api";
1501
1555
  function registerTxCommand(cli) {
1502
1556
  cli.command("tx [target] [...args]", "Submit an extrinsic (e.g. Balances.transferKeepAlive <dest> <amount>)").option("--from <name>", "Account to sign with (required)").option("--dry-run", "Estimate fees without submitting").option("--encode", "Encode call to hex without signing or submitting").option("--ext <json>", `Custom signed extension values as JSON, e.g. '{"ExtName":{"value":...}}'`).action(async (target, args, opts) => {
1503
1557
  if (!target) {
1504
- console.log("Usage: dot tx <Pallet.Call|0xCallHex> [...args] --from <account> [--dry-run] [--encode]");
1558
+ console.log("Usage: dot tx <[Chain.]Pallet.Call|0xCallHex> [...args] --from <account> [--dry-run] [--encode]");
1505
1559
  console.log("");
1506
1560
  console.log("Examples:");
1507
1561
  console.log(" $ dot tx Balances.transferKeepAlive 5FHn... 1000000000000 --from alice");
1508
1562
  console.log(" $ dot tx System.remark 0xdeadbeef --from alice --dry-run");
1509
1563
  console.log(" $ dot tx 0x0001076465616462656566 --from alice");
1510
1564
  console.log(" $ dot tx Assets.force_create 4 owner true 10 --encode --chain people");
1565
+ console.log(" $ dot tx kusama.Balances.transferKeepAlive 5FHn... 1000000000000 --from alice");
1511
1566
  return;
1512
1567
  }
1513
1568
  if (!opts.from && !opts.encode) {
@@ -1521,7 +1576,14 @@ function registerTxCommand(cli) {
1521
1576
  throw new Error("--encode cannot be used with raw call hex (already encoded)");
1522
1577
  }
1523
1578
  const config = await loadConfig();
1524
- const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
1579
+ let effectiveChain = opts.chain;
1580
+ let parsedTarget;
1581
+ if (!isRawCall) {
1582
+ const knownChains = Object.keys(config.chains);
1583
+ parsedTarget = parseTarget(target, { knownChains });
1584
+ effectiveChain = resolveTargetChain(parsedTarget, opts.chain);
1585
+ }
1586
+ const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
1525
1587
  const signer = opts.encode ? undefined : await resolveAccountSigner(opts.from);
1526
1588
  let clientHandle;
1527
1589
  if (!opts.encode) {
@@ -1558,7 +1620,8 @@ function registerTxCommand(cli) {
1558
1620
  tx = await unsafeApi.txFromCallData(callBinary);
1559
1621
  callHex = target;
1560
1622
  } else {
1561
- const { pallet, item: callName } = parseTarget(target);
1623
+ const pallet = parsedTarget.pallet;
1624
+ const callName = parsedTarget.item;
1562
1625
  const palletNames = getPalletNames(meta);
1563
1626
  const palletInfo = findPallet(meta, pallet);
1564
1627
  if (!palletInfo) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,8 @@
17
17
  "typecheck": "tsc --noEmit",
18
18
  "lint": "biome check .",
19
19
  "lint:fix": "biome check --write .",
20
- "test": "bun test",
20
+ "test": "bun test --concurrent",
21
+ "test:coverage": "bun test --concurrent --coverage --coverage-reporter=lcov --coverage-dir=coverage",
21
22
  "prepare": "husky",
22
23
  "changeset": "changeset",
23
24
  "version": "changeset version",