polkadot-cli 0.12.0 → 0.14.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 +123 -5
- package/dist/cli.mjs +711 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,6 +103,45 @@ MY_SECRET="word1 word2 ..." dot tx System.remark 0xdead --from ci-signer
|
|
|
103
103
|
# Remove one or more accounts
|
|
104
104
|
dot account remove my-validator
|
|
105
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"}
|
|
106
145
|
```
|
|
107
146
|
|
|
108
147
|
#### Env-var-backed accounts
|
|
@@ -181,7 +220,7 @@ dot query System.Account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
|
|
|
181
220
|
# All map entries (default limit: 100)
|
|
182
221
|
dot query System.Account --limit 10
|
|
183
222
|
|
|
184
|
-
# Pipe
|
|
223
|
+
# Pipe-safe — stdout is clean data, progress messages go to stderr
|
|
185
224
|
dot query System.Account --limit 5 | jq '.[0].value.data.free'
|
|
186
225
|
dot query System.Number --output json | jq '.+1'
|
|
187
226
|
|
|
@@ -210,7 +249,7 @@ dot const Balances.ExistentialDeposit
|
|
|
210
249
|
dot const System.SS58Prefix --chain kusama
|
|
211
250
|
dot const kusama.Balances.ExistentialDeposit
|
|
212
251
|
|
|
213
|
-
# Pipe
|
|
252
|
+
# Pipe-safe — stdout is clean JSON, progress messages go to stderr
|
|
214
253
|
dot const Balances.ExistentialDeposit --output json | jq
|
|
215
254
|
```
|
|
216
255
|
|
|
@@ -219,20 +258,70 @@ dot const Balances.ExistentialDeposit --output json | jq
|
|
|
219
258
|
Works offline from cached metadata after the first fetch.
|
|
220
259
|
|
|
221
260
|
```bash
|
|
222
|
-
# List all pallets
|
|
261
|
+
# List all pallets (shows storage, constants, calls, events, and errors counts)
|
|
223
262
|
dot inspect
|
|
224
263
|
|
|
225
|
-
# List a pallet's storage items and
|
|
264
|
+
# List a pallet's storage items, constants, calls, events, and errors
|
|
226
265
|
dot inspect System
|
|
227
266
|
|
|
228
|
-
# Detailed type info for a specific item
|
|
267
|
+
# Detailed type info for a specific storage item or constant
|
|
229
268
|
dot inspect System.Account
|
|
230
269
|
|
|
270
|
+
# Call detail — shows argument signature and docs
|
|
271
|
+
dot inspect Balances.transfer_allow_death
|
|
272
|
+
|
|
273
|
+
# Event detail — shows field signature and docs
|
|
274
|
+
dot inspect Balances.Transfer
|
|
275
|
+
|
|
276
|
+
# Error detail — shows docs
|
|
277
|
+
dot inspect Balances.InsufficientBalance
|
|
278
|
+
|
|
231
279
|
# Inspect a specific chain using chain prefix
|
|
232
280
|
dot inspect kusama.System
|
|
233
281
|
dot inspect kusama.System.Account
|
|
234
282
|
```
|
|
235
283
|
|
|
284
|
+
The pallet listing view shows type information inline so you can understand item shapes at a glance:
|
|
285
|
+
|
|
286
|
+
- **Storage**: key/value types with `[map]` tag for map items (e.g. `Account: AccountId32 → { nonce: u32, ... } [map]`)
|
|
287
|
+
- **Constants**: the constant's type (e.g. `ExistentialDeposit: u128`)
|
|
288
|
+
- **Calls**: full argument signature (e.g. `transfer_allow_death(dest: enum(5 variants), value: Compact<u128>)`)
|
|
289
|
+
- **Events**: field signature (e.g. `Transfer(from: AccountId32, to: AccountId32, amount: u128)`)
|
|
290
|
+
- **Errors**: name and documentation (e.g. `InsufficientBalance`)
|
|
291
|
+
|
|
292
|
+
Documentation from the runtime metadata is shown on an indented line below each item. The detail view (`dot inspect Balances.transfer_allow_death`) shows the full argument signature and complete documentation text. Use call inspection to discover argument names, types, and docs before constructing `dot tx` commands.
|
|
293
|
+
|
|
294
|
+
### Focused commands
|
|
295
|
+
|
|
296
|
+
Browse specific metadata categories directly without using `dot inspect`:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# List all pallets
|
|
300
|
+
dot pallets
|
|
301
|
+
|
|
302
|
+
# List pallet calls with argument signatures
|
|
303
|
+
dot calls Balances
|
|
304
|
+
dot calls Balances.transfer_allow_death # call detail
|
|
305
|
+
|
|
306
|
+
# List pallet events with field signatures
|
|
307
|
+
dot events Balances
|
|
308
|
+
dot events Balances.Transfer # event detail
|
|
309
|
+
|
|
310
|
+
# List pallet errors
|
|
311
|
+
dot errors Balances
|
|
312
|
+
dot errors Balances.InsufficientBalance # error detail
|
|
313
|
+
|
|
314
|
+
# List pallet storage items with types
|
|
315
|
+
dot storage System
|
|
316
|
+
dot storage System.Account # storage detail
|
|
317
|
+
|
|
318
|
+
# List pallet constants (dual-purpose — also works as value lookup)
|
|
319
|
+
dot const Balances # list constants
|
|
320
|
+
dot const Balances.ExistentialDeposit # look up value
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Each command supports `--chain <name>`, `--rpc <url>`, and chain prefix syntax. Singular and plural forms are interchangeable (e.g. `dot call` = `dot calls`, `dot event` = `dot events`).
|
|
324
|
+
|
|
236
325
|
### Submit extrinsics
|
|
237
326
|
|
|
238
327
|
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.
|
|
@@ -299,6 +388,12 @@ Both dry-run and submission display the encoded call hex and a decoded human-rea
|
|
|
299
388
|
Status: ok
|
|
300
389
|
```
|
|
301
390
|
|
|
391
|
+
Complex calls (e.g. XCM teleports) that the primary decoder cannot handle are automatically decoded via a fallback path:
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
Decode: PolkadotXcm.limited_teleport_assets { dest: V3 { parents: 1, interior: X1(Parachain(5140)) }, beneficiary: V3 { ... }, assets: V3 [...], fee_asset_item: 0, weight_limit: Unlimited }
|
|
395
|
+
```
|
|
396
|
+
|
|
302
397
|
#### Exit codes
|
|
303
398
|
|
|
304
399
|
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`).
|
|
@@ -310,6 +405,18 @@ dot tx Balances.transferKeepAlive 5FHneW46... 999999999999999999 --from alice
|
|
|
310
405
|
echo $? # 1
|
|
311
406
|
```
|
|
312
407
|
|
|
408
|
+
#### Argument parsing errors
|
|
409
|
+
|
|
410
|
+
When a call argument is invalid, the CLI shows a contextual error message with the argument name, the expected type, and a hint:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
dot tx Balances.transferKeepAlive 5GrwvaEF... abc --encode
|
|
414
|
+
# Error: Invalid value for argument 'value' (expected Compact<u128>): "abc"
|
|
415
|
+
# Hint: Compact<u128>
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
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.
|
|
419
|
+
|
|
313
420
|
#### Custom signed extensions
|
|
314
421
|
|
|
315
422
|
Chains with non-standard signed extensions (e.g. `people-preview`) are auto-handled:
|
|
@@ -369,6 +476,17 @@ dot hash --help # same as `dot hash` — shows algorithms and examples
|
|
|
369
476
|
| `--output json` | Raw JSON output (default: pretty) |
|
|
370
477
|
| `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
|
|
371
478
|
|
|
479
|
+
### Pipe-safe output
|
|
480
|
+
|
|
481
|
+
All commands follow Unix conventions: **data goes to stdout, progress goes to stderr**. This means you can safely pipe `--output json` into `jq` or other tools without progress messages ("Fetching metadata...", spinner output, "Connecting...") corrupting the data stream:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
dot const System.SS58Prefix --output json | jq '.+1'
|
|
485
|
+
dot query System.Number --output json | jq
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
In an interactive terminal, both streams render together so you see progress and results normally.
|
|
489
|
+
|
|
372
490
|
## How it compares
|
|
373
491
|
|
|
374
492
|
| | polkadot-cli | @polkadot/api-cli | subxt-cli | Pop CLI |
|
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.14.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";
|
|
@@ -247,6 +247,7 @@ import {
|
|
|
247
247
|
generateMnemonic,
|
|
248
248
|
mnemonicToEntropy,
|
|
249
249
|
ss58Address,
|
|
250
|
+
ss58Decode,
|
|
250
251
|
validateMnemonic
|
|
251
252
|
} from "@polkadot-labs/hdkd-helpers";
|
|
252
253
|
import { getPolkadotSigner } from "polkadot-api/signer";
|
|
@@ -314,6 +315,13 @@ function toSs58(publicKey, prefix = 42) {
|
|
|
314
315
|
}
|
|
315
316
|
return ss58Address(publicKey, prefix);
|
|
316
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
|
+
}
|
|
317
325
|
function resolveSecret(secret) {
|
|
318
326
|
if (isEnvSecret(secret)) {
|
|
319
327
|
const value = process.env[secret.env];
|
|
@@ -421,7 +429,7 @@ class Spinner {
|
|
|
421
429
|
start(msg) {
|
|
422
430
|
this.stop();
|
|
423
431
|
if (!isTTY) {
|
|
424
|
-
console.
|
|
432
|
+
console.error(msg);
|
|
425
433
|
return;
|
|
426
434
|
}
|
|
427
435
|
process.stdout.write(`${SPINNER_FRAMES[0]} ${msg}`);
|
|
@@ -441,9 +449,16 @@ class Spinner {
|
|
|
441
449
|
}
|
|
442
450
|
succeed(msg) {
|
|
443
451
|
this.stop();
|
|
444
|
-
console.
|
|
452
|
+
console.error(`${GREEN}${CHECK_MARK}${RESET} ${msg}`);
|
|
445
453
|
}
|
|
446
454
|
}
|
|
455
|
+
function firstSentence(docs) {
|
|
456
|
+
const text = docs.join(" ").trim();
|
|
457
|
+
if (!text)
|
|
458
|
+
return "";
|
|
459
|
+
const match = text.match(/^.*?(?<![ei]\.g|[ei]\.[eg])(?<!\betc)[.!?](?:\s|$)/);
|
|
460
|
+
return match ? match[0].trim() : text;
|
|
461
|
+
}
|
|
447
462
|
|
|
448
463
|
// src/commands/account.ts
|
|
449
464
|
var ACCOUNT_HELP = `
|
|
@@ -452,6 +467,7 @@ ${BOLD}Usage:${RESET}
|
|
|
452
467
|
$ dot account import|add <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic
|
|
453
468
|
$ dot account import|add <name> --env <VAR> [--path <derivation>] Import account backed by env variable
|
|
454
469
|
$ dot account derive <source> <new-name> --path <derivation> Derive a child account
|
|
470
|
+
$ dot account inspect <input> [--prefix <N>] Inspect an account/address/key
|
|
455
471
|
$ dot account list List all accounts
|
|
456
472
|
$ dot account remove|delete <name> [name2] ... Remove stored account(s)
|
|
457
473
|
|
|
@@ -462,6 +478,9 @@ ${BOLD}Examples:${RESET}
|
|
|
462
478
|
$ dot account import treasury --secret "word1 word2 ... word12"
|
|
463
479
|
$ dot account import ci-signer --env MY_SECRET --path //ci
|
|
464
480
|
$ dot account derive treasury treasury-staking --path //staking
|
|
481
|
+
$ dot account inspect alice
|
|
482
|
+
$ dot account inspect 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
|
|
483
|
+
$ dot account inspect 0xd435...a27d --prefix 0
|
|
465
484
|
$ dot account list
|
|
466
485
|
$ dot account remove my-validator stale-key
|
|
467
486
|
|
|
@@ -470,7 +489,7 @@ ${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
|
|
|
470
489
|
Hex seed import (0x...) is not supported via CLI.${RESET}
|
|
471
490
|
`.trimStart();
|
|
472
491
|
function registerAccountCommands(cli) {
|
|
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) => {
|
|
492
|
+
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) => {
|
|
474
493
|
if (!action) {
|
|
475
494
|
if (process.argv[2] === "accounts")
|
|
476
495
|
return accountList();
|
|
@@ -491,11 +510,10 @@ function registerAccountCommands(cli) {
|
|
|
491
510
|
case "delete":
|
|
492
511
|
case "remove":
|
|
493
512
|
return accountRemove(names);
|
|
513
|
+
case "inspect":
|
|
514
|
+
return accountInspect(names[0], opts);
|
|
494
515
|
default:
|
|
495
|
-
|
|
496
|
-
`);
|
|
497
|
-
console.log(ACCOUNT_HELP);
|
|
498
|
-
process.exit(1);
|
|
516
|
+
return accountInspect(action, opts);
|
|
499
517
|
}
|
|
500
518
|
});
|
|
501
519
|
}
|
|
@@ -743,6 +761,70 @@ async function accountRemove(names) {
|
|
|
743
761
|
console.log(`Account "${name}" removed.`);
|
|
744
762
|
}
|
|
745
763
|
}
|
|
764
|
+
async function accountInspect(input, opts) {
|
|
765
|
+
if (!input) {
|
|
766
|
+
console.error(`Input is required.
|
|
767
|
+
`);
|
|
768
|
+
console.error("Usage: dot account inspect <name|ss58-address|0x-public-key> [--prefix <N>]");
|
|
769
|
+
process.exit(1);
|
|
770
|
+
}
|
|
771
|
+
const prefix = opts.prefix != null ? Number(opts.prefix) : 42;
|
|
772
|
+
if (Number.isNaN(prefix) || prefix < 0) {
|
|
773
|
+
console.error(`Invalid prefix "${opts.prefix}". Must be a non-negative integer.`);
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
let name;
|
|
777
|
+
let publicKeyHex;
|
|
778
|
+
if (isDevAccount(input)) {
|
|
779
|
+
name = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
|
|
780
|
+
const devAddr = getDevAddress(input);
|
|
781
|
+
publicKeyHex = publicKeyToHex(fromSs58(devAddr));
|
|
782
|
+
} else {
|
|
783
|
+
const accountsFile = await loadAccounts();
|
|
784
|
+
const account = findAccount(accountsFile, input);
|
|
785
|
+
if (account) {
|
|
786
|
+
name = account.name;
|
|
787
|
+
if (account.publicKey) {
|
|
788
|
+
publicKeyHex = account.publicKey;
|
|
789
|
+
} else if (isEnvSecret(account.secret)) {
|
|
790
|
+
const derived = tryDerivePublicKey(account.secret.env, account.derivationPath);
|
|
791
|
+
if (!derived) {
|
|
792
|
+
console.error(`Cannot derive public key for "${account.name}": $${account.secret.env} is not set.`);
|
|
793
|
+
process.exit(1);
|
|
794
|
+
}
|
|
795
|
+
publicKeyHex = derived;
|
|
796
|
+
} else {
|
|
797
|
+
console.error(`Account "${account.name}" has no public key.`);
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
} else if (isHexPublicKey(input)) {
|
|
801
|
+
publicKeyHex = input;
|
|
802
|
+
} else {
|
|
803
|
+
try {
|
|
804
|
+
const decoded = fromSs58(input);
|
|
805
|
+
publicKeyHex = publicKeyToHex(decoded);
|
|
806
|
+
} catch {
|
|
807
|
+
console.error(`Cannot identify "${input}" as an account name, SS58 address, or hex public key.`);
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
const ss58 = toSs58(publicKeyHex, prefix);
|
|
813
|
+
if (opts.output === "json") {
|
|
814
|
+
const result = { publicKey: publicKeyHex, ss58, prefix };
|
|
815
|
+
if (name)
|
|
816
|
+
result.name = name;
|
|
817
|
+
console.log(formatJson(result));
|
|
818
|
+
} else {
|
|
819
|
+
printHeading("Account Info");
|
|
820
|
+
if (name)
|
|
821
|
+
console.log(` ${BOLD}Name:${RESET} ${name}`);
|
|
822
|
+
console.log(` ${BOLD}Public Key:${RESET} ${publicKeyHex}`);
|
|
823
|
+
console.log(` ${BOLD}SS58:${RESET} ${ss58}`);
|
|
824
|
+
console.log(` ${BOLD}Prefix:${RESET} ${prefix}`);
|
|
825
|
+
console.log();
|
|
826
|
+
}
|
|
827
|
+
}
|
|
746
828
|
|
|
747
829
|
// src/core/client.ts
|
|
748
830
|
import { createClient } from "polkadot-api";
|
|
@@ -902,26 +984,28 @@ function listPallets(meta) {
|
|
|
902
984
|
docs: c.docs ?? [],
|
|
903
985
|
typeId: c.type
|
|
904
986
|
})),
|
|
905
|
-
calls:
|
|
987
|
+
calls: extractEnumVariants(meta, p.calls),
|
|
988
|
+
events: extractEnumVariants(meta, p.events),
|
|
989
|
+
errors: extractEnumVariants(meta, p.errors).map(({ name, docs }) => ({ name, docs }))
|
|
906
990
|
}));
|
|
907
991
|
}
|
|
908
|
-
function
|
|
909
|
-
if (!
|
|
992
|
+
function extractEnumVariants(meta, ref) {
|
|
993
|
+
if (!ref)
|
|
910
994
|
return [];
|
|
911
995
|
try {
|
|
912
|
-
const entry = meta.lookup(
|
|
996
|
+
const entry = meta.lookup(ref.type);
|
|
913
997
|
if (entry.type !== "enum")
|
|
914
998
|
return [];
|
|
915
999
|
return Object.entries(entry.value).map(([name, variant]) => ({
|
|
916
1000
|
name,
|
|
917
|
-
docs:
|
|
918
|
-
typeId:
|
|
1001
|
+
docs: entry.innerDocs?.[name] ?? [],
|
|
1002
|
+
typeId: resolveVariantTypeId(variant)
|
|
919
1003
|
}));
|
|
920
1004
|
} catch {
|
|
921
1005
|
return [];
|
|
922
1006
|
}
|
|
923
1007
|
}
|
|
924
|
-
function
|
|
1008
|
+
function resolveVariantTypeId(variant) {
|
|
925
1009
|
if (variant.type === "lookupEntry")
|
|
926
1010
|
return variant.value?.id ?? null;
|
|
927
1011
|
if (variant.type === "struct")
|
|
@@ -984,6 +1068,78 @@ function formatLookupEntry(entry) {
|
|
|
984
1068
|
return "unknown";
|
|
985
1069
|
}
|
|
986
1070
|
}
|
|
1071
|
+
function describeCallArgs(meta, palletName, callName) {
|
|
1072
|
+
try {
|
|
1073
|
+
const palletMeta = meta.unified.pallets.find((p) => p.name === palletName);
|
|
1074
|
+
if (!palletMeta?.calls)
|
|
1075
|
+
return "";
|
|
1076
|
+
const callsEntry = meta.lookup(palletMeta.calls.type);
|
|
1077
|
+
if (callsEntry.type !== "enum")
|
|
1078
|
+
return "";
|
|
1079
|
+
const variant = callsEntry.value[callName];
|
|
1080
|
+
if (!variant)
|
|
1081
|
+
return "";
|
|
1082
|
+
if (variant.type === "void")
|
|
1083
|
+
return "()";
|
|
1084
|
+
if (variant.type === "struct") {
|
|
1085
|
+
const fields = Object.entries(variant.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
|
|
1086
|
+
return `(${fields})`;
|
|
1087
|
+
}
|
|
1088
|
+
if (variant.type === "lookupEntry") {
|
|
1089
|
+
const inner = variant.value;
|
|
1090
|
+
if (inner.type === "void")
|
|
1091
|
+
return "()";
|
|
1092
|
+
if (inner.type === "struct") {
|
|
1093
|
+
const fields = Object.entries(inner.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
|
|
1094
|
+
return `(${fields})`;
|
|
1095
|
+
}
|
|
1096
|
+
return `(${formatLookupEntry(inner)})`;
|
|
1097
|
+
}
|
|
1098
|
+
if (variant.type === "tuple") {
|
|
1099
|
+
const types = variant.value.map(formatLookupEntry).join(", ");
|
|
1100
|
+
return `(${types})`;
|
|
1101
|
+
}
|
|
1102
|
+
return "";
|
|
1103
|
+
} catch {
|
|
1104
|
+
return "";
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
function describeEventFields(meta, palletName, eventName) {
|
|
1108
|
+
try {
|
|
1109
|
+
const palletMeta = meta.unified.pallets.find((p) => p.name === palletName);
|
|
1110
|
+
if (!palletMeta?.events)
|
|
1111
|
+
return "";
|
|
1112
|
+
const eventsEntry = meta.lookup(palletMeta.events.type);
|
|
1113
|
+
if (eventsEntry.type !== "enum")
|
|
1114
|
+
return "";
|
|
1115
|
+
const variant = eventsEntry.value[eventName];
|
|
1116
|
+
if (!variant)
|
|
1117
|
+
return "";
|
|
1118
|
+
if (variant.type === "void")
|
|
1119
|
+
return "()";
|
|
1120
|
+
if (variant.type === "struct") {
|
|
1121
|
+
const fields = Object.entries(variant.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
|
|
1122
|
+
return `(${fields})`;
|
|
1123
|
+
}
|
|
1124
|
+
if (variant.type === "lookupEntry") {
|
|
1125
|
+
const inner = variant.value;
|
|
1126
|
+
if (inner.type === "void")
|
|
1127
|
+
return "()";
|
|
1128
|
+
if (inner.type === "struct") {
|
|
1129
|
+
const fields = Object.entries(inner.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ");
|
|
1130
|
+
return `(${fields})`;
|
|
1131
|
+
}
|
|
1132
|
+
return `(${formatLookupEntry(inner)})`;
|
|
1133
|
+
}
|
|
1134
|
+
if (variant.type === "tuple") {
|
|
1135
|
+
const types = variant.value.map(formatLookupEntry).join(", ");
|
|
1136
|
+
return `(${types})`;
|
|
1137
|
+
}
|
|
1138
|
+
return "";
|
|
1139
|
+
} catch {
|
|
1140
|
+
return "";
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
987
1143
|
function hexToBytes(hex) {
|
|
988
1144
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
989
1145
|
const bytes = new Uint8Array(clean.length / 2);
|
|
@@ -1059,10 +1215,10 @@ async function chainAdd(name, opts) {
|
|
|
1059
1215
|
rpc: opts.rpc ?? "",
|
|
1060
1216
|
...opts.lightClient ? { lightClient: true } : {}
|
|
1061
1217
|
};
|
|
1062
|
-
console.
|
|
1218
|
+
console.error(`Connecting to ${name}...`);
|
|
1063
1219
|
const clientHandle = await createChainClient(name, chainConfig, opts.rpc);
|
|
1064
1220
|
try {
|
|
1065
|
-
console.
|
|
1221
|
+
console.error("Fetching metadata...");
|
|
1066
1222
|
await fetchMetadataFromChain(clientHandle, name);
|
|
1067
1223
|
const config = await loadConfig();
|
|
1068
1224
|
config.chains[name] = chainConfig;
|
|
@@ -1115,10 +1271,10 @@ async function chainList() {
|
|
|
1115
1271
|
async function chainUpdate(name, opts) {
|
|
1116
1272
|
const config = await loadConfig();
|
|
1117
1273
|
const { name: chainName, chain: chainConfig } = resolveChain(config, name);
|
|
1118
|
-
console.
|
|
1274
|
+
console.error(`Connecting to ${chainName}...`);
|
|
1119
1275
|
const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
1120
1276
|
try {
|
|
1121
|
-
console.
|
|
1277
|
+
console.error("Fetching metadata...");
|
|
1122
1278
|
await fetchMetadataFromChain(clientHandle, chainName);
|
|
1123
1279
|
console.log(`Metadata for "${chainName}" updated.`);
|
|
1124
1280
|
} finally {
|
|
@@ -1228,23 +1384,58 @@ function resolveTargetChain(target, chainFlag) {
|
|
|
1228
1384
|
|
|
1229
1385
|
// src/commands/const.ts
|
|
1230
1386
|
function registerConstCommand(cli) {
|
|
1231
|
-
cli.command("const [target]", "Look up
|
|
1387
|
+
cli.command("const [target]", "Look up or list pallet constants (e.g. Balances.ExistentialDeposit)").alias("consts").alias("constants").action(async (target, opts) => {
|
|
1232
1388
|
if (!target) {
|
|
1233
|
-
console.log("Usage: dot const <[Chain.]Pallet.Constant> [--chain <name>] [--output json]");
|
|
1389
|
+
console.log("Usage: dot const <[Chain.]Pallet[.Constant]> [--chain <name>] [--output json]");
|
|
1234
1390
|
console.log("");
|
|
1235
1391
|
console.log("Examples:");
|
|
1236
|
-
console.log(" $ dot const Balances
|
|
1392
|
+
console.log(" $ dot const Balances # list constants");
|
|
1393
|
+
console.log(" $ dot const Balances.ExistentialDeposit # look up value");
|
|
1237
1394
|
console.log(" $ dot const System.SS58Prefix --chain kusama");
|
|
1238
1395
|
console.log(" $ dot const kusama.Balances.ExistentialDeposit # chain prefix");
|
|
1239
1396
|
return;
|
|
1240
1397
|
}
|
|
1241
1398
|
const config = await loadConfig();
|
|
1242
1399
|
const knownChains = Object.keys(config.chains);
|
|
1243
|
-
const parsed = parseTarget(target, { knownChains });
|
|
1400
|
+
const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
|
|
1244
1401
|
const effectiveChain = resolveTargetChain(parsed, opts.chain);
|
|
1245
1402
|
const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
|
|
1246
1403
|
const pallet = parsed.pallet;
|
|
1247
1404
|
const item = parsed.item;
|
|
1405
|
+
if (!item) {
|
|
1406
|
+
let meta;
|
|
1407
|
+
try {
|
|
1408
|
+
meta = await getOrFetchMetadata(chainName);
|
|
1409
|
+
} catch {
|
|
1410
|
+
console.error(`Fetching metadata from ${chainName}...`);
|
|
1411
|
+
const clientHandle2 = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
1412
|
+
try {
|
|
1413
|
+
meta = await getOrFetchMetadata(chainName, clientHandle2);
|
|
1414
|
+
} finally {
|
|
1415
|
+
clientHandle2.destroy();
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
const palletNames = getPalletNames(meta);
|
|
1419
|
+
const palletInfo = findPallet(meta, pallet);
|
|
1420
|
+
if (!palletInfo) {
|
|
1421
|
+
throw new Error(suggestMessage("pallet", pallet, palletNames));
|
|
1422
|
+
}
|
|
1423
|
+
if (palletInfo.constants.length === 0) {
|
|
1424
|
+
console.log(`No constants in ${palletInfo.name}.`);
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
printHeading(`${palletInfo.name} Constants`);
|
|
1428
|
+
for (const c of palletInfo.constants) {
|
|
1429
|
+
const typeStr = describeType(meta.lookup, c.typeId);
|
|
1430
|
+
console.log(` ${CYAN}${c.name}${RESET}${DIM}: ${typeStr}${RESET}`);
|
|
1431
|
+
const summary = firstSentence(c.docs);
|
|
1432
|
+
if (summary) {
|
|
1433
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
console.log();
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1248
1439
|
const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
1249
1440
|
try {
|
|
1250
1441
|
const meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
@@ -1269,6 +1460,279 @@ function registerConstCommand(cli) {
|
|
|
1269
1460
|
});
|
|
1270
1461
|
}
|
|
1271
1462
|
|
|
1463
|
+
// src/commands/focused-inspect.ts
|
|
1464
|
+
async function loadMeta(chainName, chainConfig, rpcOverride) {
|
|
1465
|
+
try {
|
|
1466
|
+
return await getOrFetchMetadata(chainName);
|
|
1467
|
+
} catch {
|
|
1468
|
+
console.error(`Fetching metadata from ${chainName}...`);
|
|
1469
|
+
const clientHandle = await createChainClient(chainName, chainConfig, rpcOverride);
|
|
1470
|
+
try {
|
|
1471
|
+
return await getOrFetchMetadata(chainName, clientHandle);
|
|
1472
|
+
} finally {
|
|
1473
|
+
clientHandle.destroy();
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function resolvePallet(meta, palletName) {
|
|
1478
|
+
const palletNames = getPalletNames(meta);
|
|
1479
|
+
const pallet = findPallet(meta, palletName);
|
|
1480
|
+
if (!pallet) {
|
|
1481
|
+
throw new Error(suggestMessage("pallet", palletName, palletNames));
|
|
1482
|
+
}
|
|
1483
|
+
return pallet;
|
|
1484
|
+
}
|
|
1485
|
+
function registerFocusedInspectCommands(cli) {
|
|
1486
|
+
cli.command("call [target]", "List or inspect pallet calls").alias("calls").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
|
|
1487
|
+
if (!target) {
|
|
1488
|
+
console.log("Usage: dot calls <[Chain.]Pallet[.Call]> [--chain <name>]");
|
|
1489
|
+
console.log("");
|
|
1490
|
+
console.log("Examples:");
|
|
1491
|
+
console.log(" $ dot calls Balances");
|
|
1492
|
+
console.log(" $ dot calls Balances.transfer_allow_death");
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
const config = await loadConfig();
|
|
1496
|
+
const knownChains = Object.keys(config.chains);
|
|
1497
|
+
const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
|
|
1498
|
+
const effectiveChain = resolveTargetChain(parsed, opts.chain);
|
|
1499
|
+
const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
|
|
1500
|
+
const meta = await loadMeta(chainName, chainConfig, opts.rpc);
|
|
1501
|
+
const pallet = resolvePallet(meta, parsed.pallet);
|
|
1502
|
+
if (!parsed.item) {
|
|
1503
|
+
if (pallet.calls.length === 0) {
|
|
1504
|
+
console.log(`No calls in ${pallet.name}.`);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
printHeading(`${pallet.name} Calls`);
|
|
1508
|
+
for (const c of pallet.calls) {
|
|
1509
|
+
const args2 = describeCallArgs(meta, pallet.name, c.name);
|
|
1510
|
+
console.log(` ${CYAN}${c.name}${RESET}${DIM}${args2}${RESET}`);
|
|
1511
|
+
const summary = firstSentence(c.docs);
|
|
1512
|
+
if (summary) {
|
|
1513
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
console.log();
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
const callItem = pallet.calls.find((c) => c.name.toLowerCase() === parsed.item.toLowerCase());
|
|
1520
|
+
if (!callItem) {
|
|
1521
|
+
const names = pallet.calls.map((c) => c.name);
|
|
1522
|
+
throw new Error(suggestMessage(`call in ${pallet.name}`, parsed.item, names));
|
|
1523
|
+
}
|
|
1524
|
+
printHeading(`${pallet.name}.${callItem.name} (Call)`);
|
|
1525
|
+
const args = describeCallArgs(meta, pallet.name, callItem.name);
|
|
1526
|
+
console.log(` ${BOLD}Args:${RESET} ${args}`);
|
|
1527
|
+
if (callItem.docs.length) {
|
|
1528
|
+
console.log();
|
|
1529
|
+
printDocs(callItem.docs);
|
|
1530
|
+
}
|
|
1531
|
+
console.log();
|
|
1532
|
+
});
|
|
1533
|
+
cli.command("event [target]", "List or inspect pallet events").alias("events").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
|
|
1534
|
+
if (!target) {
|
|
1535
|
+
console.log("Usage: dot events <[Chain.]Pallet[.Event]> [--chain <name>]");
|
|
1536
|
+
console.log("");
|
|
1537
|
+
console.log("Examples:");
|
|
1538
|
+
console.log(" $ dot events Balances");
|
|
1539
|
+
console.log(" $ dot events Balances.Transfer");
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
const config = await loadConfig();
|
|
1543
|
+
const knownChains = Object.keys(config.chains);
|
|
1544
|
+
const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
|
|
1545
|
+
const effectiveChain = resolveTargetChain(parsed, opts.chain);
|
|
1546
|
+
const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
|
|
1547
|
+
const meta = await loadMeta(chainName, chainConfig, opts.rpc);
|
|
1548
|
+
const pallet = resolvePallet(meta, parsed.pallet);
|
|
1549
|
+
if (!parsed.item) {
|
|
1550
|
+
if (pallet.events.length === 0) {
|
|
1551
|
+
console.log(`No events in ${pallet.name}.`);
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
printHeading(`${pallet.name} Events`);
|
|
1555
|
+
for (const e of pallet.events) {
|
|
1556
|
+
const fields2 = describeEventFields(meta, pallet.name, e.name);
|
|
1557
|
+
console.log(` ${CYAN}${e.name}${RESET}${DIM}${fields2}${RESET}`);
|
|
1558
|
+
const summary = firstSentence(e.docs);
|
|
1559
|
+
if (summary) {
|
|
1560
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
console.log();
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
const eventItem = pallet.events.find((e) => e.name.toLowerCase() === parsed.item.toLowerCase());
|
|
1567
|
+
if (!eventItem) {
|
|
1568
|
+
const names = pallet.events.map((e) => e.name);
|
|
1569
|
+
throw new Error(suggestMessage(`event in ${pallet.name}`, parsed.item, names));
|
|
1570
|
+
}
|
|
1571
|
+
printHeading(`${pallet.name}.${eventItem.name} (Event)`);
|
|
1572
|
+
const fields = describeEventFields(meta, pallet.name, eventItem.name);
|
|
1573
|
+
console.log(` ${BOLD}Fields:${RESET} ${fields}`);
|
|
1574
|
+
if (eventItem.docs.length) {
|
|
1575
|
+
console.log();
|
|
1576
|
+
printDocs(eventItem.docs);
|
|
1577
|
+
}
|
|
1578
|
+
console.log();
|
|
1579
|
+
});
|
|
1580
|
+
cli.command("error [target]", "List or inspect pallet errors").alias("errors").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
|
|
1581
|
+
if (!target) {
|
|
1582
|
+
console.log("Usage: dot errors <[Chain.]Pallet[.Error]> [--chain <name>]");
|
|
1583
|
+
console.log("");
|
|
1584
|
+
console.log("Examples:");
|
|
1585
|
+
console.log(" $ dot errors Balances");
|
|
1586
|
+
console.log(" $ dot errors Balances.InsufficientBalance");
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
const config = await loadConfig();
|
|
1590
|
+
const knownChains = Object.keys(config.chains);
|
|
1591
|
+
const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
|
|
1592
|
+
const effectiveChain = resolveTargetChain(parsed, opts.chain);
|
|
1593
|
+
const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
|
|
1594
|
+
const meta = await loadMeta(chainName, chainConfig, opts.rpc);
|
|
1595
|
+
const pallet = resolvePallet(meta, parsed.pallet);
|
|
1596
|
+
if (!parsed.item) {
|
|
1597
|
+
if (pallet.errors.length === 0) {
|
|
1598
|
+
console.log(`No errors in ${pallet.name}.`);
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
printHeading(`${pallet.name} Errors`);
|
|
1602
|
+
for (const e of pallet.errors) {
|
|
1603
|
+
console.log(` ${CYAN}${e.name}${RESET}`);
|
|
1604
|
+
const summary = firstSentence(e.docs);
|
|
1605
|
+
if (summary) {
|
|
1606
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
console.log();
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
const errorItem = pallet.errors.find((e) => e.name.toLowerCase() === parsed.item.toLowerCase());
|
|
1613
|
+
if (!errorItem) {
|
|
1614
|
+
const names = pallet.errors.map((e) => e.name);
|
|
1615
|
+
throw new Error(suggestMessage(`error in ${pallet.name}`, parsed.item, names));
|
|
1616
|
+
}
|
|
1617
|
+
printHeading(`${pallet.name}.${errorItem.name} (Error)`);
|
|
1618
|
+
if (errorItem.docs.length) {
|
|
1619
|
+
printDocs(errorItem.docs);
|
|
1620
|
+
}
|
|
1621
|
+
console.log();
|
|
1622
|
+
});
|
|
1623
|
+
cli.command("storage [target]", "List or inspect pallet storage items").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
|
|
1624
|
+
if (!target) {
|
|
1625
|
+
console.log("Usage: dot storage <[Chain.]Pallet[.Item]> [--chain <name>]");
|
|
1626
|
+
console.log("");
|
|
1627
|
+
console.log("Examples:");
|
|
1628
|
+
console.log(" $ dot storage System");
|
|
1629
|
+
console.log(" $ dot storage System.Account");
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
const config = await loadConfig();
|
|
1633
|
+
const knownChains = Object.keys(config.chains);
|
|
1634
|
+
const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
|
|
1635
|
+
const effectiveChain = resolveTargetChain(parsed, opts.chain);
|
|
1636
|
+
const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
|
|
1637
|
+
const meta = await loadMeta(chainName, chainConfig, opts.rpc);
|
|
1638
|
+
const pallet = resolvePallet(meta, parsed.pallet);
|
|
1639
|
+
if (!parsed.item) {
|
|
1640
|
+
if (pallet.storage.length === 0) {
|
|
1641
|
+
console.log(`No storage items in ${pallet.name}.`);
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
printHeading(`${pallet.name} Storage`);
|
|
1645
|
+
for (const s of pallet.storage) {
|
|
1646
|
+
const valueType = describeType(meta.lookup, s.valueTypeId);
|
|
1647
|
+
let typeSuffix;
|
|
1648
|
+
if (s.keyTypeId != null) {
|
|
1649
|
+
const keyType = describeType(meta.lookup, s.keyTypeId);
|
|
1650
|
+
typeSuffix = `: ${keyType} → ${valueType} [map]`;
|
|
1651
|
+
} else {
|
|
1652
|
+
typeSuffix = `: ${valueType}`;
|
|
1653
|
+
}
|
|
1654
|
+
console.log(` ${CYAN}${s.name}${RESET}${DIM}${typeSuffix}${RESET}`);
|
|
1655
|
+
const summary = firstSentence(s.docs);
|
|
1656
|
+
if (summary) {
|
|
1657
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
console.log();
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
const storageItem = pallet.storage.find((s) => s.name.toLowerCase() === parsed.item.toLowerCase());
|
|
1664
|
+
if (!storageItem) {
|
|
1665
|
+
const names = pallet.storage.map((s) => s.name);
|
|
1666
|
+
throw new Error(suggestMessage(`storage item in ${pallet.name}`, parsed.item, names));
|
|
1667
|
+
}
|
|
1668
|
+
printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
|
|
1669
|
+
console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
|
|
1670
|
+
console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
|
|
1671
|
+
if (storageItem.keyTypeId != null) {
|
|
1672
|
+
console.log(` ${BOLD}Key:${RESET} ${describeType(meta.lookup, storageItem.keyTypeId)}`);
|
|
1673
|
+
}
|
|
1674
|
+
if (storageItem.docs.length) {
|
|
1675
|
+
console.log();
|
|
1676
|
+
printDocs(storageItem.docs);
|
|
1677
|
+
}
|
|
1678
|
+
console.log();
|
|
1679
|
+
});
|
|
1680
|
+
cli.command("pallet [target]", "List all pallets or inspect a specific pallet").alias("pallets").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
|
|
1681
|
+
const config = await loadConfig();
|
|
1682
|
+
const knownChains = Object.keys(config.chains);
|
|
1683
|
+
let effectiveChain = opts.chain;
|
|
1684
|
+
let palletName;
|
|
1685
|
+
if (target) {
|
|
1686
|
+
const parsed = parseTarget(target, { knownChains, allowPalletOnly: true });
|
|
1687
|
+
effectiveChain = resolveTargetChain(parsed, opts.chain);
|
|
1688
|
+
palletName = parsed.pallet;
|
|
1689
|
+
}
|
|
1690
|
+
const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
|
|
1691
|
+
const meta = await loadMeta(chainName, chainConfig, opts.rpc);
|
|
1692
|
+
if (!palletName) {
|
|
1693
|
+
const pallets = listPallets(meta);
|
|
1694
|
+
printHeading(`Pallets on ${chainName} (${pallets.length})`);
|
|
1695
|
+
for (const p of pallets) {
|
|
1696
|
+
const counts2 = [];
|
|
1697
|
+
if (p.storage.length)
|
|
1698
|
+
counts2.push(`${p.storage.length} storage`);
|
|
1699
|
+
if (p.constants.length)
|
|
1700
|
+
counts2.push(`${p.constants.length} constants`);
|
|
1701
|
+
if (p.calls.length)
|
|
1702
|
+
counts2.push(`${p.calls.length} calls`);
|
|
1703
|
+
if (p.events.length)
|
|
1704
|
+
counts2.push(`${p.events.length} events`);
|
|
1705
|
+
if (p.errors.length)
|
|
1706
|
+
counts2.push(`${p.errors.length} errors`);
|
|
1707
|
+
printItem(p.name, counts2.join(", "));
|
|
1708
|
+
}
|
|
1709
|
+
console.log();
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const pallet = resolvePallet(meta, palletName);
|
|
1713
|
+
printHeading(`${pallet.name} Pallet`);
|
|
1714
|
+
if (pallet.docs.length) {
|
|
1715
|
+
printDocs(pallet.docs);
|
|
1716
|
+
console.log();
|
|
1717
|
+
}
|
|
1718
|
+
const counts = [];
|
|
1719
|
+
if (pallet.storage.length)
|
|
1720
|
+
counts.push(`${pallet.storage.length} storage`);
|
|
1721
|
+
if (pallet.constants.length)
|
|
1722
|
+
counts.push(`${pallet.constants.length} constants`);
|
|
1723
|
+
if (pallet.calls.length)
|
|
1724
|
+
counts.push(`${pallet.calls.length} calls`);
|
|
1725
|
+
if (pallet.events.length)
|
|
1726
|
+
counts.push(`${pallet.events.length} events`);
|
|
1727
|
+
if (pallet.errors.length)
|
|
1728
|
+
counts.push(`${pallet.errors.length} errors`);
|
|
1729
|
+
if (counts.length) {
|
|
1730
|
+
console.log(` ${counts.join(", ")}`);
|
|
1731
|
+
}
|
|
1732
|
+
console.log();
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1272
1736
|
// src/core/hash.ts
|
|
1273
1737
|
import { blake2b } from "@noble/hashes/blake2.js";
|
|
1274
1738
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
@@ -1402,7 +1866,7 @@ function registerHashCommand(cli) {
|
|
|
1402
1866
|
|
|
1403
1867
|
// src/commands/inspect.ts
|
|
1404
1868
|
function registerInspectCommand(cli) {
|
|
1405
|
-
cli.command("inspect [target]", "Inspect chain metadata (pallets, storage, constants)").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
|
|
1869
|
+
cli.command("inspect [target]", "Inspect chain metadata (pallets, storage, constants, calls, events, errors)").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
|
|
1406
1870
|
const config = await loadConfig();
|
|
1407
1871
|
const knownChains = Object.keys(config.chains);
|
|
1408
1872
|
let effectiveChain = opts.chain;
|
|
@@ -1419,7 +1883,7 @@ function registerInspectCommand(cli) {
|
|
|
1419
1883
|
try {
|
|
1420
1884
|
meta = await getOrFetchMetadata(chainName);
|
|
1421
1885
|
} catch {
|
|
1422
|
-
console.
|
|
1886
|
+
console.error(`Fetching metadata from ${chainName}...`);
|
|
1423
1887
|
const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
1424
1888
|
try {
|
|
1425
1889
|
meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
@@ -1436,6 +1900,12 @@ function registerInspectCommand(cli) {
|
|
|
1436
1900
|
counts.push(`${p.storage.length} storage`);
|
|
1437
1901
|
if (p.constants.length)
|
|
1438
1902
|
counts.push(`${p.constants.length} constants`);
|
|
1903
|
+
if (p.calls.length)
|
|
1904
|
+
counts.push(`${p.calls.length} calls`);
|
|
1905
|
+
if (p.events.length)
|
|
1906
|
+
counts.push(`${p.events.length} events`);
|
|
1907
|
+
if (p.errors.length)
|
|
1908
|
+
counts.push(`${p.errors.length} errors`);
|
|
1439
1909
|
printItem(p.name, counts.join(", "));
|
|
1440
1910
|
}
|
|
1441
1911
|
console.log();
|
|
@@ -1455,16 +1925,66 @@ function registerInspectCommand(cli) {
|
|
|
1455
1925
|
if (pallet2.storage.length) {
|
|
1456
1926
|
console.log(` ${BOLD}Storage Items:${RESET}`);
|
|
1457
1927
|
for (const s of pallet2.storage) {
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1928
|
+
const valueType = describeType(meta.lookup, s.valueTypeId);
|
|
1929
|
+
let typeSuffix;
|
|
1930
|
+
if (s.keyTypeId != null) {
|
|
1931
|
+
const keyType = describeType(meta.lookup, s.keyTypeId);
|
|
1932
|
+
typeSuffix = `: ${keyType} → ${valueType} [map]`;
|
|
1933
|
+
} else {
|
|
1934
|
+
typeSuffix = `: ${valueType}`;
|
|
1935
|
+
}
|
|
1936
|
+
console.log(` ${CYAN}${s.name}${RESET}${DIM}${typeSuffix}${RESET}`);
|
|
1937
|
+
const summary = firstSentence(s.docs);
|
|
1938
|
+
if (summary) {
|
|
1939
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1940
|
+
}
|
|
1460
1941
|
}
|
|
1461
1942
|
console.log();
|
|
1462
1943
|
}
|
|
1463
1944
|
if (pallet2.constants.length) {
|
|
1464
1945
|
console.log(` ${BOLD}Constants:${RESET}`);
|
|
1465
1946
|
for (const c of pallet2.constants) {
|
|
1466
|
-
const
|
|
1467
|
-
console.log(` ${CYAN}${c.name}${RESET}${DIM}${
|
|
1947
|
+
const typeStr = describeType(meta.lookup, c.typeId);
|
|
1948
|
+
console.log(` ${CYAN}${c.name}${RESET}${DIM}: ${typeStr}${RESET}`);
|
|
1949
|
+
const summary = firstSentence(c.docs);
|
|
1950
|
+
if (summary) {
|
|
1951
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
console.log();
|
|
1955
|
+
}
|
|
1956
|
+
if (pallet2.calls.length) {
|
|
1957
|
+
console.log(` ${BOLD}Calls:${RESET}`);
|
|
1958
|
+
for (const c of pallet2.calls) {
|
|
1959
|
+
const args = describeCallArgs(meta, pallet2.name, c.name);
|
|
1960
|
+
console.log(` ${CYAN}${c.name}${RESET}${DIM}${args}${RESET}`);
|
|
1961
|
+
const summary = firstSentence(c.docs);
|
|
1962
|
+
if (summary) {
|
|
1963
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
console.log();
|
|
1967
|
+
}
|
|
1968
|
+
if (pallet2.events.length) {
|
|
1969
|
+
console.log(` ${BOLD}Events:${RESET}`);
|
|
1970
|
+
for (const e of pallet2.events) {
|
|
1971
|
+
const fields = describeEventFields(meta, pallet2.name, e.name);
|
|
1972
|
+
console.log(` ${CYAN}${e.name}${RESET}${DIM}${fields}${RESET}`);
|
|
1973
|
+
const summary = firstSentence(e.docs);
|
|
1974
|
+
if (summary) {
|
|
1975
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
console.log();
|
|
1979
|
+
}
|
|
1980
|
+
if (pallet2.errors.length) {
|
|
1981
|
+
console.log(` ${BOLD}Errors:${RESET}`);
|
|
1982
|
+
for (const e of pallet2.errors) {
|
|
1983
|
+
console.log(` ${CYAN}${e.name}${RESET}`);
|
|
1984
|
+
const summary = firstSentence(e.docs);
|
|
1985
|
+
if (summary) {
|
|
1986
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
1987
|
+
}
|
|
1468
1988
|
}
|
|
1469
1989
|
console.log();
|
|
1470
1990
|
}
|
|
@@ -1501,9 +2021,45 @@ function registerInspectCommand(cli) {
|
|
|
1501
2021
|
console.log();
|
|
1502
2022
|
return;
|
|
1503
2023
|
}
|
|
2024
|
+
const callItem = pallet.calls.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
|
|
2025
|
+
if (callItem) {
|
|
2026
|
+
printHeading(`${pallet.name}.${callItem.name} (Call)`);
|
|
2027
|
+
const args = describeCallArgs(meta, pallet.name, callItem.name);
|
|
2028
|
+
console.log(` ${BOLD}Args:${RESET} ${args}`);
|
|
2029
|
+
if (callItem.docs.length) {
|
|
2030
|
+
console.log();
|
|
2031
|
+
printDocs(callItem.docs);
|
|
2032
|
+
}
|
|
2033
|
+
console.log();
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
const eventItem = pallet.events.find((e) => e.name.toLowerCase() === itemName.toLowerCase());
|
|
2037
|
+
if (eventItem) {
|
|
2038
|
+
printHeading(`${pallet.name}.${eventItem.name} (Event)`);
|
|
2039
|
+
const fields = describeEventFields(meta, pallet.name, eventItem.name);
|
|
2040
|
+
console.log(` ${BOLD}Fields:${RESET} ${fields}`);
|
|
2041
|
+
if (eventItem.docs.length) {
|
|
2042
|
+
console.log();
|
|
2043
|
+
printDocs(eventItem.docs);
|
|
2044
|
+
}
|
|
2045
|
+
console.log();
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
const errorItem = pallet.errors.find((e) => e.name.toLowerCase() === itemName.toLowerCase());
|
|
2049
|
+
if (errorItem) {
|
|
2050
|
+
printHeading(`${pallet.name}.${errorItem.name} (Error)`);
|
|
2051
|
+
if (errorItem.docs.length) {
|
|
2052
|
+
printDocs(errorItem.docs);
|
|
2053
|
+
}
|
|
2054
|
+
console.log();
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
1504
2057
|
const allItems = [
|
|
1505
2058
|
...pallet.storage.map((s) => s.name),
|
|
1506
|
-
...pallet.constants.map((c) => c.name)
|
|
2059
|
+
...pallet.constants.map((c) => c.name),
|
|
2060
|
+
...pallet.calls.map((c) => c.name),
|
|
2061
|
+
...pallet.events.map((e) => e.name),
|
|
2062
|
+
...pallet.errors.map((e) => e.name)
|
|
1507
2063
|
];
|
|
1508
2064
|
throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems));
|
|
1509
2065
|
});
|
|
@@ -1549,10 +2105,36 @@ function parseStructArgs(meta, fields, args, callLabel) {
|
|
|
1549
2105
|
for (let i = 0;i < fieldNames.length; i++) {
|
|
1550
2106
|
const name = fieldNames[i];
|
|
1551
2107
|
const entry = fields[name];
|
|
1552
|
-
|
|
2108
|
+
try {
|
|
2109
|
+
result[name] = parseTypedArg(meta, entry, args[i]);
|
|
2110
|
+
} catch (err) {
|
|
2111
|
+
const typeDesc = describeType(meta.lookup, entry.id);
|
|
2112
|
+
throw new Error(`Invalid value for argument '${name}' (expected ${typeDesc}): ${JSON.stringify(args[i])}
|
|
2113
|
+
` + ` Hint: ${typeHint(entry, meta)}`, { cause: err });
|
|
2114
|
+
}
|
|
1553
2115
|
}
|
|
1554
2116
|
return result;
|
|
1555
2117
|
}
|
|
2118
|
+
function typeHint(entry, meta) {
|
|
2119
|
+
const resolved = entry.type === "lookupEntry" ? entry.value : entry;
|
|
2120
|
+
switch (resolved.type) {
|
|
2121
|
+
case "enum": {
|
|
2122
|
+
const variants = Object.keys(resolved.value);
|
|
2123
|
+
if (variants.length <= 6)
|
|
2124
|
+
return `a variant: ${variants.join(" | ")}`;
|
|
2125
|
+
return `one of ${variants.length} variants (e.g. ${variants.slice(0, 3).join(", ")})`;
|
|
2126
|
+
}
|
|
2127
|
+
case "struct":
|
|
2128
|
+
return `a JSON object with fields: ${Object.keys(resolved.value).join(", ")}`;
|
|
2129
|
+
case "tuple":
|
|
2130
|
+
return "a JSON array";
|
|
2131
|
+
case "sequence":
|
|
2132
|
+
case "array":
|
|
2133
|
+
return "a JSON array or hex-encoded bytes";
|
|
2134
|
+
default:
|
|
2135
|
+
return describeType(meta.lookup, entry.id);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
1556
2138
|
function normalizeValue(lookup, entry, value) {
|
|
1557
2139
|
let resolved = entry;
|
|
1558
2140
|
while (resolved.type === "lookupEntry") {
|
|
@@ -2084,10 +2666,66 @@ function decodeCall(meta, callHex) {
|
|
|
2084
2666
|
const callName = decoded.call.value.name;
|
|
2085
2667
|
const argsStr = formatDecodedArgs(decoded.args.value);
|
|
2086
2668
|
return `${palletName}.${callName}${argsStr}`;
|
|
2669
|
+
} catch {}
|
|
2670
|
+
try {
|
|
2671
|
+
return decodeCallFallback(meta, callHex);
|
|
2087
2672
|
} catch {
|
|
2088
2673
|
return "(unable to decode)";
|
|
2089
2674
|
}
|
|
2090
2675
|
}
|
|
2676
|
+
function decodeCallFallback(meta, callHex) {
|
|
2677
|
+
const callTypeId = meta.lookup.call;
|
|
2678
|
+
if (callTypeId == null)
|
|
2679
|
+
throw new Error("No RuntimeCall type ID");
|
|
2680
|
+
const codec = meta.builder.buildDefinition(callTypeId);
|
|
2681
|
+
const decoded = codec.dec(Binary3.fromHex(callHex).asBytes());
|
|
2682
|
+
const palletName = decoded.type;
|
|
2683
|
+
const call = decoded.value;
|
|
2684
|
+
const callName = call.type;
|
|
2685
|
+
const args = call.value;
|
|
2686
|
+
if (args === undefined || args === null) {
|
|
2687
|
+
return `${palletName}.${callName}`;
|
|
2688
|
+
}
|
|
2689
|
+
const argsStr = formatRawDecoded(args);
|
|
2690
|
+
return `${palletName}.${callName} ${argsStr}`;
|
|
2691
|
+
}
|
|
2692
|
+
function formatRawDecoded(value) {
|
|
2693
|
+
if (value === undefined || value === null)
|
|
2694
|
+
return "null";
|
|
2695
|
+
if (value instanceof Binary3)
|
|
2696
|
+
return value.asHex();
|
|
2697
|
+
if (typeof value === "bigint")
|
|
2698
|
+
return value.toString();
|
|
2699
|
+
if (typeof value === "string")
|
|
2700
|
+
return value;
|
|
2701
|
+
if (typeof value === "number")
|
|
2702
|
+
return value.toString();
|
|
2703
|
+
if (typeof value === "boolean")
|
|
2704
|
+
return value.toString();
|
|
2705
|
+
if (Array.isArray(value)) {
|
|
2706
|
+
if (value.length === 0)
|
|
2707
|
+
return "[]";
|
|
2708
|
+
return `[${value.map(formatRawDecoded).join(", ")}]`;
|
|
2709
|
+
}
|
|
2710
|
+
if (typeof value === "object") {
|
|
2711
|
+
const obj = value;
|
|
2712
|
+
if ("type" in obj && typeof obj.type === "string") {
|
|
2713
|
+
const inner = obj.value;
|
|
2714
|
+
if (inner === undefined || inner === null)
|
|
2715
|
+
return obj.type;
|
|
2716
|
+
const innerStr = formatRawDecoded(inner);
|
|
2717
|
+
if (innerStr.startsWith("{"))
|
|
2718
|
+
return `${obj.type} ${innerStr}`;
|
|
2719
|
+
return `${obj.type}(${innerStr})`;
|
|
2720
|
+
}
|
|
2721
|
+
const entries = Object.entries(obj);
|
|
2722
|
+
if (entries.length === 0)
|
|
2723
|
+
return "{}";
|
|
2724
|
+
const fields = entries.map(([k, v]) => `${k}: ${formatRawDecoded(v)}`).join(", ");
|
|
2725
|
+
return `{ ${fields} }`;
|
|
2726
|
+
}
|
|
2727
|
+
return String(value);
|
|
2728
|
+
}
|
|
2091
2729
|
function formatDecodedArgs(decoded) {
|
|
2092
2730
|
return formatDecoded(decoded);
|
|
2093
2731
|
}
|
|
@@ -2211,14 +2849,26 @@ function parseCallArgs(meta, palletName, callName, args) {
|
|
|
2211
2849
|
if (args.length !== 1) {
|
|
2212
2850
|
throw new Error(`${palletName}.${callName} takes 1 argument (${describeType(meta.lookup, inner.id)}), but ${args.length} provided.`);
|
|
2213
2851
|
}
|
|
2214
|
-
|
|
2852
|
+
try {
|
|
2853
|
+
return parseTypedArg2(meta, inner, args[0]);
|
|
2854
|
+
} catch (err) {
|
|
2855
|
+
const typeDesc = describeType(meta.lookup, inner.id);
|
|
2856
|
+
throw new Error(`Invalid value for argument 0 (expected ${typeDesc}): ${JSON.stringify(args[0])}`, { cause: err });
|
|
2857
|
+
}
|
|
2215
2858
|
}
|
|
2216
2859
|
if (variant.type === "tuple") {
|
|
2217
2860
|
const entries = variant.value;
|
|
2218
2861
|
if (args.length !== entries.length) {
|
|
2219
2862
|
throw new Error(`${palletName}.${callName} takes ${entries.length} arguments, but ${args.length} provided.`);
|
|
2220
2863
|
}
|
|
2221
|
-
return entries.map((entry, i) =>
|
|
2864
|
+
return entries.map((entry, i) => {
|
|
2865
|
+
try {
|
|
2866
|
+
return parseTypedArg2(meta, entry, args[i]);
|
|
2867
|
+
} catch (err) {
|
|
2868
|
+
const typeDesc = describeType(meta.lookup, entry.id);
|
|
2869
|
+
throw new Error(`Invalid value for argument ${i} (expected ${typeDesc}): ${JSON.stringify(args[i])}`, { cause: err });
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2222
2872
|
}
|
|
2223
2873
|
return args.length === 0 ? undefined : args.map(parseValue);
|
|
2224
2874
|
}
|
|
@@ -2233,10 +2883,36 @@ function parseStructArgs2(meta, fields, args, callLabel) {
|
|
|
2233
2883
|
for (let i = 0;i < fieldNames.length; i++) {
|
|
2234
2884
|
const name = fieldNames[i];
|
|
2235
2885
|
const entry = fields[name];
|
|
2236
|
-
|
|
2886
|
+
try {
|
|
2887
|
+
result[name] = parseTypedArg2(meta, entry, args[i]);
|
|
2888
|
+
} catch (err) {
|
|
2889
|
+
const typeDesc = describeType(meta.lookup, entry.id);
|
|
2890
|
+
throw new Error(`Invalid value for argument '${name}' (expected ${typeDesc}): ${JSON.stringify(args[i])}
|
|
2891
|
+
` + ` Hint: ${typeHint2(entry, meta)}`, { cause: err });
|
|
2892
|
+
}
|
|
2237
2893
|
}
|
|
2238
2894
|
return result;
|
|
2239
2895
|
}
|
|
2896
|
+
function typeHint2(entry, meta) {
|
|
2897
|
+
const resolved = entry.type === "lookupEntry" ? entry.value : entry;
|
|
2898
|
+
switch (resolved.type) {
|
|
2899
|
+
case "enum": {
|
|
2900
|
+
const variants = Object.keys(resolved.value);
|
|
2901
|
+
if (variants.length <= 6)
|
|
2902
|
+
return `a variant: ${variants.join(" | ")}`;
|
|
2903
|
+
return `one of ${variants.length} variants (e.g. ${variants.slice(0, 3).join(", ")})`;
|
|
2904
|
+
}
|
|
2905
|
+
case "struct":
|
|
2906
|
+
return `a JSON object with fields: ${Object.keys(resolved.value).join(", ")}`;
|
|
2907
|
+
case "tuple":
|
|
2908
|
+
return "a JSON array";
|
|
2909
|
+
case "sequence":
|
|
2910
|
+
case "array":
|
|
2911
|
+
return "a JSON array or hex-encoded bytes";
|
|
2912
|
+
default:
|
|
2913
|
+
return describeType(meta.lookup, entry.id);
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2240
2916
|
function normalizeValue2(lookup, entry, value) {
|
|
2241
2917
|
let resolved = entry;
|
|
2242
2918
|
while (resolved.type === "lookupEntry") {
|
|
@@ -2716,6 +3392,7 @@ registerChainCommands(cli);
|
|
|
2716
3392
|
registerInspectCommand(cli);
|
|
2717
3393
|
registerQueryCommand(cli);
|
|
2718
3394
|
registerConstCommand(cli);
|
|
3395
|
+
registerFocusedInspectCommands(cli);
|
|
2719
3396
|
registerAccountCommands(cli);
|
|
2720
3397
|
registerTxCommand(cli);
|
|
2721
3398
|
registerHashCommand(cli);
|