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.
- package/README.md +142 -7
- package/dist/cli.mjs +549 -58
- 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,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
|
|
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
|
|
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.
|
|
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: {
|
|
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));
|
|
@@ -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>
|
|
349
|
-
$ dot account import|add <name> --secret <s>
|
|
350
|
-
$ dot account import|add <name> --env <VAR>
|
|
351
|
-
$ dot account
|
|
352
|
-
$ dot 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] [
|
|
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(
|
|
495
|
+
return accountCreate(names[0], opts);
|
|
375
496
|
case "import":
|
|
376
497
|
case "add":
|
|
377
|
-
return accountImport(
|
|
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(
|
|
505
|
+
return accountRemove(names);
|
|
506
|
+
case "inspect":
|
|
507
|
+
return accountInspect(names[0], opts);
|
|
383
508
|
default:
|
|
384
|
-
|
|
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
|
|
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(
|
|
516
|
-
if (
|
|
517
|
-
console.error(`
|
|
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
|
-
|
|
523
|
-
|
|
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
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
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
|
-
|
|
884
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
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);
|