polkadot-cli 0.11.0 → 0.13.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 +142 -7
  2. package/dist/cli.mjs +549 -58
  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,66 @@ 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
106
+
107
+ # Inspect an account — show public key and SS58 address
108
+ dot account inspect alice
109
+ dot account alice # shorthand (same as inspect)
110
+ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
111
+ dot account inspect 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
112
+ dot account inspect alice --prefix 0 # Polkadot mainnet prefix
113
+ dot account inspect alice --output json # JSON output
114
+ ```
115
+
116
+ #### Inspect accounts
117
+
118
+ Convert between SS58 addresses, hex public keys, and account names. Accepts any of:
119
+
120
+ - **Dev account name** (`alice`, `bob`, etc.) — resolves to public key and SS58
121
+ - **Stored account name** — looks up the public key from the accounts file
122
+ - **SS58 address** — decodes to the underlying public key
123
+ - **Hex public key** (`0x` + 64 hex chars) — encodes to SS58
124
+
125
+ ```bash
126
+ dot account inspect alice
127
+ dot account alice # shorthand — unknown subcommands fall through to inspect
128
+
129
+ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
130
+ dot account inspect 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
131
+ ```
132
+
133
+ Use `--prefix` to encode the SS58 address with a specific network prefix (default: 42):
134
+
135
+ ```bash
136
+ dot account inspect alice --prefix 0 # Polkadot mainnet (prefix 0, starts with '1')
137
+ dot account inspect alice --prefix 2 # Kusama (prefix 2)
138
+ ```
139
+
140
+ JSON output:
141
+
142
+ ```bash
143
+ dot account inspect alice --output json
144
+ # {"publicKey":"0xd435...a27d","ss58":"5Grw...utQY","prefix":42,"name":"Alice"}
72
145
  ```
73
146
 
74
147
  #### Env-var-backed accounts
@@ -83,6 +156,33 @@ dot account import ci-signer --env MY_SECRET
83
156
 
84
157
  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
158
 
159
+ #### Derivation paths
160
+
161
+ 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.
162
+
163
+ ```bash
164
+ # Create with a derivation path
165
+ dot account create my-staking --path //staking
166
+
167
+ # Multi-segment path (hard + soft junctions)
168
+ dot account create multi --path //polkadot//0/wallet
169
+
170
+ # Import with a path
171
+ dot account import hot --secret "word1 word2 ..." --path //hot
172
+
173
+ # Derive a child from an existing account
174
+ dot account derive treasury treasury-staking --path //staking
175
+ ```
176
+
177
+ `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.
178
+
179
+ `account list` shows the derivation path next to the account name:
180
+
181
+ ```
182
+ treasury-staking (//staking) 5FHneW46...
183
+ ci-signer (//ci) (env: MY_SECRET) 5EPCUjPx...
184
+ ```
185
+
86
186
  **Supported secret formats for import:**
87
187
 
88
188
  | Format | Example | Status |
@@ -158,20 +258,25 @@ dot const Balances.ExistentialDeposit --output json | jq
158
258
  Works offline from cached metadata after the first fetch.
159
259
 
160
260
  ```bash
161
- # List all pallets
261
+ # List all pallets (shows storage, constants, and calls counts)
162
262
  dot inspect
163
263
 
164
- # List a pallet's storage items and constants
264
+ # List a pallet's storage items, constants, and calls
165
265
  dot inspect System
166
266
 
167
- # Detailed type info for a specific item
267
+ # Detailed type info for a specific storage item or constant
168
268
  dot inspect System.Account
169
269
 
270
+ # Call detail — shows argument signature and docs
271
+ dot inspect Balances.transfer_allow_death
272
+
170
273
  # Inspect a specific chain using chain prefix
171
274
  dot inspect kusama.System
172
275
  dot inspect kusama.System.Account
173
276
  ```
174
277
 
278
+ Use call inspection to discover argument names and types before constructing `dot tx` commands.
279
+
175
280
  ### Submit extrinsics
176
281
 
177
282
  Build, sign, and submit transactions. Pass a `Pallet.Call` with arguments, or a raw SCALE-encoded call hex (e.g. from a multisig proposal or governance). Both forms display a decoded human-readable representation of the call.
@@ -238,6 +343,12 @@ Both dry-run and submission display the encoded call hex and a decoded human-rea
238
343
  Status: ok
239
344
  ```
240
345
 
346
+ Complex calls (e.g. XCM teleports) that the primary decoder cannot handle are automatically decoded via a fallback path:
347
+
348
+ ```
349
+ Decode: PolkadotXcm.limited_teleport_assets { dest: V3 { parents: 1, interior: X1(Parachain(5140)) }, beneficiary: V3 { ... }, assets: V3 [...], fee_asset_item: 0, weight_limit: Unlimited }
350
+ ```
351
+
241
352
  #### Exit codes
242
353
 
243
354
  The CLI exits with code **1** when a finalized transaction has a dispatch error (e.g. insufficient balance, bad origin). The full transaction output (events, explorer links) is still printed before the error so you can debug the failure. Module errors are formatted as `PalletName.ErrorVariant` (e.g. `Balances.InsufficientBalance`).
@@ -249,6 +360,18 @@ dot tx Balances.transferKeepAlive 5FHneW46... 999999999999999999 --from alice
249
360
  echo $? # 1
250
361
  ```
251
362
 
363
+ #### Argument parsing errors
364
+
365
+ When a call argument is invalid, the CLI shows a contextual error message with the argument name, the expected type, and a hint:
366
+
367
+ ```bash
368
+ dot tx Balances.transferKeepAlive 5GrwvaEF... abc --encode
369
+ # Error: Invalid value for argument 'value' (expected Compact<u128>): "abc"
370
+ # Hint: Compact<u128>
371
+ ```
372
+
373
+ For struct-based calls, the error identifies the specific field that failed. For tuple-based calls, it shows the argument index. The original parse error is preserved as the `cause` for programmatic access.
374
+
252
375
  #### Custom signed extensions
253
376
 
254
377
  Chains with non-standard signed extensions (e.g. `people-preview`) are auto-handled:
@@ -286,12 +409,24 @@ dot hash blake2b256 0xdeadbeef --output json
286
409
 
287
410
  Run `dot hash` with no arguments to see all available algorithms.
288
411
 
412
+ ### Getting help
413
+
414
+ Every command supports `--help` to show its detailed usage, available actions, and examples:
415
+
416
+ ```bash
417
+ dot --help # global help with all commands
418
+ dot account --help # same as `dot account` — shows account actions
419
+ dot chain --help # same as `dot chain` — shows chain actions
420
+ dot hash --help # same as `dot hash` — shows algorithms and examples
421
+ ```
422
+
289
423
  ### Global options
290
424
 
291
425
  | Flag | Description |
292
426
  |------|-------------|
427
+ | `--help` | Show help (global or command-specific) |
293
428
  | `--chain <name>` | Target chain (default from config) |
294
- | `--rpc <url>` | Override RPC endpoint for this call |
429
+ | `--rpc <url>` | Override RPC endpoint(s) for this call (repeat for fallback) |
295
430
  | `--light-client` | Use Smoldot light client |
296
431
  | `--output json` | Raw JSON output (default: pretty) |
297
432
  | `--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.13.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));
@@ -144,6 +247,7 @@ import {
144
247
  generateMnemonic,
145
248
  mnemonicToEntropy,
146
249
  ss58Address,
250
+ ss58Decode,
147
251
  validateMnemonic
148
252
  } from "@polkadot-labs/hdkd-helpers";
149
253
  import { getPolkadotSigner } from "polkadot-api/signer";
@@ -177,24 +281,24 @@ function getDevAddress(name, prefix = 42) {
177
281
  const keypair = getDevKeypair(name);
178
282
  return ss58Address(keypair.publicKey, prefix);
179
283
  }
180
- function createNewAccount() {
284
+ function createNewAccount(path = "") {
181
285
  const mnemonic = generateMnemonic();
182
286
  const entropy = mnemonicToEntropy(mnemonic);
183
287
  const miniSecret = entropyToMiniSecret(entropy);
184
288
  const derive = sr25519CreateDerive(miniSecret);
185
- const keypair = derive("");
289
+ const keypair = derive(path);
186
290
  return { mnemonic, publicKey: keypair.publicKey };
187
291
  }
188
- function importAccount(secret) {
292
+ function importAccount(secret, path = "") {
189
293
  const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
190
294
  if (isHexSeed) {
191
- const keypair2 = deriveFromHexSeed(secret, "");
295
+ const keypair2 = deriveFromHexSeed(secret, path);
192
296
  return { publicKey: keypair2.publicKey };
193
297
  }
194
298
  if (!validateMnemonic(secret)) {
195
299
  throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
196
300
  }
197
- const keypair = deriveFromMnemonic(secret, "");
301
+ const keypair = deriveFromMnemonic(secret, path);
198
302
  return { publicKey: keypair.publicKey };
199
303
  }
200
304
  function publicKeyToHex(publicKey) {
@@ -211,6 +315,13 @@ function toSs58(publicKey, prefix = 42) {
211
315
  }
212
316
  return ss58Address(publicKey, prefix);
213
317
  }
318
+ function fromSs58(address) {
319
+ const [payload] = ss58Decode(address);
320
+ return payload;
321
+ }
322
+ function isHexPublicKey(input) {
323
+ return /^0x[0-9a-fA-F]{64}$/.test(input);
324
+ }
214
325
  function resolveSecret(secret) {
215
326
  if (isEnvSecret(secret)) {
216
327
  const value = process.env[secret.env];
@@ -221,12 +332,12 @@ function resolveSecret(secret) {
221
332
  }
222
333
  return secret;
223
334
  }
224
- function tryDerivePublicKey(envVarName) {
335
+ function tryDerivePublicKey(envVarName, path = "") {
225
336
  const value = process.env[envVarName];
226
337
  if (!value)
227
338
  return null;
228
339
  try {
229
- const { publicKey } = importAccount(value);
340
+ const { publicKey } = importAccount(value, path);
230
341
  return publicKeyToHex(publicKey);
231
342
  } catch {
232
343
  return null;
@@ -345,50 +456,61 @@ class Spinner {
345
456
  // src/commands/account.ts
346
457
  var ACCOUNT_HELP = `
347
458
  ${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
459
+ $ dot account create|new <name> [--path <derivation>] Create a new account
460
+ $ dot account import|add <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic
461
+ $ dot account import|add <name> --env <VAR> [--path <derivation>] Import account backed by env variable
462
+ $ dot account derive <source> <new-name> --path <derivation> Derive a child account
463
+ $ dot account inspect <input> [--prefix <N>] Inspect an account/address/key
464
+ $ dot account list List all accounts
465
+ $ dot account remove|delete <name> [name2] ... Remove stored account(s)
353
466
 
354
467
  ${BOLD}Examples:${RESET}
355
468
  $ dot account create my-validator
469
+ $ dot account create my-staking --path //staking
470
+ $ dot account create multi --path //polkadot//0/wallet
356
471
  $ dot account import treasury --secret "word1 word2 ... word12"
357
- $ dot account import ci-signer --env MY_SECRET
472
+ $ dot account import ci-signer --env MY_SECRET --path //ci
473
+ $ dot account derive treasury treasury-staking --path //staking
474
+ $ dot account inspect alice
475
+ $ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
476
+ $ dot account inspect 0xd435...a27d --prefix 0
358
477
  $ dot account list
359
- $ dot account remove my-validator
478
+ $ dot account remove my-validator stale-key
360
479
 
361
480
  ${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
362
481
  Use --env to keep secrets off disk entirely.
363
482
  Hex seed import (0x...) is not supported via CLI.${RESET}
364
483
  `.trimStart();
365
484
  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) => {
485
+ 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)").option("--prefix <number>", "SS58 prefix for address encoding (default: 42)").action(async (action, names, opts) => {
367
486
  if (!action) {
487
+ if (process.argv[2] === "accounts")
488
+ return accountList();
368
489
  console.log(ACCOUNT_HELP);
369
490
  return;
370
491
  }
371
492
  switch (action) {
372
493
  case "new":
373
494
  case "create":
374
- return accountCreate(name);
495
+ return accountCreate(names[0], opts);
375
496
  case "import":
376
497
  case "add":
377
- return accountImport(name, opts);
498
+ return accountImport(names[0], opts);
499
+ case "derive":
500
+ return accountDerive(names[0], names[1], opts);
378
501
  case "list":
379
502
  return accountList();
380
503
  case "delete":
381
504
  case "remove":
382
- return accountRemove(name);
505
+ return accountRemove(names);
506
+ case "inspect":
507
+ return accountInspect(names[0], opts);
383
508
  default:
384
- console.error(`Unknown action "${action}".
385
- `);
386
- console.log(ACCOUNT_HELP);
387
- process.exit(1);
509
+ return accountInspect(action, opts);
388
510
  }
389
511
  });
390
512
  }
391
- async function accountCreate(name) {
513
+ async function accountCreate(name, opts) {
392
514
  if (!name) {
393
515
  console.error(`Account name is required.
394
516
  `);
@@ -402,18 +524,21 @@ async function accountCreate(name) {
402
524
  if (findAccount(accountsFile, name)) {
403
525
  throw new Error(`Account "${name}" already exists.`);
404
526
  }
405
- const { mnemonic, publicKey } = createNewAccount();
527
+ const path = opts.path ?? "";
528
+ const { mnemonic, publicKey } = createNewAccount(path);
406
529
  const hexPub = publicKeyToHex(publicKey);
407
530
  const address = toSs58(publicKey);
408
531
  accountsFile.accounts.push({
409
532
  name,
410
533
  secret: mnemonic,
411
534
  publicKey: hexPub,
412
- derivationPath: ""
535
+ derivationPath: path
413
536
  });
414
537
  await saveAccounts(accountsFile);
415
538
  printHeading("Account Created");
416
539
  console.log(` ${BOLD}Name:${RESET} ${name}`);
540
+ if (path)
541
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
417
542
  console.log(` ${BOLD}Address:${RESET} ${address}`);
418
543
  console.log(` ${BOLD}Mnemonic:${RESET} ${mnemonic}`);
419
544
  console.log();
@@ -446,17 +571,20 @@ async function accountImport(name, opts) {
446
571
  if (findAccount(accountsFile, name)) {
447
572
  throw new Error(`Account "${name}" already exists.`);
448
573
  }
574
+ const path = opts.path ?? "";
449
575
  if (opts.env) {
450
- const publicKey = tryDerivePublicKey(opts.env) ?? "";
576
+ const publicKey = tryDerivePublicKey(opts.env, path) ?? "";
451
577
  accountsFile.accounts.push({
452
578
  name,
453
579
  secret: { env: opts.env },
454
580
  publicKey,
455
- derivationPath: ""
581
+ derivationPath: path
456
582
  });
457
583
  await saveAccounts(accountsFile);
458
584
  printHeading("Account Imported");
459
585
  console.log(` ${BOLD}Name:${RESET} ${name}`);
586
+ if (path)
587
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
460
588
  console.log(` ${BOLD}Env:${RESET} ${opts.env}`);
461
589
  if (publicKey) {
462
590
  console.log(` ${BOLD}Address:${RESET} ${toSs58(publicKey)}`);
@@ -465,18 +593,90 @@ async function accountImport(name, opts) {
465
593
  }
466
594
  console.log();
467
595
  } else {
468
- const { publicKey } = importAccount(opts.secret);
596
+ const { publicKey } = importAccount(opts.secret, path);
469
597
  const hexPub = publicKeyToHex(publicKey);
470
598
  const address = toSs58(publicKey);
471
599
  accountsFile.accounts.push({
472
600
  name,
473
601
  secret: opts.secret,
474
602
  publicKey: hexPub,
475
- derivationPath: ""
603
+ derivationPath: path
476
604
  });
477
605
  await saveAccounts(accountsFile);
478
606
  printHeading("Account Imported");
479
607
  console.log(` ${BOLD}Name:${RESET} ${name}`);
608
+ if (path)
609
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
610
+ console.log(` ${BOLD}Address:${RESET} ${address}`);
611
+ console.log();
612
+ }
613
+ }
614
+ async function accountDerive(sourceName, newName, opts) {
615
+ if (!sourceName) {
616
+ console.error(`Source account name is required.
617
+ `);
618
+ console.error("Usage: dot account derive <source> <new-name> --path <derivation>");
619
+ process.exit(1);
620
+ }
621
+ if (!newName) {
622
+ console.error(`New account name is required.
623
+ `);
624
+ console.error("Usage: dot account derive <source> <new-name> --path <derivation>");
625
+ process.exit(1);
626
+ }
627
+ if (!opts.path) {
628
+ console.error(`--path is required for derive.
629
+ `);
630
+ console.error("Usage: dot account derive <source> <new-name> --path <derivation>");
631
+ process.exit(1);
632
+ }
633
+ if (isDevAccount(newName)) {
634
+ throw new Error(`"${newName}" is a built-in dev account and cannot be used as a custom account name.`);
635
+ }
636
+ const accountsFile = await loadAccounts();
637
+ const source = findAccount(accountsFile, sourceName);
638
+ if (!source) {
639
+ throw new Error(`Source account "${sourceName}" not found.`);
640
+ }
641
+ if (findAccount(accountsFile, newName)) {
642
+ throw new Error(`Account "${newName}" already exists.`);
643
+ }
644
+ const path = opts.path;
645
+ if (isEnvSecret(source.secret)) {
646
+ const publicKey = tryDerivePublicKey(source.secret.env, path) ?? "";
647
+ accountsFile.accounts.push({
648
+ name: newName,
649
+ secret: source.secret,
650
+ publicKey,
651
+ derivationPath: path
652
+ });
653
+ await saveAccounts(accountsFile);
654
+ printHeading("Account Derived");
655
+ console.log(` ${BOLD}Name:${RESET} ${newName}`);
656
+ console.log(` ${BOLD}Source:${RESET} ${sourceName}`);
657
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
658
+ console.log(` ${BOLD}Env:${RESET} ${source.secret.env}`);
659
+ if (publicKey) {
660
+ console.log(` ${BOLD}Address:${RESET} ${toSs58(publicKey)}`);
661
+ } else {
662
+ console.log(` ${YELLOW}Address will resolve when $${source.secret.env} is set.${RESET}`);
663
+ }
664
+ console.log();
665
+ } else {
666
+ const { publicKey } = importAccount(source.secret, path);
667
+ const hexPub = publicKeyToHex(publicKey);
668
+ const address = toSs58(publicKey);
669
+ accountsFile.accounts.push({
670
+ name: newName,
671
+ secret: source.secret,
672
+ publicKey: hexPub,
673
+ derivationPath: path
674
+ });
675
+ await saveAccounts(accountsFile);
676
+ printHeading("Account Derived");
677
+ console.log(` ${BOLD}Name:${RESET} ${newName}`);
678
+ console.log(` ${BOLD}Source:${RESET} ${sourceName}`);
679
+ console.log(` ${BOLD}Path:${RESET} ${path}`);
480
680
  console.log(` ${BOLD}Address:${RESET} ${address}`);
481
681
  console.log();
482
682
  }
@@ -493,12 +693,15 @@ async function accountList() {
493
693
  printHeading("Stored Accounts");
494
694
  for (const account of accountsFile.accounts) {
495
695
  let displayName = account.name;
696
+ if (account.derivationPath) {
697
+ displayName += ` (${account.derivationPath})`;
698
+ }
496
699
  let address;
497
700
  if (isEnvSecret(account.secret)) {
498
701
  displayName += ` (env: ${account.secret.env})`;
499
702
  let pubKey = account.publicKey;
500
703
  if (!pubKey) {
501
- pubKey = tryDerivePublicKey(account.secret.env) ?? "";
704
+ pubKey = tryDerivePublicKey(account.secret.env, account.derivationPath) ?? "";
502
705
  }
503
706
  address = pubKey ? toSs58(pubKey) : "n/a";
504
707
  } else {
@@ -512,24 +715,108 @@ async function accountList() {
512
715
  }
513
716
  console.log();
514
717
  }
515
- async function accountRemove(name) {
516
- if (!name) {
517
- console.error(`Account name is required.
718
+ async function accountRemove(names) {
719
+ if (names.length === 0) {
720
+ console.error(`At least one account name is required.
518
721
  `);
519
- console.error("Usage: dot account remove <name>");
722
+ console.error("Usage: dot account remove <name> [name2] ...");
520
723
  process.exit(1);
521
724
  }
522
- if (isDevAccount(name)) {
523
- throw new Error("Cannot remove built-in dev accounts.");
725
+ const errors = [];
726
+ for (const name of names) {
727
+ if (isDevAccount(name)) {
728
+ errors.push(`Cannot remove built-in dev account "${name}".`);
729
+ }
730
+ }
731
+ if (errors.length > 0) {
732
+ throw new Error(errors.join(`
733
+ `));
524
734
  }
525
735
  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.`);
736
+ const indicesToRemove = new Set;
737
+ for (const name of names) {
738
+ const idx = accountsFile.accounts.findIndex((a) => a.name.toLowerCase() === name.toLowerCase());
739
+ if (idx === -1) {
740
+ errors.push(`Account "${name}" not found.`);
741
+ } else {
742
+ indicesToRemove.add(idx);
743
+ }
744
+ }
745
+ if (errors.length > 0) {
746
+ throw new Error(errors.join(`
747
+ `));
748
+ }
749
+ for (const idx of [...indicesToRemove].sort((a, b) => b - a)) {
750
+ accountsFile.accounts.splice(idx, 1);
529
751
  }
530
- accountsFile.accounts.splice(idx, 1);
531
752
  await saveAccounts(accountsFile);
532
- console.log(`Account "${name}" removed.`);
753
+ for (const name of names) {
754
+ console.log(`Account "${name}" removed.`);
755
+ }
756
+ }
757
+ async function accountInspect(input, opts) {
758
+ if (!input) {
759
+ console.error(`Input is required.
760
+ `);
761
+ console.error("Usage: dot account inspect <name|ss58-address|0x-public-key> [--prefix <N>]");
762
+ process.exit(1);
763
+ }
764
+ const prefix = opts.prefix != null ? Number(opts.prefix) : 42;
765
+ if (Number.isNaN(prefix) || prefix < 0) {
766
+ console.error(`Invalid prefix "${opts.prefix}". Must be a non-negative integer.`);
767
+ process.exit(1);
768
+ }
769
+ let name;
770
+ let publicKeyHex;
771
+ if (isDevAccount(input)) {
772
+ name = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
773
+ const devAddr = getDevAddress(input);
774
+ publicKeyHex = publicKeyToHex(fromSs58(devAddr));
775
+ } else {
776
+ const accountsFile = await loadAccounts();
777
+ const account = findAccount(accountsFile, input);
778
+ if (account) {
779
+ name = account.name;
780
+ if (account.publicKey) {
781
+ publicKeyHex = account.publicKey;
782
+ } else if (isEnvSecret(account.secret)) {
783
+ const derived = tryDerivePublicKey(account.secret.env, account.derivationPath);
784
+ if (!derived) {
785
+ console.error(`Cannot derive public key for "${account.name}": $${account.secret.env} is not set.`);
786
+ process.exit(1);
787
+ }
788
+ publicKeyHex = derived;
789
+ } else {
790
+ console.error(`Account "${account.name}" has no public key.`);
791
+ process.exit(1);
792
+ }
793
+ } else if (isHexPublicKey(input)) {
794
+ publicKeyHex = input;
795
+ } else {
796
+ try {
797
+ const decoded = fromSs58(input);
798
+ publicKeyHex = publicKeyToHex(decoded);
799
+ } catch {
800
+ console.error(`Cannot identify "${input}" as an account name, SS58 address, or hex public key.`);
801
+ process.exit(1);
802
+ }
803
+ }
804
+ }
805
+ const ss58 = toSs58(publicKeyHex, prefix);
806
+ if (opts.output === "json") {
807
+ const result = { publicKey: publicKeyHex, ss58, prefix };
808
+ if (name)
809
+ result.name = name;
810
+ console.log(formatJson(result));
811
+ } else {
812
+ printHeading("Account Info");
813
+ if (name)
814
+ console.log(` ${BOLD}Name:${RESET} ${name}`);
815
+ console.log(` ${BOLD}Public Key:${RESET} ${publicKeyHex}`);
816
+ console.log(` ${BOLD}SS58:${RESET} ${ss58}`);
817
+ console.log(` ${BOLD}Prefix:${RESET} ${prefix}`);
818
+ console.log();
819
+ }
533
820
  }
534
821
 
535
822
  // src/core/client.ts
@@ -566,8 +853,13 @@ var KNOWN_CHAIN_SPECS = {
566
853
  westend: { spec: "polkadot-api/chains/westend2" },
567
854
  paseo: { spec: "polkadot-api/chains/paseo" },
568
855
  "polkadot-asset-hub": { spec: "polkadot-api/chains/polkadot_asset_hub", relay: "polkadot" },
856
+ "polkadot-bridge-hub": { spec: "polkadot-api/chains/polkadot_bridge_hub", relay: "polkadot" },
857
+ "polkadot-collectives": { spec: "polkadot-api/chains/polkadot_collectives", relay: "polkadot" },
858
+ "polkadot-coretime": { spec: "polkadot-api/chains/polkadot_coretime", relay: "polkadot" },
569
859
  "polkadot-people": { spec: "polkadot-api/chains/polkadot_people", relay: "polkadot" },
570
- "paseo-asset-hub": { spec: "polkadot-api/chains/paseo_asset_hub", relay: "paseo" }
860
+ "paseo-asset-hub": { spec: "polkadot-api/chains/paseo_asset_hub", relay: "paseo" },
861
+ "paseo-coretime": { spec: "polkadot-api/chains/paseo_coretime", relay: "paseo" },
862
+ "paseo-people": { spec: "polkadot-api/chains/paseo_people", relay: "paseo" }
571
863
  };
572
864
  function suppressWsNoise() {
573
865
  const orig = console.error;
@@ -767,6 +1059,42 @@ function formatLookupEntry(entry) {
767
1059
  return "unknown";
768
1060
  }
769
1061
  }
1062
+ function describeCallArgs(meta, palletName, callName) {
1063
+ try {
1064
+ const palletMeta = meta.unified.pallets.find((p) => p.name === palletName);
1065
+ if (!palletMeta?.calls)
1066
+ return "";
1067
+ const callsEntry = meta.lookup(palletMeta.calls.type);
1068
+ if (callsEntry.type !== "enum")
1069
+ return "";
1070
+ const variant = callsEntry.value[callName];
1071
+ if (!variant)
1072
+ return "";
1073
+ if (variant.type === "void")
1074
+ return "()";
1075
+ if (variant.type === "struct") {
1076
+ const fields = Object.entries(variant.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
1077
+ return `(${fields})`;
1078
+ }
1079
+ if (variant.type === "lookupEntry") {
1080
+ const inner = variant.value;
1081
+ if (inner.type === "void")
1082
+ return "()";
1083
+ if (inner.type === "struct") {
1084
+ const fields = Object.entries(inner.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
1085
+ return `(${fields})`;
1086
+ }
1087
+ return `(${formatLookupEntry(inner)})`;
1088
+ }
1089
+ if (variant.type === "tuple") {
1090
+ const types = variant.value.map(formatLookupEntry).join(", ");
1091
+ return `(${types})`;
1092
+ }
1093
+ return "";
1094
+ } catch {
1095
+ return "";
1096
+ }
1097
+ }
770
1098
  function hexToBytes(hex) {
771
1099
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
772
1100
  const bytes = new Uint8Array(clean.length / 2);
@@ -788,6 +1116,7 @@ ${BOLD}Usage:${RESET}
788
1116
 
789
1117
  ${BOLD}Examples:${RESET}
790
1118
  $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
1119
+ $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io --rpc wss://kusama-rpc.dwellir.com
791
1120
  $ dot chain add westend --light-client
792
1121
  $ dot chain default kusama
793
1122
  $ dot chain list
@@ -798,6 +1127,8 @@ ${BOLD}Examples:${RESET}
798
1127
  function registerChainCommands(cli) {
799
1128
  cli.command("chain [action] [name]", "Manage chains (add, remove, update, list, default)").alias("chains").action(async (action, name, opts) => {
800
1129
  if (!action) {
1130
+ if (process.argv[2] === "chains")
1131
+ return chainList();
801
1132
  console.log(CHAIN_HELP);
802
1133
  return;
803
1134
  }
@@ -880,8 +1211,15 @@ async function chainList() {
880
1211
  for (const [name, chainConfig] of Object.entries(config.chains)) {
881
1212
  const isDefault = name === config.defaultChain;
882
1213
  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}`);
1214
+ if (chainConfig.lightClient) {
1215
+ console.log(` ${CYAN}${name}${RESET}${marker} ${DIM}light-client${RESET}`);
1216
+ } else {
1217
+ const rpcs = Array.isArray(chainConfig.rpc) ? chainConfig.rpc : [chainConfig.rpc];
1218
+ console.log(` ${CYAN}${name}${RESET}${marker} ${DIM}${rpcs[0]}${RESET}`);
1219
+ for (let i = 1;i < rpcs.length; i++) {
1220
+ console.log(` ${DIM}${rpcs[i]}${RESET}`);
1221
+ }
1222
+ }
885
1223
  }
886
1224
  console.log();
887
1225
  }
@@ -1209,6 +1547,8 @@ function registerInspectCommand(cli) {
1209
1547
  counts.push(`${p.storage.length} storage`);
1210
1548
  if (p.constants.length)
1211
1549
  counts.push(`${p.constants.length} constants`);
1550
+ if (p.calls.length)
1551
+ counts.push(`${p.calls.length} calls`);
1212
1552
  printItem(p.name, counts.join(", "));
1213
1553
  }
1214
1554
  console.log();
@@ -1241,6 +1581,14 @@ function registerInspectCommand(cli) {
1241
1581
  }
1242
1582
  console.log();
1243
1583
  }
1584
+ if (pallet2.calls.length) {
1585
+ console.log(` ${BOLD}Calls:${RESET}`);
1586
+ for (const c of pallet2.calls) {
1587
+ const doc = c.docs[0] ? ` — ${c.docs[0].slice(0, 80)}` : "";
1588
+ console.log(` ${CYAN}${c.name}${RESET}${DIM}${doc}${RESET}`);
1589
+ }
1590
+ console.log();
1591
+ }
1244
1592
  return;
1245
1593
  }
1246
1594
  const palletNames = getPalletNames(meta);
@@ -1274,9 +1622,22 @@ function registerInspectCommand(cli) {
1274
1622
  console.log();
1275
1623
  return;
1276
1624
  }
1625
+ const callItem = pallet.calls.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
1626
+ if (callItem) {
1627
+ printHeading(`${pallet.name}.${callItem.name} (Call)`);
1628
+ const args = describeCallArgs(meta, pallet.name, callItem.name);
1629
+ console.log(` ${BOLD}Args:${RESET} ${args}`);
1630
+ if (callItem.docs.length) {
1631
+ console.log();
1632
+ printDocs(callItem.docs);
1633
+ }
1634
+ console.log();
1635
+ return;
1636
+ }
1277
1637
  const allItems = [
1278
1638
  ...pallet.storage.map((s) => s.name),
1279
- ...pallet.constants.map((c) => c.name)
1639
+ ...pallet.constants.map((c) => c.name),
1640
+ ...pallet.calls.map((c) => c.name)
1280
1641
  ];
1281
1642
  throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems));
1282
1643
  });
@@ -1322,10 +1683,36 @@ function parseStructArgs(meta, fields, args, callLabel) {
1322
1683
  for (let i = 0;i < fieldNames.length; i++) {
1323
1684
  const name = fieldNames[i];
1324
1685
  const entry = fields[name];
1325
- result[name] = parseTypedArg(meta, entry, args[i]);
1686
+ try {
1687
+ result[name] = parseTypedArg(meta, entry, args[i]);
1688
+ } catch (err) {
1689
+ const typeDesc = describeType(meta.lookup, entry.id);
1690
+ throw new Error(`Invalid value for argument '${name}' (expected ${typeDesc}): ${JSON.stringify(args[i])}
1691
+ ` + ` Hint: ${typeHint(entry, meta)}`, { cause: err });
1692
+ }
1326
1693
  }
1327
1694
  return result;
1328
1695
  }
1696
+ function typeHint(entry, meta) {
1697
+ const resolved = entry.type === "lookupEntry" ? entry.value : entry;
1698
+ switch (resolved.type) {
1699
+ case "enum": {
1700
+ const variants = Object.keys(resolved.value);
1701
+ if (variants.length <= 6)
1702
+ return `a variant: ${variants.join(" | ")}`;
1703
+ return `one of ${variants.length} variants (e.g. ${variants.slice(0, 3).join(", ")})`;
1704
+ }
1705
+ case "struct":
1706
+ return `a JSON object with fields: ${Object.keys(resolved.value).join(", ")}`;
1707
+ case "tuple":
1708
+ return "a JSON array";
1709
+ case "sequence":
1710
+ case "array":
1711
+ return "a JSON array or hex-encoded bytes";
1712
+ default:
1713
+ return describeType(meta.lookup, entry.id);
1714
+ }
1715
+ }
1329
1716
  function normalizeValue(lookup, entry, value) {
1330
1717
  let resolved = entry;
1331
1718
  while (resolved.type === "lookupEntry") {
@@ -1816,7 +2203,7 @@ function registerTxCommand(cli) {
1816
2203
  }
1817
2204
  }
1818
2205
  }
1819
- const rpcUrl = opts.rpc ?? chainConfig.rpc;
2206
+ const rpcUrl = primaryRpc(opts.rpc ?? chainConfig.rpc);
1820
2207
  if (rpcUrl) {
1821
2208
  const blockHash = result.block.hash;
1822
2209
  console.log(` ${BOLD}Explorer:${RESET}`);
@@ -1857,10 +2244,66 @@ function decodeCall(meta, callHex) {
1857
2244
  const callName = decoded.call.value.name;
1858
2245
  const argsStr = formatDecodedArgs(decoded.args.value);
1859
2246
  return `${palletName}.${callName}${argsStr}`;
2247
+ } catch {}
2248
+ try {
2249
+ return decodeCallFallback(meta, callHex);
1860
2250
  } catch {
1861
2251
  return "(unable to decode)";
1862
2252
  }
1863
2253
  }
2254
+ function decodeCallFallback(meta, callHex) {
2255
+ const callTypeId = meta.lookup.call;
2256
+ if (callTypeId == null)
2257
+ throw new Error("No RuntimeCall type ID");
2258
+ const codec = meta.builder.buildDefinition(callTypeId);
2259
+ const decoded = codec.dec(Binary3.fromHex(callHex).asBytes());
2260
+ const palletName = decoded.type;
2261
+ const call = decoded.value;
2262
+ const callName = call.type;
2263
+ const args = call.value;
2264
+ if (args === undefined || args === null) {
2265
+ return `${palletName}.${callName}`;
2266
+ }
2267
+ const argsStr = formatRawDecoded(args);
2268
+ return `${palletName}.${callName} ${argsStr}`;
2269
+ }
2270
+ function formatRawDecoded(value) {
2271
+ if (value === undefined || value === null)
2272
+ return "null";
2273
+ if (value instanceof Binary3)
2274
+ return value.asHex();
2275
+ if (typeof value === "bigint")
2276
+ return value.toString();
2277
+ if (typeof value === "string")
2278
+ return value;
2279
+ if (typeof value === "number")
2280
+ return value.toString();
2281
+ if (typeof value === "boolean")
2282
+ return value.toString();
2283
+ if (Array.isArray(value)) {
2284
+ if (value.length === 0)
2285
+ return "[]";
2286
+ return `[${value.map(formatRawDecoded).join(", ")}]`;
2287
+ }
2288
+ if (typeof value === "object") {
2289
+ const obj = value;
2290
+ if ("type" in obj && typeof obj.type === "string") {
2291
+ const inner = obj.value;
2292
+ if (inner === undefined || inner === null)
2293
+ return obj.type;
2294
+ const innerStr = formatRawDecoded(inner);
2295
+ if (innerStr.startsWith("{"))
2296
+ return `${obj.type} ${innerStr}`;
2297
+ return `${obj.type}(${innerStr})`;
2298
+ }
2299
+ const entries = Object.entries(obj);
2300
+ if (entries.length === 0)
2301
+ return "{}";
2302
+ const fields = entries.map(([k, v]) => `${k}: ${formatRawDecoded(v)}`).join(", ");
2303
+ return `{ ${fields} }`;
2304
+ }
2305
+ return String(value);
2306
+ }
1864
2307
  function formatDecodedArgs(decoded) {
1865
2308
  return formatDecoded(decoded);
1866
2309
  }
@@ -1984,14 +2427,26 @@ function parseCallArgs(meta, palletName, callName, args) {
1984
2427
  if (args.length !== 1) {
1985
2428
  throw new Error(`${palletName}.${callName} takes 1 argument (${describeType(meta.lookup, inner.id)}), but ${args.length} provided.`);
1986
2429
  }
1987
- return parseTypedArg2(meta, inner, args[0]);
2430
+ try {
2431
+ return parseTypedArg2(meta, inner, args[0]);
2432
+ } catch (err) {
2433
+ const typeDesc = describeType(meta.lookup, inner.id);
2434
+ throw new Error(`Invalid value for argument 0 (expected ${typeDesc}): ${JSON.stringify(args[0])}`, { cause: err });
2435
+ }
1988
2436
  }
1989
2437
  if (variant.type === "tuple") {
1990
2438
  const entries = variant.value;
1991
2439
  if (args.length !== entries.length) {
1992
2440
  throw new Error(`${palletName}.${callName} takes ${entries.length} arguments, but ${args.length} provided.`);
1993
2441
  }
1994
- return entries.map((entry, i) => parseTypedArg2(meta, entry, args[i]));
2442
+ return entries.map((entry, i) => {
2443
+ try {
2444
+ return parseTypedArg2(meta, entry, args[i]);
2445
+ } catch (err) {
2446
+ const typeDesc = describeType(meta.lookup, entry.id);
2447
+ throw new Error(`Invalid value for argument ${i} (expected ${typeDesc}): ${JSON.stringify(args[i])}`, { cause: err });
2448
+ }
2449
+ });
1995
2450
  }
1996
2451
  return args.length === 0 ? undefined : args.map(parseValue);
1997
2452
  }
@@ -2006,10 +2461,36 @@ function parseStructArgs2(meta, fields, args, callLabel) {
2006
2461
  for (let i = 0;i < fieldNames.length; i++) {
2007
2462
  const name = fieldNames[i];
2008
2463
  const entry = fields[name];
2009
- result[name] = parseTypedArg2(meta, entry, args[i]);
2464
+ try {
2465
+ result[name] = parseTypedArg2(meta, entry, args[i]);
2466
+ } catch (err) {
2467
+ const typeDesc = describeType(meta.lookup, entry.id);
2468
+ throw new Error(`Invalid value for argument '${name}' (expected ${typeDesc}): ${JSON.stringify(args[i])}
2469
+ ` + ` Hint: ${typeHint2(entry, meta)}`, { cause: err });
2470
+ }
2010
2471
  }
2011
2472
  return result;
2012
2473
  }
2474
+ function typeHint2(entry, meta) {
2475
+ const resolved = entry.type === "lookupEntry" ? entry.value : entry;
2476
+ switch (resolved.type) {
2477
+ case "enum": {
2478
+ const variants = Object.keys(resolved.value);
2479
+ if (variants.length <= 6)
2480
+ return `a variant: ${variants.join(" | ")}`;
2481
+ return `one of ${variants.length} variants (e.g. ${variants.slice(0, 3).join(", ")})`;
2482
+ }
2483
+ case "struct":
2484
+ return `a JSON object with fields: ${Object.keys(resolved.value).join(", ")}`;
2485
+ case "tuple":
2486
+ return "a JSON array";
2487
+ case "sequence":
2488
+ case "array":
2489
+ return "a JSON array or hex-encoded bytes";
2490
+ default:
2491
+ return describeType(meta.lookup, entry.id);
2492
+ }
2493
+ }
2013
2494
  function normalizeValue2(lookup, entry, value) {
2014
2495
  let resolved = entry;
2015
2496
  while (resolved.type === "lookupEntry") {
@@ -2492,7 +2973,7 @@ registerConstCommand(cli);
2492
2973
  registerAccountCommands(cli);
2493
2974
  registerTxCommand(cli);
2494
2975
  registerHashCommand(cli);
2495
- cli.help();
2976
+ cli.option("--help, -h", "Display this message");
2496
2977
  cli.version(version);
2497
2978
  async function showUpdateAndExit(code) {
2498
2979
  await waitForPendingCheck();
@@ -2515,8 +2996,18 @@ async function handleError(err) {
2515
2996
  async function main() {
2516
2997
  try {
2517
2998
  cli.parse(process.argv, { run: false });
2518
- if (cli.options.version || cli.options.help) {
2999
+ if (cli.options.version) {
2519
3000
  await showUpdateAndExit(0);
3001
+ } else if (cli.options.help) {
3002
+ if (cli.matchedCommandName) {
3003
+ const result = cli.runMatchedCommand();
3004
+ if (result && typeof result.then === "function") {
3005
+ await result.then(() => showUpdateAndExit(0), handleError);
3006
+ }
3007
+ } else {
3008
+ cli.outputHelp();
3009
+ await showUpdateAndExit(0);
3010
+ }
2520
3011
  } else if (!cli.matchedCommandName) {
2521
3012
  cli.outputHelp();
2522
3013
  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.13.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {