polkadot-cli 1.8.0 → 1.9.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 +30 -1
  2. package/dist/cli.mjs +55 -34
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![npm version](https://img.shields.io/npm/v/polkadot-cli)](https://www.npmjs.com/package/polkadot-cli)
1
2
  [![codecov](https://codecov.io/gh/peetzweg/polkadot-cli/branch/main/graph/badge.svg)](https://codecov.io/gh/peetzweg/polkadot-cli)
2
3
 
3
4
  # polkadot-cli
@@ -309,12 +310,32 @@ dot query System.Number --output json | jq '.+1'
309
310
  dot query kusama.System.Account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
310
311
  ```
311
312
 
313
+ #### Partial key queries
314
+
315
+ For storage maps with multiple keys (NMaps), you can provide fewer keys than
316
+ expected to retrieve all entries matching that prefix. This uses the chain's
317
+ prefix-based iteration and does not require `--dump`.
318
+
319
+ ```bash
320
+ # Full key — returns a single value
321
+ dot query Staking.ErasStakers 100 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
322
+
323
+ # Partial key — returns all entries matching the first key
324
+ dot query Staking.ErasStakers 100
325
+
326
+ # No keys — requires --dump (safety net for large maps)
327
+ dot query Staking.ErasStakers --dump --limit 10
328
+ ```
329
+
330
+ The `--limit` option applies to partial key results just like it does for
331
+ `--dump` (default: 100, use `--limit 0` for unlimited).
332
+
312
333
  #### Output formatting
313
334
 
314
335
  Query results automatically convert on-chain types for readability:
315
336
 
316
337
  - **BigInt** values (e.g. balances) render as decimal strings
317
- - **Binary** fields (e.g. token `name`, `symbol`) render as text when valid UTF-8, or as `0x`-prefixed hex otherwise
338
+ - **Binary** fields (e.g. token `name`, `symbol`) render as text when the value contains only printable characters, or as `0x`-prefixed hex otherwise (values containing control characters, Private Use Area code points, or invalid UTF-8 sequences always fall back to hex)
318
339
  - **Uint8Array** values render as `0x`-prefixed hex
319
340
 
320
341
  ```bash
@@ -773,6 +794,14 @@ tx:
773
794
  value: ${AMOUNT}
774
795
  ```
775
796
 
797
+ Hex values passed via `--var` are preserved as-is, including leading zeros. This is important for encoded call data in XCM `Transact` instructions or similar byte-array fields:
798
+
799
+ ```bash
800
+ # Encode a remark, then embed it in an XCM Transact via --var
801
+ CALL=$(dot tx.System.remark 0xdead --encode)
802
+ dot ./xcm-transact.yaml --var CALL=$CALL --encode
803
+ ```
804
+
776
805
  All existing flags work with file input — `--chain` overrides the file's `chain:` field, `--from`, `--dry-run`, `--encode`, `--yaml`, `--json`, `--output`, etc. behave identically to inline commands.
777
806
 
778
807
  ### Compute hashes
package/dist/cli.mjs CHANGED
@@ -387,14 +387,35 @@ var init_accounts = __esm(() => {
387
387
  DEV_NAMES = ["alice", "bob", "charlie", "dave", "eve", "ferdie"];
388
388
  });
389
389
 
390
+ // src/utils/binary-display.ts
391
+ function isReadableText(text) {
392
+ for (let i = 0;i < text.length; i++) {
393
+ const code = text.charCodeAt(i);
394
+ if (code <= 31 && code !== 9 && code !== 10 && code !== 13)
395
+ return false;
396
+ if (code === 127)
397
+ return false;
398
+ if (code >= 128 && code <= 159)
399
+ return false;
400
+ if (code === 65533)
401
+ return false;
402
+ if (code >= 57344 && code <= 63743)
403
+ return false;
404
+ }
405
+ return true;
406
+ }
407
+ function binaryToDisplay(value) {
408
+ const text = value.asText();
409
+ return isReadableText(text) ? text : value.asHex();
410
+ }
411
+
390
412
  // src/core/output.ts
391
413
  import { Binary } from "polkadot-api";
392
414
  function replacer(_key, value) {
393
415
  if (typeof value === "bigint")
394
416
  return value.toString();
395
417
  if (value instanceof Binary) {
396
- const text = value.asText();
397
- return text.includes("�") ? value.asHex() : text;
418
+ return binaryToDisplay(value);
398
419
  }
399
420
  if (value instanceof Uint8Array)
400
421
  return `0x${Buffer.from(value).toString("hex")}`;
@@ -2158,7 +2179,7 @@ var init_complete = __esm(() => {
2158
2179
  // src/cli.ts
2159
2180
  import cac from "cac";
2160
2181
  // package.json
2161
- var version = "1.8.0";
2182
+ var version = "1.9.0";
2162
2183
 
2163
2184
  // src/commands/account.ts
2164
2185
  init_accounts_store();
@@ -3440,6 +3461,7 @@ async function showItemHelp2(category, target, opts) {
3440
3461
  init_hash();
3441
3462
  init_output();
3442
3463
  init_errors();
3464
+ import { readFile as readFile3 } from "node:fs/promises";
3443
3465
  async function resolveInput(data, opts) {
3444
3466
  const sources = [data !== undefined, !!opts.file, !!opts.stdin].filter(Boolean).length;
3445
3467
  if (sources > 1) {
@@ -3449,26 +3471,15 @@ async function resolveInput(data, opts) {
3449
3471
  throw new CliError("No input provided. Pass data as argument, or use --file or --stdin");
3450
3472
  }
3451
3473
  if (opts.file) {
3452
- const buf = await Bun.file(opts.file).arrayBuffer();
3474
+ const buf = await readFile3(opts.file);
3453
3475
  return new Uint8Array(buf);
3454
3476
  }
3455
3477
  if (opts.stdin) {
3456
- const reader = Bun.stdin.stream().getReader();
3457
3478
  const chunks = [];
3458
- while (true) {
3459
- const { done, value } = await reader.read();
3460
- if (done)
3461
- break;
3462
- chunks.push(value);
3479
+ for await (const chunk of process.stdin) {
3480
+ chunks.push(chunk);
3463
3481
  }
3464
- const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
3465
- const result = new Uint8Array(totalLen);
3466
- let offset = 0;
3467
- for (const chunk of chunks) {
3468
- result.set(chunk, offset);
3469
- offset += chunk.length;
3470
- }
3471
- return result;
3482
+ return new Uint8Array(Buffer.concat(chunks));
3472
3483
  }
3473
3484
  return parseInputData(data);
3474
3485
  }
@@ -3937,14 +3948,15 @@ async function handleQuery(target, keys, opts) {
3937
3948
  ];
3938
3949
  const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, effectiveKeys);
3939
3950
  const format = opts.output ?? "pretty";
3940
- if (storageItem.type === "map" && parsedKeys.length === 0) {
3941
- if (!opts.dump) {
3951
+ const expectedLen = storageItem.type === "map" && storageItem.keyTypeId != null ? meta.builder.buildStorage(palletInfo.name, storageItem.name).len : 0;
3952
+ if (storageItem.type === "map" && parsedKeys.length < expectedLen) {
3953
+ if (parsedKeys.length === 0 && !opts.dump) {
3942
3954
  clientHandle.destroy();
3943
3955
  await showItemHelp("query", target, { chain: opts.chain, rpc: opts.rpc });
3944
3956
  console.log(`${DIM}Hint: use --dump to fetch all entries${RESET}`);
3945
3957
  return;
3946
3958
  }
3947
- const entries = await storageApi.getEntries();
3959
+ const entries = await storageApi.getEntries(...parsedKeys);
3948
3960
  const limit = Number(opts.limit);
3949
3961
  const truncated = limit > 0 && entries.length > limit;
3950
3962
  const display = truncated ? entries.slice(0, limit) : entries;
@@ -3988,18 +4000,18 @@ async function parseStorageKeys(meta, palletName2, storageItem, args) {
3988
4000
  throw new Error(`${palletName2}.${storageItem.name} key expects ${typeDesc}
3989
4001
  ` + ` Pass 1 argument. Got ${args.length}.`);
3990
4002
  }
3991
- if (args.length !== len) {
4003
+ if (args.length > len) {
3992
4004
  let typeDesc;
3993
4005
  if (keyEntry.type === "tuple") {
3994
4006
  typeDesc = keyEntry.value.map((e) => describeType(meta.lookup, e.id)).join(", ");
3995
4007
  } else {
3996
4008
  typeDesc = describeType(meta.lookup, storageItem.keyTypeId);
3997
4009
  }
3998
- throw new Error(`${palletName2}.${storageItem.name} expects ${len} key arg(s): (${typeDesc}). Got ${args.length}.`);
4010
+ throw new Error(`${palletName2}.${storageItem.name} expects at most ${len} key arg(s): (${typeDesc}). Got ${args.length}.`);
3999
4011
  }
4000
4012
  if (keyEntry.type === "tuple") {
4001
4013
  const entries = keyEntry.value;
4002
- return Promise.all(entries.map((entry, i) => parseTypedArg(meta, entry, args[i])));
4014
+ return Promise.all(entries.slice(0, args.length).map((entry, i) => parseTypedArg(meta, entry, args[i])));
4003
4015
  }
4004
4016
  return Promise.all(args.map((arg) => parseTypedArg(meta, keyEntry, arg)));
4005
4017
  }
@@ -4462,8 +4474,7 @@ function formatEventValue(v) {
4462
4474
  if (v === null || v === undefined)
4463
4475
  return "null";
4464
4476
  if (v instanceof Binary3) {
4465
- const text = v.asText();
4466
- return text.includes("�") ? v.asHex() : text;
4477
+ return binaryToDisplay(v);
4467
4478
  }
4468
4479
  return JSON.stringify(v, (_k, val) => typeof val === "bigint" ? val.toString() : val);
4469
4480
  }
@@ -4958,7 +4969,7 @@ function watchTransaction(observable, level) {
4958
4969
 
4959
4970
  // src/config/store.ts
4960
4971
  init_types();
4961
- import { access as access3, mkdir as mkdir3, readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
4972
+ import { access as access3, mkdir as mkdir3, readFile as readFile4, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
4962
4973
  import { homedir as homedir2 } from "node:os";
4963
4974
  import { join as join3 } from "node:path";
4964
4975
  var DOT_DIR2 = join3(homedir2(), ".polkadot");
@@ -4978,7 +4989,7 @@ async function fileExists3(path) {
4978
4989
  async function loadConfig2() {
4979
4990
  await ensureDir3(DOT_DIR2);
4980
4991
  if (await fileExists3(CONFIG_PATH2)) {
4981
- const saved = JSON.parse(await readFile3(CONFIG_PATH2, "utf-8"));
4992
+ const saved = JSON.parse(await readFile4(CONFIG_PATH2, "utf-8"));
4982
4993
  return {
4983
4994
  ...saved,
4984
4995
  chains: { ...DEFAULT_CONFIG.chains, ...saved.chains }
@@ -4995,6 +5006,7 @@ async function saveConfig2(config) {
4995
5006
 
4996
5007
  // src/core/file-loader.ts
4997
5008
  init_errors();
5009
+ import { access as access4, readFile as readFile5 } from "node:fs/promises";
4998
5010
  import { parse as parseYaml } from "yaml";
4999
5011
  var CATEGORIES = ["tx", "query", "const", "apis"];
5000
5012
  var FILE_EXTENSIONS = [".json", ".yaml", ".yml"];
@@ -5042,16 +5054,24 @@ function substituteVars(text, vars) {
5042
5054
  return envVal;
5043
5055
  if (defaultValue !== undefined)
5044
5056
  return defaultValue;
5045
- throw new CliError(`Undefined variable "\${${varName}}" in file. ` + `Provide it via --var ${varName}=VALUE, as an environment variable, ` + `or add a default with \${${varName}:-default}.`);
5057
+ throw new CliError(`Undefined variable "\${${varName}}" in file.
5058
+
5059
+ ` + ` Provide it using one of:
5060
+ ` + ` --var ${varName}=VALUE
5061
+ ` + ` ${varName}=VALUE dot ... (environment variable)
5062
+ ` + ` \${${varName}:-default} (inline default in file)`);
5046
5063
  });
5047
5064
  }
5065
+ function quoteYamlHexValues(text) {
5066
+ return text.replace(/^(\s*(?:[^:]+:\s+|-\s+))(0x[0-9a-fA-F]+)\s*$/gm, '$1"$2"');
5067
+ }
5048
5068
  async function loadCommandFile(filePath, cliVars) {
5049
- const file = Bun.file(filePath);
5050
- const exists = await file.exists();
5051
- if (!exists) {
5069
+ try {
5070
+ await access4(filePath);
5071
+ } catch {
5052
5072
  throw new CliError(`File not found: ${filePath}`);
5053
5073
  }
5054
- const rawText = await file.text();
5074
+ const rawText = await readFile5(filePath, "utf-8");
5055
5075
  if (!rawText.trim()) {
5056
5076
  throw new CliError(`File is empty: ${filePath}`);
5057
5077
  }
@@ -5070,9 +5090,10 @@ async function loadCommandFile(filePath, cliVars) {
5070
5090
  } catch {}
5071
5091
  const mergedVars = { ...fileVars, ...cliVars };
5072
5092
  const substituted = substituteVars(rawText, mergedVars);
5093
+ const textToParse = isJson ? substituted : quoteYamlHexValues(substituted);
5073
5094
  let parsed;
5074
5095
  try {
5075
- parsed = isJson ? JSON.parse(substituted) : parseYaml(substituted);
5096
+ parsed = isJson ? JSON.parse(textToParse) : parseYaml(textToParse);
5076
5097
  } catch (err) {
5077
5098
  const format = isJson ? "JSON" : "YAML";
5078
5099
  const msg = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {