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.
- package/README.md +77 -4
- package/dist/cli.mjs +286 -49
- 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
|
|
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
|
|
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.
|
|
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: {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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>
|
|
349
|
-
$ dot account import|add <name> --secret <s>
|
|
350
|
-
$ dot account import|add <name> --env <VAR>
|
|
351
|
-
$ dot account
|
|
352
|
-
$ dot 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] [
|
|
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(
|
|
483
|
+
return accountCreate(names[0], opts);
|
|
375
484
|
case "import":
|
|
376
485
|
case "add":
|
|
377
|
-
return accountImport(
|
|
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(
|
|
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
|
|
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(
|
|
516
|
-
if (
|
|
517
|
-
console.error(`
|
|
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
|
-
|
|
523
|
-
|
|
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
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
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
|
-
|
|
884
|
-
|
|
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
|
|
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);
|