polkadot-cli 0.11.0 → 0.12.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 +77 -4
  2. package/dist/cli.mjs +286 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,26 @@
4
4
 
5
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.
6
6
 
7
- Ships with Polkadot as the default chain. Add any Substrate-based chain by pointing to its RPC endpoint.
7
+ Ships with Polkadot and all system parachains preconfigured with multiple fallback RPC endpoints. Add any Substrate-based chain by pointing to its RPC endpoint(s).
8
+
9
+ ### Preconfigured chains
10
+
11
+ | Network | Chain | Light client |
12
+ |---------|-------|:---:|
13
+ | Polkadot | `polkadot` (relay, default) | yes |
14
+ | | `polkadot-asset-hub` | yes |
15
+ | | `polkadot-bridge-hub` | yes |
16
+ | | `polkadot-collectives` | yes |
17
+ | | `polkadot-coretime` | yes |
18
+ | | `polkadot-people` | yes |
19
+ | Paseo (testnet) | `paseo` (relay) | yes |
20
+ | | `paseo-asset-hub` | yes |
21
+ | | `paseo-bridge-hub` | — |
22
+ | | `paseo-collectives` | — |
23
+ | | `paseo-coretime` | yes |
24
+ | | `paseo-people` | yes |
25
+
26
+ Each chain ships with multiple RPC endpoints from decentralized infrastructure providers (IBP, Dotters, Dwellir, and others). The CLI automatically falls back to the next endpoint if the primary is unreachable.
8
27
 
9
28
  ## Install
10
29
 
@@ -23,8 +42,13 @@ This installs the `dot` command globally.
23
42
  dot chain # shows available actions
24
43
  dot chains # shorthand, same as above
25
44
 
26
- # Add a chain
45
+ # Add a chain (single RPC)
27
46
  dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
47
+
48
+ # Add a chain with fallback RPCs (repeat --rpc for each endpoint)
49
+ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io --rpc wss://kusama-rpc.dwellir.com
50
+
51
+ # Add a chain via light client
28
52
  dot chain add westend --light-client
29
53
 
30
54
  # List configured chains
@@ -58,17 +82,27 @@ dot account list
58
82
  # Create a new account (generates a mnemonic)
59
83
  dot account create my-validator
60
84
 
85
+ # Create with a derivation path
86
+ dot account create my-staking --path //staking
87
+
61
88
  # Import from a BIP39 mnemonic
62
89
  dot account import treasury --secret "word1 word2 ... word12"
63
90
 
91
+ # Import with a derivation path
92
+ dot account import hot-wallet --secret "word1 word2 ... word12" --path //hot
93
+
64
94
  # Import an env-var-backed account (secret stays off disk)
65
95
  dot account import ci-signer --env MY_SECRET
66
96
 
97
+ # Derive a child account from an existing one
98
+ dot account derive treasury treasury-staking --path //staking
99
+
67
100
  # Use it — the env var is read at signing time
68
101
  MY_SECRET="word1 word2 ..." dot tx System.remark 0xdead --from ci-signer
69
102
 
70
- # Remove an account
103
+ # Remove one or more accounts
71
104
  dot account remove my-validator
105
+ dot account delete my-validator stale-key
72
106
  ```
73
107
 
74
108
  #### Env-var-backed accounts
@@ -83,6 +117,33 @@ dot account import ci-signer --env MY_SECRET
83
117
 
84
118
  The secret is never written to disk. At signing time, the CLI reads `$MY_SECRET` and derives the keypair. If the variable is not set, the CLI errors with a clear message. `account list` shows an `(env: MY_SECRET)` badge and resolves the address live when the variable is available.
85
119
 
120
+ #### Derivation paths
121
+
122
+ Use `--path` with `create`, `import`, or the `derive` action to derive child keys from the same secret. Different paths produce different keypairs, enabling key separation (e.g. staking vs. governance) without managing multiple mnemonics.
123
+
124
+ ```bash
125
+ # Create with a derivation path
126
+ dot account create my-staking --path //staking
127
+
128
+ # Multi-segment path (hard + soft junctions)
129
+ dot account create multi --path //polkadot//0/wallet
130
+
131
+ # Import with a path
132
+ dot account import hot --secret "word1 word2 ..." --path //hot
133
+
134
+ # Derive a child from an existing account
135
+ dot account derive treasury treasury-staking --path //staking
136
+ ```
137
+
138
+ `derive` copies the source account's secret and applies the given path. It requires both a source name, a new name, and `--path`. Works with env-backed accounts too — the derived account shares the same env var reference.
139
+
140
+ `account list` shows the derivation path next to the account name:
141
+
142
+ ```
143
+ treasury-staking (//staking) 5FHneW46...
144
+ ci-signer (//ci) (env: MY_SECRET) 5EPCUjPx...
145
+ ```
146
+
86
147
  **Supported secret formats for import:**
87
148
 
88
149
  | Format | Example | Status |
@@ -286,12 +347,24 @@ dot hash blake2b256 0xdeadbeef --output json
286
347
 
287
348
  Run `dot hash` with no arguments to see all available algorithms.
288
349
 
350
+ ### Getting help
351
+
352
+ Every command supports `--help` to show its detailed usage, available actions, and examples:
353
+
354
+ ```bash
355
+ dot --help # global help with all commands
356
+ dot account --help # same as `dot account` — shows account actions
357
+ dot chain --help # same as `dot chain` — shows chain actions
358
+ dot hash --help # same as `dot hash` — shows algorithms and examples
359
+ ```
360
+
289
361
  ### Global options
290
362
 
291
363
  | Flag | Description |
292
364
  |------|-------------|
365
+ | `--help` | Show help (global or command-specific) |
293
366
  | `--chain <name>` | Target chain (default from config) |
294
- | `--rpc <url>` | Override RPC endpoint for this call |
367
+ | `--rpc <url>` | Override RPC endpoint(s) for this call (repeat for fallback) |
295
368
  | `--light-client` | Use Smoldot light client |
296
369
  | `--output json` | Raw JSON output (default: pretty) |
297
370
  | `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
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.11.0";
8
+ var version = "0.12.0";
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";
@@ -17,14 +17,117 @@ import { homedir } from "node:os";
17
17
  import { join } from "node:path";
18
18
 
19
19
  // src/config/types.ts
20
+ function primaryRpc(rpc) {
21
+ return Array.isArray(rpc) ? rpc[0] : rpc;
22
+ }
20
23
  var DEFAULT_CONFIG = {
21
24
  defaultChain: "polkadot",
22
25
  chains: {
23
- polkadot: { rpc: "wss://rpc.polkadot.io" },
24
- paseo: { rpc: "wss://rpc.ibp.network/paseo" },
25
- "polkadot-asset-hub": { rpc: "wss://polkadot-asset-hub-rpc.polkadot.io" },
26
- "paseo-asset-hub": { rpc: "wss://asset-hub-paseo-rpc.dwellir.com" },
27
- "polkadot-people": { rpc: "wss://polkadot-people-rpc.polkadot.io" }
26
+ polkadot: {
27
+ rpc: [
28
+ "wss://polkadot.ibp.network",
29
+ "wss://polkadot.dotters.network",
30
+ "wss://polkadot-rpc.n.dwellir.com",
31
+ "wss://polkadot-rpc.publicnode.com",
32
+ "wss://rpc-polkadot.luckyfriday.io",
33
+ "wss://polkadot.api.onfinality.io/public-ws",
34
+ "wss://rpc-polkadot.helixstreet.io",
35
+ "wss://polkadot-rpc-tn.dwellir.com",
36
+ "wss://polkadot.public.curie.radiumblock.co/ws",
37
+ "wss://rpc-polkadot.stakeworld.io",
38
+ "wss://polkadot.rpc.subquery.network/public/ws",
39
+ "wss://rpc.polkadot.io"
40
+ ]
41
+ },
42
+ "polkadot-asset-hub": {
43
+ rpc: [
44
+ "wss://polkadot-asset-hub-rpc.polkadot.io",
45
+ "wss://asset-hub-polkadot.ibp.network",
46
+ "wss://asset-hub-polkadot.dotters.network",
47
+ "wss://asset-hub-polkadot-rpc.n.dwellir.com",
48
+ "wss://rpc-asset-hub-polkadot.luckyfriday.io",
49
+ "wss://statemint.api.onfinality.io/public-ws",
50
+ "wss://statemint-rpc-tn.dwellir.com",
51
+ "wss://statemint.public.curie.radiumblock.co/ws",
52
+ "wss://asset-hub-polkadot.rpc.permanence.io"
53
+ ]
54
+ },
55
+ "polkadot-bridge-hub": {
56
+ rpc: [
57
+ "wss://polkadot-bridge-hub-rpc.polkadot.io",
58
+ "wss://bridge-hub-polkadot.ibp.network",
59
+ "wss://bridge-hub-polkadot.dotters.network",
60
+ "wss://bridge-hub-polkadot-rpc.n.dwellir.com",
61
+ "wss://rpc-bridge-hub-polkadot.luckyfriday.io",
62
+ "wss://bridgehub-polkadot.api.onfinality.io/public-ws",
63
+ "wss://polkadot-bridge-hub-rpc-tn.dwellir.com",
64
+ "wss://bridgehub-polkadot.public.curie.radiumblock.co/ws"
65
+ ]
66
+ },
67
+ "polkadot-collectives": {
68
+ rpc: [
69
+ "wss://polkadot-collectives-rpc.polkadot.io",
70
+ "wss://collectives-polkadot.ibp.network",
71
+ "wss://collectives-polkadot.dotters.network",
72
+ "wss://collectives-polkadot-rpc.n.dwellir.com",
73
+ "wss://rpc-collectives-polkadot.luckyfriday.io",
74
+ "wss://collectives.api.onfinality.io/public-ws",
75
+ "wss://polkadot-collectives-rpc-tn.dwellir.com",
76
+ "wss://collectives.public.curie.radiumblock.co/ws"
77
+ ]
78
+ },
79
+ "polkadot-coretime": {
80
+ rpc: [
81
+ "wss://polkadot-coretime-rpc.polkadot.io",
82
+ "wss://coretime-polkadot.ibp.network",
83
+ "wss://coretime-polkadot.dotters.network",
84
+ "wss://coretime-polkadot-rpc.n.dwellir.com",
85
+ "wss://rpc-coretime-polkadot.luckyfriday.io",
86
+ "wss://coretime-polkadot.api.onfinality.io/public-ws"
87
+ ]
88
+ },
89
+ "polkadot-people": {
90
+ rpc: [
91
+ "wss://polkadot-people-rpc.polkadot.io",
92
+ "wss://people-polkadot.ibp.network",
93
+ "wss://people-polkadot.dotters.network",
94
+ "wss://people-polkadot-rpc.n.dwellir.com",
95
+ "wss://rpc-people-polkadot.luckyfriday.io",
96
+ "wss://people-polkadot.api.onfinality.io/public-ws"
97
+ ]
98
+ },
99
+ paseo: {
100
+ rpc: [
101
+ "wss://paseo.ibp.network",
102
+ "wss://paseo.dotters.network",
103
+ "wss://paseo-rpc.n.dwellir.com",
104
+ "wss://paseo.rpc.amforc.com"
105
+ ]
106
+ },
107
+ "paseo-asset-hub": {
108
+ rpc: [
109
+ "wss://asset-hub-paseo.ibp.network",
110
+ "wss://asset-hub-paseo.dotters.network",
111
+ "wss://asset-hub-paseo-rpc.n.dwellir.com",
112
+ "wss://sys.turboflakes.io/asset-hub-paseo"
113
+ ]
114
+ },
115
+ "paseo-bridge-hub": {
116
+ rpc: ["wss://bridge-hub-paseo.ibp.network", "wss://bridge-hub-paseo.dotters.network"]
117
+ },
118
+ "paseo-collectives": {
119
+ rpc: ["wss://collectives-paseo.ibp.network", "wss://collectives-paseo.dotters.network"]
120
+ },
121
+ "paseo-coretime": {
122
+ rpc: ["wss://coretime-paseo.ibp.network", "wss://coretime-paseo.dotters.network"]
123
+ },
124
+ "paseo-people": {
125
+ rpc: [
126
+ "wss://people-paseo.ibp.network",
127
+ "wss://people-paseo.dotters.network",
128
+ "wss://people-paseo.rpc.amforc.com"
129
+ ]
130
+ }
28
131
  }
29
132
  };
30
133
  var BUILTIN_CHAIN_NAMES = new Set(Object.keys(DEFAULT_CONFIG.chains));
@@ -177,24 +280,24 @@ function getDevAddress(name, prefix = 42) {
177
280
  const keypair = getDevKeypair(name);
178
281
  return ss58Address(keypair.publicKey, prefix);
179
282
  }
180
- function createNewAccount() {
283
+ function createNewAccount(path = "") {
181
284
  const mnemonic = generateMnemonic();
182
285
  const entropy = mnemonicToEntropy(mnemonic);
183
286
  const miniSecret = entropyToMiniSecret(entropy);
184
287
  const derive = sr25519CreateDerive(miniSecret);
185
- const keypair = derive("");
288
+ const keypair = derive(path);
186
289
  return { mnemonic, publicKey: keypair.publicKey };
187
290
  }
188
- function importAccount(secret) {
291
+ function importAccount(secret, path = "") {
189
292
  const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
190
293
  if (isHexSeed) {
191
- const keypair2 = deriveFromHexSeed(secret, "");
294
+ const keypair2 = deriveFromHexSeed(secret, path);
192
295
  return { publicKey: keypair2.publicKey };
193
296
  }
194
297
  if (!validateMnemonic(secret)) {
195
298
  throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
196
299
  }
197
- const keypair = deriveFromMnemonic(secret, "");
300
+ const keypair = deriveFromMnemonic(secret, path);
198
301
  return { publicKey: keypair.publicKey };
199
302
  }
200
303
  function publicKeyToHex(publicKey) {
@@ -221,12 +324,12 @@ function resolveSecret(secret) {
221
324
  }
222
325
  return secret;
223
326
  }
224
- function tryDerivePublicKey(envVarName) {
327
+ function tryDerivePublicKey(envVarName, path = "") {
225
328
  const value = process.env[envVarName];
226
329
  if (!value)
227
330
  return null;
228
331
  try {
229
- const { publicKey } = importAccount(value);
332
+ const { publicKey } = importAccount(value, path);
230
333
  return publicKeyToHex(publicKey);
231
334
  } catch {
232
335
  return null;
@@ -345,41 +448,49 @@ class Spinner {
345
448
  // src/commands/account.ts
346
449
  var ACCOUNT_HELP = `
347
450
  ${BOLD}Usage:${RESET}
348
- $ dot account create|new <name> Create a new account
349
- $ dot account import|add <name> --secret <s> Import from BIP39 mnemonic
350
- $ dot account import|add <name> --env <VAR> Import account backed by env variable
351
- $ dot account list List all accounts
352
- $ dot account remove|delete <name> Remove a stored account
451
+ $ dot account create|new <name> [--path <derivation>] Create a new account
452
+ $ dot account import|add <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic
453
+ $ dot account import|add <name> --env <VAR> [--path <derivation>] Import account backed by env variable
454
+ $ dot account derive <source> <new-name> --path <derivation> Derive a child account
455
+ $ dot account list List all accounts
456
+ $ dot account remove|delete <name> [name2] ... Remove stored account(s)
353
457
 
354
458
  ${BOLD}Examples:${RESET}
355
459
  $ dot account create my-validator
460
+ $ dot account create my-staking --path //staking
461
+ $ dot account create multi --path //polkadot//0/wallet
356
462
  $ dot account import treasury --secret "word1 word2 ... word12"
357
- $ dot account import ci-signer --env MY_SECRET
463
+ $ dot account import ci-signer --env MY_SECRET --path //ci
464
+ $ dot account derive treasury treasury-staking --path //staking
358
465
  $ dot account list
359
- $ dot account remove my-validator
466
+ $ dot account remove my-validator stale-key
360
467
 
361
468
  ${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
362
469
  Use --env to keep secrets off disk entirely.
363
470
  Hex seed import (0x...) is not supported via CLI.${RESET}
364
471
  `.trimStart();
365
472
  function registerAccountCommands(cli) {
366
- cli.command("account [action] [name]", "Manage local accounts (create, import, list, remove)").alias("accounts").option("--secret <value>", "Secret key (mnemonic or hex seed) for import").option("--env <varName>", "Environment variable name holding the secret").action(async (action, name, opts) => {
473
+ cli.command("account [action] [...names]", "Manage local accounts (create, import, list, remove)").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)").action(async (action, names, opts) => {
367
474
  if (!action) {
475
+ if (process.argv[2] === "accounts")
476
+ return accountList();
368
477
  console.log(ACCOUNT_HELP);
369
478
  return;
370
479
  }
371
480
  switch (action) {
372
481
  case "new":
373
482
  case "create":
374
- return accountCreate(name);
483
+ return accountCreate(names[0], opts);
375
484
  case "import":
376
485
  case "add":
377
- return accountImport(name, opts);
486
+ return accountImport(names[0], opts);
487
+ case "derive":
488
+ return accountDerive(names[0], names[1], opts);
378
489
  case "list":
379
490
  return accountList();
380
491
  case "delete":
381
492
  case "remove":
382
- return accountRemove(name);
493
+ return accountRemove(names);
383
494
  default:
384
495
  console.error(`Unknown action "${action}".
385
496
  `);
@@ -388,7 +499,7 @@ function registerAccountCommands(cli) {
388
499
  }
389
500
  });
390
501
  }
391
- async function accountCreate(name) {
502
+ async function accountCreate(name, opts) {
392
503
  if (!name) {
393
504
  console.error(`Account name is required.
394
505
  `);
@@ -402,18 +513,21 @@ async function accountCreate(name) {
402
513
  if (findAccount(accountsFile, name)) {
403
514
  throw new Error(`Account "${name}" already exists.`);
404
515
  }
405
- const { mnemonic, publicKey } = createNewAccount();
516
+ const path = opts.path ?? "";
517
+ const { mnemonic, publicKey } = createNewAccount(path);
406
518
  const hexPub = publicKeyToHex(publicKey);
407
519
  const address = toSs58(publicKey);
408
520
  accountsFile.accounts.push({
409
521
  name,
410
522
  secret: mnemonic,
411
523
  publicKey: hexPub,
412
- derivationPath: ""
524
+ derivationPath: path
413
525
  });
414
526
  await saveAccounts(accountsFile);
415
527
  printHeading("Account Created");
416
528
  console.log(` ${BOLD}Name:${RESET} ${name}`);
529
+ if (path)
530
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
417
531
  console.log(` ${BOLD}Address:${RESET} ${address}`);
418
532
  console.log(` ${BOLD}Mnemonic:${RESET} ${mnemonic}`);
419
533
  console.log();
@@ -446,17 +560,20 @@ async function accountImport(name, opts) {
446
560
  if (findAccount(accountsFile, name)) {
447
561
  throw new Error(`Account "${name}" already exists.`);
448
562
  }
563
+ const path = opts.path ?? "";
449
564
  if (opts.env) {
450
- const publicKey = tryDerivePublicKey(opts.env) ?? "";
565
+ const publicKey = tryDerivePublicKey(opts.env, path) ?? "";
451
566
  accountsFile.accounts.push({
452
567
  name,
453
568
  secret: { env: opts.env },
454
569
  publicKey,
455
- derivationPath: ""
570
+ derivationPath: path
456
571
  });
457
572
  await saveAccounts(accountsFile);
458
573
  printHeading("Account Imported");
459
574
  console.log(` ${BOLD}Name:${RESET} ${name}`);
575
+ if (path)
576
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
460
577
  console.log(` ${BOLD}Env:${RESET} ${opts.env}`);
461
578
  if (publicKey) {
462
579
  console.log(` ${BOLD}Address:${RESET} ${toSs58(publicKey)}`);
@@ -465,18 +582,90 @@ async function accountImport(name, opts) {
465
582
  }
466
583
  console.log();
467
584
  } else {
468
- const { publicKey } = importAccount(opts.secret);
585
+ const { publicKey } = importAccount(opts.secret, path);
469
586
  const hexPub = publicKeyToHex(publicKey);
470
587
  const address = toSs58(publicKey);
471
588
  accountsFile.accounts.push({
472
589
  name,
473
590
  secret: opts.secret,
474
591
  publicKey: hexPub,
475
- derivationPath: ""
592
+ derivationPath: path
476
593
  });
477
594
  await saveAccounts(accountsFile);
478
595
  printHeading("Account Imported");
479
596
  console.log(` ${BOLD}Name:${RESET} ${name}`);
597
+ if (path)
598
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
599
+ console.log(` ${BOLD}Address:${RESET} ${address}`);
600
+ console.log();
601
+ }
602
+ }
603
+ async function accountDerive(sourceName, newName, opts) {
604
+ if (!sourceName) {
605
+ console.error(`Source account name is required.
606
+ `);
607
+ console.error("Usage: dot account derive <source> <new-name> --path <derivation>");
608
+ process.exit(1);
609
+ }
610
+ if (!newName) {
611
+ console.error(`New account name is required.
612
+ `);
613
+ console.error("Usage: dot account derive <source> <new-name> --path <derivation>");
614
+ process.exit(1);
615
+ }
616
+ if (!opts.path) {
617
+ console.error(`--path is required for derive.
618
+ `);
619
+ console.error("Usage: dot account derive <source> <new-name> --path <derivation>");
620
+ process.exit(1);
621
+ }
622
+ if (isDevAccount(newName)) {
623
+ throw new Error(`"${newName}" is a built-in dev account and cannot be used as a custom account name.`);
624
+ }
625
+ const accountsFile = await loadAccounts();
626
+ const source = findAccount(accountsFile, sourceName);
627
+ if (!source) {
628
+ throw new Error(`Source account "${sourceName}" not found.`);
629
+ }
630
+ if (findAccount(accountsFile, newName)) {
631
+ throw new Error(`Account "${newName}" already exists.`);
632
+ }
633
+ const path = opts.path;
634
+ if (isEnvSecret(source.secret)) {
635
+ const publicKey = tryDerivePublicKey(source.secret.env, path) ?? "";
636
+ accountsFile.accounts.push({
637
+ name: newName,
638
+ secret: source.secret,
639
+ publicKey,
640
+ derivationPath: path
641
+ });
642
+ await saveAccounts(accountsFile);
643
+ printHeading("Account Derived");
644
+ console.log(` ${BOLD}Name:${RESET} ${newName}`);
645
+ console.log(` ${BOLD}Source:${RESET} ${sourceName}`);
646
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
647
+ console.log(` ${BOLD}Env:${RESET} ${source.secret.env}`);
648
+ if (publicKey) {
649
+ console.log(` ${BOLD}Address:${RESET} ${toSs58(publicKey)}`);
650
+ } else {
651
+ console.log(` ${YELLOW}Address will resolve when $${source.secret.env} is set.${RESET}`);
652
+ }
653
+ console.log();
654
+ } else {
655
+ const { publicKey } = importAccount(source.secret, path);
656
+ const hexPub = publicKeyToHex(publicKey);
657
+ const address = toSs58(publicKey);
658
+ accountsFile.accounts.push({
659
+ name: newName,
660
+ secret: source.secret,
661
+ publicKey: hexPub,
662
+ derivationPath: path
663
+ });
664
+ await saveAccounts(accountsFile);
665
+ printHeading("Account Derived");
666
+ console.log(` ${BOLD}Name:${RESET} ${newName}`);
667
+ console.log(` ${BOLD}Source:${RESET} ${sourceName}`);
668
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
480
669
  console.log(` ${BOLD}Address:${RESET} ${address}`);
481
670
  console.log();
482
671
  }
@@ -493,12 +682,15 @@ async function accountList() {
493
682
  printHeading("Stored Accounts");
494
683
  for (const account of accountsFile.accounts) {
495
684
  let displayName = account.name;
685
+ if (account.derivationPath) {
686
+ displayName += ` (${account.derivationPath})`;
687
+ }
496
688
  let address;
497
689
  if (isEnvSecret(account.secret)) {
498
690
  displayName += ` (env: ${account.secret.env})`;
499
691
  let pubKey = account.publicKey;
500
692
  if (!pubKey) {
501
- pubKey = tryDerivePublicKey(account.secret.env) ?? "";
693
+ pubKey = tryDerivePublicKey(account.secret.env, account.derivationPath) ?? "";
502
694
  }
503
695
  address = pubKey ? toSs58(pubKey) : "n/a";
504
696
  } else {
@@ -512,24 +704,44 @@ async function accountList() {
512
704
  }
513
705
  console.log();
514
706
  }
515
- async function accountRemove(name) {
516
- if (!name) {
517
- console.error(`Account name is required.
707
+ async function accountRemove(names) {
708
+ if (names.length === 0) {
709
+ console.error(`At least one account name is required.
518
710
  `);
519
- console.error("Usage: dot account remove <name>");
711
+ console.error("Usage: dot account remove <name> [name2] ...");
520
712
  process.exit(1);
521
713
  }
522
- if (isDevAccount(name)) {
523
- throw new Error("Cannot remove built-in dev accounts.");
714
+ const errors = [];
715
+ for (const name of names) {
716
+ if (isDevAccount(name)) {
717
+ errors.push(`Cannot remove built-in dev account "${name}".`);
718
+ }
719
+ }
720
+ if (errors.length > 0) {
721
+ throw new Error(errors.join(`
722
+ `));
524
723
  }
525
724
  const accountsFile = await loadAccounts();
526
- const idx = accountsFile.accounts.findIndex((a) => a.name.toLowerCase() === name.toLowerCase());
527
- if (idx === -1) {
528
- throw new Error(`Account "${name}" not found.`);
725
+ const indicesToRemove = new Set;
726
+ for (const name of names) {
727
+ const idx = accountsFile.accounts.findIndex((a) => a.name.toLowerCase() === name.toLowerCase());
728
+ if (idx === -1) {
729
+ errors.push(`Account "${name}" not found.`);
730
+ } else {
731
+ indicesToRemove.add(idx);
732
+ }
733
+ }
734
+ if (errors.length > 0) {
735
+ throw new Error(errors.join(`
736
+ `));
737
+ }
738
+ for (const idx of [...indicesToRemove].sort((a, b) => b - a)) {
739
+ accountsFile.accounts.splice(idx, 1);
529
740
  }
530
- accountsFile.accounts.splice(idx, 1);
531
741
  await saveAccounts(accountsFile);
532
- console.log(`Account "${name}" removed.`);
742
+ for (const name of names) {
743
+ console.log(`Account "${name}" removed.`);
744
+ }
533
745
  }
534
746
 
535
747
  // src/core/client.ts
@@ -566,8 +778,13 @@ var KNOWN_CHAIN_SPECS = {
566
778
  westend: { spec: "polkadot-api/chains/westend2" },
567
779
  paseo: { spec: "polkadot-api/chains/paseo" },
568
780
  "polkadot-asset-hub": { spec: "polkadot-api/chains/polkadot_asset_hub", relay: "polkadot" },
781
+ "polkadot-bridge-hub": { spec: "polkadot-api/chains/polkadot_bridge_hub", relay: "polkadot" },
782
+ "polkadot-collectives": { spec: "polkadot-api/chains/polkadot_collectives", relay: "polkadot" },
783
+ "polkadot-coretime": { spec: "polkadot-api/chains/polkadot_coretime", relay: "polkadot" },
569
784
  "polkadot-people": { spec: "polkadot-api/chains/polkadot_people", relay: "polkadot" },
570
- "paseo-asset-hub": { spec: "polkadot-api/chains/paseo_asset_hub", relay: "paseo" }
785
+ "paseo-asset-hub": { spec: "polkadot-api/chains/paseo_asset_hub", relay: "paseo" },
786
+ "paseo-coretime": { spec: "polkadot-api/chains/paseo_coretime", relay: "paseo" },
787
+ "paseo-people": { spec: "polkadot-api/chains/paseo_people", relay: "paseo" }
571
788
  };
572
789
  function suppressWsNoise() {
573
790
  const orig = console.error;
@@ -788,6 +1005,7 @@ ${BOLD}Usage:${RESET}
788
1005
 
789
1006
  ${BOLD}Examples:${RESET}
790
1007
  $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
1008
+ $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io --rpc wss://kusama-rpc.dwellir.com
791
1009
  $ dot chain add westend --light-client
792
1010
  $ dot chain default kusama
793
1011
  $ dot chain list
@@ -798,6 +1016,8 @@ ${BOLD}Examples:${RESET}
798
1016
  function registerChainCommands(cli) {
799
1017
  cli.command("chain [action] [name]", "Manage chains (add, remove, update, list, default)").alias("chains").action(async (action, name, opts) => {
800
1018
  if (!action) {
1019
+ if (process.argv[2] === "chains")
1020
+ return chainList();
801
1021
  console.log(CHAIN_HELP);
802
1022
  return;
803
1023
  }
@@ -880,8 +1100,15 @@ async function chainList() {
880
1100
  for (const [name, chainConfig] of Object.entries(config.chains)) {
881
1101
  const isDefault = name === config.defaultChain;
882
1102
  const marker = isDefault ? ` ${BOLD}(default)${RESET}` : "";
883
- const provider = chainConfig.lightClient ? `${DIM}light-client${RESET}` : `${DIM}${chainConfig.rpc}${RESET}`;
884
- console.log(` ${CYAN}${name}${RESET}${marker} ${provider}`);
1103
+ if (chainConfig.lightClient) {
1104
+ console.log(` ${CYAN}${name}${RESET}${marker} ${DIM}light-client${RESET}`);
1105
+ } else {
1106
+ const rpcs = Array.isArray(chainConfig.rpc) ? chainConfig.rpc : [chainConfig.rpc];
1107
+ console.log(` ${CYAN}${name}${RESET}${marker} ${DIM}${rpcs[0]}${RESET}`);
1108
+ for (let i = 1;i < rpcs.length; i++) {
1109
+ console.log(` ${DIM}${rpcs[i]}${RESET}`);
1110
+ }
1111
+ }
885
1112
  }
886
1113
  console.log();
887
1114
  }
@@ -1816,7 +2043,7 @@ function registerTxCommand(cli) {
1816
2043
  }
1817
2044
  }
1818
2045
  }
1819
- const rpcUrl = opts.rpc ?? chainConfig.rpc;
2046
+ const rpcUrl = primaryRpc(opts.rpc ?? chainConfig.rpc);
1820
2047
  if (rpcUrl) {
1821
2048
  const blockHash = result.block.hash;
1822
2049
  console.log(` ${BOLD}Explorer:${RESET}`);
@@ -2492,7 +2719,7 @@ registerConstCommand(cli);
2492
2719
  registerAccountCommands(cli);
2493
2720
  registerTxCommand(cli);
2494
2721
  registerHashCommand(cli);
2495
- cli.help();
2722
+ cli.option("--help, -h", "Display this message");
2496
2723
  cli.version(version);
2497
2724
  async function showUpdateAndExit(code) {
2498
2725
  await waitForPendingCheck();
@@ -2515,8 +2742,18 @@ async function handleError(err) {
2515
2742
  async function main() {
2516
2743
  try {
2517
2744
  cli.parse(process.argv, { run: false });
2518
- if (cli.options.version || cli.options.help) {
2745
+ if (cli.options.version) {
2519
2746
  await showUpdateAndExit(0);
2747
+ } else if (cli.options.help) {
2748
+ if (cli.matchedCommandName) {
2749
+ const result = cli.runMatchedCommand();
2750
+ if (result && typeof result.then === "function") {
2751
+ await result.then(() => showUpdateAndExit(0), handleError);
2752
+ }
2753
+ } else {
2754
+ cli.outputHelp();
2755
+ await showUpdateAndExit(0);
2756
+ }
2520
2757
  } else if (!cli.matchedCommandName) {
2521
2758
  cli.outputHelp();
2522
2759
  await showUpdateAndExit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {