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.
Files changed (3) hide show
  1. package/README.md +63 -8
  2. package/dist/cli.mjs +256 -33
  3. 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, default limit: 100)
300
- dot query System.Account --dump --limit 10
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 --limit 5 | jq '.[0].value.data.free'
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 --limit 10
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 resolveAccountSigner(name) {
365
+ async function resolveAccountKeypair(name) {
366
366
  if (isDevAccount(name)) {
367
- const keypair2 = getDevKeypair(name);
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
- const keypair = isHexSeed ? deriveFromHexSeed(secret, account.derivationPath) : deriveFromMnemonic(secret, account.derivationPath);
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) => blake2b(data, { dkLen: 32 }),
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) => blake2b(data, { dkLen: 16 }),
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 = ["--limit"];
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.11.0";
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} ${name}`);
2320
+ console.log(` ${BOLD}Name:${RESET} ${name}`);
2296
2321
  if (path)
2297
- console.log(` ${BOLD}Path:${RESET} ${path}`);
2298
- console.log(` ${BOLD}Address:${RESET} ${address}`);
2299
- console.log(` ${BOLD}Mnemonic:${RESET} ${mnemonic}`);
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
- const limit = Number(opts.limit);
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 readFile4, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
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 readFile4(CONFIG_PATH2, "utf-8"));
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 readFile5 } from "node:fs/promises";
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 readFile5(filePath, "utf-8");
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, limit: opts.limit, dump: opts.dump });
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.11.0",
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
  },