polkadot-cli 0.10.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 +98 -13
- package/dist/cli.mjs +403 -130
- 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
|
|
|
@@ -19,13 +38,21 @@ This installs the `dot` command globally.
|
|
|
19
38
|
### Manage chains
|
|
20
39
|
|
|
21
40
|
```bash
|
|
22
|
-
#
|
|
41
|
+
# Show chain help
|
|
42
|
+
dot chain # shows available actions
|
|
43
|
+
dot chains # shorthand, same as above
|
|
44
|
+
|
|
45
|
+
# Add a chain (single RPC)
|
|
23
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
|
|
24
52
|
dot chain add westend --light-client
|
|
25
53
|
|
|
26
54
|
# List configured chains
|
|
27
|
-
dot
|
|
28
|
-
dot chain list # equivalent
|
|
55
|
+
dot chain list
|
|
29
56
|
|
|
30
57
|
# Re-fetch metadata after a runtime upgrade
|
|
31
58
|
dot chain update # updates default chain
|
|
@@ -45,24 +72,37 @@ Dev accounts (Alice, Bob, Charlie, Dave, Eve, Ferdie) are always available for t
|
|
|
45
72
|
> **Security warning:** Account secrets (mnemonics and seeds) are currently stored **unencrypted** in `~/.polkadot/accounts.json`. Do not use this for high-value accounts on mainnet. Encrypted storage is planned for a future release. Use `--env` to keep secrets off disk entirely.
|
|
46
73
|
|
|
47
74
|
```bash
|
|
75
|
+
# Show account help
|
|
76
|
+
dot account # shows available actions
|
|
77
|
+
dot accounts # shorthand, same as above
|
|
78
|
+
|
|
48
79
|
# List all accounts (dev + stored)
|
|
49
|
-
dot
|
|
50
|
-
dot account list # equivalent
|
|
80
|
+
dot account list
|
|
51
81
|
|
|
52
82
|
# Create a new account (generates a mnemonic)
|
|
53
83
|
dot account create my-validator
|
|
54
84
|
|
|
85
|
+
# Create with a derivation path
|
|
86
|
+
dot account create my-staking --path //staking
|
|
87
|
+
|
|
55
88
|
# Import from a BIP39 mnemonic
|
|
56
89
|
dot account import treasury --secret "word1 word2 ... word12"
|
|
57
90
|
|
|
58
|
-
#
|
|
59
|
-
dot account
|
|
91
|
+
# Import with a derivation path
|
|
92
|
+
dot account import hot-wallet --secret "word1 word2 ... word12" --path //hot
|
|
93
|
+
|
|
94
|
+
# Import an env-var-backed account (secret stays off disk)
|
|
95
|
+
dot account import ci-signer --env MY_SECRET
|
|
96
|
+
|
|
97
|
+
# Derive a child account from an existing one
|
|
98
|
+
dot account derive treasury treasury-staking --path //staking
|
|
60
99
|
|
|
61
100
|
# Use it — the env var is read at signing time
|
|
62
101
|
MY_SECRET="word1 word2 ..." dot tx System.remark 0xdead --from ci-signer
|
|
63
102
|
|
|
64
|
-
# Remove
|
|
103
|
+
# Remove one or more accounts
|
|
65
104
|
dot account remove my-validator
|
|
105
|
+
dot account delete my-validator stale-key
|
|
66
106
|
```
|
|
67
107
|
|
|
68
108
|
#### Env-var-backed accounts
|
|
@@ -70,11 +110,40 @@ dot account remove my-validator
|
|
|
70
110
|
For CI/CD and security-conscious workflows, store a reference to an environment variable instead of the secret itself:
|
|
71
111
|
|
|
72
112
|
```bash
|
|
73
|
-
dot account
|
|
113
|
+
dot account import ci-signer --env MY_SECRET
|
|
74
114
|
```
|
|
75
115
|
|
|
116
|
+
`--secret` and `--env` are mutually exclusive. `add` is an alias for `import`.
|
|
117
|
+
|
|
76
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.
|
|
77
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
|
+
|
|
78
147
|
**Supported secret formats for import:**
|
|
79
148
|
|
|
80
149
|
| Format | Example | Status |
|
|
@@ -96,6 +165,8 @@ dot inspect kusama.System
|
|
|
96
165
|
dot inspect kusama.System.Account
|
|
97
166
|
```
|
|
98
167
|
|
|
168
|
+
Chain names are case-insensitive — `Polkadot.System.Account`, `POLKADOT.System.Account`, and `polkadot.System.Account` all resolve the same way. The same applies to `--chain Polkadot` and `dot chain default Polkadot`.
|
|
169
|
+
|
|
99
170
|
The `--chain` flag and default chain still work as before. If both a chain prefix and `--chain` flag are provided, the CLI errors.
|
|
100
171
|
|
|
101
172
|
### Query storage
|
|
@@ -276,12 +347,24 @@ dot hash blake2b256 0xdeadbeef --output json
|
|
|
276
347
|
|
|
277
348
|
Run `dot hash` with no arguments to see all available algorithms.
|
|
278
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
|
+
|
|
279
361
|
### Global options
|
|
280
362
|
|
|
281
363
|
| Flag | Description |
|
|
282
364
|
|------|-------------|
|
|
365
|
+
| `--help` | Show help (global or command-specific) |
|
|
283
366
|
| `--chain <name>` | Target chain (default from config) |
|
|
284
|
-
| `--rpc <url>` | Override RPC endpoint for this call |
|
|
367
|
+
| `--rpc <url>` | Override RPC endpoint(s) for this call (repeat for fallback) |
|
|
285
368
|
| `--light-client` | Use Smoldot light client |
|
|
286
369
|
| `--output json` | Raw JSON output (default: pretty) |
|
|
287
370
|
| `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
|
|
@@ -314,13 +397,15 @@ After each command, the CLI checks whether a newer version is available on npm a
|
|
|
314
397
|
╰───────────────────────────────────────────────╯
|
|
315
398
|
```
|
|
316
399
|
|
|
317
|
-
The version check runs in the background on startup and caches the result for 24 hours in `~/.polkadot/update-check.json`.
|
|
400
|
+
The version check runs in the background on startup and caches the result for 24 hours in `~/.polkadot/update-check.json`. Before exiting, the CLI waits up to 500ms for the check to finish so the cache file is written — even for fast commands like `--help` and `--version`. Long-running commands (queries, transactions) are unaffected since the check completes well before they finish.
|
|
401
|
+
|
|
402
|
+
If the network is unreachable, the failed check is cached for 1 hour so subsequent runs don't incur the 500ms wait repeatedly.
|
|
318
403
|
|
|
319
404
|
The notification is automatically suppressed when:
|
|
320
405
|
|
|
321
406
|
- `DOT_NO_UPDATE_CHECK=1` is set
|
|
322
407
|
- `CI` environment variable is set (any value)
|
|
323
|
-
-
|
|
408
|
+
- stderr is not a TTY (e.g. piped output)
|
|
324
409
|
|
|
325
410
|
## Configuration
|
|
326
411
|
|
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,120 @@ 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
26
|
polkadot: {
|
|
24
|
-
rpc:
|
|
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
|
+
]
|
|
25
130
|
}
|
|
26
131
|
}
|
|
27
132
|
};
|
|
133
|
+
var BUILTIN_CHAIN_NAMES = new Set(Object.keys(DEFAULT_CONFIG.chains));
|
|
28
134
|
|
|
29
135
|
// src/config/store.ts
|
|
30
136
|
var DOT_DIR = join(homedir(), ".polkadot");
|
|
@@ -53,8 +159,11 @@ async function fileExists(path) {
|
|
|
53
159
|
async function loadConfig() {
|
|
54
160
|
await ensureDir(DOT_DIR);
|
|
55
161
|
if (await fileExists(CONFIG_PATH)) {
|
|
56
|
-
const
|
|
57
|
-
return
|
|
162
|
+
const saved = JSON.parse(await readFile(CONFIG_PATH, "utf-8"));
|
|
163
|
+
return {
|
|
164
|
+
...saved,
|
|
165
|
+
chains: { ...DEFAULT_CONFIG.chains, ...saved.chains }
|
|
166
|
+
};
|
|
58
167
|
}
|
|
59
168
|
await saveConfig(DEFAULT_CONFIG);
|
|
60
169
|
return DEFAULT_CONFIG;
|
|
@@ -80,14 +189,19 @@ async function removeChainData(chainName) {
|
|
|
80
189
|
const dir = getChainDir(chainName);
|
|
81
190
|
await rm(dir, { recursive: true, force: true });
|
|
82
191
|
}
|
|
192
|
+
function findChainName(config, input) {
|
|
193
|
+
if (config.chains[input])
|
|
194
|
+
return input;
|
|
195
|
+
return Object.keys(config.chains).find((k) => k.toLowerCase() === input.toLowerCase());
|
|
196
|
+
}
|
|
83
197
|
function resolveChain(config, chainFlag) {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
198
|
+
const input = chainFlag ?? config.defaultChain;
|
|
199
|
+
const name = findChainName(config, input);
|
|
200
|
+
if (!name) {
|
|
87
201
|
const available = Object.keys(config.chains).join(", ");
|
|
88
|
-
throw new Error(`Unknown chain "${
|
|
202
|
+
throw new Error(`Unknown chain "${input}". Available chains: ${available}`);
|
|
89
203
|
}
|
|
90
|
-
return { name, chain };
|
|
204
|
+
return { name, chain: config.chains[name] };
|
|
91
205
|
}
|
|
92
206
|
|
|
93
207
|
// src/config/accounts-store.ts
|
|
@@ -166,24 +280,24 @@ function getDevAddress(name, prefix = 42) {
|
|
|
166
280
|
const keypair = getDevKeypair(name);
|
|
167
281
|
return ss58Address(keypair.publicKey, prefix);
|
|
168
282
|
}
|
|
169
|
-
function createNewAccount() {
|
|
283
|
+
function createNewAccount(path = "") {
|
|
170
284
|
const mnemonic = generateMnemonic();
|
|
171
285
|
const entropy = mnemonicToEntropy(mnemonic);
|
|
172
286
|
const miniSecret = entropyToMiniSecret(entropy);
|
|
173
287
|
const derive = sr25519CreateDerive(miniSecret);
|
|
174
|
-
const keypair = derive(
|
|
288
|
+
const keypair = derive(path);
|
|
175
289
|
return { mnemonic, publicKey: keypair.publicKey };
|
|
176
290
|
}
|
|
177
|
-
function importAccount(secret) {
|
|
291
|
+
function importAccount(secret, path = "") {
|
|
178
292
|
const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
|
|
179
293
|
if (isHexSeed) {
|
|
180
|
-
const keypair2 = deriveFromHexSeed(secret,
|
|
294
|
+
const keypair2 = deriveFromHexSeed(secret, path);
|
|
181
295
|
return { publicKey: keypair2.publicKey };
|
|
182
296
|
}
|
|
183
297
|
if (!validateMnemonic(secret)) {
|
|
184
298
|
throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
|
|
185
299
|
}
|
|
186
|
-
const keypair = deriveFromMnemonic(secret,
|
|
300
|
+
const keypair = deriveFromMnemonic(secret, path);
|
|
187
301
|
return { publicKey: keypair.publicKey };
|
|
188
302
|
}
|
|
189
303
|
function publicKeyToHex(publicKey) {
|
|
@@ -210,12 +324,12 @@ function resolveSecret(secret) {
|
|
|
210
324
|
}
|
|
211
325
|
return secret;
|
|
212
326
|
}
|
|
213
|
-
function tryDerivePublicKey(envVarName) {
|
|
327
|
+
function tryDerivePublicKey(envVarName, path = "") {
|
|
214
328
|
const value = process.env[envVarName];
|
|
215
329
|
if (!value)
|
|
216
330
|
return null;
|
|
217
331
|
try {
|
|
218
|
-
const { publicKey } = importAccount(value);
|
|
332
|
+
const { publicKey } = importAccount(value, path);
|
|
219
333
|
return publicKeyToHex(publicKey);
|
|
220
334
|
} catch {
|
|
221
335
|
return null;
|
|
@@ -334,39 +448,49 @@ class Spinner {
|
|
|
334
448
|
// src/commands/account.ts
|
|
335
449
|
var ACCOUNT_HELP = `
|
|
336
450
|
${BOLD}Usage:${RESET}
|
|
337
|
-
$ dot account create <name>
|
|
338
|
-
$ dot account import <name> --secret <s>
|
|
339
|
-
$ dot account add <name> --env <VAR>
|
|
340
|
-
$ dot account
|
|
341
|
-
$ 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)
|
|
342
457
|
|
|
343
458
|
${BOLD}Examples:${RESET}
|
|
344
459
|
$ dot account create my-validator
|
|
460
|
+
$ dot account create my-staking --path //staking
|
|
461
|
+
$ dot account create multi --path //polkadot//0/wallet
|
|
345
462
|
$ dot account import treasury --secret "word1 word2 ... word12"
|
|
346
|
-
$ dot account
|
|
463
|
+
$ dot account import ci-signer --env MY_SECRET --path //ci
|
|
464
|
+
$ dot account derive treasury treasury-staking --path //staking
|
|
347
465
|
$ dot account list
|
|
348
|
-
$ dot account remove my-validator
|
|
466
|
+
$ dot account remove my-validator stale-key
|
|
349
467
|
|
|
350
468
|
${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
|
|
351
469
|
Use --env to keep secrets off disk entirely.
|
|
352
470
|
Hex seed import (0x...) is not supported via CLI.${RESET}
|
|
353
471
|
`.trimStart();
|
|
354
472
|
function registerAccountCommands(cli) {
|
|
355
|
-
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) => {
|
|
356
474
|
if (!action) {
|
|
357
|
-
|
|
475
|
+
if (process.argv[2] === "accounts")
|
|
476
|
+
return accountList();
|
|
477
|
+
console.log(ACCOUNT_HELP);
|
|
478
|
+
return;
|
|
358
479
|
}
|
|
359
480
|
switch (action) {
|
|
481
|
+
case "new":
|
|
360
482
|
case "create":
|
|
361
|
-
return accountCreate(
|
|
483
|
+
return accountCreate(names[0], opts);
|
|
362
484
|
case "import":
|
|
363
|
-
return accountImport(name, opts);
|
|
364
485
|
case "add":
|
|
365
|
-
return
|
|
486
|
+
return accountImport(names[0], opts);
|
|
487
|
+
case "derive":
|
|
488
|
+
return accountDerive(names[0], names[1], opts);
|
|
366
489
|
case "list":
|
|
367
490
|
return accountList();
|
|
491
|
+
case "delete":
|
|
368
492
|
case "remove":
|
|
369
|
-
return accountRemove(
|
|
493
|
+
return accountRemove(names);
|
|
370
494
|
default:
|
|
371
495
|
console.error(`Unknown action "${action}".
|
|
372
496
|
`);
|
|
@@ -375,7 +499,7 @@ function registerAccountCommands(cli) {
|
|
|
375
499
|
}
|
|
376
500
|
});
|
|
377
501
|
}
|
|
378
|
-
async function accountCreate(name) {
|
|
502
|
+
async function accountCreate(name, opts) {
|
|
379
503
|
if (!name) {
|
|
380
504
|
console.error(`Account name is required.
|
|
381
505
|
`);
|
|
@@ -389,18 +513,21 @@ async function accountCreate(name) {
|
|
|
389
513
|
if (findAccount(accountsFile, name)) {
|
|
390
514
|
throw new Error(`Account "${name}" already exists.`);
|
|
391
515
|
}
|
|
392
|
-
const
|
|
516
|
+
const path = opts.path ?? "";
|
|
517
|
+
const { mnemonic, publicKey } = createNewAccount(path);
|
|
393
518
|
const hexPub = publicKeyToHex(publicKey);
|
|
394
519
|
const address = toSs58(publicKey);
|
|
395
520
|
accountsFile.accounts.push({
|
|
396
521
|
name,
|
|
397
522
|
secret: mnemonic,
|
|
398
523
|
publicKey: hexPub,
|
|
399
|
-
derivationPath:
|
|
524
|
+
derivationPath: path
|
|
400
525
|
});
|
|
401
526
|
await saveAccounts(accountsFile);
|
|
402
527
|
printHeading("Account Created");
|
|
403
528
|
console.log(` ${BOLD}Name:${RESET} ${name}`);
|
|
529
|
+
if (path)
|
|
530
|
+
console.log(` ${BOLD}Path:${RESET} ${path}`);
|
|
404
531
|
console.log(` ${BOLD}Address:${RESET} ${address}`);
|
|
405
532
|
console.log(` ${BOLD}Mnemonic:${RESET} ${mnemonic}`);
|
|
406
533
|
console.log();
|
|
@@ -414,10 +541,16 @@ async function accountImport(name, opts) {
|
|
|
414
541
|
console.error('Usage: dot account import <name> --secret "mnemonic or hex seed"');
|
|
415
542
|
process.exit(1);
|
|
416
543
|
}
|
|
417
|
-
if (
|
|
418
|
-
console.error(
|
|
544
|
+
if (opts.secret && opts.env) {
|
|
545
|
+
console.error(`Use --secret or --env, not both.
|
|
546
|
+
`);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
if (!opts.secret && !opts.env) {
|
|
550
|
+
console.error(`--secret or --env is required.
|
|
419
551
|
`);
|
|
420
552
|
console.error('Usage: dot account import <name> --secret "mnemonic or hex seed"');
|
|
553
|
+
console.error(" dot account import <name> --env <VAR>");
|
|
421
554
|
process.exit(1);
|
|
422
555
|
}
|
|
423
556
|
if (isDevAccount(name)) {
|
|
@@ -427,58 +560,115 @@ async function accountImport(name, opts) {
|
|
|
427
560
|
if (findAccount(accountsFile, name)) {
|
|
428
561
|
throw new Error(`Account "${name}" already exists.`);
|
|
429
562
|
}
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
563
|
+
const path = opts.path ?? "";
|
|
564
|
+
if (opts.env) {
|
|
565
|
+
const publicKey = tryDerivePublicKey(opts.env, path) ?? "";
|
|
566
|
+
accountsFile.accounts.push({
|
|
567
|
+
name,
|
|
568
|
+
secret: { env: opts.env },
|
|
569
|
+
publicKey,
|
|
570
|
+
derivationPath: path
|
|
571
|
+
});
|
|
572
|
+
await saveAccounts(accountsFile);
|
|
573
|
+
printHeading("Account Imported");
|
|
574
|
+
console.log(` ${BOLD}Name:${RESET} ${name}`);
|
|
575
|
+
if (path)
|
|
576
|
+
console.log(` ${BOLD}Path:${RESET} ${path}`);
|
|
577
|
+
console.log(` ${BOLD}Env:${RESET} ${opts.env}`);
|
|
578
|
+
if (publicKey) {
|
|
579
|
+
console.log(` ${BOLD}Address:${RESET} ${toSs58(publicKey)}`);
|
|
580
|
+
} else {
|
|
581
|
+
console.log(` ${YELLOW}Address will resolve when $${opts.env} is set.${RESET}`);
|
|
582
|
+
}
|
|
583
|
+
console.log();
|
|
584
|
+
} else {
|
|
585
|
+
const { publicKey } = importAccount(opts.secret, path);
|
|
586
|
+
const hexPub = publicKeyToHex(publicKey);
|
|
587
|
+
const address = toSs58(publicKey);
|
|
588
|
+
accountsFile.accounts.push({
|
|
589
|
+
name,
|
|
590
|
+
secret: opts.secret,
|
|
591
|
+
publicKey: hexPub,
|
|
592
|
+
derivationPath: path
|
|
593
|
+
});
|
|
594
|
+
await saveAccounts(accountsFile);
|
|
595
|
+
printHeading("Account Imported");
|
|
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.
|
|
448
606
|
`);
|
|
449
|
-
console.error("Usage: dot account
|
|
607
|
+
console.error("Usage: dot account derive <source> <new-name> --path <derivation>");
|
|
450
608
|
process.exit(1);
|
|
451
609
|
}
|
|
452
|
-
if (!
|
|
453
|
-
console.error(
|
|
610
|
+
if (!newName) {
|
|
611
|
+
console.error(`New account name is required.
|
|
454
612
|
`);
|
|
455
|
-
console.error("Usage: dot account
|
|
613
|
+
console.error("Usage: dot account derive <source> <new-name> --path <derivation>");
|
|
456
614
|
process.exit(1);
|
|
457
615
|
}
|
|
458
|
-
if (
|
|
459
|
-
|
|
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);
|
|
460
621
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
throw new Error(`Account "${name}" already exists.`);
|
|
622
|
+
if (isDevAccount(newName)) {
|
|
623
|
+
throw new Error(`"${newName}" is a built-in dev account and cannot be used as a custom account name.`);
|
|
464
624
|
}
|
|
465
|
-
const
|
|
466
|
-
accountsFile
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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();
|
|
478
654
|
} else {
|
|
479
|
-
|
|
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}`);
|
|
669
|
+
console.log(` ${BOLD}Address:${RESET} ${address}`);
|
|
670
|
+
console.log();
|
|
480
671
|
}
|
|
481
|
-
console.log();
|
|
482
672
|
}
|
|
483
673
|
async function accountList() {
|
|
484
674
|
printHeading("Dev Accounts");
|
|
@@ -492,12 +682,15 @@ async function accountList() {
|
|
|
492
682
|
printHeading("Stored Accounts");
|
|
493
683
|
for (const account of accountsFile.accounts) {
|
|
494
684
|
let displayName = account.name;
|
|
685
|
+
if (account.derivationPath) {
|
|
686
|
+
displayName += ` (${account.derivationPath})`;
|
|
687
|
+
}
|
|
495
688
|
let address;
|
|
496
689
|
if (isEnvSecret(account.secret)) {
|
|
497
690
|
displayName += ` (env: ${account.secret.env})`;
|
|
498
691
|
let pubKey = account.publicKey;
|
|
499
692
|
if (!pubKey) {
|
|
500
|
-
pubKey = tryDerivePublicKey(account.secret.env) ?? "";
|
|
693
|
+
pubKey = tryDerivePublicKey(account.secret.env, account.derivationPath) ?? "";
|
|
501
694
|
}
|
|
502
695
|
address = pubKey ? toSs58(pubKey) : "n/a";
|
|
503
696
|
} else {
|
|
@@ -511,24 +704,44 @@ async function accountList() {
|
|
|
511
704
|
}
|
|
512
705
|
console.log();
|
|
513
706
|
}
|
|
514
|
-
async function accountRemove(
|
|
515
|
-
if (
|
|
516
|
-
console.error(`
|
|
707
|
+
async function accountRemove(names) {
|
|
708
|
+
if (names.length === 0) {
|
|
709
|
+
console.error(`At least one account name is required.
|
|
517
710
|
`);
|
|
518
|
-
console.error("Usage: dot account remove <name>");
|
|
711
|
+
console.error("Usage: dot account remove <name> [name2] ...");
|
|
519
712
|
process.exit(1);
|
|
520
713
|
}
|
|
521
|
-
|
|
522
|
-
|
|
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
|
+
`));
|
|
523
723
|
}
|
|
524
724
|
const accountsFile = await loadAccounts();
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
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);
|
|
528
740
|
}
|
|
529
|
-
accountsFile.accounts.splice(idx, 1);
|
|
530
741
|
await saveAccounts(accountsFile);
|
|
531
|
-
|
|
742
|
+
for (const name of names) {
|
|
743
|
+
console.log(`Account "${name}" removed.`);
|
|
744
|
+
}
|
|
532
745
|
}
|
|
533
746
|
|
|
534
747
|
// src/core/client.ts
|
|
@@ -560,10 +773,18 @@ class MetadataError extends CliError {
|
|
|
560
773
|
|
|
561
774
|
// src/core/client.ts
|
|
562
775
|
var KNOWN_CHAIN_SPECS = {
|
|
563
|
-
polkadot: "polkadot-api/chains/polkadot",
|
|
564
|
-
kusama: "polkadot-api/chains/ksmcc3",
|
|
565
|
-
westend: "polkadot-api/chains/westend2",
|
|
566
|
-
paseo: "polkadot-api/chains/paseo"
|
|
776
|
+
polkadot: { spec: "polkadot-api/chains/polkadot" },
|
|
777
|
+
kusama: { spec: "polkadot-api/chains/ksmcc3" },
|
|
778
|
+
westend: { spec: "polkadot-api/chains/westend2" },
|
|
779
|
+
paseo: { spec: "polkadot-api/chains/paseo" },
|
|
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" },
|
|
784
|
+
"polkadot-people": { spec: "polkadot-api/chains/polkadot_people", relay: "polkadot" },
|
|
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" }
|
|
567
788
|
};
|
|
568
789
|
function suppressWsNoise() {
|
|
569
790
|
const orig = console.error;
|
|
@@ -607,12 +828,22 @@ async function createChainClient(chainName, chainConfig, rpcOverride) {
|
|
|
607
828
|
async function createSmoldotProvider(chainName) {
|
|
608
829
|
const { start } = await import("polkadot-api/smoldot");
|
|
609
830
|
const { getSmProvider } = await import("polkadot-api/sm-provider");
|
|
610
|
-
const
|
|
611
|
-
if (!
|
|
831
|
+
const entry = KNOWN_CHAIN_SPECS[chainName];
|
|
832
|
+
if (!entry) {
|
|
612
833
|
throw new ConnectionError(`Light client is only supported for known chains: ${Object.keys(KNOWN_CHAIN_SPECS).join(", ")}. Use --rpc to connect to "${chainName}" instead.`);
|
|
613
834
|
}
|
|
614
|
-
const { chainSpec } = await import(
|
|
835
|
+
const { chainSpec } = await import(entry.spec);
|
|
615
836
|
const smoldot = start();
|
|
837
|
+
if (entry.relay) {
|
|
838
|
+
const relayEntry = KNOWN_CHAIN_SPECS[entry.relay];
|
|
839
|
+
if (!relayEntry) {
|
|
840
|
+
throw new ConnectionError(`Relay chain "${entry.relay}" not found in known chain specs.`);
|
|
841
|
+
}
|
|
842
|
+
const { chainSpec: relaySpec } = await import(relayEntry.spec);
|
|
843
|
+
const relayChain = await smoldot.addChain({ chainSpec: relaySpec, disableJsonRpc: true });
|
|
844
|
+
const chain2 = await smoldot.addChain({ chainSpec, potentialRelayChains: [relayChain] });
|
|
845
|
+
return getSmProvider(chain2);
|
|
846
|
+
}
|
|
616
847
|
const chain = await smoldot.addChain({ chainSpec });
|
|
617
848
|
return getSmProvider(chain);
|
|
618
849
|
}
|
|
@@ -774,6 +1005,7 @@ ${BOLD}Usage:${RESET}
|
|
|
774
1005
|
|
|
775
1006
|
${BOLD}Examples:${RESET}
|
|
776
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
|
|
777
1009
|
$ dot chain add westend --light-client
|
|
778
1010
|
$ dot chain default kusama
|
|
779
1011
|
$ dot chain list
|
|
@@ -784,7 +1016,10 @@ ${BOLD}Examples:${RESET}
|
|
|
784
1016
|
function registerChainCommands(cli) {
|
|
785
1017
|
cli.command("chain [action] [name]", "Manage chains (add, remove, update, list, default)").alias("chains").action(async (action, name, opts) => {
|
|
786
1018
|
if (!action) {
|
|
787
|
-
|
|
1019
|
+
if (process.argv[2] === "chains")
|
|
1020
|
+
return chainList();
|
|
1021
|
+
console.log(CHAIN_HELP);
|
|
1022
|
+
return;
|
|
788
1023
|
}
|
|
789
1024
|
switch (action) {
|
|
790
1025
|
case "add":
|
|
@@ -843,20 +1078,21 @@ async function chainRemove(name) {
|
|
|
843
1078
|
process.exit(1);
|
|
844
1079
|
}
|
|
845
1080
|
const config = await loadConfig();
|
|
846
|
-
|
|
1081
|
+
const resolved = findChainName(config, name);
|
|
1082
|
+
if (!resolved) {
|
|
847
1083
|
throw new Error(`Chain "${name}" not found.`);
|
|
848
1084
|
}
|
|
849
|
-
if (
|
|
850
|
-
throw new Error(
|
|
1085
|
+
if (BUILTIN_CHAIN_NAMES.has(resolved)) {
|
|
1086
|
+
throw new Error(`Cannot remove the built-in "${resolved}" chain.`);
|
|
851
1087
|
}
|
|
852
|
-
delete config.chains[
|
|
853
|
-
if (config.defaultChain ===
|
|
1088
|
+
delete config.chains[resolved];
|
|
1089
|
+
if (config.defaultChain === resolved) {
|
|
854
1090
|
config.defaultChain = "polkadot";
|
|
855
1091
|
console.log(`Default chain reset to "polkadot".`);
|
|
856
1092
|
}
|
|
857
1093
|
await saveConfig(config);
|
|
858
|
-
await removeChainData(
|
|
859
|
-
console.log(`Chain "${
|
|
1094
|
+
await removeChainData(resolved);
|
|
1095
|
+
console.log(`Chain "${resolved}" removed.`);
|
|
860
1096
|
}
|
|
861
1097
|
async function chainList() {
|
|
862
1098
|
const config = await loadConfig();
|
|
@@ -864,8 +1100,15 @@ async function chainList() {
|
|
|
864
1100
|
for (const [name, chainConfig] of Object.entries(config.chains)) {
|
|
865
1101
|
const isDefault = name === config.defaultChain;
|
|
866
1102
|
const marker = isDefault ? ` ${BOLD}(default)${RESET}` : "";
|
|
867
|
-
|
|
868
|
-
|
|
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
|
+
}
|
|
869
1112
|
}
|
|
870
1113
|
console.log();
|
|
871
1114
|
}
|
|
@@ -888,13 +1131,14 @@ async function chainDefault(name) {
|
|
|
888
1131
|
process.exit(1);
|
|
889
1132
|
}
|
|
890
1133
|
const config = await loadConfig();
|
|
891
|
-
|
|
1134
|
+
const resolved = findChainName(config, name);
|
|
1135
|
+
if (!resolved) {
|
|
892
1136
|
const available = Object.keys(config.chains).join(", ");
|
|
893
1137
|
throw new Error(`Chain "${name}" not found. Available: ${available}`);
|
|
894
1138
|
}
|
|
895
|
-
config.defaultChain =
|
|
1139
|
+
config.defaultChain = resolved;
|
|
896
1140
|
await saveConfig(config);
|
|
897
|
-
console.log(`Default chain set to "${
|
|
1141
|
+
console.log(`Default chain set to "${resolved}".`);
|
|
898
1142
|
}
|
|
899
1143
|
|
|
900
1144
|
// src/utils/fuzzy-match.ts
|
|
@@ -1799,7 +2043,7 @@ function registerTxCommand(cli) {
|
|
|
1799
2043
|
}
|
|
1800
2044
|
}
|
|
1801
2045
|
}
|
|
1802
|
-
const rpcUrl = opts.rpc ?? chainConfig.rpc;
|
|
2046
|
+
const rpcUrl = primaryRpc(opts.rpc ?? chainConfig.rpc);
|
|
1803
2047
|
if (rpcUrl) {
|
|
1804
2048
|
const blockHash = result.block.hash;
|
|
1805
2049
|
console.log(` ${BOLD}Explorer:${RESET}`);
|
|
@@ -2339,7 +2583,10 @@ import { join as join3 } from "node:path";
|
|
|
2339
2583
|
var CACHE_FILE = "update-check.json";
|
|
2340
2584
|
var STALE_MS = 24 * 60 * 60 * 1000;
|
|
2341
2585
|
var FETCH_TIMEOUT_MS = 5000;
|
|
2586
|
+
var EXIT_WAIT_TIMEOUT_MS = 500;
|
|
2587
|
+
var RETRY_AFTER_FAILURE_MS = 60 * 60 * 1000;
|
|
2342
2588
|
var REGISTRY_URL = "https://registry.npmjs.org/polkadot-cli/latest";
|
|
2589
|
+
var pendingCheck = null;
|
|
2343
2590
|
function parseSemver(v) {
|
|
2344
2591
|
const clean = v.replace(/^v/, "").split("-")[0] ?? v;
|
|
2345
2592
|
const parts = clean.split(".").map(Number);
|
|
@@ -2405,20 +2652,32 @@ async function writeCache(cache) {
|
|
|
2405
2652
|
`);
|
|
2406
2653
|
} catch {}
|
|
2407
2654
|
}
|
|
2408
|
-
function startBackgroundCheck(
|
|
2655
|
+
function startBackgroundCheck(currentVersion) {
|
|
2409
2656
|
const cache = readCache();
|
|
2410
2657
|
const now = Date.now();
|
|
2411
2658
|
if (cache && now - cache.lastCheck < STALE_MS) {
|
|
2412
2659
|
return;
|
|
2413
2660
|
}
|
|
2414
|
-
fetch(REGISTRY_URL, {
|
|
2661
|
+
pendingCheck = fetch(REGISTRY_URL, {
|
|
2415
2662
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
2416
2663
|
}).then((res) => res.json()).then((data) => {
|
|
2417
2664
|
const latestVersion = data.version;
|
|
2418
2665
|
if (typeof latestVersion === "string") {
|
|
2419
|
-
writeCache({ lastCheck: now, latestVersion });
|
|
2666
|
+
return writeCache({ lastCheck: now, latestVersion });
|
|
2420
2667
|
}
|
|
2421
|
-
}).catch(() => {
|
|
2668
|
+
}).catch(() => {
|
|
2669
|
+
return writeCache({
|
|
2670
|
+
lastCheck: now - STALE_MS + RETRY_AFTER_FAILURE_MS,
|
|
2671
|
+
latestVersion: currentVersion
|
|
2672
|
+
});
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
async function waitForPendingCheck() {
|
|
2676
|
+
if (!pendingCheck)
|
|
2677
|
+
return;
|
|
2678
|
+
const timeout = new Promise((resolve) => setTimeout(resolve, EXIT_WAIT_TIMEOUT_MS));
|
|
2679
|
+
await Promise.race([pendingCheck.catch(() => {}), timeout]);
|
|
2680
|
+
pendingCheck = null;
|
|
2422
2681
|
}
|
|
2423
2682
|
function getUpdateNotification(currentVersion) {
|
|
2424
2683
|
if (process.env.DOT_NO_UPDATE_CHECK === "1")
|
|
@@ -2460,16 +2719,17 @@ registerConstCommand(cli);
|
|
|
2460
2719
|
registerAccountCommands(cli);
|
|
2461
2720
|
registerTxCommand(cli);
|
|
2462
2721
|
registerHashCommand(cli);
|
|
2463
|
-
cli.help
|
|
2722
|
+
cli.option("--help, -h", "Display this message");
|
|
2464
2723
|
cli.version(version);
|
|
2465
|
-
function showUpdateAndExit(code) {
|
|
2724
|
+
async function showUpdateAndExit(code) {
|
|
2725
|
+
await waitForPendingCheck();
|
|
2466
2726
|
const note = getUpdateNotification(version);
|
|
2467
2727
|
if (note)
|
|
2468
2728
|
process.stderr.write(`${note}
|
|
2469
2729
|
`);
|
|
2470
2730
|
process.exit(code);
|
|
2471
2731
|
}
|
|
2472
|
-
function handleError(err) {
|
|
2732
|
+
async function handleError(err) {
|
|
2473
2733
|
if (err instanceof CliError2) {
|
|
2474
2734
|
console.error(`Error: ${err.message}`);
|
|
2475
2735
|
} else if (err instanceof Error) {
|
|
@@ -2477,21 +2737,34 @@ function handleError(err) {
|
|
|
2477
2737
|
} else {
|
|
2478
2738
|
console.error("An unexpected error occurred:", err);
|
|
2479
2739
|
}
|
|
2480
|
-
showUpdateAndExit(1);
|
|
2740
|
+
return showUpdateAndExit(1);
|
|
2481
2741
|
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
cli.
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2742
|
+
async function main() {
|
|
2743
|
+
try {
|
|
2744
|
+
cli.parse(process.argv, { run: false });
|
|
2745
|
+
if (cli.options.version) {
|
|
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
|
+
}
|
|
2757
|
+
} else if (!cli.matchedCommandName) {
|
|
2758
|
+
cli.outputHelp();
|
|
2759
|
+
await showUpdateAndExit(0);
|
|
2760
|
+
} else {
|
|
2761
|
+
const result = cli.runMatchedCommand();
|
|
2762
|
+
if (result && typeof result.then === "function") {
|
|
2763
|
+
await result.then(() => showUpdateAndExit(0), handleError);
|
|
2764
|
+
}
|
|
2493
2765
|
}
|
|
2766
|
+
} catch (err) {
|
|
2767
|
+
await handleError(err);
|
|
2494
2768
|
}
|
|
2495
|
-
} catch (err) {
|
|
2496
|
-
handleError(err);
|
|
2497
2769
|
}
|
|
2770
|
+
main();
|