polkadot-cli 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -10
- package/dist/cli.mjs +276 -37
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Ships with Polkadot and all system parachains preconfigured with multiple fallba
|
|
|
23
23
|
- ✅ Batteries included — all system parachains and testnets already setup to be used
|
|
24
24
|
- ✅ File-based commands — run any command from a YAML/JSON file with variable substitution
|
|
25
25
|
- ✅ Parachain sovereign accounts — derive child and sibling addresses from a parachain ID
|
|
26
|
+
- ✅ Message signing — sign arbitrary bytes with account keypairs for use as `MultiSignature` arguments
|
|
27
|
+
- ✅ Bandersnatch member keys — derive Ring VRF member keys from mnemonics for on-chain member sets
|
|
26
28
|
|
|
27
29
|
### Preconfigured chains
|
|
28
30
|
|
|
@@ -296,14 +298,14 @@ dot query System.Account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
|
|
|
296
298
|
# Map without key — shows help/usage (use --dump to fetch all entries)
|
|
297
299
|
dot query System.Account
|
|
298
300
|
|
|
299
|
-
# Dump all map entries (requires --dump
|
|
300
|
-
dot query System.Account --dump
|
|
301
|
+
# Dump all map entries (requires --dump)
|
|
302
|
+
dot query System.Account --dump
|
|
301
303
|
|
|
302
304
|
# Enum variant as map key (case-insensitive)
|
|
303
305
|
dot query people-preview.ChunksManager.Chunks R2e9 1
|
|
304
306
|
|
|
305
307
|
# Pipe-safe — stdout is clean data, progress messages go to stderr
|
|
306
|
-
dot query System.Account --dump
|
|
308
|
+
dot query System.Account --dump | jq '.[0].value.data.free'
|
|
307
309
|
dot query System.Number --output json | jq '.+1'
|
|
308
310
|
|
|
309
311
|
# Query a specific chain using chain prefix
|
|
@@ -324,12 +326,9 @@ dot query Staking.ErasStakers 100 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKut
|
|
|
324
326
|
dot query Staking.ErasStakers 100
|
|
325
327
|
|
|
326
328
|
# No keys — requires --dump (safety net for large maps)
|
|
327
|
-
dot query Staking.ErasStakers --dump
|
|
329
|
+
dot query Staking.ErasStakers --dump
|
|
328
330
|
```
|
|
329
331
|
|
|
330
|
-
The `--limit` option applies to partial key results just like it does for
|
|
331
|
-
`--dump` (default: 100, use `--limit 0` for unlimited).
|
|
332
|
-
|
|
333
332
|
#### Output formatting
|
|
334
333
|
|
|
335
334
|
Query results automatically convert on-chain types for readability:
|
|
@@ -478,8 +477,10 @@ dot tx Balances.transferKeepAlive 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694
|
|
|
478
477
|
# Submit a raw SCALE-encoded call (e.g. from a multisig proposal or another tool)
|
|
479
478
|
dot tx 0x0503008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48 --from alice
|
|
480
479
|
|
|
481
|
-
# Batch multiple transfers with Utility.batchAll
|
|
482
|
-
dot tx
|
|
480
|
+
# Batch multiple transfers with Utility.batchAll (comma-separated encoded calls)
|
|
481
|
+
A=$(dot tx Balances.transfer_keep_alive 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty 1000000000000 --encode)
|
|
482
|
+
B=$(dot tx Balances.transfer_keep_alive 5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y 2000000000000 --encode)
|
|
483
|
+
dot tx Utility.batchAll $A,$B --from alice
|
|
483
484
|
```
|
|
484
485
|
|
|
485
486
|
#### Enum shorthand
|
|
@@ -861,6 +862,31 @@ dot hash blake2b256 0xdeadbeef --output json
|
|
|
861
862
|
|
|
862
863
|
Run `dot hash` with no arguments to see all available algorithms.
|
|
863
864
|
|
|
865
|
+
### Sign messages
|
|
866
|
+
|
|
867
|
+
Sign arbitrary messages with an account keypair. Output is a `Sr25519(0x...)` value directly usable as a `MultiSignature` enum argument in transaction calls.
|
|
868
|
+
|
|
869
|
+
```bash
|
|
870
|
+
# Sign a text message
|
|
871
|
+
dot sign "hello world" --from alice
|
|
872
|
+
|
|
873
|
+
# Sign hex-encoded bytes
|
|
874
|
+
dot sign 0xdeadbeef --from alice
|
|
875
|
+
|
|
876
|
+
# Sign file contents
|
|
877
|
+
dot sign --file ./payload.bin --from alice
|
|
878
|
+
|
|
879
|
+
# Read from stdin
|
|
880
|
+
echo -n "hello" | dot sign --stdin --from alice
|
|
881
|
+
|
|
882
|
+
# JSON output (for scripting)
|
|
883
|
+
dot sign "hello" --from alice --output json
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
Output shows the crypto type, message bytes in hex, raw signature, and an `Enum` value directly pasteable into tx arguments (e.g. `Sr25519(0x...)`).
|
|
887
|
+
|
|
888
|
+
Use `--type` to select the signature algorithm (default: `sr25519`). Run `dot sign` with no arguments to see usage and examples.
|
|
889
|
+
|
|
864
890
|
### Parachain sovereign accounts
|
|
865
891
|
|
|
866
892
|
Derive the sovereign account addresses for a parachain. These are deterministic accounts derived from a parachain ID — no chain connection required.
|
|
@@ -887,6 +913,38 @@ dot parachain 1000 --output json
|
|
|
887
913
|
|
|
888
914
|
Run `dot parachain` with no arguments to see usage and examples.
|
|
889
915
|
|
|
916
|
+
### Bandersnatch member keys
|
|
917
|
+
|
|
918
|
+
Derive Bandersnatch member keys from account mnemonics for on-chain member set registration (personhood proofs, Ring VRF). Uses the [`verifiablejs`](https://github.com/paritytech/verifiablejs) WASM library.
|
|
919
|
+
|
|
920
|
+
```bash
|
|
921
|
+
# Derive unkeyed member key (lite person)
|
|
922
|
+
dot verifiable alice
|
|
923
|
+
|
|
924
|
+
# Derive keyed member key (full person — "candidate" context)
|
|
925
|
+
dot verifiable alice --context candidate
|
|
926
|
+
|
|
927
|
+
# Arbitrary context string
|
|
928
|
+
dot verifiable alice --context pps
|
|
929
|
+
|
|
930
|
+
# JSON output (for scripting)
|
|
931
|
+
dot verifiable alice --context candidate --output json
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
The derivation flow:
|
|
935
|
+
|
|
936
|
+
```
|
|
937
|
+
Mnemonic → mnemonicToEntropy() → blake2b256(entropy, context?) → member_from_entropy() → 32-byte member key
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
- **Unkeyed** (no `--context`): `blake2b256(entropy)` — used for lite person registration
|
|
941
|
+
- **With context** (e.g. `--context candidate`): `blake2b256(entropy, key="candidate")` — used for full person registration. The `--context` value is passed as the raw UTF-8 bytes of the blake2b key parameter.
|
|
942
|
+
- Both 12-word and 24-word mnemonics are supported
|
|
943
|
+
|
|
944
|
+
Derived keys are saved to the account store. For stored accounts, saved keys appear in `dot account inspect` output. When creating a new account with `dot account create`, both unkeyed and `candidate` keys are automatically derived and saved.
|
|
945
|
+
|
|
946
|
+
Run `dot verifiable` with no arguments to see usage and the full derivation diagram.
|
|
947
|
+
|
|
890
948
|
### Getting help
|
|
891
949
|
|
|
892
950
|
Every command supports `--help` to show its detailed usage, available actions, and examples:
|
|
@@ -927,7 +985,6 @@ dot tx.System.remark 0xdead # shows call help (no error)
|
|
|
927
985
|
| `--light-client` | Use Smoldot light client |
|
|
928
986
|
| `--output json` | Raw JSON output (default: pretty) |
|
|
929
987
|
| `--dump` | Dump all entries of a storage map (required for keyless map queries) |
|
|
930
|
-
| `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
|
|
931
988
|
| `-w, --wait <level>` | Tx wait level: `broadcast`, `best-block` / `best`, `finalized` (default) |
|
|
932
989
|
|
|
933
990
|
### Pipe-safe output
|
package/dist/cli.mjs
CHANGED
|
@@ -362,10 +362,9 @@ function tryDerivePublicKey(envVarName, path = "") {
|
|
|
362
362
|
return null;
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
|
-
async function
|
|
365
|
+
async function resolveAccountKeypair(name) {
|
|
366
366
|
if (isDevAccount(name)) {
|
|
367
|
-
|
|
368
|
-
return getPolkadotSigner(keypair2.publicKey, "Sr25519", keypair2.sign);
|
|
367
|
+
return getDevKeypair(name);
|
|
369
368
|
}
|
|
370
369
|
const accountsFile = await loadAccounts();
|
|
371
370
|
const account = findAccount(accountsFile, name);
|
|
@@ -378,7 +377,10 @@ async function resolveAccountSigner(name) {
|
|
|
378
377
|
}
|
|
379
378
|
const secret = resolveSecret(account.secret);
|
|
380
379
|
const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
|
|
381
|
-
|
|
380
|
+
return isHexSeed ? deriveFromHexSeed(secret, account.derivationPath) : deriveFromMnemonic(secret, account.derivationPath);
|
|
381
|
+
}
|
|
382
|
+
async function resolveAccountSigner(name) {
|
|
383
|
+
const keypair = await resolveAccountKeypair(name);
|
|
382
384
|
return getPolkadotSigner(keypair.publicKey, "Sr25519", keypair.sign);
|
|
383
385
|
}
|
|
384
386
|
var DEV_NAMES;
|
|
@@ -510,6 +512,25 @@ var init_output = __esm(() => {
|
|
|
510
512
|
SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
511
513
|
});
|
|
512
514
|
|
|
515
|
+
// src/core/bandersnatch.ts
|
|
516
|
+
var exports_bandersnatch = {};
|
|
517
|
+
__export(exports_bandersnatch, {
|
|
518
|
+
deriveBandersnatchMember: () => deriveBandersnatchMember
|
|
519
|
+
});
|
|
520
|
+
import { blake2b } from "@noble/hashes/blake2.js";
|
|
521
|
+
import { mnemonicToEntropy as mnemonicToEntropy2 } from "@polkadot-labs/hdkd-helpers";
|
|
522
|
+
import { member_from_entropy } from "verifiablejs/nodejs";
|
|
523
|
+
function deriveBandersnatchMember(mnemonic, context) {
|
|
524
|
+
const entropy = mnemonicToEntropy2(mnemonic);
|
|
525
|
+
const opts = { dkLen: 32 };
|
|
526
|
+
if (context) {
|
|
527
|
+
opts.key = new TextEncoder().encode(context);
|
|
528
|
+
}
|
|
529
|
+
const hashed = blake2b(entropy, opts);
|
|
530
|
+
return member_from_entropy(hashed);
|
|
531
|
+
}
|
|
532
|
+
var init_bandersnatch = () => {};
|
|
533
|
+
|
|
513
534
|
// src/utils/errors.ts
|
|
514
535
|
var CliError, ConnectionError, MetadataError;
|
|
515
536
|
var init_errors = __esm(() => {
|
|
@@ -1023,8 +1044,12 @@ function typeHint(entry, meta) {
|
|
|
1023
1044
|
case "tuple":
|
|
1024
1045
|
return "a JSON array";
|
|
1025
1046
|
case "sequence":
|
|
1026
|
-
case "array":
|
|
1027
|
-
|
|
1047
|
+
case "array": {
|
|
1048
|
+
const inner = resolved.value;
|
|
1049
|
+
if (inner?.type === "primitive" && inner.value === "u8")
|
|
1050
|
+
return "hex-encoded bytes or text";
|
|
1051
|
+
return "a JSON array, comma-separated values, or hex-encoded bytes";
|
|
1052
|
+
}
|
|
1028
1053
|
default:
|
|
1029
1054
|
return describeType(meta.lookup, entry.id);
|
|
1030
1055
|
}
|
|
@@ -1233,6 +1258,10 @@ async function parseTypedArg(meta, entry, arg) {
|
|
|
1233
1258
|
return normalizeValue(meta.lookup, entry, JSON.parse(arg));
|
|
1234
1259
|
} catch {}
|
|
1235
1260
|
}
|
|
1261
|
+
if (arg.includes(",")) {
|
|
1262
|
+
const elements = arg.split(",");
|
|
1263
|
+
return Promise.all(elements.map((el) => parseTypedArg(meta, inner, el.trim())));
|
|
1264
|
+
}
|
|
1236
1265
|
if (/^0x[0-9a-fA-F]*$/.test(arg))
|
|
1237
1266
|
return Binary2.fromHex(arg);
|
|
1238
1267
|
return parseValue(arg);
|
|
@@ -1758,7 +1787,6 @@ async function showItemHelp(category, target, opts) {
|
|
|
1758
1787
|
console.log();
|
|
1759
1788
|
console.log(`${BOLD}Options:${RESET}`);
|
|
1760
1789
|
console.log(` --dump Dump all entries of a map (required for keyless map queries)`);
|
|
1761
|
-
console.log(` --limit <n> Max entries for map queries (0 = unlimited, default: 100)`);
|
|
1762
1790
|
console.log();
|
|
1763
1791
|
return;
|
|
1764
1792
|
}
|
|
@@ -1825,7 +1853,7 @@ var init_focused_inspect = __esm(() => {
|
|
|
1825
1853
|
});
|
|
1826
1854
|
|
|
1827
1855
|
// src/core/hash.ts
|
|
1828
|
-
import { blake2b } from "@noble/hashes/blake2.js";
|
|
1856
|
+
import { blake2b as blake2b2 } from "@noble/hashes/blake2.js";
|
|
1829
1857
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
1830
1858
|
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
1831
1859
|
import { bytesToHex, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
|
|
@@ -1859,12 +1887,12 @@ var ALGORITHMS;
|
|
|
1859
1887
|
var init_hash = __esm(() => {
|
|
1860
1888
|
ALGORITHMS = {
|
|
1861
1889
|
blake2b256: {
|
|
1862
|
-
compute: (data) =>
|
|
1890
|
+
compute: (data) => blake2b2(data, { dkLen: 32 }),
|
|
1863
1891
|
outputLen: 32,
|
|
1864
1892
|
description: "BLAKE2b with 256-bit output"
|
|
1865
1893
|
},
|
|
1866
1894
|
blake2b128: {
|
|
1867
|
-
compute: (data) =>
|
|
1895
|
+
compute: (data) => blake2b2(data, { dkLen: 16 }),
|
|
1868
1896
|
outputLen: 16,
|
|
1869
1897
|
description: "BLAKE2b with 128-bit output"
|
|
1870
1898
|
},
|
|
@@ -2158,7 +2186,7 @@ var init_complete = __esm(() => {
|
|
|
2158
2186
|
apis: "apis",
|
|
2159
2187
|
api: "apis"
|
|
2160
2188
|
};
|
|
2161
|
-
NAMED_COMMANDS = ["chain", "account", "inspect", "hash", "parachain", "completions"];
|
|
2189
|
+
NAMED_COMMANDS = ["chain", "account", "inspect", "hash", "sign", "parachain", "completions"];
|
|
2162
2190
|
CHAIN_SUBCOMMANDS = ["add", "remove", "update", "list", "default"];
|
|
2163
2191
|
ACCOUNT_SUBCOMMANDS = [
|
|
2164
2192
|
"add",
|
|
@@ -2183,13 +2211,13 @@ var init_complete = __esm(() => {
|
|
|
2183
2211
|
"--mortality",
|
|
2184
2212
|
"--at"
|
|
2185
2213
|
];
|
|
2186
|
-
QUERY_OPTIONS = [
|
|
2214
|
+
QUERY_OPTIONS = [];
|
|
2187
2215
|
});
|
|
2188
2216
|
|
|
2189
2217
|
// src/cli.ts
|
|
2190
2218
|
import cac from "cac";
|
|
2191
2219
|
// package.json
|
|
2192
|
-
var version = "1.
|
|
2220
|
+
var version = "1.12.0";
|
|
2193
2221
|
|
|
2194
2222
|
// src/commands/account.ts
|
|
2195
2223
|
init_accounts_store();
|
|
@@ -2276,19 +2304,26 @@ async function accountCreate(name, opts) {
|
|
|
2276
2304
|
const { mnemonic, publicKey } = createNewAccount(path);
|
|
2277
2305
|
const hexPub = publicKeyToHex(publicKey);
|
|
2278
2306
|
const address = toSs58(publicKey);
|
|
2307
|
+
const { deriveBandersnatchMember: deriveBandersnatchMember2 } = await Promise.resolve().then(() => (init_bandersnatch(), exports_bandersnatch));
|
|
2308
|
+
const bandersnatch = {};
|
|
2309
|
+
bandersnatch[""] = publicKeyToHex(deriveBandersnatchMember2(mnemonic));
|
|
2310
|
+
bandersnatch.candidate = publicKeyToHex(deriveBandersnatchMember2(mnemonic, "candidate"));
|
|
2279
2311
|
accountsFile.accounts.push({
|
|
2280
2312
|
name,
|
|
2281
2313
|
secret: mnemonic,
|
|
2282
2314
|
publicKey: hexPub,
|
|
2283
|
-
derivationPath: path
|
|
2315
|
+
derivationPath: path,
|
|
2316
|
+
bandersnatch
|
|
2284
2317
|
});
|
|
2285
2318
|
await saveAccounts(accountsFile);
|
|
2286
2319
|
printHeading("Account Created");
|
|
2287
|
-
console.log(` ${BOLD}Name:${RESET}
|
|
2320
|
+
console.log(` ${BOLD}Name:${RESET} ${name}`);
|
|
2288
2321
|
if (path)
|
|
2289
|
-
console.log(` ${BOLD}Path:${RESET}
|
|
2290
|
-
console.log(` ${BOLD}Address:${RESET}
|
|
2291
|
-
console.log(` ${BOLD}
|
|
2322
|
+
console.log(` ${BOLD}Path:${RESET} ${path}`);
|
|
2323
|
+
console.log(` ${BOLD}Address:${RESET} ${address}`);
|
|
2324
|
+
console.log(` ${BOLD}Bandersnatch:${RESET} ${bandersnatch[""]}`);
|
|
2325
|
+
console.log(` ${BOLD}(candidate)${RESET} ${bandersnatch.candidate}`);
|
|
2326
|
+
console.log(` ${BOLD}Mnemonic:${RESET} ${mnemonic}`);
|
|
2292
2327
|
console.log();
|
|
2293
2328
|
console.log(` ${YELLOW}Save this mnemonic phrase! It is the only way to recover this account.${RESET}`);
|
|
2294
2329
|
console.log();
|
|
@@ -2565,6 +2600,7 @@ async function accountInspect(input, opts) {
|
|
|
2565
2600
|
}
|
|
2566
2601
|
let name;
|
|
2567
2602
|
let publicKeyHex;
|
|
2603
|
+
let bandersnatch;
|
|
2568
2604
|
if (isDevAccount(input)) {
|
|
2569
2605
|
name = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
|
|
2570
2606
|
const devAddr = getDevAddress(input);
|
|
@@ -2574,6 +2610,7 @@ async function accountInspect(input, opts) {
|
|
|
2574
2610
|
const account = findAccount(accountsFile, input);
|
|
2575
2611
|
if (account) {
|
|
2576
2612
|
name = account.name;
|
|
2613
|
+
bandersnatch = account.bandersnatch;
|
|
2577
2614
|
if (account.publicKey) {
|
|
2578
2615
|
publicKeyHex = account.publicKey;
|
|
2579
2616
|
} else if (account.secret !== undefined && isEnvSecret(account.secret)) {
|
|
@@ -2604,6 +2641,8 @@ async function accountInspect(input, opts) {
|
|
|
2604
2641
|
const result = { publicKey: publicKeyHex, ss58, prefix };
|
|
2605
2642
|
if (name)
|
|
2606
2643
|
result.name = name;
|
|
2644
|
+
if (bandersnatch && Object.keys(bandersnatch).length > 0)
|
|
2645
|
+
result.bandersnatch = bandersnatch;
|
|
2607
2646
|
console.log(formatJson(result));
|
|
2608
2647
|
} else {
|
|
2609
2648
|
printHeading("Account Info");
|
|
@@ -2611,6 +2650,18 @@ async function accountInspect(input, opts) {
|
|
|
2611
2650
|
console.log(` ${BOLD}Name:${RESET} ${name}`);
|
|
2612
2651
|
console.log(` ${BOLD}Public Key:${RESET} ${publicKeyHex}`);
|
|
2613
2652
|
console.log(` ${BOLD}SS58:${RESET} ${ss58}`);
|
|
2653
|
+
if (bandersnatch && Object.keys(bandersnatch).length > 0) {
|
|
2654
|
+
const entries = Object.entries(bandersnatch);
|
|
2655
|
+
for (let i = 0;i < entries.length; i++) {
|
|
2656
|
+
const [key, hex] = entries[i];
|
|
2657
|
+
const label = key ? `(${key})` : "";
|
|
2658
|
+
if (i === 0) {
|
|
2659
|
+
console.log(` ${BOLD}Bandersnatch:${RESET}${label ? ` ${label}` : ""} ${hex}`);
|
|
2660
|
+
} else {
|
|
2661
|
+
console.log(` ${label ? `${label} ` : ""}${hex}`);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2614
2665
|
console.log(` ${BOLD}Prefix:${RESET} ${prefix}`);
|
|
2615
2666
|
console.log();
|
|
2616
2667
|
}
|
|
@@ -3407,7 +3458,6 @@ async function showItemHelp2(category, target, opts) {
|
|
|
3407
3458
|
console.log();
|
|
3408
3459
|
console.log(`${BOLD}Options:${RESET}`);
|
|
3409
3460
|
console.log(` --dump Dump all entries of a map (required for keyless map queries)`);
|
|
3410
|
-
console.log(` --limit <n> Max entries for map queries (0 = unlimited, default: 100)`);
|
|
3411
3461
|
console.log();
|
|
3412
3462
|
return;
|
|
3413
3463
|
}
|
|
@@ -3967,17 +4017,10 @@ async function handleQuery(target, keys, opts) {
|
|
|
3967
4017
|
return;
|
|
3968
4018
|
}
|
|
3969
4019
|
const entries = await storageApi.getEntries(...parsedKeys);
|
|
3970
|
-
|
|
3971
|
-
const truncated = limit > 0 && entries.length > limit;
|
|
3972
|
-
const display = truncated ? entries.slice(0, limit) : entries;
|
|
3973
|
-
printResult(display.map((e) => ({
|
|
4020
|
+
printResult(entries.map((e) => ({
|
|
3974
4021
|
keys: e.keyArgs,
|
|
3975
4022
|
value: e.value
|
|
3976
4023
|
})), format);
|
|
3977
|
-
if (truncated) {
|
|
3978
|
-
console.error(`
|
|
3979
|
-
${DIM}Showing ${limit} of ${entries.length} entries. Use --limit 0 for all.${RESET}`);
|
|
3980
|
-
}
|
|
3981
4024
|
} else {
|
|
3982
4025
|
const result = await storageApi.getValue(...parsedKeys);
|
|
3983
4026
|
printResult(result, format);
|
|
@@ -4026,6 +4069,101 @@ async function parseStorageKeys(meta, palletName2, storageItem, args) {
|
|
|
4026
4069
|
return Promise.all(args.map((arg) => parseTypedArg(meta, keyEntry, arg)));
|
|
4027
4070
|
}
|
|
4028
4071
|
|
|
4072
|
+
// src/commands/sign.ts
|
|
4073
|
+
init_accounts();
|
|
4074
|
+
init_hash();
|
|
4075
|
+
init_output();
|
|
4076
|
+
init_errors();
|
|
4077
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
4078
|
+
var SUPPORTED_TYPES = ["sr25519"];
|
|
4079
|
+
function isSupportedType(type) {
|
|
4080
|
+
return SUPPORTED_TYPES.includes(type.toLowerCase());
|
|
4081
|
+
}
|
|
4082
|
+
function variantName(type) {
|
|
4083
|
+
switch (type) {
|
|
4084
|
+
case "sr25519":
|
|
4085
|
+
return "Sr25519";
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
async function resolveInput2(data, opts) {
|
|
4089
|
+
const sources = [data !== undefined, !!opts.file, !!opts.stdin].filter(Boolean).length;
|
|
4090
|
+
if (sources > 1) {
|
|
4091
|
+
throw new CliError("Provide only one of: inline data, --file, or --stdin");
|
|
4092
|
+
}
|
|
4093
|
+
if (sources === 0) {
|
|
4094
|
+
throw new CliError("No input provided. Pass data as argument, or use --file or --stdin");
|
|
4095
|
+
}
|
|
4096
|
+
if (opts.file) {
|
|
4097
|
+
const buf = await readFile4(opts.file);
|
|
4098
|
+
return new Uint8Array(buf);
|
|
4099
|
+
}
|
|
4100
|
+
if (opts.stdin) {
|
|
4101
|
+
const chunks = [];
|
|
4102
|
+
for await (const chunk of process.stdin) {
|
|
4103
|
+
chunks.push(chunk);
|
|
4104
|
+
}
|
|
4105
|
+
return new Uint8Array(Buffer.concat(chunks));
|
|
4106
|
+
}
|
|
4107
|
+
return parseInputData(data);
|
|
4108
|
+
}
|
|
4109
|
+
function printSignHelp() {
|
|
4110
|
+
console.log(`${BOLD}Usage:${RESET} dot sign <message> --from <account> [options]
|
|
4111
|
+
`);
|
|
4112
|
+
console.log(`${BOLD}Arguments:${RESET}`);
|
|
4113
|
+
console.log(` ${CYAN}message${RESET} ${DIM}Message to sign (text or 0x-prefixed hex)${RESET}`);
|
|
4114
|
+
console.log(`
|
|
4115
|
+
${BOLD}Options:${RESET}`);
|
|
4116
|
+
console.log(` ${CYAN}--from <name>${RESET} ${DIM}Account to sign with (required)${RESET}`);
|
|
4117
|
+
console.log(` ${CYAN}--type <algo>${RESET} ${DIM}Signature type: sr25519 (default)${RESET}`);
|
|
4118
|
+
console.log(` ${CYAN}--file <path>${RESET} ${DIM}Sign file contents${RESET}`);
|
|
4119
|
+
console.log(` ${CYAN}--stdin${RESET} ${DIM}Read from stdin${RESET}`);
|
|
4120
|
+
console.log(` ${CYAN}--output json${RESET} ${DIM}Output as JSON${RESET}`);
|
|
4121
|
+
console.log(`
|
|
4122
|
+
${BOLD}Examples:${RESET}`);
|
|
4123
|
+
console.log(` ${DIM}$ dot sign "hello world" --from alice${RESET}`);
|
|
4124
|
+
console.log(` ${DIM}$ dot sign 0xdeadbeef --from alice${RESET}`);
|
|
4125
|
+
console.log(` ${DIM}$ dot sign --file ./payload.bin --from alice${RESET}`);
|
|
4126
|
+
console.log(` ${DIM}$ echo -n "hello" | dot sign --stdin --from alice${RESET}`);
|
|
4127
|
+
console.log(` ${DIM}$ dot sign "hello" --from alice --output json${RESET}`);
|
|
4128
|
+
}
|
|
4129
|
+
function registerSignCommand(cli) {
|
|
4130
|
+
cli.command("sign [message]", "Sign a message with an account keypair").option("--from <name>", "Account to sign with").option("--type <algo>", "Signature type (default: sr25519)").option("--file <path>", "Sign file contents (raw bytes)").option("--stdin", "Read data from stdin").action(async (message, opts) => {
|
|
4131
|
+
if (!message && !opts.file && !opts.stdin) {
|
|
4132
|
+
printSignHelp();
|
|
4133
|
+
return;
|
|
4134
|
+
}
|
|
4135
|
+
if (!opts.from) {
|
|
4136
|
+
throw new CliError("--from is required. Specify the account to sign with.");
|
|
4137
|
+
}
|
|
4138
|
+
const type = opts.type?.toLowerCase() ?? "sr25519";
|
|
4139
|
+
if (!isSupportedType(type)) {
|
|
4140
|
+
throw new CliError(`Unsupported signature type "${opts.type}". Supported: ${SUPPORTED_TYPES.join(", ")}`);
|
|
4141
|
+
}
|
|
4142
|
+
const input = await resolveInput2(message, opts);
|
|
4143
|
+
const keypair = await resolveAccountKeypair(opts.from);
|
|
4144
|
+
const signature = keypair.sign(input);
|
|
4145
|
+
const hexMessage = toHex2(input);
|
|
4146
|
+
const hexSignature = toHex2(signature);
|
|
4147
|
+
const variant = variantName(type);
|
|
4148
|
+
const enumValue = `${variant}(${hexSignature})`;
|
|
4149
|
+
const result = {
|
|
4150
|
+
type: variant,
|
|
4151
|
+
message: hexMessage,
|
|
4152
|
+
signature: hexSignature,
|
|
4153
|
+
enum: enumValue
|
|
4154
|
+
};
|
|
4155
|
+
const format = opts.output ?? "pretty";
|
|
4156
|
+
if (format === "json") {
|
|
4157
|
+
printResult(result, "json");
|
|
4158
|
+
} else {
|
|
4159
|
+
console.log(` ${BOLD}Type:${RESET} ${result.type}`);
|
|
4160
|
+
console.log(` ${BOLD}Message:${RESET} ${result.message}`);
|
|
4161
|
+
console.log(` ${BOLD}Signature:${RESET} ${result.signature}`);
|
|
4162
|
+
console.log(` ${BOLD}Enum:${RESET} ${result.enum}`);
|
|
4163
|
+
}
|
|
4164
|
+
});
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4029
4167
|
// src/commands/tx.ts
|
|
4030
4168
|
init_store();
|
|
4031
4169
|
init_types();
|
|
@@ -4650,8 +4788,12 @@ function typeHint2(entry, meta) {
|
|
|
4650
4788
|
case "tuple":
|
|
4651
4789
|
return "a JSON array";
|
|
4652
4790
|
case "sequence":
|
|
4653
|
-
case "array":
|
|
4654
|
-
|
|
4791
|
+
case "array": {
|
|
4792
|
+
const inner = resolved.value;
|
|
4793
|
+
if (inner?.type === "primitive" && inner.value === "u8")
|
|
4794
|
+
return "hex-encoded bytes or text";
|
|
4795
|
+
return "a JSON array, comma-separated values, or hex-encoded bytes";
|
|
4796
|
+
}
|
|
4655
4797
|
default:
|
|
4656
4798
|
return describeType(meta.lookup, entry.id);
|
|
4657
4799
|
}
|
|
@@ -4880,6 +5022,10 @@ async function parseTypedArg2(meta, entry, arg) {
|
|
|
4880
5022
|
return normalizeValue2(meta.lookup, entry, JSON.parse(arg));
|
|
4881
5023
|
} catch {}
|
|
4882
5024
|
}
|
|
5025
|
+
if (arg.includes(",")) {
|
|
5026
|
+
const elements = arg.split(",");
|
|
5027
|
+
return Promise.all(elements.map((el) => parseTypedArg2(meta, inner, el.trim())));
|
|
5028
|
+
}
|
|
4883
5029
|
if (/^0x[0-9a-fA-F]*$/.test(arg))
|
|
4884
5030
|
return Binary3.fromHex(arg);
|
|
4885
5031
|
return parseValue(arg);
|
|
@@ -5052,9 +5198,101 @@ function watchTransaction(observable, level) {
|
|
|
5052
5198
|
});
|
|
5053
5199
|
}
|
|
5054
5200
|
|
|
5201
|
+
// src/commands/verifiable.ts
|
|
5202
|
+
init_accounts_store();
|
|
5203
|
+
init_accounts();
|
|
5204
|
+
init_bandersnatch();
|
|
5205
|
+
init_output();
|
|
5206
|
+
var DEV_PHRASE2 = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
|
5207
|
+
var VERIFIABLE_HELP = `
|
|
5208
|
+
${BOLD}Usage:${RESET}
|
|
5209
|
+
$ dot verifiable <account> [--context <value>]
|
|
5210
|
+
|
|
5211
|
+
${BOLD}Arguments:${RESET}
|
|
5212
|
+
account Account name (stored or dev account)
|
|
5213
|
+
|
|
5214
|
+
${BOLD}Options:${RESET}
|
|
5215
|
+
--context <value> Blake2b context for keyed derivation (e.g. "candidate")
|
|
5216
|
+
|
|
5217
|
+
${BOLD}Examples:${RESET}
|
|
5218
|
+
$ dot verifiable alice Unkeyed derivation (lite person)
|
|
5219
|
+
$ dot verifiable alice --context candidate Keyed with "candidate" (full person)
|
|
5220
|
+
$ dot verifiable my-account --context candidate
|
|
5221
|
+
|
|
5222
|
+
${BOLD}How it works:${RESET}
|
|
5223
|
+
|
|
5224
|
+
Mnemonic (12/24 words)
|
|
5225
|
+
│ mnemonicToEntropy() (raw BIP39 entropy, NOT miniSecret)
|
|
5226
|
+
▼
|
|
5227
|
+
blake2b256(entropy, context?) keyed or unkeyed
|
|
5228
|
+
▼
|
|
5229
|
+
member_from_entropy() verifiablejs WASM (Bandersnatch curve)
|
|
5230
|
+
▼
|
|
5231
|
+
32-byte member key for on-chain member set registration
|
|
5232
|
+
`.trimStart();
|
|
5233
|
+
function registerVerifiableCommands(cli) {
|
|
5234
|
+
cli.command("verifiable [account]", "Derive Bandersnatch member key from account mnemonic").option("--context <value>", "Blake2b context for keyed derivation (e.g. candidate)").action(async (account, opts) => {
|
|
5235
|
+
if (!account) {
|
|
5236
|
+
console.log(VERIFIABLE_HELP);
|
|
5237
|
+
return;
|
|
5238
|
+
}
|
|
5239
|
+
return deriveVerifiable(account, opts);
|
|
5240
|
+
});
|
|
5241
|
+
}
|
|
5242
|
+
async function deriveVerifiable(account, opts) {
|
|
5243
|
+
const mnemonic = await resolveMnemonic(account);
|
|
5244
|
+
const memberKey = deriveBandersnatchMember(mnemonic, opts.context);
|
|
5245
|
+
const memberKeyHex = publicKeyToHex(memberKey);
|
|
5246
|
+
if (!isDevAccount(account)) {
|
|
5247
|
+
const accountsFile = await loadAccounts();
|
|
5248
|
+
const stored = findAccount(accountsFile, account);
|
|
5249
|
+
if (stored) {
|
|
5250
|
+
if (!stored.bandersnatch)
|
|
5251
|
+
stored.bandersnatch = {};
|
|
5252
|
+
stored.bandersnatch[opts.context ?? ""] = memberKeyHex;
|
|
5253
|
+
await saveAccounts(accountsFile);
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
if (opts.output === "json") {
|
|
5257
|
+
const result = {
|
|
5258
|
+
account,
|
|
5259
|
+
memberKey: memberKeyHex
|
|
5260
|
+
};
|
|
5261
|
+
if (opts.context)
|
|
5262
|
+
result.context = opts.context;
|
|
5263
|
+
console.log(formatJson(result));
|
|
5264
|
+
} else {
|
|
5265
|
+
printHeading("Bandersnatch Member Key");
|
|
5266
|
+
console.log(` ${BOLD}Account:${RESET} ${account}`);
|
|
5267
|
+
if (opts.context)
|
|
5268
|
+
console.log(` ${BOLD}Context:${RESET} ${opts.context}`);
|
|
5269
|
+
console.log(` ${BOLD}Member Key:${RESET} ${memberKeyHex}`);
|
|
5270
|
+
console.log();
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
async function resolveMnemonic(account) {
|
|
5274
|
+
if (isDevAccount(account)) {
|
|
5275
|
+
return DEV_PHRASE2;
|
|
5276
|
+
}
|
|
5277
|
+
const accountsFile = await loadAccounts();
|
|
5278
|
+
const stored = findAccount(accountsFile, account);
|
|
5279
|
+
if (!stored) {
|
|
5280
|
+
const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)];
|
|
5281
|
+
throw new Error(`Unknown account "${account}". Available accounts: ${available.join(", ")}`);
|
|
5282
|
+
}
|
|
5283
|
+
if (isWatchOnly(stored)) {
|
|
5284
|
+
throw new Error(`Account "${account}" is watch-only (no secret). Cannot derive Bandersnatch key.`);
|
|
5285
|
+
}
|
|
5286
|
+
const secret = resolveSecret(stored.secret);
|
|
5287
|
+
if (isHexPublicKey(`0x${secret.replace(/^0x/, "")}`)) {
|
|
5288
|
+
throw new Error(`Account "${account}" uses a hex seed. Bandersnatch derivation requires a BIP39 mnemonic.`);
|
|
5289
|
+
}
|
|
5290
|
+
return secret;
|
|
5291
|
+
}
|
|
5292
|
+
|
|
5055
5293
|
// src/config/store.ts
|
|
5056
5294
|
init_types();
|
|
5057
|
-
import { access as access3, mkdir as mkdir3, readFile as
|
|
5295
|
+
import { access as access3, mkdir as mkdir3, readFile as readFile5, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
5058
5296
|
import { homedir as homedir2 } from "node:os";
|
|
5059
5297
|
import { join as join3 } from "node:path";
|
|
5060
5298
|
var DOT_DIR2 = join3(homedir2(), ".polkadot");
|
|
@@ -5074,7 +5312,7 @@ async function fileExists3(path) {
|
|
|
5074
5312
|
async function loadConfig2() {
|
|
5075
5313
|
await ensureDir3(DOT_DIR2);
|
|
5076
5314
|
if (await fileExists3(CONFIG_PATH2)) {
|
|
5077
|
-
const saved = JSON.parse(await
|
|
5315
|
+
const saved = JSON.parse(await readFile5(CONFIG_PATH2, "utf-8"));
|
|
5078
5316
|
return {
|
|
5079
5317
|
...saved,
|
|
5080
5318
|
chains: { ...DEFAULT_CONFIG.chains, ...saved.chains }
|
|
@@ -5091,7 +5329,7 @@ async function saveConfig2(config) {
|
|
|
5091
5329
|
|
|
5092
5330
|
// src/core/file-loader.ts
|
|
5093
5331
|
init_errors();
|
|
5094
|
-
import { access as access4, readFile as
|
|
5332
|
+
import { access as access4, readFile as readFile6 } from "node:fs/promises";
|
|
5095
5333
|
import { parse as parseYaml } from "yaml";
|
|
5096
5334
|
var CATEGORIES = ["tx", "query", "const", "apis"];
|
|
5097
5335
|
var FILE_EXTENSIONS = [".json", ".yaml", ".yml"];
|
|
@@ -5156,7 +5394,7 @@ async function loadCommandFile(filePath, cliVars) {
|
|
|
5156
5394
|
} catch {
|
|
5157
5395
|
throw new CliError(`File not found: ${filePath}`);
|
|
5158
5396
|
}
|
|
5159
|
-
const rawText = await
|
|
5397
|
+
const rawText = await readFile6(filePath, "utf-8");
|
|
5160
5398
|
if (!rawText.trim()) {
|
|
5161
5399
|
throw new CliError(`File is empty: ${filePath}`);
|
|
5162
5400
|
}
|
|
@@ -5477,7 +5715,9 @@ if (process.argv[2] === "__complete") {
|
|
|
5477
5715
|
console.log(" chain Manage chain configurations");
|
|
5478
5716
|
console.log(" account Manage accounts");
|
|
5479
5717
|
console.log(" hash Hash utilities");
|
|
5718
|
+
console.log(" sign Sign a message with an account keypair");
|
|
5480
5719
|
console.log(" parachain Derive parachain sovereign accounts");
|
|
5720
|
+
console.log(" verifiable Derive Bandersnatch member key from mnemonic");
|
|
5481
5721
|
console.log(" completions <sh> Generate shell completions (zsh, bash, fish)");
|
|
5482
5722
|
console.log();
|
|
5483
5723
|
console.log("Global options:");
|
|
@@ -5500,12 +5740,12 @@ if (process.argv[2] === "__complete") {
|
|
|
5500
5740
|
registerInspectCommand(cli);
|
|
5501
5741
|
registerAccountCommands(cli);
|
|
5502
5742
|
registerHashCommand(cli);
|
|
5743
|
+
registerSignCommand(cli);
|
|
5503
5744
|
registerParachainCommand(cli);
|
|
5504
5745
|
registerCompletionsCommand(cli);
|
|
5746
|
+
registerVerifiableCommands(cli);
|
|
5505
5747
|
cli.command("[dotpath] [...args]").option("--from <name>", "Account to sign with (for tx)").option("--dry-run", "Estimate fees without submitting (for tx)").option("--encode", "Encode call to hex without signing (for tx)").option("--yaml", "Decode call to YAML file format (for tx)").option("--json", "Decode call to JSON file format (for tx)").option("--ext <json>", "Custom signed extension values as JSON (for tx)").option("-w, --wait <level>", "Resolve at: broadcast, best-block (or best), finalized (for tx)", {
|
|
5506
5748
|
default: "finalized"
|
|
5507
|
-
}).option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
|
|
5508
|
-
default: 100
|
|
5509
5749
|
}).option("--dump", "Dump all entries of a storage map (without specifying a key)").option("--var <kv>", "Template variable for file input (KEY=VALUE, repeatable)").option("--nonce <n>", "Custom nonce for manual tx sequencing (for tx)").option("--tip <amount>", "Tip to prioritize transaction (for tx)").option("--mortality <spec>", '"immortal" or period number (for tx)').option("--at <block>", 'Block hash, "best", or "finalized" to validate against (for tx)').action(async (dotpath, args, opts) => {
|
|
5510
5750
|
if (!dotpath) {
|
|
5511
5751
|
printHelp();
|
|
@@ -5538,7 +5778,6 @@ if (process.argv[2] === "__complete") {
|
|
|
5538
5778
|
case "query":
|
|
5539
5779
|
await handleQuery(target2, args, {
|
|
5540
5780
|
...handlerOpts2,
|
|
5541
|
-
limit: opts.limit,
|
|
5542
5781
|
dump: opts.dump,
|
|
5543
5782
|
parsedArgs: cmd.args
|
|
5544
5783
|
});
|
|
@@ -5581,7 +5820,7 @@ if (process.argv[2] === "__complete") {
|
|
|
5581
5820
|
}
|
|
5582
5821
|
switch (parsed.category) {
|
|
5583
5822
|
case "query":
|
|
5584
|
-
await handleQuery(target, args, { ...handlerOpts,
|
|
5823
|
+
await handleQuery(target, args, { ...handlerOpts, dump: opts.dump });
|
|
5585
5824
|
break;
|
|
5586
5825
|
case "tx": {
|
|
5587
5826
|
const txOpts = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polkadot-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "CLI tool for querying Polkadot-ecosystem on-chain state",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@polkadot-labs/hdkd-helpers": "^0.0.27",
|
|
46
46
|
"cac": "^6.7.14",
|
|
47
47
|
"polkadot-api": "^1.23.3",
|
|
48
|
+
"verifiablejs": "^1.2.0",
|
|
48
49
|
"ws": "^8.19.0",
|
|
49
50
|
"yaml": "^2.8.3"
|
|
50
51
|
},
|