polkadot-cli 1.8.1 → 1.10.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 +56 -1
  2. package/dist/cli.mjs +142 -35
  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
@@ -596,6 +617,40 @@ For manual override, use `--ext` with a JSON object:
596
617
  dot tx System.remark 0xdeadbeef --from alice --ext '{"MyExtension":{"value":"..."}}'
597
618
  ```
598
619
 
620
+ #### Transaction options
621
+
622
+ Override low-level transaction parameters. Useful for rapid-fire submission (custom nonce), priority fees (tip), or controlling transaction lifetime (mortality).
623
+
624
+ | Flag | Value | Description |
625
+ |------|-------|-------------|
626
+ | `--nonce <n>` | non-negative integer | Override the auto-detected nonce |
627
+ | `--tip <amount>` | non-negative integer (planck) | Priority tip for the transaction pool |
628
+ | `--mortality <spec>` | `immortal` or period (min 4) | Transaction mortality window |
629
+ | `--at <block>` | `best`, `finalized`, or 0x-prefixed block hash | Block state to validate against |
630
+
631
+ ```bash
632
+ # Fire-and-forget: submit two txs in rapid succession with manual nonces
633
+ dot tx System.remark 0xdead --from alice --nonce 0 --wait broadcast
634
+ dot tx System.remark 0xbeef --from alice --nonce 1 --wait broadcast
635
+
636
+ # Add a priority tip (in planck)
637
+ dot tx Balances.transferKeepAlive 5FHneW46... 1000000000000 --from alice --tip 1000000
638
+
639
+ # Submit an immortal transaction (no expiry)
640
+ dot tx System.remark 0xdead --from alice --mortality immortal
641
+
642
+ # Set a custom mortality period (rounds up to nearest power of two)
643
+ dot tx System.remark 0xdead --from alice --mortality 128
644
+
645
+ # Validate against the best (not finalized) block
646
+ dot tx System.remark 0xdead --from alice --at best
647
+
648
+ # Combine: rapid-fire with tip and broadcast-only
649
+ dot tx System.remark 0xdead --from alice --nonce 5 --tip 500000 --wait broadcast
650
+ ```
651
+
652
+ When set, nonce / tip / mortality / at are shown in both `--dry-run` and submission output. These flags are silently ignored with `--encode`, `--yaml`, and `--json` (which return before signing).
653
+
599
654
  ### File-based commands
600
655
 
601
656
  Run any `dot` command from a YAML or JSON file. Especially useful for complex calls like XCM messages that are hard to construct inline.
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")}`;
@@ -2151,14 +2172,24 @@ var init_complete = __esm(() => {
2151
2172
  "inspect"
2152
2173
  ];
2153
2174
  GLOBAL_OPTIONS = ["--chain", "--rpc", "--light-client", "--output", "--help", "--version"];
2154
- TX_OPTIONS = ["--from", "--dry-run", "--encode", "--ext", "--wait"];
2175
+ TX_OPTIONS = [
2176
+ "--from",
2177
+ "--dry-run",
2178
+ "--encode",
2179
+ "--ext",
2180
+ "--wait",
2181
+ "--nonce",
2182
+ "--tip",
2183
+ "--mortality",
2184
+ "--at"
2185
+ ];
2155
2186
  QUERY_OPTIONS = ["--limit"];
2156
2187
  });
2157
2188
 
2158
2189
  // src/cli.ts
2159
2190
  import cac from "cac";
2160
2191
  // package.json
2161
- var version = "1.8.1";
2192
+ var version = "1.10.0";
2162
2193
 
2163
2194
  // src/commands/account.ts
2164
2195
  init_accounts_store();
@@ -3927,14 +3958,15 @@ async function handleQuery(target, keys, opts) {
3927
3958
  ];
3928
3959
  const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, effectiveKeys);
3929
3960
  const format = opts.output ?? "pretty";
3930
- if (storageItem.type === "map" && parsedKeys.length === 0) {
3931
- if (!opts.dump) {
3961
+ const expectedLen = storageItem.type === "map" && storageItem.keyTypeId != null ? meta.builder.buildStorage(palletInfo.name, storageItem.name).len : 0;
3962
+ if (storageItem.type === "map" && parsedKeys.length < expectedLen) {
3963
+ if (parsedKeys.length === 0 && !opts.dump) {
3932
3964
  clientHandle.destroy();
3933
3965
  await showItemHelp("query", target, { chain: opts.chain, rpc: opts.rpc });
3934
3966
  console.log(`${DIM}Hint: use --dump to fetch all entries${RESET}`);
3935
3967
  return;
3936
3968
  }
3937
- const entries = await storageApi.getEntries();
3969
+ const entries = await storageApi.getEntries(...parsedKeys);
3938
3970
  const limit = Number(opts.limit);
3939
3971
  const truncated = limit > 0 && entries.length > limit;
3940
3972
  const display = truncated ? entries.slice(0, limit) : entries;
@@ -3978,18 +4010,18 @@ async function parseStorageKeys(meta, palletName2, storageItem, args) {
3978
4010
  throw new Error(`${palletName2}.${storageItem.name} key expects ${typeDesc}
3979
4011
  ` + ` Pass 1 argument. Got ${args.length}.`);
3980
4012
  }
3981
- if (args.length !== len) {
4013
+ if (args.length > len) {
3982
4014
  let typeDesc;
3983
4015
  if (keyEntry.type === "tuple") {
3984
4016
  typeDesc = keyEntry.value.map((e) => describeType(meta.lookup, e.id)).join(", ");
3985
4017
  } else {
3986
4018
  typeDesc = describeType(meta.lookup, storageItem.keyTypeId);
3987
4019
  }
3988
- throw new Error(`${palletName2}.${storageItem.name} expects ${len} key arg(s): (${typeDesc}). Got ${args.length}.`);
4020
+ throw new Error(`${palletName2}.${storageItem.name} expects at most ${len} key arg(s): (${typeDesc}). Got ${args.length}.`);
3989
4021
  }
3990
4022
  if (keyEntry.type === "tuple") {
3991
4023
  const entries = keyEntry.value;
3992
- return Promise.all(entries.map((entry, i) => parseTypedArg(meta, entry, args[i])));
4024
+ return Promise.all(entries.slice(0, args.length).map((entry, i) => parseTypedArg(meta, entry, args[i])));
3993
4025
  }
3994
4026
  return Promise.all(args.map((arg) => parseTypedArg(meta, keyEntry, arg)));
3995
4027
  }
@@ -4021,6 +4053,50 @@ function parseWaitLevel(raw) {
4021
4053
  throw new CliError(`Invalid --wait value "${raw}". Valid: broadcast, best-block, best, finalized`);
4022
4054
  }
4023
4055
  }
4056
+ function parseNonceOption(raw) {
4057
+ if (raw === undefined)
4058
+ return;
4059
+ const n = Number(raw);
4060
+ if (!Number.isInteger(n) || n < 0) {
4061
+ throw new CliError(`Invalid --nonce value "${raw}". Must be a non-negative integer.`);
4062
+ }
4063
+ return n;
4064
+ }
4065
+ function parseTipOption(raw) {
4066
+ if (raw === undefined)
4067
+ return;
4068
+ try {
4069
+ const t = BigInt(raw);
4070
+ if (t < 0n) {
4071
+ throw new CliError(`Invalid --tip value "${raw}". Must be a non-negative integer.`);
4072
+ }
4073
+ return t;
4074
+ } catch (err) {
4075
+ if (err instanceof CliError)
4076
+ throw err;
4077
+ throw new CliError(`Invalid --tip value "${raw}". Must be a non-negative integer.`);
4078
+ }
4079
+ }
4080
+ function parseMortalityOption(raw) {
4081
+ if (raw === undefined)
4082
+ return;
4083
+ if (raw === "immortal")
4084
+ return { mortal: false };
4085
+ const n = Number(raw);
4086
+ if (!Number.isInteger(n) || n < 4) {
4087
+ throw new CliError(`Invalid --mortality value "${raw}". Use "immortal" or a period number (minimum 4).`);
4088
+ }
4089
+ return { mortal: true, period: n };
4090
+ }
4091
+ function parseAtOption(raw) {
4092
+ if (raw === undefined)
4093
+ return;
4094
+ if (raw === "best" || raw === "finalized")
4095
+ return raw;
4096
+ if (/^0x[0-9a-fA-F]{64}$/.test(raw))
4097
+ return raw;
4098
+ throw new CliError(`Invalid --at value "${raw}". Use "best", "finalized", or a 0x-prefixed 32-byte block hash.`);
4099
+ }
4024
4100
  async function handleTx(target, args, opts) {
4025
4101
  if (!target) {
4026
4102
  const config2 = await loadConfig();
@@ -4109,10 +4185,25 @@ async function handleTx(target, args, opts) {
4109
4185
  }
4110
4186
  let unsafeApi;
4111
4187
  let txOptions;
4188
+ const nonce = parseNonceOption(opts.nonce);
4189
+ const tip = parseTipOption(opts.tip);
4190
+ const mortality = parseMortalityOption(opts.mortality);
4191
+ const at = parseAtOption(opts.at);
4112
4192
  if (!decodeOnly) {
4113
4193
  const userExtOverrides = parseExtOption(opts.ext);
4114
4194
  const customSignedExtensions = buildCustomSignedExtensions(meta, userExtOverrides);
4115
- txOptions = Object.keys(customSignedExtensions).length > 0 ? { customSignedExtensions } : undefined;
4195
+ const built = {};
4196
+ if (Object.keys(customSignedExtensions).length > 0)
4197
+ built.customSignedExtensions = customSignedExtensions;
4198
+ if (nonce !== undefined)
4199
+ built.nonce = nonce;
4200
+ if (tip !== undefined)
4201
+ built.tip = tip;
4202
+ if (mortality !== undefined)
4203
+ built.mortality = mortality;
4204
+ if (at !== undefined)
4205
+ built.at = at;
4206
+ txOptions = Object.keys(built).length > 0 ? built : undefined;
4116
4207
  unsafeApi = clientHandle?.client.getUnsafeApi();
4117
4208
  }
4118
4209
  let tx;
@@ -4167,6 +4258,14 @@ async function handleTx(target, args, opts) {
4167
4258
  console.log(` ${BOLD}From:${RESET} ${opts.from} (${signerAddress})`);
4168
4259
  console.log(` ${BOLD}Call:${RESET} ${callHex}`);
4169
4260
  console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
4261
+ if (nonce !== undefined)
4262
+ console.log(` ${BOLD}Nonce:${RESET} ${nonce}`);
4263
+ if (tip !== undefined)
4264
+ console.log(` ${BOLD}Tip:${RESET} ${tip}`);
4265
+ if (mortality !== undefined)
4266
+ console.log(` ${BOLD}Mortality:${RESET} ${mortality.mortal ? `mortal (period ${mortality.period})` : "immortal"}`);
4267
+ if (at !== undefined)
4268
+ console.log(` ${BOLD}At:${RESET} ${at}`);
4170
4269
  try {
4171
4270
  const fees = await tx.getEstimatedFees(signer?.publicKey, txOptions);
4172
4271
  console.log(` ${BOLD}Estimated fees:${RESET} ${fees}`);
@@ -4182,6 +4281,14 @@ async function handleTx(target, args, opts) {
4182
4281
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
4183
4282
  console.log(` ${BOLD}Call:${RESET} ${callHex}`);
4184
4283
  console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
4284
+ if (nonce !== undefined)
4285
+ console.log(` ${BOLD}Nonce:${RESET} ${nonce}`);
4286
+ if (tip !== undefined)
4287
+ console.log(` ${BOLD}Tip:${RESET} ${tip}`);
4288
+ if (mortality !== undefined)
4289
+ console.log(` ${BOLD}Mortality:${RESET} ${mortality.mortal ? `mortal (period ${mortality.period})` : "immortal"}`);
4290
+ if (at !== undefined)
4291
+ console.log(` ${BOLD}At:${RESET} ${at}`);
4185
4292
  console.log(` ${BOLD}Tx:${RESET} ${result.txHash}`);
4186
4293
  if (result.type === "broadcasted") {
4187
4294
  console.log(` ${BOLD}Status:${RESET} ${GREEN}broadcasted${RESET}`);
@@ -4452,8 +4559,7 @@ function formatEventValue(v) {
4452
4559
  if (v === null || v === undefined)
4453
4560
  return "null";
4454
4561
  if (v instanceof Binary3) {
4455
- const text = v.asText();
4456
- return text.includes("�") ? v.asHex() : text;
4562
+ return binaryToDisplay(v);
4457
4563
  }
4458
4564
  return JSON.stringify(v, (_k, val) => typeof val === "bigint" ? val.toString() : val);
4459
4565
  }
@@ -5400,7 +5506,7 @@ if (process.argv[2] === "__complete") {
5400
5506
  default: "finalized"
5401
5507
  }).option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
5402
5508
  default: 100
5403
- }).option("--dump", "Dump all entries of a storage map (without specifying a key)").option("--var <kv>", "Template variable for file input (KEY=VALUE, repeatable)").action(async (dotpath, args, opts) => {
5509
+ }).option("--dump", "Dump all entries of a storage map (without specifying a key)").option("--var <kv>", "Template variable for file input (KEY=VALUE, repeatable)").option("--nonce <n>", "Custom nonce for manual tx sequencing (for tx)").option("--tip <amount>", "Tip to prioritize transaction (for tx)").option("--mortality <spec>", '"immortal" or period number (for tx)').option("--at <block>", 'Block hash, "best", or "finalized" to validate against (for tx)').action(async (dotpath, args, opts) => {
5404
5510
  if (!dotpath) {
5405
5511
  printHelp();
5406
5512
  return;
@@ -5422,6 +5528,10 @@ if (process.argv[2] === "__complete") {
5422
5528
  json: opts.json,
5423
5529
  ext: opts.ext,
5424
5530
  wait: opts.wait,
5531
+ nonce: opts.nonce,
5532
+ tip: opts.tip,
5533
+ mortality: opts.mortality,
5534
+ at: opts.at,
5425
5535
  parsedArgs: cmd.args
5426
5536
  });
5427
5537
  break;
@@ -5473,31 +5583,28 @@ if (process.argv[2] === "__complete") {
5473
5583
  case "query":
5474
5584
  await handleQuery(target, args, { ...handlerOpts, limit: opts.limit, dump: opts.dump });
5475
5585
  break;
5476
- case "tx":
5586
+ case "tx": {
5587
+ const txOpts = {
5588
+ ...handlerOpts,
5589
+ from: opts.from,
5590
+ dryRun: opts.dryRun,
5591
+ encode: opts.encode,
5592
+ yaml: opts.yaml,
5593
+ json: opts.json,
5594
+ ext: opts.ext,
5595
+ wait: opts.wait,
5596
+ nonce: opts.nonce,
5597
+ tip: opts.tip,
5598
+ mortality: opts.mortality,
5599
+ at: opts.at
5600
+ };
5477
5601
  if (parsed.pallet && /^0x[0-9a-fA-F]+$/.test(parsed.pallet)) {
5478
- await handleTx(parsed.pallet, args, {
5479
- ...handlerOpts,
5480
- from: opts.from,
5481
- dryRun: opts.dryRun,
5482
- encode: opts.encode,
5483
- yaml: opts.yaml,
5484
- json: opts.json,
5485
- ext: opts.ext,
5486
- wait: opts.wait
5487
- });
5602
+ await handleTx(parsed.pallet, args, txOpts);
5488
5603
  } else {
5489
- await handleTx(target, args, {
5490
- ...handlerOpts,
5491
- from: opts.from,
5492
- dryRun: opts.dryRun,
5493
- encode: opts.encode,
5494
- yaml: opts.yaml,
5495
- json: opts.json,
5496
- ext: opts.ext,
5497
- wait: opts.wait
5498
- });
5604
+ await handleTx(target, args, txOpts);
5499
5605
  }
5500
5606
  break;
5607
+ }
5501
5608
  case "const":
5502
5609
  await handleConst(target, handlerOpts);
5503
5610
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {