polkadot-cli 1.18.0 → 1.20.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 +120 -15
  2. package/dist/cli.mjs +350 -58
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -280,6 +280,10 @@ dot account create my-staking --path //staking
280
280
  # Add a keyed account from a BIP39 mnemonic
281
281
  dot account add treasury --secret "word1 word2 ... word12"
282
282
 
283
+ # Add from a 32-byte hex seed or a 64-byte raw sr25519 private key
284
+ dot account add seeded --secret 0x1111111111111111111111111111111111111111111111111111111111111111
285
+ dot account add raw-key --secret 0x<128-hex-char expanded secret> # no --path
286
+
283
287
  # Add with a derivation path
284
288
  dot account add hot-wallet --secret "word1 word2 ... word12" --path //hot
285
289
 
@@ -313,6 +317,7 @@ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
313
317
  dot account inspect 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
314
318
  dot account inspect alice --prefix 0 # Polkadot mainnet prefix
315
319
  dot account inspect alice --json # JSON output
320
+ dot account inspect dave --show-secret # reveal mnemonic + sr25519 private key
316
321
  ```
317
322
 
318
323
  #### Watch-only accounts
@@ -373,6 +378,11 @@ dot account alice # shorthand — unknown subcommands fall th
373
378
 
374
379
  dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
375
380
  dot account inspect 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
381
+
382
+ # pallet-revive H160 — the 20-byte address shape from EVM tooling. Same
383
+ # input slot as SS58 / hex pubkey; resolved to the deterministic fallback
384
+ # AccountId32 (`H160 || 0xEE * 12`).
385
+ dot account inspect 0x9621DDe636dE098B43Efb0fA9b61fAcFE328F99D
376
386
  ```
377
387
 
378
388
  Use `--prefix` to encode the SS58 address with a specific network prefix (default: 42):
@@ -390,6 +400,7 @@ dot account inspect alice --json
390
400
  # {
391
401
  # "publicKey": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
392
402
  # "ss58": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
403
+ # "h160": "0x9621DDe636dE098B43Efb0fA9b61fAcFE328F99D",
393
404
  # "prefix": 42,
394
405
  # "name": "Alice",
395
406
  # "kind": "dev"
@@ -407,10 +418,38 @@ dot account inspect alice
407
418
  # Kind: dev
408
419
  # Public Key: 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
409
420
  # SS58: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
421
+ # H160: 0x9621DDe636dE098B43Efb0fA9b61fAcFE328F99D
422
+ # Prefix: 42
423
+ ```
424
+
425
+ The `Kind:` line categorises the account: `dev` (built-in), `signer` (has a secret/env), `watch-only` (raw external address), `pallet sovereign` (derived from a `PalletId`), `parachain sovereign (child|sibling)` (derived from a parachain ID), or `revive H160 fallback` (a 20-byte input resolved to its deterministic Substrate AccountId32). For derived sovereigns, an extra `Source:` line shows what the address was derived from. For env-backed signers, an `Env:` line shows the variable; for derived child keys, `Derivation:` shows the path.
426
+
427
+ ##### pallet-revive H160 address
428
+
429
+ Every Substrate account has a corresponding 20-byte H160 address under [pallet-revive](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive) (the new EVM-compatible smart-contracts pallet on Polkadot Hub / Asset Hub). `dot account inspect` always shows it, EIP-55 checksummed, on the `H160:` line. The mapping is offline and prefix-independent:
430
+
431
+ - **AccountId32 → H160:** if the last 12 bytes are `0xEE`, strip them (the account originated from an Eth address); otherwise `keccak256(accountId32)` and take the last 20 bytes.
432
+ - **H160 → AccountId32:** deterministic fallback is `H160 || 0xEE * 12`. (The full mapping after a successful `pallet_revive.map_account` extrinsic lives in on-chain `AddressSuffix` storage and isn't recoverable offline — that's a chain-state lookup.)
433
+
434
+ Pass a 20-byte hex value as the inspect input to resolve it back to its fallback Substrate account:
435
+
436
+ ```bash
437
+ dot account inspect 0x9621DDe636dE098B43Efb0fA9b61fAcFE328F99D
438
+ # Output:
439
+ # Account Info
440
+ #
441
+ # Kind: revive H160 fallback
442
+ # Public Key: 0x9621dde636de098b43efb0fa9b61facfe328f99deeeeeeeeeeeeeeeeeeeeeeee
443
+ # SS58: 5FTZ6n1wY3GBqEZ2DWEdspbTarvRnp8DM8x2YXbWubu7JN98
444
+ # H160: 0x9621DDe636dE098B43Efb0fA9b61fAcFE328F99D
410
445
  # Prefix: 42
446
+
447
+ # Script-friendly: just the H160 for a given account
448
+ dot account inspect alice --json | jq -r .h160
449
+ # 0x9621DDe636dE098B43Efb0fA9b61fAcFE328F99D
411
450
  ```
412
451
 
413
- The `Kind:` line categorises the account: `dev` (built-in), `signer` (has a secret/env), `watch-only` (raw external address), `pallet sovereign` (derived from a `PalletId`), or `parachain sovereign (child|sibling)` (derived from a parachain ID). For derived sovereigns, an extra `Source:` line shows what the address was derived from. For env-backed signers, an `Env:` line shows the variable; for derived child keys, `Derivation:` shows the path.
452
+ Note: `dot` implements the current `pallet-revive` master variant (keccak fallback). Older `stable2412` runtimes used plain `accountId32[..20]` truncation; if you target one, compute it manually until a `--revive-truncate` flag lands.
414
453
 
415
454
  ##### Stateless sovereign derivation (script-friendly)
416
455
 
@@ -450,16 +489,21 @@ dot account inspect --pallet-id py/trsry --json
450
489
 
451
490
  Constraints (will error): cannot combine a positional input with derivation flags; `--pallet-id` and `--parachain` are mutually exclusive; `--parachain` requires `--parachain-type child|sibling`; `--show-secret` doesn't apply (derived sovereigns have no key).
452
491
 
453
- #### Reveal the sr25519 private key
492
+ #### Reveal the mnemonic and sr25519 private key
454
493
 
455
- For provisioning another signer (e.g. a server that expects a raw hex private key in an env var), add `--show-secret` to print the **64-byte sr25519 expanded secret** as `0x`-prefixed hex:
494
+ For provisioning another signer (e.g. a server that expects a raw hex private key in an env var), add `--show-secret` to print the **64-byte sr25519 expanded secret** as `0x`-prefixed hex. It also reveals the **stored mnemonic** (or hex seed) so you can back it up:
456
495
 
457
496
  ```bash
458
- dot account inspect dave --show-secret
459
- # Private Key: 0x<128 hex chars> (sr25519 expanded, 64 bytes never share)
497
+ dot account inspect my-validator --show-secret
498
+ # Mnemonic: word1 word2 ... word12 (only for accounts stored as a phrase)
499
+ # Private Key: 0x<128 hex chars> (sr25519 expanded, 64 bytes — never share)
460
500
  ```
461
501
 
462
- Works for dev accounts (derived on-the-fly from the standard dev mnemonic) and for stored accounts that have a secret (mnemonic or hex seed). Refuses on watch-only accounts, bare SS58 addresses, or hex public keys. The hex is the final secret after any derivation path is applied, so it can be fed directly to signers that don't accept a mnemonic+path (e.g. `@scure/sr25519`'s `sign`, or services like identity-backend that read a `PROXY_PRIVATE_KEY`). Combine with `--json` to include it under the `privateKey` field.
502
+ Works for dev accounts (derived on-the-fly from the standard dev mnemonic) and for stored accounts that have a secret. Refuses on watch-only accounts, bare SS58 addresses, or hex public keys. The revealed line depends on how the account was stored: a phrase shows under `Mnemonic`, a 32-byte hex seed under `Seed`, and a raw private key shows only the `Private Key` (the stored secret already _is_ the expanded key). **Env-backed secrets are never resolved to disk output** — only the `$VAR` reference is shown. The expanded `Private Key` is the final secret after any derivation path is applied, so it can be fed directly to signers that don't accept a mnemonic+path (e.g. `@scure/sr25519`'s `sign`, or services like identity-backend that read a `PROXY_PRIVATE_KEY`). Combine with `--json` to include the values under the `mnemonic`/`seed` and `privateKey` fields.
503
+
504
+ The revealed `Private Key` round-trips: you can re-import it as a usable account (see [Import a raw private key](#import-a-raw-private-key) below).
505
+
506
+ > **Note:** if the account was stored with a derivation path, the revealed `Mnemonic`/`Seed` reproduces the original address **only when re-imported with the same `--path`** (shown on the `Derivation` line). The `Private Key` already bakes in the path, so it round-trips on its own.
463
507
 
464
508
  #### Env-var-backed accounts
465
509
 
@@ -506,12 +550,29 @@ Signers
506
550
 
507
551
  **Supported secret formats for import:**
508
552
 
509
- | Format | Example | Status |
510
- |--------|---------|--------|
511
- | BIP39 mnemonic (12/24 words) | `"abandon abandon ... about"` | Supported |
512
- | Hex seed (`0x` + 64 hex chars) | `0xabcdef0123...` | Not supported via CLI (see below) |
553
+ | Format | Example | `--path`? |
554
+ |--------|---------|-----------|
555
+ | BIP39 mnemonic (12/24 words) | `"abandon abandon ... about"` | Yes |
556
+ | Hex seed (`0x` + 64 hex chars = 32 bytes) | `0x1111...1111` | Yes |
557
+ | Raw private key (`0x` + 128 hex chars = 64-byte sr25519 expanded secret) | `0x20e0...5568` | No (cannot be HD-derived) |
558
+
559
+ All three formats work directly from the command line via `--secret` or via `--env`.
513
560
 
514
- **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`.
561
+ #### Import a raw private key
562
+
563
+ The 64-byte expanded secret that `--show-secret` prints can be re-imported as a fully usable, signing-capable account. This is the round-trip companion to `--show-secret` — handy when a key only exists in expanded form (e.g. exported from another tool or read from a `PROXY_PRIVATE_KEY` env var):
564
+
565
+ ```bash
566
+ # Round-trip: reveal dave's expanded secret, then import it under a new name
567
+ SECRET=$(dot account inspect dave --show-secret --json | jq -r .privateKey)
568
+ dot account add raw-dave --secret "$SECRET"
569
+ # raw-dave now has the same address as dave and can sign
570
+
571
+ # Or from an environment variable (secret stays off disk)
572
+ dot account add server-signer --env PROXY_PRIVATE_KEY
573
+ ```
574
+
575
+ A raw private key cannot be HD-derived, so `--path` is rejected for this format. Imported expanded-secret accounts sign exactly like mnemonic-backed ones.
515
576
 
516
577
  #### Export/import accounts
517
578
 
@@ -659,6 +720,40 @@ dot polkadot-asset-hub.query.Assets.Metadata 1984
659
720
  # }
660
721
  ```
661
722
 
723
+ #### Historical reads — `--at <block>`
724
+
725
+ Storage queries default to the latest finalized head. Pass `--at` to read
726
+ state at a specific block hash, the chain head (`best`), or `finalized`
727
+ (explicit). Accepted on both `query.*` and `apis.*` runtime calls.
728
+
729
+ ```bash
730
+ # Read at the current best (non-finalized) head — useful for low-latency reads
731
+ dot polkadot.query.System.Number --at best
732
+
733
+ # Read at the last finalized block — same as the default, but explicit
734
+ dot polkadot.query.System.Number --at finalized
735
+
736
+ # Pin a finalized hash and read multiple items at that exact block
737
+ HASH=$(dot polkadot.rpc.chain_getFinalizedHead | tr -d '"')
738
+ dot polkadot.query.System.Number --at "$HASH"
739
+ dot polkadot.apis.Core.version --at "$HASH" --json | jq .spec_version
740
+ ```
741
+
742
+ `--at` accepts a 32-byte `0x…` block hash, `"best"`, or `"finalized"`.
743
+ Anything else errors before any network call. Tx submission rejects `"best"`.
744
+
745
+ > **Archive-only blocks**: papi v2 talks to the `chainHead_v1_*` JSON-RPC API,
746
+ > which only serves *pinned* (recent) blocks. Querying a hash older than a
747
+ > few minutes against a non-archive node fails with a clean error that
748
+ > includes a copy-pasteable `--rpc wss://<archive-endpoint>` hint:
749
+ >
750
+ > ```
751
+ > ⚠ 0x… is not available on the current RPC endpoint.
752
+ > Public nodes serve only recent (pinned) blocks via chainHead_v1_*.
753
+ > For deep historical reads, point --rpc at an archive endpoint, e.g.:
754
+ > dot ... --at 0x… --rpc wss://<archive-endpoint>
755
+ > ```
756
+
662
757
  ### Look up constants
663
758
 
664
759
  ```bash
@@ -1340,7 +1435,7 @@ Override low-level transaction parameters. Useful for rapid-fire submission (cus
1340
1435
  | `--nonce <n>` | non-negative integer | Override the auto-detected nonce |
1341
1436
  | `--tip <amount>` | non-negative integer (planck) | Priority tip for the transaction pool |
1342
1437
  | `--mortality <spec>` | `immortal` or period (min 4) | Transaction mortality window |
1343
- | `--at <block>` | 0x-prefixed block hash | Block hash to validate against (defaults to finalized) |
1438
+ | `--at <block>` | 0x-prefixed block hash, `"best"`, or `"finalized"` | Block to read/validate against (defaults to finalized). Also honored on `query.*` and `apis.*` for historical reads; tx submission rejects `"best"`. |
1344
1439
 
1345
1440
  ```bash
1346
1441
  # Fire-and-forget: submit two txs in rapid succession with manual nonces
@@ -1626,7 +1721,7 @@ All existing flags work with file input — `--chain` overrides the file's `chai
1626
1721
 
1627
1722
  ### Compute hashes
1628
1723
 
1629
- Compute cryptographic hashes commonly used in Substrate. Supports BLAKE2b-256, BLAKE2b-128, Keccak-256, and SHA-256.
1724
+ Compute cryptographic hashes commonly used in Substrate. Supports BLAKE2b-256, BLAKE2b-128, Keccak-256, SHA-256, and the XXH64-based `twox64` / `twox128` / `twox256` family used to build Substrate storage keys.
1630
1725
 
1631
1726
  ```bash
1632
1727
  # Hash hex-encoded data
@@ -1655,6 +1750,16 @@ dot hash blake2b256 0xdeadbeef --json
1655
1750
  # "input": "0xdeadbeef",
1656
1751
  # "hash": "0xf3e925002fed7cc0ded46842569eb5c90c910c091d8d04a1bdf96e0db719fd91"
1657
1752
  # }
1753
+
1754
+ # Substrate twox128 — pallet/storage prefix used everywhere in Substrate state
1755
+ dot hash twox128 System
1756
+ # Output:
1757
+ # 0x26aa394eea5630e07c48ae0c9558cef7
1758
+
1759
+ # Build a full storage key for `System.Number` and read it raw via JSON-RPC
1760
+ PALLET=$(dot hash twox128 System)
1761
+ ITEM=$(dot hash twox128 Number)
1762
+ dot polkadot.rpc.state_getStorage "${PALLET}${ITEM:2}"
1658
1763
  ```
1659
1764
 
1660
1765
  Run `dot hash` with no arguments to see all available algorithms.
@@ -1793,7 +1898,7 @@ dot verifiable alice
1793
1898
  # Bandersnatch Member Key
1794
1899
  #
1795
1900
  # Account: alice
1796
- # Member Key: 0x66813b70ba616b374c90ac92edff6e3be95e12adbc93ea7a6c37cbf334ab87e2
1901
+ # Member Key: 0xbb6ee099b568f1844d62fc00e6305c2e83aa8da30ce59e664ef39e089204d43c
1797
1902
 
1798
1903
  # Derive keyed member key (full person — "candidate" context)
1799
1904
  dot verifiable alice --context candidate
@@ -1802,7 +1907,7 @@ dot verifiable alice --context candidate
1802
1907
  #
1803
1908
  # Account: alice
1804
1909
  # Context: candidate
1805
- # Member Key: 0x2fd5b74033d904cf5575b932507939c5d43811e488223229eaf5596565f15ae6
1910
+ # Member Key: 0x5f915576987547d3e55bb4129ac8cae1d338f8933073dc74272b4c825f738592
1806
1911
 
1807
1912
  # Arbitrary context string
1808
1913
  dot verifiable alice --context pps
package/dist/cli.mjs CHANGED
@@ -22,6 +22,12 @@ function isLikelyStaleMetadataError(err) {
22
22
  return false;
23
23
  return STALE_METADATA_PATTERNS.some((re) => re.test(msg));
24
24
  }
25
+ function isBlockUnavailableError(err) {
26
+ const msg = err instanceof Error ? err.message : typeof err === "string" ? err : "";
27
+ if (!msg)
28
+ return false;
29
+ return BLOCK_UNAVAILABLE_PATTERNS.some((re) => re.test(msg));
30
+ }
25
31
  function formatRuntimeError(err) {
26
32
  const msg = err instanceof Error ? err.message : String(err);
27
33
  if (/wasm trap|wasm `?unreachable`? instruction|Execution aborted due to trap/i.test(msg)) {
@@ -40,7 +46,7 @@ function formatRuntimeError(err) {
40
46
  }
41
47
  return msg;
42
48
  }
43
- var CliError, ConnectionError, MetadataError, STALE_METADATA_PATTERNS;
49
+ var CliError, ConnectionError, MetadataError, STALE_METADATA_PATTERNS, BLOCK_UNAVAILABLE_PATTERNS;
44
50
  var init_errors = __esm(() => {
45
51
  CliError = class CliError extends Error {
46
52
  constructor(message) {
@@ -69,6 +75,11 @@ var init_errors = __esm(() => {
69
75
  /Lookup failed/i,
70
76
  /metadata.*mismatch/i
71
77
  ];
78
+ BLOCK_UNAVAILABLE_PATTERNS = [
79
+ /is not pinned/i,
80
+ /Invalid BlockHash/i,
81
+ /UnknownBlock.*Header was not found/i
82
+ ];
72
83
  });
73
84
 
74
85
  // src/utils/runtime-fingerprint.ts
@@ -450,6 +461,7 @@ function suggestMessage(kind, input, candidates) {
450
461
  }
451
462
 
452
463
  // src/core/accounts.ts
464
+ import { hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js";
453
465
  import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
454
466
  import {
455
467
  DEV_PHRASE,
@@ -460,7 +472,7 @@ import {
460
472
  ss58Decode,
461
473
  validateMnemonic
462
474
  } from "@polkadot-labs/hdkd-helpers";
463
- import { HDKD, secretFromSeed } from "@scure/sr25519";
475
+ import { getPublicKey, HDKD, secretFromSeed, sign } from "@scure/sr25519";
464
476
  import { getPolkadotSigner } from "polkadot-api/signer";
465
477
  function isDevAccount(name) {
466
478
  return DEV_NAMES.includes(name.toLowerCase());
@@ -475,18 +487,54 @@ function deriveFromMnemonic(mnemonic, path) {
475
487
  return derive(path);
476
488
  }
477
489
  function deriveFromHexSeed(hexSeed, path) {
478
- const clean = hexSeed.startsWith("0x") ? hexSeed.slice(2) : hexSeed;
479
- const seed = new Uint8Array(clean.length / 2);
480
- for (let i = 0;i < clean.length; i += 2) {
481
- seed[i / 2] = parseInt(clean.substring(i, i + 2), 16);
482
- }
483
- const derive = sr25519CreateDerive(seed);
490
+ const derive = sr25519CreateDerive(hexToBytes(hexSeed));
484
491
  return derive(path);
485
492
  }
486
493
  function getDevKeypair(name) {
487
494
  const path = devDerivationPath(name);
488
495
  return deriveFromMnemonic(DEV_PHRASE, path);
489
496
  }
497
+ function hexToBytes(hex) {
498
+ return nobleHexToBytes(hex.startsWith("0x") ? hex.slice(2) : hex);
499
+ }
500
+ function isExpandedSecret(secret) {
501
+ return EXPANDED_SECRET_RE.test(secret);
502
+ }
503
+ function isHexSeed(secret) {
504
+ return HEX_SEED_RE.test(secret);
505
+ }
506
+ function secretKind(secret) {
507
+ if (isExpandedSecret(secret))
508
+ return "expanded";
509
+ if (isHexSeed(secret))
510
+ return "seed";
511
+ return "mnemonic";
512
+ }
513
+ function assertNoPathForExpandedSecret(derivationPath) {
514
+ if (derivationPath) {
515
+ throw new Error("Stored account has a 64-byte expanded secret with a derivation path, which cannot be applied. An expanded secret cannot be HD-derived; remove the derivationPath from accounts.json.");
516
+ }
517
+ }
518
+ function keypairFromExpandedSecret(secret) {
519
+ return {
520
+ publicKey: getPublicKey(secret),
521
+ sign: (msg) => sign(secret, msg)
522
+ };
523
+ }
524
+ function keypairFromSecret(secret, derivationPath = "") {
525
+ if (isExpandedSecret(secret)) {
526
+ assertNoPathForExpandedSecret(derivationPath);
527
+ return keypairFromExpandedSecret(hexToBytes(secret));
528
+ }
529
+ return isHexSeed(secret) ? deriveFromHexSeed(secret, derivationPath) : deriveFromMnemonic(secret, derivationPath);
530
+ }
531
+ function expandedSecretFromStored(secret, derivationPath = "") {
532
+ if (isExpandedSecret(secret)) {
533
+ assertNoPathForExpandedSecret(derivationPath);
534
+ return hexToBytes(secret);
535
+ }
536
+ return deriveExpandedSecret(miniSecretFromSecret(secret), derivationPath);
537
+ }
490
538
  function parseDerivations(path) {
491
539
  const out = [];
492
540
  for (const [, type, code] of path.matchAll(DERIVATION_RE)) {
@@ -517,17 +565,11 @@ function deriveExpandedSecret(miniSecret, path) {
517
565
  return parseDerivations(path).reduce((sk, [type, code]) => type === "hard" ? HDKD.secretHard(sk, createChainCode(code)) : HDKD.secretSoft(sk, createChainCode(code)), secretFromSeed(miniSecret));
518
566
  }
519
567
  function miniSecretFromSecret(secret) {
520
- const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
521
- if (isHexSeed) {
522
- const clean = secret.slice(2);
523
- const bytes = new Uint8Array(32);
524
- for (let i = 0;i < clean.length; i += 2) {
525
- bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
526
- }
527
- return bytes;
568
+ if (isHexSeed(secret)) {
569
+ return hexToBytes(secret);
528
570
  }
529
571
  if (!validateMnemonic(secret)) {
530
- throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
572
+ throw new Error("Invalid secret. Expected a BIP39 mnemonic or a 0x-prefixed 32-byte hex seed.");
531
573
  }
532
574
  return entropyToMiniSecret(mnemonicToEntropy(secret));
533
575
  }
@@ -544,13 +586,19 @@ function createNewAccount(path = "") {
544
586
  return { mnemonic, publicKey: keypair.publicKey };
545
587
  }
546
588
  function importAccount(secret, path = "") {
547
- const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
548
- if (isHexSeed) {
589
+ if (isExpandedSecret(secret)) {
590
+ if (path) {
591
+ throw new Error("Derivation paths are not supported for raw private key (64-byte expanded secret) import. An expanded secret cannot be HD-derived; omit --path.");
592
+ }
593
+ const keypair2 = keypairFromExpandedSecret(hexToBytes(secret));
594
+ return { publicKey: keypair2.publicKey };
595
+ }
596
+ if (isHexSeed(secret)) {
549
597
  const keypair2 = deriveFromHexSeed(secret, path);
550
598
  return { publicKey: keypair2.publicKey };
551
599
  }
552
600
  if (!validateMnemonic(secret)) {
553
- throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
601
+ throw new Error("Invalid secret. Expected a BIP39 mnemonic, a 0x-prefixed 32-byte hex seed, or a 0x-prefixed 64-byte sr25519 expanded secret.");
554
602
  }
555
603
  const keypair = deriveFromMnemonic(secret, path);
556
604
  return { publicKey: keypair.publicKey };
@@ -616,9 +664,7 @@ async function resolveAccountKeypair(name) {
616
664
  if (account.secret === undefined) {
617
665
  throw new Error(`Account "${name}" is watch-only (no secret). Cannot sign. Import with --secret or --env.`);
618
666
  }
619
- const secret = resolveSecret(account.secret);
620
- const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
621
- return isHexSeed ? deriveFromHexSeed(secret, account.derivationPath) : deriveFromMnemonic(secret, account.derivationPath);
667
+ return keypairFromSecret(resolveSecret(account.secret), account.derivationPath);
622
668
  }
623
669
  async function resolveAccountSigner(name) {
624
670
  const keypair = await resolveAccountKeypair(name);
@@ -626,8 +672,8 @@ async function resolveAccountSigner(name) {
626
672
  }
627
673
  async function resolveAccountExpandedSecret(name) {
628
674
  if (isDevAccount(name)) {
629
- const miniSecret2 = entropyToMiniSecret(mnemonicToEntropy(DEV_PHRASE));
630
- return deriveExpandedSecret(miniSecret2, devDerivationPath(name));
675
+ const miniSecret = entropyToMiniSecret(mnemonicToEntropy(DEV_PHRASE));
676
+ return deriveExpandedSecret(miniSecret, devDerivationPath(name));
631
677
  }
632
678
  const accountsFile = await loadAccounts();
633
679
  const account = findAccount(accountsFile, name);
@@ -644,16 +690,17 @@ async function resolveAccountExpandedSecret(name) {
644
690
  if (account.secret === undefined) {
645
691
  throw new Error(`Account "${name}" is watch-only (no secret). Cannot derive private key. Import with --secret or --env.`);
646
692
  }
647
- const miniSecret = miniSecretFromSecret(resolveSecret(account.secret));
648
- return deriveExpandedSecret(miniSecret, account.derivationPath);
693
+ return expandedSecretFromStored(resolveSecret(account.secret), account.derivationPath);
649
694
  }
650
695
  function bytesToHex(bytes) {
651
696
  return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
652
697
  }
653
- var DEV_NAMES, DERIVATION_RE;
698
+ var DEV_NAMES, EXPANDED_SECRET_RE, HEX_SEED_RE, DERIVATION_RE;
654
699
  var init_accounts = __esm(() => {
655
700
  init_accounts_store();
656
701
  DEV_NAMES = ["alice", "bob", "charlie", "dave", "eve", "ferdie"];
702
+ EXPANDED_SECRET_RE = /^0x[0-9a-fA-F]{128}$/;
703
+ HEX_SEED_RE = /^0x[0-9a-fA-F]{64}$/;
657
704
  DERIVATION_RE = /(\/{1,2})([^/]+)/g;
658
705
  });
659
706
 
@@ -1253,7 +1300,7 @@ async function fetchMetadataFromChain(clientHandle, chainName) {
1253
1300
  let bytes;
1254
1301
  try {
1255
1302
  const hex = await withTimeout(client._request("state_call", ["Metadata_metadata_at_version", v15Arg]), chainName);
1256
- const raw = hexToBytes(hex);
1303
+ const raw = hexToBytes3(hex);
1257
1304
  const decoded = optionalOpaqueBytes.dec(raw);
1258
1305
  if (decoded !== undefined) {
1259
1306
  bytes = new Uint8Array(decoded);
@@ -1262,7 +1309,7 @@ async function fetchMetadataFromChain(clientHandle, chainName) {
1262
1309
  if (!bytes) {
1263
1310
  try {
1264
1311
  const hex = await withTimeout(client._request("state_getMetadata", []), chainName);
1265
- bytes = hexToBytes(hex);
1312
+ bytes = hexToBytes3(hex);
1266
1313
  } catch (err) {
1267
1314
  if (err instanceof ConnectionError)
1268
1315
  throw err;
@@ -1311,6 +1358,24 @@ async function withStalenessSuggestion(chainName, clientHandle, task) {
1311
1358
  ` + ` Run: dot chain update ${chainName}`);
1312
1359
  }
1313
1360
  }
1361
+ async function withBlockAvailabilityHint(atRaw, task) {
1362
+ try {
1363
+ return await task();
1364
+ } catch (err) {
1365
+ if (!isBlockUnavailableError(err))
1366
+ throw err;
1367
+ const original = err instanceof Error ? err.message : String(err);
1368
+ const isExplicitHash = atRaw && atRaw !== "best" && atRaw !== "finalized";
1369
+ const target = isExplicitHash ? atRaw : "the requested block";
1370
+ const exampleAt = isExplicitHash ? atRaw : "<hash>";
1371
+ throw new CliError(`${original}
1372
+
1373
+ ` + `⚠ ${target} is not available on the current RPC endpoint.
1374
+ ` + ` Public nodes serve only recent (pinned) blocks via chainHead_v1_*.
1375
+ ` + ` For deep historical reads, point --rpc at an archive endpoint, e.g.:
1376
+ ` + ` dot ... --at ${exampleAt} --rpc wss://<archive-endpoint>`);
1377
+ }
1378
+ }
1314
1379
  async function getOrFetchMetadata(chainName, clientHandle) {
1315
1380
  let raw = await loadMetadata(chainName);
1316
1381
  if (!raw) {
@@ -1440,7 +1505,7 @@ function describeCallArgs(meta, palletName, callName) {
1440
1505
  function describeEventFields(meta, palletName, eventName) {
1441
1506
  return compactArgsString(getEventFields(meta, palletName, eventName));
1442
1507
  }
1443
- function hexToBytes(hex) {
1508
+ function hexToBytes3(hex) {
1444
1509
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1445
1510
  const bytes = new Uint8Array(clean.length / 2);
1446
1511
  for (let i = 0;i < clean.length; i += 2) {
@@ -1535,6 +1600,15 @@ import { compact as scaleCompact } from "@polkadot-api/substrate-bindings";
1535
1600
  import { getViewBuilder } from "@polkadot-api/view-builder";
1536
1601
  import { Binary as Binary2 } from "polkadot-api";
1537
1602
  import { stringify as stringifyYaml } from "yaml";
1603
+ function parseAtForRead(raw) {
1604
+ if (raw === undefined)
1605
+ return;
1606
+ if (raw === "best" || raw === "finalized")
1607
+ return raw;
1608
+ if (/^0x[0-9a-fA-F]{64}$/.test(raw))
1609
+ return raw;
1610
+ throw new CliError(`Invalid --at value "${raw}". Use "best", "finalized", or a 0x-prefixed 32-byte block hash.`);
1611
+ }
1538
1612
  async function parseStructArgs(meta, fields, args, callLabel) {
1539
1613
  const fieldNames = Object.keys(fields);
1540
1614
  if (args.length !== fieldNames.length) {
@@ -1943,8 +2017,10 @@ async function handleApis(target, args, opts) {
1943
2017
  typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
1944
2018
  ];
1945
2019
  const parsedArgs = await parseRuntimeApiArgs(meta, method, effectiveArgs);
2020
+ const atArg = parseAtForRead(opts.at);
2021
+ const pullOpts = atArg !== undefined ? [{ at: atArg }] : [];
1946
2022
  const unsafeApi = clientHandle.client.getUnsafeApi();
1947
- const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
2023
+ const result = await withBlockAvailabilityHint(opts.at, () => unsafeApi.apis[api.name][method.name](...parsedArgs, ...pullOpts));
1948
2024
  const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
1949
2025
  printResult(result, format);
1950
2026
  } finally {
@@ -3067,11 +3143,98 @@ var init_rpc_registry = __esm(() => {
3067
3143
  };
3068
3144
  });
3069
3145
 
3146
+ // src/core/xxh64.ts
3147
+ function rotl(v, r) {
3148
+ return (v << r | v >> 64n - r) & MASK;
3149
+ }
3150
+ function round(acc, val) {
3151
+ acc = acc + val * P2 & MASK;
3152
+ acc = rotl(acc, 31n);
3153
+ return acc * P1 & MASK;
3154
+ }
3155
+ function mergeRound(h, val) {
3156
+ const k = round(0n, val);
3157
+ return (h ^ k) * P1 + P4 & MASK;
3158
+ }
3159
+ function readU64LE(data, p) {
3160
+ return BigInt(data[p]) | BigInt(data[p + 1]) << 8n | BigInt(data[p + 2]) << 16n | BigInt(data[p + 3]) << 24n | BigInt(data[p + 4]) << 32n | BigInt(data[p + 5]) << 40n | BigInt(data[p + 6]) << 48n | BigInt(data[p + 7]) << 56n;
3161
+ }
3162
+ function readU32LE(data, p) {
3163
+ return BigInt(data[p]) | BigInt(data[p + 1]) << 8n | BigInt(data[p + 2]) << 16n | BigInt(data[p + 3]) << 24n;
3164
+ }
3165
+ function xxh64(input, seed = 0n) {
3166
+ const len = input.length;
3167
+ let p = 0;
3168
+ let h;
3169
+ if (len >= 32) {
3170
+ let v1 = seed + P1 + P2 & MASK;
3171
+ let v2 = seed + P2 & MASK;
3172
+ let v3 = seed & MASK;
3173
+ let v4 = seed - P1 & MASK;
3174
+ while (p <= len - 32) {
3175
+ v1 = round(v1, readU64LE(input, p));
3176
+ p += 8;
3177
+ v2 = round(v2, readU64LE(input, p));
3178
+ p += 8;
3179
+ v3 = round(v3, readU64LE(input, p));
3180
+ p += 8;
3181
+ v4 = round(v4, readU64LE(input, p));
3182
+ p += 8;
3183
+ }
3184
+ h = rotl(v1, 1n) + rotl(v2, 7n) + rotl(v3, 12n) + rotl(v4, 18n) & MASK;
3185
+ h = mergeRound(h, v1);
3186
+ h = mergeRound(h, v2);
3187
+ h = mergeRound(h, v3);
3188
+ h = mergeRound(h, v4);
3189
+ } else {
3190
+ h = seed + P5 & MASK;
3191
+ }
3192
+ h = h + BigInt(len) & MASK;
3193
+ while (p + 8 <= len) {
3194
+ const k = round(0n, readU64LE(input, p));
3195
+ h = rotl(h ^ k, 27n) * P1 + P4 & MASK;
3196
+ p += 8;
3197
+ }
3198
+ if (p + 4 <= len) {
3199
+ const k = readU32LE(input, p) * P1 & MASK;
3200
+ h = rotl(h ^ k, 23n) * P2 + P3 & MASK;
3201
+ p += 4;
3202
+ }
3203
+ while (p < len) {
3204
+ const k = BigInt(input[p]) * P5 & MASK;
3205
+ h = rotl(h ^ k, 11n) * P1 & MASK;
3206
+ p++;
3207
+ }
3208
+ h ^= h >> 33n;
3209
+ h = h * P2 & MASK;
3210
+ h ^= h >> 29n;
3211
+ h = h * P3 & MASK;
3212
+ h ^= h >> 32n;
3213
+ return h;
3214
+ }
3215
+ function u64ToLEBytes(v, out, offset) {
3216
+ for (let i = 0;i < 8; i++) {
3217
+ out[offset + i] = Number(v >> BigInt(i * 8) & 0xffn);
3218
+ }
3219
+ }
3220
+ function twox(input, bits) {
3221
+ const lanes = bits / 64;
3222
+ const out = new Uint8Array(bits / 8);
3223
+ for (let i = 0;i < lanes; i++) {
3224
+ u64ToLEBytes(xxh64(input, BigInt(i)), out, i * 8);
3225
+ }
3226
+ return out;
3227
+ }
3228
+ var MASK, P1 = 11400714785074694791n, P2 = 14029467366897019727n, P3 = 1609587929392839161n, P4 = 9650029242287828579n, P5 = 2870177450012600261n;
3229
+ var init_xxh64 = __esm(() => {
3230
+ MASK = (1n << 64n) - 1n;
3231
+ });
3232
+
3070
3233
  // src/core/hash.ts
3071
3234
  import { blake2b as blake2b2 } from "@noble/hashes/blake2.js";
3072
3235
  import { sha256 } from "@noble/hashes/sha2.js";
3073
- import { keccak_256 } from "@noble/hashes/sha3.js";
3074
- import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
3236
+ import { keccak_256 as keccak_2562 } from "@noble/hashes/sha3.js";
3237
+ import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
3075
3238
  function computeHash(algorithm, data) {
3076
3239
  const algo = ALGORITHMS[algorithm];
3077
3240
  if (!algo) {
@@ -3085,12 +3248,12 @@ function parseInputData(input) {
3085
3248
  if (hex.length % 2 !== 0) {
3086
3249
  throw new Error(`Invalid hex input: odd number of characters`);
3087
3250
  }
3088
- return hexToBytes2(hex);
3251
+ return hexToBytes4(hex);
3089
3252
  }
3090
3253
  return new TextEncoder().encode(input);
3091
3254
  }
3092
3255
  function toHex2(bytes) {
3093
- return `0x${bytesToHex2(bytes)}`;
3256
+ return `0x${bytesToHex3(bytes)}`;
3094
3257
  }
3095
3258
  function isValidAlgorithm(name) {
3096
3259
  return name in ALGORITHMS;
@@ -3100,6 +3263,7 @@ function getAlgorithmNames() {
3100
3263
  }
3101
3264
  var ALGORITHMS;
3102
3265
  var init_hash = __esm(() => {
3266
+ init_xxh64();
3103
3267
  ALGORITHMS = {
3104
3268
  blake2b256: {
3105
3269
  compute: (data) => blake2b2(data, { dkLen: 32 }),
@@ -3112,7 +3276,7 @@ var init_hash = __esm(() => {
3112
3276
  description: "BLAKE2b with 128-bit output"
3113
3277
  },
3114
3278
  keccak256: {
3115
- compute: (data) => keccak_256(data),
3279
+ compute: (data) => keccak_2562(data),
3116
3280
  outputLen: 32,
3117
3281
  description: "Keccak-256 (Ethereum-compatible)"
3118
3282
  },
@@ -3120,6 +3284,21 @@ var init_hash = __esm(() => {
3120
3284
  compute: (data) => sha256(data),
3121
3285
  outputLen: 32,
3122
3286
  description: "SHA-256"
3287
+ },
3288
+ twox64: {
3289
+ compute: (data) => twox(data, 64),
3290
+ outputLen: 8,
3291
+ description: "XXH64 with seed 0 (Substrate twox64)"
3292
+ },
3293
+ twox128: {
3294
+ compute: (data) => twox(data, 128),
3295
+ outputLen: 16,
3296
+ description: "XXH64 with seeds 0,1 (Substrate twox128, pallet/storage prefix)"
3297
+ },
3298
+ twox256: {
3299
+ compute: (data) => twox(data, 256),
3300
+ outputLen: 32,
3301
+ description: "XXH64 with seeds 0,1,2,3 (Substrate twox256)"
3123
3302
  }
3124
3303
  };
3125
3304
  });
@@ -3517,13 +3696,73 @@ var init_complete = __esm(() => {
3517
3696
  // src/cli.ts
3518
3697
  import cac from "cac";
3519
3698
  // package.json
3520
- var version = "1.18.0";
3699
+ var version = "1.20.0";
3521
3700
 
3522
3701
  // src/commands/account.ts
3523
3702
  init_accounts_store();
3524
3703
  init_accounts();
3525
- init_output();
3526
3704
  import { readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
3705
+ import { hexToBytes as nobleHexToBytes2 } from "@noble/hashes/utils.js";
3706
+
3707
+ // src/core/h160.ts
3708
+ import { keccak_256 } from "@noble/hashes/sha3.js";
3709
+ import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
3710
+ var ETH_DERIVED_SUFFIX_BYTE = 238;
3711
+ var H160_LEN = 20;
3712
+ var ACCOUNT_ID_LEN = 32;
3713
+ function hasEthDerivedSuffix(accountId) {
3714
+ if (accountId.length !== ACCOUNT_ID_LEN)
3715
+ return false;
3716
+ for (let i = H160_LEN;i < ACCOUNT_ID_LEN; i++) {
3717
+ if (accountId[i] !== ETH_DERIVED_SUFFIX_BYTE)
3718
+ return false;
3719
+ }
3720
+ return true;
3721
+ }
3722
+ function accountIdToH160(accountId) {
3723
+ if (accountId.length !== ACCOUNT_ID_LEN) {
3724
+ throw new Error(`accountIdToH160 expects 32 bytes, got ${accountId.length}`);
3725
+ }
3726
+ if (hasEthDerivedSuffix(accountId)) {
3727
+ return accountId.slice(0, H160_LEN);
3728
+ }
3729
+ return keccak_256(accountId).slice(ACCOUNT_ID_LEN - H160_LEN);
3730
+ }
3731
+ function h160ToFallbackAccountId(h160) {
3732
+ if (h160.length !== H160_LEN) {
3733
+ throw new Error(`h160ToFallbackAccountId expects 20 bytes, got ${h160.length}`);
3734
+ }
3735
+ const out = new Uint8Array(ACCOUNT_ID_LEN);
3736
+ out.set(h160, 0);
3737
+ out.fill(ETH_DERIVED_SUFFIX_BYTE, H160_LEN);
3738
+ return out;
3739
+ }
3740
+ function toEip55(h160) {
3741
+ if (h160.length !== H160_LEN) {
3742
+ throw new Error(`toEip55 expects 20 bytes, got ${h160.length}`);
3743
+ }
3744
+ const lowerHex = bytesToHex2(h160);
3745
+ const hashBytes = keccak_256(new TextEncoder().encode(lowerHex));
3746
+ let out = "0x";
3747
+ for (let i = 0;i < lowerHex.length; i++) {
3748
+ const c = lowerHex[i];
3749
+ const nibble = i % 2 === 0 ? hashBytes[i >> 1] >> 4 : hashBytes[i >> 1] & 15;
3750
+ out += nibble >= 8 ? c.toUpperCase() : c;
3751
+ }
3752
+ return out;
3753
+ }
3754
+ function isH160Hex(input) {
3755
+ return /^0x[0-9a-fA-F]{40}$/.test(input);
3756
+ }
3757
+ function h160FromHex(input) {
3758
+ if (!isH160Hex(input)) {
3759
+ throw new Error(`Not a valid 0x-prefixed 20-byte hex string: ${input}`);
3760
+ }
3761
+ return hexToBytes2(input.slice(2));
3762
+ }
3763
+
3764
+ // src/commands/account.ts
3765
+ init_output();
3527
3766
 
3528
3767
  // src/core/pallet.ts
3529
3768
  init_errors();
@@ -3594,7 +3833,8 @@ function isValidParaId(value) {
3594
3833
  var ACCOUNT_HELP = `
3595
3834
  ${BOLD}Usage:${RESET}
3596
3835
  $ dot account add <name> <ss58|hex> Add a watch-only address (no secret)
3597
- $ dot account add <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic
3836
+ $ dot account add <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic or 32-byte hex seed
3837
+ $ dot account add <name> --secret 0x<128 hex> Import a raw 64-byte sr25519 private key (no --path)
3598
3838
  $ dot account add <name> --env <VAR> [--path <derivation>] Import account backed by env variable
3599
3839
  $ dot account add <name> --parachain <id> --parachain-type <t> Derive a parachain sovereign (t = child|sibling)
3600
3840
  $ dot account add <name> --pallet-id <8 chars or 0x hex> Derive a pallet sovereign (e.g. py/trsry)
@@ -3611,6 +3851,7 @@ ${BOLD}Usage:${RESET}
3611
3851
  ${BOLD}Examples:${RESET}
3612
3852
  $ dot account add treasury 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
3613
3853
  $ dot account add treasury --secret "word1 word2 ... word12"
3854
+ $ dot account add raw-key --secret 0x<128-hex-char sr25519 expanded secret>
3614
3855
  $ dot account add ci-signer --env MY_SECRET --path //ci
3615
3856
  $ dot account add Treasury --pallet-id py/trsry
3616
3857
  $ dot account add Bounties --pallet-id 0x70792f626f756e74
@@ -3638,16 +3879,21 @@ ${BOLD}Examples:${RESET}
3638
3879
 
3639
3880
  ${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
3640
3881
  Use --env to keep secrets off disk entirely.
3641
- Hex seed import (0x...) is not supported via CLI.${RESET}
3882
+ --secret accepts a BIP39 mnemonic, a 0x 32-byte hex seed, or a
3883
+ 0x 64-byte raw sr25519 private key (the value --show-secret prints).
3884
+ Raw private keys cannot be HD-derived, so --path is rejected for them.${RESET}
3642
3885
  `.trimStart();
3643
3886
  function registerAccountCommands(cli) {
3644
- cli.command("account [action] [...names]", "Manage local accounts (create, import, list, remove, export)").alias("accounts").option("--secret <value>", "Secret key (mnemonic or hex seed) for import").option("--env <varName>", "Environment variable name holding the secret").option("--path <derivation>", "Derivation path (e.g. //staking, //polkadot//0/wallet)").option("--parachain <id>", "Derive a parachain sovereign account (requires --parachain-type)").option("--parachain-type <type>", "Parachain sovereign type: child or sibling").option("--pallet-id <id>", "Derive a pallet sovereign account from an 8-byte PalletId").option("--prefix <number>", "SS58 prefix for address encoding (default: 42)").option("--file <path>", "Input/output file for batch import/export").option("--overwrite", "Overwrite existing accounts on batch import").option("--dry-run", "Preview batch import without applying changes").option("--include-secrets", "Include secrets in export (redacted by default)").option("--watch-only", "Export only watch-only accounts").option("--show-secret", "Reveal the 64-byte sr25519 expanded private key (inspect only)").action(async (action, names, opts) => {
3887
+ cli.command("account [action] [...names]", "Manage local accounts (create, import, list, remove, export)").alias("accounts").option("--secret <value>", "Secret for import: BIP39 mnemonic, 0x 32-byte hex seed, or 0x 64-byte raw private key").option("--env <varName>", "Environment variable name holding the secret").option("--path <derivation>", "Derivation path (e.g. //staking, //polkadot//0/wallet)").option("--parachain <id>", "Derive a parachain sovereign account (requires --parachain-type)").option("--parachain-type <type>", "Parachain sovereign type: child or sibling").option("--pallet-id <id>", "Derive a pallet sovereign account from an 8-byte PalletId").option("--prefix <number>", "SS58 prefix for address encoding (default: 42)").option("--file <path>", "Input/output file for batch import/export").option("--overwrite", "Overwrite existing accounts on batch import").option("--dry-run", "Preview batch import without applying changes").option("--include-secrets", "Include secrets in export (redacted by default)").option("--watch-only", "Export only watch-only accounts").option("--show-secret", "Reveal the 64-byte sr25519 expanded private key (inspect only)").action(async (action, names, opts) => {
3645
3888
  if (!action) {
3646
3889
  if (process.argv[2] === "accounts")
3647
3890
  return accountList(opts);
3648
3891
  console.log(ACCOUNT_HELP);
3649
3892
  return;
3650
3893
  }
3894
+ const rawSecret = rawArgValue("--secret");
3895
+ if (rawSecret != null)
3896
+ opts.secret = rawSecret;
3651
3897
  switch (action) {
3652
3898
  case "new":
3653
3899
  case "create":
@@ -4226,6 +4472,7 @@ async function accountInspect(input, opts) {
4226
4472
  let hasSecret = false;
4227
4473
  let storedAccount;
4228
4474
  let isDev = false;
4475
+ let isH160Fallback = false;
4229
4476
  let virtualSource;
4230
4477
  if (sovereignSource) {
4231
4478
  if (sovereignSource.kind === "pallet") {
@@ -4273,18 +4520,24 @@ async function accountInspect(input, opts) {
4273
4520
  }
4274
4521
  } else if (isHexPublicKey(input)) {
4275
4522
  publicKeyHex = input;
4523
+ } else if (isH160Hex(input)) {
4524
+ const fallback = h160ToFallbackAccountId(h160FromHex(input));
4525
+ publicKeyHex = publicKeyToHex(fallback);
4526
+ isH160Fallback = true;
4276
4527
  } else {
4277
4528
  try {
4278
4529
  const decoded = fromSs58(input);
4279
4530
  publicKeyHex = publicKeyToHex(decoded);
4280
4531
  } catch {
4281
- console.error(`Cannot identify "${input}" as an account name, SS58 address, or hex public key.`);
4532
+ console.error(`Cannot identify "${input}" as an account name, SS58 address, hex public key, or H160.`);
4282
4533
  process.exit(1);
4283
4534
  }
4284
4535
  }
4285
4536
  }
4286
4537
  const ss58 = toSs58(publicKeyHex, prefix);
4538
+ const h160Hex = toEip55(accountIdToH160(nobleHexToBytes2(publicKeyHex.slice(2))));
4287
4539
  let privateKeyHex;
4540
+ let revealedSecret;
4288
4541
  if (opts.showSecret) {
4289
4542
  if (!name) {
4290
4543
  console.error("--show-secret requires an account name; raw addresses and hex keys have no secret to reveal.");
@@ -4300,6 +4553,14 @@ async function accountInspect(input, opts) {
4300
4553
  console.error(err.message);
4301
4554
  process.exit(1);
4302
4555
  }
4556
+ if (storedAccount?.secret !== undefined && !isEnvSecret(storedAccount.secret)) {
4557
+ const kind = secretKind(storedAccount.secret);
4558
+ if (kind === "mnemonic") {
4559
+ revealedSecret = { label: "Mnemonic", field: "mnemonic", value: storedAccount.secret };
4560
+ } else if (kind === "seed") {
4561
+ revealedSecret = { label: "Seed", field: "seed", value: storedAccount.secret };
4562
+ }
4563
+ }
4303
4564
  }
4304
4565
  let kindLabel;
4305
4566
  let sourceLine;
@@ -4314,6 +4575,8 @@ async function accountInspect(input, opts) {
4314
4575
  sourceLine = `parachain ${virtualSource.paraId}`;
4315
4576
  } else if (isDev) {
4316
4577
  kindLabel = "dev";
4578
+ } else if (isH160Fallback) {
4579
+ kindLabel = "revive H160 fallback";
4317
4580
  } else if (storedAccount) {
4318
4581
  const k = classifyAccount(storedAccount);
4319
4582
  if (k === "pallet" && storedAccount.source?.kind === "pallet") {
@@ -4335,7 +4598,12 @@ async function accountInspect(input, opts) {
4335
4598
  }
4336
4599
  }
4337
4600
  if (isJsonOutput(opts)) {
4338
- const result = { publicKey: publicKeyHex, ss58, prefix };
4601
+ const result = {
4602
+ publicKey: publicKeyHex,
4603
+ ss58,
4604
+ h160: h160Hex,
4605
+ prefix
4606
+ };
4339
4607
  if (name)
4340
4608
  result.name = name;
4341
4609
  if (kindLabel)
@@ -4371,6 +4639,8 @@ async function accountInspect(input, opts) {
4371
4639
  result.env = envLine.replace(/^\$/, "");
4372
4640
  if (bandersnatch && Object.keys(bandersnatch).length > 0)
4373
4641
  result.bandersnatch = bandersnatch;
4642
+ if (revealedSecret)
4643
+ result[revealedSecret.field] = revealedSecret.value;
4374
4644
  if (privateKeyHex)
4375
4645
  result.privateKey = privateKeyHex;
4376
4646
  console.log(formatJson(result));
@@ -4382,6 +4652,7 @@ async function accountInspect(input, opts) {
4382
4652
  console.log(` ${BOLD}Kind:${RESET} ${kindLabel}`);
4383
4653
  console.log(` ${BOLD}Public Key:${RESET} ${publicKeyHex}`);
4384
4654
  console.log(` ${BOLD}SS58:${RESET} ${ss58}`);
4655
+ console.log(` ${BOLD}H160:${RESET} ${h160Hex}`);
4385
4656
  if (sourceLine)
4386
4657
  console.log(` ${BOLD}Source:${RESET} ${sourceLine}`);
4387
4658
  if (derivationLine)
@@ -4401,6 +4672,12 @@ async function accountInspect(input, opts) {
4401
4672
  }
4402
4673
  }
4403
4674
  console.log(` ${BOLD}Prefix:${RESET} ${prefix}`);
4675
+ if (revealedSecret) {
4676
+ console.log(` ${BOLD}${`${revealedSecret.label}:`.padEnd(13)}${RESET}${revealedSecret.value}`);
4677
+ if (derivationLine) {
4678
+ console.log(` ${YELLOW}(derived with ${derivationLine} — re-import needs --path ${derivationLine})${RESET}`);
4679
+ }
4680
+ }
4404
4681
  if (privateKeyHex) {
4405
4682
  console.log(` ${BOLD}Private Key:${RESET} ${privateKeyHex}`);
4406
4683
  console.log(` ${YELLOW}(sr25519 expanded, 64 bytes — never share)${RESET}`);
@@ -4655,8 +4932,10 @@ async function handleApis2(target, args, opts) {
4655
4932
  typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
4656
4933
  ];
4657
4934
  const parsedArgs = await parseRuntimeApiArgs2(meta, method, effectiveArgs);
4935
+ const atArg = parseAtForRead(opts.at);
4936
+ const pullOpts = atArg !== undefined ? [{ at: atArg }] : [];
4658
4937
  const unsafeApi = clientHandle.client.getUnsafeApi();
4659
- const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
4938
+ const result = await withBlockAvailabilityHint(opts.at, () => unsafeApi.apis[api.name][method.name](...parsedArgs, ...pullOpts));
4660
4939
  const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
4661
4940
  printResult(result, format);
4662
4941
  } finally {
@@ -6826,6 +7105,8 @@ async function handleQuery(target, keys, opts) {
6826
7105
  typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
6827
7106
  ];
6828
7107
  const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, effectiveKeys);
7108
+ const atArg = parseAtForRead(opts.at);
7109
+ const pullOpts = atArg !== undefined ? [{ at: atArg }] : [];
6829
7110
  const format = isJsonOutput(opts) ? "json" : opts.output ?? "pretty";
6830
7111
  const expectedLen = storageItem.type === "map" && storageItem.keyTypeId != null ? meta.builder.buildStorage(palletInfo.name, storageItem.name).len : 0;
6831
7112
  if (storageItem.type === "map" && parsedKeys.length < expectedLen) {
@@ -6835,7 +7116,7 @@ async function handleQuery(target, keys, opts) {
6835
7116
  console.log(`${DIM}Hint: use --dump to fetch all entries${RESET}`);
6836
7117
  return;
6837
7118
  }
6838
- const entries = await withStalenessSuggestion(chainName, clientHandle, () => storageApi.getEntries(...parsedKeys));
7119
+ const entries = await withStalenessSuggestion(chainName, clientHandle, () => withBlockAvailabilityHint(opts.at, () => storageApi.getEntries(...parsedKeys, ...pullOpts)));
6839
7120
  const rows = entries.map((e) => ({
6840
7121
  keys: e.keyArgs,
6841
7122
  value: e.value
@@ -6844,7 +7125,7 @@ async function handleQuery(target, keys, opts) {
6844
7125
  await writeStdout(`${text}
6845
7126
  `);
6846
7127
  } else {
6847
- const result = await withStalenessSuggestion(chainName, clientHandle, () => storageApi.getValue(...parsedKeys));
7128
+ const result = await withStalenessSuggestion(chainName, clientHandle, () => withBlockAvailabilityHint(opts.at, () => storageApi.getValue(...parsedKeys, ...pullOpts)));
6848
7129
  const text = format === "json" ? formatJson(result) : formatPretty(result);
6849
7130
  await writeStdout(`${text}
6850
7131
  `);
@@ -7468,7 +7749,7 @@ async function handleTx(target, args, opts) {
7468
7749
  let estimatedFees;
7469
7750
  let estimationError;
7470
7751
  try {
7471
- estimatedFees = String(await withStalenessSuggestion(chainName, clientHandle, () => tx.getEstimatedFees(signer?.publicKey, txOptions)));
7752
+ estimatedFees = String(await withStalenessSuggestion(chainName, clientHandle, () => withBlockAvailabilityHint(opts.at, () => tx.getEstimatedFees(signer?.publicKey, txOptions))));
7472
7753
  } catch (err) {
7473
7754
  estimationError = err instanceof Error ? err.message : String(err);
7474
7755
  }
@@ -7538,7 +7819,7 @@ async function handleTx(target, args, opts) {
7538
7819
  }
7539
7820
  const observable = clientHandle.client.submitAndWatch(generalTx, at);
7540
7821
  if (isJsonOutput(opts)) {
7541
- const result3 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransactionJson(observable, waitLevel, { unsigned: true }));
7822
+ const result3 = await withStalenessSuggestion(chainName, clientHandle, () => withBlockAvailabilityHint(opts.at, () => watchTransactionJson(observable, waitLevel, { unsigned: true })));
7542
7823
  const rpcUrl3 = primaryRpc(opts.rpc ?? chainConfig.rpc);
7543
7824
  if (result3.type === "broadcasted") {
7544
7825
  printJsonLine({ event: "broadcasted", txHash: result3.txHash });
@@ -7570,7 +7851,7 @@ async function handleTx(target, args, opts) {
7570
7851
  }
7571
7852
  return;
7572
7853
  }
7573
- const result2 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransaction(observable, waitLevel, { unsigned: true }));
7854
+ const result2 = await withStalenessSuggestion(chainName, clientHandle, () => withBlockAvailabilityHint(opts.at, () => watchTransaction(observable, waitLevel, { unsigned: true })));
7574
7855
  console.log();
7575
7856
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
7576
7857
  console.log(` ${BOLD}Type:${RESET} unsigned (bare)`);
@@ -7619,7 +7900,7 @@ async function handleTx(target, args, opts) {
7619
7900
  return;
7620
7901
  }
7621
7902
  if (isJsonOutput(opts)) {
7622
- const result2 = await withStalenessSuggestion(chainName, clientHandle, () => watchTransactionJson(tx.signSubmitAndWatch(signer, txOptions), waitLevel));
7903
+ const result2 = await withStalenessSuggestion(chainName, clientHandle, () => withBlockAvailabilityHint(opts.at, () => watchTransactionJson(tx.signSubmitAndWatch(signer, txOptions), waitLevel)));
7623
7904
  const rpcUrl2 = primaryRpc(opts.rpc ?? chainConfig.rpc);
7624
7905
  if (result2.type === "broadcasted") {
7625
7906
  printJsonLine({ event: "broadcasted", txHash: result2.txHash });
@@ -7650,7 +7931,7 @@ async function handleTx(target, args, opts) {
7650
7931
  }
7651
7932
  return;
7652
7933
  }
7653
- const result = await withStalenessSuggestion(chainName, clientHandle, () => watchTransaction(tx.signSubmitAndWatch(signer, txOptions), waitLevel));
7934
+ const result = await withStalenessSuggestion(chainName, clientHandle, () => withBlockAvailabilityHint(opts.at, () => watchTransaction(tx.signSubmitAndWatch(signer, txOptions), waitLevel)));
7654
7935
  console.log();
7655
7936
  console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
7656
7937
  console.log(` ${BOLD}Call:${RESET} ${callHex}`);
@@ -9125,7 +9406,15 @@ if (process.argv[2] === "__complete") {
9125
9406
  process.exit(0);
9126
9407
  })();
9127
9408
  } else {
9128
- let collectVarFlags = function(argv) {
9409
+ let readAtFromArgv = function(argv) {
9410
+ for (let i = 0;i < argv.length; i++) {
9411
+ if (argv[i] === "--at" && i + 1 < argv.length)
9412
+ return argv[i + 1];
9413
+ if (argv[i].startsWith("--at="))
9414
+ return argv[i].slice(5);
9415
+ }
9416
+ return;
9417
+ }, collectVarFlags = function(argv) {
9129
9418
  const vars = [];
9130
9419
  for (let i = 0;i < argv.length; i++) {
9131
9420
  if (argv[i] === "--var" && i + 1 < argv.length) {
@@ -9211,11 +9500,12 @@ if (process.argv[2] === "__complete") {
9211
9500
  registerVerifiableCommands(cli);
9212
9501
  cli.command("[dotpath] [...args]").option("--from <name>", "Account to sign with (for tx)").option("--dry-run", "Estimate fees without submitting (for tx)").option("--encode", "Encode call to hex without signing (for tx)").option("--to-yaml", "Decode call to YAML file format (for tx)").option("--to-json", "Decode call to JSON file format (for tx)").option("--ext <json>", "Custom signed extension values as JSON (for tx)").option("--asset <json>", "Pay fees in an alternative asset (XCM location JSON, for tx)").option("-w, --wait <level>", "Resolve at: broadcast, best-block (or best), finalized (for tx)", {
9213
9502
  default: "finalized"
9214
- }).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)').option("--unsigned", "Submit as unsigned/bare transaction (no signer required, for tx)").option("--refresh", "Refresh the cached RPC method list from the node (for rpc)").action(async (dotpath, args, opts) => {
9503
+ }).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 read/validate against (tx, query, apis)').option("--unsigned", "Submit as unsigned/bare transaction (no signer required, for tx)").option("--refresh", "Refresh the cached RPC method list from the node (for rpc)").action(async (dotpath, args, opts) => {
9215
9504
  if (!dotpath) {
9216
9505
  printHelp();
9217
9506
  return;
9218
9507
  }
9508
+ const atRaw = readAtFromArgv(process.argv);
9219
9509
  if (isFilePath(dotpath)) {
9220
9510
  const cliVars = collectVarFlags(process.argv);
9221
9511
  const cmd = await loadCommandFile(dotpath, cliVars);
@@ -9243,7 +9533,7 @@ if (process.argv[2] === "__complete") {
9243
9533
  nonce: opts.nonce,
9244
9534
  tip: opts.tip,
9245
9535
  mortality: opts.mortality,
9246
- at: opts.at,
9536
+ at: atRaw,
9247
9537
  parsedArgs: cmd.args
9248
9538
  });
9249
9539
  break;
@@ -9251,6 +9541,7 @@ if (process.argv[2] === "__complete") {
9251
9541
  await handleQuery(target2, args, {
9252
9542
  ...handlerOpts2,
9253
9543
  dump: opts.dump,
9544
+ at: atRaw,
9254
9545
  parsedArgs: cmd.args
9255
9546
  });
9256
9547
  break;
@@ -9260,6 +9551,7 @@ if (process.argv[2] === "__complete") {
9260
9551
  case "apis":
9261
9552
  await handleApis2(target2, args, {
9262
9553
  ...handlerOpts2,
9554
+ at: atRaw,
9263
9555
  parsedArgs: cmd.args
9264
9556
  });
9265
9557
  break;
@@ -9298,7 +9590,7 @@ if (process.argv[2] === "__complete") {
9298
9590
  }
9299
9591
  switch (parsed.category) {
9300
9592
  case "query":
9301
- await handleQuery(target, args, { ...handlerOpts, dump: opts.dump });
9593
+ await handleQuery(target, args, { ...handlerOpts, dump: opts.dump, at: atRaw });
9302
9594
  break;
9303
9595
  case "tx": {
9304
9596
  const txOpts = {
@@ -9315,7 +9607,7 @@ if (process.argv[2] === "__complete") {
9315
9607
  nonce: opts.nonce,
9316
9608
  tip: opts.tip,
9317
9609
  mortality: opts.mortality,
9318
- at: opts.at
9610
+ at: atRaw
9319
9611
  };
9320
9612
  if (parsed.pallet && /^0x[0-9a-fA-F]+$/.test(parsed.pallet)) {
9321
9613
  await handleTx(parsed.pallet, args, txOpts);
@@ -9334,7 +9626,7 @@ if (process.argv[2] === "__complete") {
9334
9626
  await handleErrors2(target, handlerOpts);
9335
9627
  break;
9336
9628
  case "apis":
9337
- await handleApis2(target, args, handlerOpts);
9629
+ await handleApis2(target, args, { ...handlerOpts, at: atRaw });
9338
9630
  break;
9339
9631
  case "extensions": {
9340
9632
  if (parsed.item) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,22 +38,22 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@noble/hashes": "^2.0.1",
41
- "@polkadot-api/metadata-builders": "^0.14.1",
42
- "@polkadot-api/substrate-bindings": "^0.20.1",
41
+ "@polkadot-api/metadata-builders": "^0.14.3",
42
+ "@polkadot-api/substrate-bindings": "^0.20.3",
43
43
  "@polkadot-api/substrate-client": "^0.7.0",
44
- "@polkadot-api/view-builder": "^0.5.1",
44
+ "@polkadot-api/view-builder": "^0.5.3",
45
45
  "@polkadot-labs/hdkd": "^0.0.28",
46
46
  "@polkadot-labs/hdkd-helpers": "^0.0.29",
47
47
  "@scure/sr25519": "^1.0.0",
48
48
  "cac": "^6.7.14",
49
- "polkadot-api": "^2.0.1",
50
- "verifiablejs": "^1.2.0",
49
+ "polkadot-api": "^2.1.4",
50
+ "verifiablejs": "1.3.0",
51
51
  "yaml": "^2.8.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@biomejs/biome": "^2.4.5",
55
55
  "@changesets/cli": "^2.29.4",
56
- "@polkadot-api/metadata-compatibility": "^0.6.1",
56
+ "@polkadot-api/metadata-compatibility": "^0.6.3",
57
57
  "@types/bun": "^1.3.9",
58
58
  "husky": "^9.1.7"
59
59
  }