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.
Files changed (3) hide show
  1. package/README.md +67 -10
  2. package/dist/cli.mjs +276 -37
  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:
@@ -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 Utility.batchAll '[{"type":"Balances","value":{"type":"transfer_keep_alive","value":{"dest":"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty","value":1000000000000}}},{"type":"Balances","value":{"type":"transfer_keep_alive","value":{"dest":"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y","value":2000000000000}}}]' --from alice
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 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(() => {
@@ -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
- return "a JSON array or hex-encoded bytes";
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) => blake2b(data, { dkLen: 32 }),
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) => blake2b(data, { dkLen: 16 }),
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 = ["--limit"];
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.10.0";
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} ${name}`);
2320
+ console.log(` ${BOLD}Name:${RESET} ${name}`);
2288
2321
  if (path)
2289
- console.log(` ${BOLD}Path:${RESET} ${path}`);
2290
- console.log(` ${BOLD}Address:${RESET} ${address}`);
2291
- 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}`);
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
- const limit = Number(opts.limit);
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
- return "a JSON array or hex-encoded bytes";
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 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";
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 readFile4(CONFIG_PATH2, "utf-8"));
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 readFile5 } from "node:fs/promises";
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 readFile5(filePath, "utf-8");
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, limit: opts.limit, dump: opts.dump });
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.10.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
  },