polkadot-cli 1.11.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 +63 -8
- package/dist/cli.mjs +256 -33
- 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:
|
|
@@ -863,6 +862,31 @@ dot hash blake2b256 0xdeadbeef --output json
|
|
|
863
862
|
|
|
864
863
|
Run `dot hash` with no arguments to see all available algorithms.
|
|
865
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
|
+
|
|
866
890
|
### Parachain sovereign accounts
|
|
867
891
|
|
|
868
892
|
Derive the sovereign account addresses for a parachain. These are deterministic accounts derived from a parachain ID — no chain connection required.
|
|
@@ -889,6 +913,38 @@ dot parachain 1000 --output json
|
|
|
889
913
|
|
|
890
914
|
Run `dot parachain` with no arguments to see usage and examples.
|
|
891
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
|
+
|
|
892
948
|
### Getting help
|
|
893
949
|
|
|
894
950
|
Every command supports `--help` to show its detailed usage, available actions, and examples:
|
|
@@ -929,7 +985,6 @@ dot tx.System.remark 0xdead # shows call help (no error)
|
|
|
929
985
|
| `--light-client` | Use Smoldot light client |
|
|
930
986
|
| `--output json` | Raw JSON output (default: pretty) |
|
|
931
987
|
| `--dump` | Dump all entries of a storage map (required for keyless map queries) |
|
|
932
|
-
| `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
|
|
933
988
|
| `-w, --wait <level>` | Tx wait level: `broadcast`, `best-block` / `best`, `finalized` (default) |
|
|
934
989
|
|
|
935
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(() => {
|
|
@@ -1766,7 +1787,6 @@ async function showItemHelp(category, target, opts) {
|
|
|
1766
1787
|
console.log();
|
|
1767
1788
|
console.log(`${BOLD}Options:${RESET}`);
|
|
1768
1789
|
console.log(` --dump Dump all entries of a map (required for keyless map queries)`);
|
|
1769
|
-
console.log(` --limit <n> Max entries for map queries (0 = unlimited, default: 100)`);
|
|
1770
1790
|
console.log();
|
|
1771
1791
|
return;
|
|
1772
1792
|
}
|
|
@@ -1833,7 +1853,7 @@ var init_focused_inspect = __esm(() => {
|
|
|
1833
1853
|
});
|
|
1834
1854
|
|
|
1835
1855
|
// src/core/hash.ts
|
|
1836
|
-
import { blake2b } from "@noble/hashes/blake2.js";
|
|
1856
|
+
import { blake2b as blake2b2 } from "@noble/hashes/blake2.js";
|
|
1837
1857
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
1838
1858
|
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
1839
1859
|
import { bytesToHex, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
|
|
@@ -1867,12 +1887,12 @@ var ALGORITHMS;
|
|
|
1867
1887
|
var init_hash = __esm(() => {
|
|
1868
1888
|
ALGORITHMS = {
|
|
1869
1889
|
blake2b256: {
|
|
1870
|
-
compute: (data) =>
|
|
1890
|
+
compute: (data) => blake2b2(data, { dkLen: 32 }),
|
|
1871
1891
|
outputLen: 32,
|
|
1872
1892
|
description: "BLAKE2b with 256-bit output"
|
|
1873
1893
|
},
|
|
1874
1894
|
blake2b128: {
|
|
1875
|
-
compute: (data) =>
|
|
1895
|
+
compute: (data) => blake2b2(data, { dkLen: 16 }),
|
|
1876
1896
|
outputLen: 16,
|
|
1877
1897
|
description: "BLAKE2b with 128-bit output"
|
|
1878
1898
|
},
|
|
@@ -2166,7 +2186,7 @@ var init_complete = __esm(() => {
|
|
|
2166
2186
|
apis: "apis",
|
|
2167
2187
|
api: "apis"
|
|
2168
2188
|
};
|
|
2169
|
-
NAMED_COMMANDS = ["chain", "account", "inspect", "hash", "parachain", "completions"];
|
|
2189
|
+
NAMED_COMMANDS = ["chain", "account", "inspect", "hash", "sign", "parachain", "completions"];
|
|
2170
2190
|
CHAIN_SUBCOMMANDS = ["add", "remove", "update", "list", "default"];
|
|
2171
2191
|
ACCOUNT_SUBCOMMANDS = [
|
|
2172
2192
|
"add",
|
|
@@ -2191,13 +2211,13 @@ var init_complete = __esm(() => {
|
|
|
2191
2211
|
"--mortality",
|
|
2192
2212
|
"--at"
|
|
2193
2213
|
];
|
|
2194
|
-
QUERY_OPTIONS = [
|
|
2214
|
+
QUERY_OPTIONS = [];
|
|
2195
2215
|
});
|
|
2196
2216
|
|
|
2197
2217
|
// src/cli.ts
|
|
2198
2218
|
import cac from "cac";
|
|
2199
2219
|
// package.json
|
|
2200
|
-
var version = "1.
|
|
2220
|
+
var version = "1.12.0";
|
|
2201
2221
|
|
|
2202
2222
|
// src/commands/account.ts
|
|
2203
2223
|
init_accounts_store();
|
|
@@ -2284,19 +2304,26 @@ async function accountCreate(name, opts) {
|
|
|
2284
2304
|
const { mnemonic, publicKey } = createNewAccount(path);
|
|
2285
2305
|
const hexPub = publicKeyToHex(publicKey);
|
|
2286
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"));
|
|
2287
2311
|
accountsFile.accounts.push({
|
|
2288
2312
|
name,
|
|
2289
2313
|
secret: mnemonic,
|
|
2290
2314
|
publicKey: hexPub,
|
|
2291
|
-
derivationPath: path
|
|
2315
|
+
derivationPath: path,
|
|
2316
|
+
bandersnatch
|
|
2292
2317
|
});
|
|
2293
2318
|
await saveAccounts(accountsFile);
|
|
2294
2319
|
printHeading("Account Created");
|
|
2295
|
-
console.log(` ${BOLD}Name:${RESET}
|
|
2320
|
+
console.log(` ${BOLD}Name:${RESET} ${name}`);
|
|
2296
2321
|
if (path)
|
|
2297
|
-
console.log(` ${BOLD}Path:${RESET}
|
|
2298
|
-
console.log(` ${BOLD}Address:${RESET}
|
|
2299
|
-
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}`);
|
|
2300
2327
|
console.log();
|
|
2301
2328
|
console.log(` ${YELLOW}Save this mnemonic phrase! It is the only way to recover this account.${RESET}`);
|
|
2302
2329
|
console.log();
|
|
@@ -2573,6 +2600,7 @@ async function accountInspect(input, opts) {
|
|
|
2573
2600
|
}
|
|
2574
2601
|
let name;
|
|
2575
2602
|
let publicKeyHex;
|
|
2603
|
+
let bandersnatch;
|
|
2576
2604
|
if (isDevAccount(input)) {
|
|
2577
2605
|
name = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
|
|
2578
2606
|
const devAddr = getDevAddress(input);
|
|
@@ -2582,6 +2610,7 @@ async function accountInspect(input, opts) {
|
|
|
2582
2610
|
const account = findAccount(accountsFile, input);
|
|
2583
2611
|
if (account) {
|
|
2584
2612
|
name = account.name;
|
|
2613
|
+
bandersnatch = account.bandersnatch;
|
|
2585
2614
|
if (account.publicKey) {
|
|
2586
2615
|
publicKeyHex = account.publicKey;
|
|
2587
2616
|
} else if (account.secret !== undefined && isEnvSecret(account.secret)) {
|
|
@@ -2612,6 +2641,8 @@ async function accountInspect(input, opts) {
|
|
|
2612
2641
|
const result = { publicKey: publicKeyHex, ss58, prefix };
|
|
2613
2642
|
if (name)
|
|
2614
2643
|
result.name = name;
|
|
2644
|
+
if (bandersnatch && Object.keys(bandersnatch).length > 0)
|
|
2645
|
+
result.bandersnatch = bandersnatch;
|
|
2615
2646
|
console.log(formatJson(result));
|
|
2616
2647
|
} else {
|
|
2617
2648
|
printHeading("Account Info");
|
|
@@ -2619,6 +2650,18 @@ async function accountInspect(input, opts) {
|
|
|
2619
2650
|
console.log(` ${BOLD}Name:${RESET} ${name}`);
|
|
2620
2651
|
console.log(` ${BOLD}Public Key:${RESET} ${publicKeyHex}`);
|
|
2621
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
|
+
}
|
|
2622
2665
|
console.log(` ${BOLD}Prefix:${RESET} ${prefix}`);
|
|
2623
2666
|
console.log();
|
|
2624
2667
|
}
|
|
@@ -3415,7 +3458,6 @@ async function showItemHelp2(category, target, opts) {
|
|
|
3415
3458
|
console.log();
|
|
3416
3459
|
console.log(`${BOLD}Options:${RESET}`);
|
|
3417
3460
|
console.log(` --dump Dump all entries of a map (required for keyless map queries)`);
|
|
3418
|
-
console.log(` --limit <n> Max entries for map queries (0 = unlimited, default: 100)`);
|
|
3419
3461
|
console.log();
|
|
3420
3462
|
return;
|
|
3421
3463
|
}
|
|
@@ -3975,17 +4017,10 @@ async function handleQuery(target, keys, opts) {
|
|
|
3975
4017
|
return;
|
|
3976
4018
|
}
|
|
3977
4019
|
const entries = await storageApi.getEntries(...parsedKeys);
|
|
3978
|
-
|
|
3979
|
-
const truncated = limit > 0 && entries.length > limit;
|
|
3980
|
-
const display = truncated ? entries.slice(0, limit) : entries;
|
|
3981
|
-
printResult(display.map((e) => ({
|
|
4020
|
+
printResult(entries.map((e) => ({
|
|
3982
4021
|
keys: e.keyArgs,
|
|
3983
4022
|
value: e.value
|
|
3984
4023
|
})), format);
|
|
3985
|
-
if (truncated) {
|
|
3986
|
-
console.error(`
|
|
3987
|
-
${DIM}Showing ${limit} of ${entries.length} entries. Use --limit 0 for all.${RESET}`);
|
|
3988
|
-
}
|
|
3989
4024
|
} else {
|
|
3990
4025
|
const result = await storageApi.getValue(...parsedKeys);
|
|
3991
4026
|
printResult(result, format);
|
|
@@ -4034,6 +4069,101 @@ async function parseStorageKeys(meta, palletName2, storageItem, args) {
|
|
|
4034
4069
|
return Promise.all(args.map((arg) => parseTypedArg(meta, keyEntry, arg)));
|
|
4035
4070
|
}
|
|
4036
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
|
+
|
|
4037
4167
|
// src/commands/tx.ts
|
|
4038
4168
|
init_store();
|
|
4039
4169
|
init_types();
|
|
@@ -5068,9 +5198,101 @@ function watchTransaction(observable, level) {
|
|
|
5068
5198
|
});
|
|
5069
5199
|
}
|
|
5070
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
|
+
|
|
5071
5293
|
// src/config/store.ts
|
|
5072
5294
|
init_types();
|
|
5073
|
-
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";
|
|
5074
5296
|
import { homedir as homedir2 } from "node:os";
|
|
5075
5297
|
import { join as join3 } from "node:path";
|
|
5076
5298
|
var DOT_DIR2 = join3(homedir2(), ".polkadot");
|
|
@@ -5090,7 +5312,7 @@ async function fileExists3(path) {
|
|
|
5090
5312
|
async function loadConfig2() {
|
|
5091
5313
|
await ensureDir3(DOT_DIR2);
|
|
5092
5314
|
if (await fileExists3(CONFIG_PATH2)) {
|
|
5093
|
-
const saved = JSON.parse(await
|
|
5315
|
+
const saved = JSON.parse(await readFile5(CONFIG_PATH2, "utf-8"));
|
|
5094
5316
|
return {
|
|
5095
5317
|
...saved,
|
|
5096
5318
|
chains: { ...DEFAULT_CONFIG.chains, ...saved.chains }
|
|
@@ -5107,7 +5329,7 @@ async function saveConfig2(config) {
|
|
|
5107
5329
|
|
|
5108
5330
|
// src/core/file-loader.ts
|
|
5109
5331
|
init_errors();
|
|
5110
|
-
import { access as access4, readFile as
|
|
5332
|
+
import { access as access4, readFile as readFile6 } from "node:fs/promises";
|
|
5111
5333
|
import { parse as parseYaml } from "yaml";
|
|
5112
5334
|
var CATEGORIES = ["tx", "query", "const", "apis"];
|
|
5113
5335
|
var FILE_EXTENSIONS = [".json", ".yaml", ".yml"];
|
|
@@ -5172,7 +5394,7 @@ async function loadCommandFile(filePath, cliVars) {
|
|
|
5172
5394
|
} catch {
|
|
5173
5395
|
throw new CliError(`File not found: ${filePath}`);
|
|
5174
5396
|
}
|
|
5175
|
-
const rawText = await
|
|
5397
|
+
const rawText = await readFile6(filePath, "utf-8");
|
|
5176
5398
|
if (!rawText.trim()) {
|
|
5177
5399
|
throw new CliError(`File is empty: ${filePath}`);
|
|
5178
5400
|
}
|
|
@@ -5493,7 +5715,9 @@ if (process.argv[2] === "__complete") {
|
|
|
5493
5715
|
console.log(" chain Manage chain configurations");
|
|
5494
5716
|
console.log(" account Manage accounts");
|
|
5495
5717
|
console.log(" hash Hash utilities");
|
|
5718
|
+
console.log(" sign Sign a message with an account keypair");
|
|
5496
5719
|
console.log(" parachain Derive parachain sovereign accounts");
|
|
5720
|
+
console.log(" verifiable Derive Bandersnatch member key from mnemonic");
|
|
5497
5721
|
console.log(" completions <sh> Generate shell completions (zsh, bash, fish)");
|
|
5498
5722
|
console.log();
|
|
5499
5723
|
console.log("Global options:");
|
|
@@ -5516,12 +5740,12 @@ if (process.argv[2] === "__complete") {
|
|
|
5516
5740
|
registerInspectCommand(cli);
|
|
5517
5741
|
registerAccountCommands(cli);
|
|
5518
5742
|
registerHashCommand(cli);
|
|
5743
|
+
registerSignCommand(cli);
|
|
5519
5744
|
registerParachainCommand(cli);
|
|
5520
5745
|
registerCompletionsCommand(cli);
|
|
5746
|
+
registerVerifiableCommands(cli);
|
|
5521
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)", {
|
|
5522
5748
|
default: "finalized"
|
|
5523
|
-
}).option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
|
|
5524
|
-
default: 100
|
|
5525
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) => {
|
|
5526
5750
|
if (!dotpath) {
|
|
5527
5751
|
printHelp();
|
|
@@ -5554,7 +5778,6 @@ if (process.argv[2] === "__complete") {
|
|
|
5554
5778
|
case "query":
|
|
5555
5779
|
await handleQuery(target2, args, {
|
|
5556
5780
|
...handlerOpts2,
|
|
5557
|
-
limit: opts.limit,
|
|
5558
5781
|
dump: opts.dump,
|
|
5559
5782
|
parsedArgs: cmd.args
|
|
5560
5783
|
});
|
|
@@ -5597,7 +5820,7 @@ if (process.argv[2] === "__complete") {
|
|
|
5597
5820
|
}
|
|
5598
5821
|
switch (parsed.category) {
|
|
5599
5822
|
case "query":
|
|
5600
|
-
await handleQuery(target, args, { ...handlerOpts,
|
|
5823
|
+
await handleQuery(target, args, { ...handlerOpts, dump: opts.dump });
|
|
5601
5824
|
break;
|
|
5602
5825
|
case "tx": {
|
|
5603
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
|
},
|