polkadot-cli 1.19.0 → 1.21.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 +148 -40
  2. package/dist/cli.mjs +879 -277
  3. package/package.json +7 -7
package/dist/cli.mjs CHANGED
@@ -73,7 +73,9 @@ var init_errors = __esm(() => {
73
73
  /codec/i,
74
74
  /decod(e|ing)/i,
75
75
  /Lookup failed/i,
76
- /metadata.*mismatch/i
76
+ /metadata.*mismatch/i,
77
+ /BadProof/,
78
+ /AncientBirthBlock/
77
79
  ];
78
80
  BLOCK_UNAVAILABLE_PATTERNS = [
79
81
  /is not pinned/i,
@@ -231,31 +233,84 @@ var init_types = __esm(() => {
231
233
  BUILTIN_CHAIN_NAMES = new Set(Object.keys(DEFAULT_CONFIG.chains));
232
234
  });
233
235
 
236
+ // src/config/workspace.ts
237
+ import { realpathSync, statSync } from "node:fs";
238
+ import { homedir } from "node:os";
239
+ import { dirname, join, resolve } from "node:path";
240
+ function isDirectory(path) {
241
+ try {
242
+ return statSync(path).isDirectory();
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
247
+ function canonicalPath(path) {
248
+ try {
249
+ return realpathSync(path);
250
+ } catch {
251
+ return resolve(path);
252
+ }
253
+ }
254
+ function findWorkspace(startDir, home = homedir()) {
255
+ const homePath = canonicalPath(home);
256
+ let dir = canonicalPath(startDir);
257
+ while (dir !== homePath) {
258
+ const candidate = join(dir, WORKSPACE_DIR_NAME);
259
+ if (isDirectory(candidate))
260
+ return candidate;
261
+ const parent = dirname(dir);
262
+ if (parent === dir)
263
+ return null;
264
+ dir = parent;
265
+ }
266
+ return null;
267
+ }
268
+ var WORKSPACE_DIR_NAME = ".polkadot";
269
+ var init_workspace = () => {};
270
+
234
271
  // src/config/store.ts
235
272
  import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
236
- import { homedir } from "node:os";
237
- import { join } from "node:path";
238
- function getConfigDir() {
273
+ import { homedir as homedir2 } from "node:os";
274
+ import { join as join2 } from "node:path";
275
+ function resolveConfigDir(cwd = process.cwd()) {
239
276
  const override = process.env.DOT_HOME;
240
- return override && override.length > 0 ? override : join(homedir(), ".polkadot");
277
+ if (override && override.length > 0)
278
+ return { path: override, source: "env" };
279
+ const workspace = findWorkspace(cwd);
280
+ if (workspace)
281
+ return { path: workspace, source: "workspace" };
282
+ return { path: join2(homedir2(), ".polkadot"), source: "global" };
283
+ }
284
+ function describeConfigDir(resolved = resolveConfigDir()) {
285
+ switch (resolved.source) {
286
+ case "env":
287
+ return `DOT_HOME ${resolved.path}`;
288
+ case "workspace":
289
+ return `workspace ${resolved.path}`;
290
+ case "global":
291
+ return `global config ${resolved.path}`;
292
+ }
293
+ }
294
+ function getConfigDir() {
295
+ return resolveConfigDir().path;
241
296
  }
242
297
  function getChainsDir() {
243
- return join(getConfigDir(), "chains");
298
+ return join2(getConfigDir(), "chains");
244
299
  }
245
300
  function getChainDir(chainName) {
246
- return join(getChainsDir(), chainName);
301
+ return join2(getChainsDir(), chainName);
247
302
  }
248
303
  function getMetadataPath(chainName) {
249
- return join(getChainDir(chainName), "metadata.bin");
304
+ return join2(getChainDir(chainName), "metadata.bin");
250
305
  }
251
306
  function getMetadataFingerprintPath(chainName) {
252
- return join(getChainDir(chainName), "metadata.fingerprint.json");
307
+ return join2(getChainDir(chainName), "metadata.fingerprint.json");
253
308
  }
254
309
  function getRpcMethodsPath(chainName) {
255
- return join(getChainDir(chainName), "rpc-methods.json");
310
+ return join2(getChainDir(chainName), "rpc-methods.json");
256
311
  }
257
312
  function getConfigPath() {
258
- return join(getConfigDir(), "config.json");
313
+ return join2(getConfigDir(), "config.json");
259
314
  }
260
315
  async function ensureDir(dir) {
261
316
  await mkdir(dir, { recursive: true });
@@ -360,20 +415,21 @@ function resolveChain(config, chainFlag) {
360
415
  }
361
416
  const name = findChainName(config, chainFlag);
362
417
  if (!name) {
363
- throw new CliError(`Unknown chain "${chainFlag}". Available chains: ${available}`);
418
+ throw new CliError(`Unknown chain "${chainFlag}" in ${describeConfigDir()}. Available chains: ${available}`);
364
419
  }
365
420
  return { name, chain: config.chains[name] };
366
421
  }
367
422
  var init_store = __esm(() => {
368
423
  init_errors();
369
424
  init_types();
425
+ init_workspace();
370
426
  });
371
427
 
372
428
  // src/config/accounts-store.ts
373
429
  import { access as access2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
374
- import { join as join2 } from "node:path";
430
+ import { join as join3 } from "node:path";
375
431
  function getAccountsPath() {
376
- return join2(getConfigDir(), "accounts.json");
432
+ return join3(getConfigDir(), "accounts.json");
377
433
  }
378
434
  async function ensureDir2(dir) {
379
435
  await mkdir2(dir, { recursive: true });
@@ -461,6 +517,7 @@ function suggestMessage(kind, input, candidates) {
461
517
  }
462
518
 
463
519
  // src/core/accounts.ts
520
+ import { hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js";
464
521
  import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
465
522
  import {
466
523
  DEV_PHRASE,
@@ -471,7 +528,7 @@ import {
471
528
  ss58Decode,
472
529
  validateMnemonic
473
530
  } from "@polkadot-labs/hdkd-helpers";
474
- import { HDKD, secretFromSeed } from "@scure/sr25519";
531
+ import { getPublicKey, HDKD, secretFromSeed, sign } from "@scure/sr25519";
475
532
  import { getPolkadotSigner } from "polkadot-api/signer";
476
533
  function isDevAccount(name) {
477
534
  return DEV_NAMES.includes(name.toLowerCase());
@@ -486,18 +543,54 @@ function deriveFromMnemonic(mnemonic, path) {
486
543
  return derive(path);
487
544
  }
488
545
  function deriveFromHexSeed(hexSeed, path) {
489
- const clean = hexSeed.startsWith("0x") ? hexSeed.slice(2) : hexSeed;
490
- const seed = new Uint8Array(clean.length / 2);
491
- for (let i = 0;i < clean.length; i += 2) {
492
- seed[i / 2] = parseInt(clean.substring(i, i + 2), 16);
493
- }
494
- const derive = sr25519CreateDerive(seed);
546
+ const derive = sr25519CreateDerive(hexToBytes(hexSeed));
495
547
  return derive(path);
496
548
  }
497
549
  function getDevKeypair(name) {
498
550
  const path = devDerivationPath(name);
499
551
  return deriveFromMnemonic(DEV_PHRASE, path);
500
552
  }
553
+ function hexToBytes(hex) {
554
+ return nobleHexToBytes(hex.startsWith("0x") ? hex.slice(2) : hex);
555
+ }
556
+ function isExpandedSecret(secret) {
557
+ return EXPANDED_SECRET_RE.test(secret);
558
+ }
559
+ function isHexSeed(secret) {
560
+ return HEX_SEED_RE.test(secret);
561
+ }
562
+ function secretKind(secret) {
563
+ if (isExpandedSecret(secret))
564
+ return "expanded";
565
+ if (isHexSeed(secret))
566
+ return "seed";
567
+ return "mnemonic";
568
+ }
569
+ function assertNoPathForExpandedSecret(derivationPath) {
570
+ if (derivationPath) {
571
+ throw new Error("Stored account has a 64-byte expanded secret with a derivation path, which cannot be applied. An expanded secret cannot be HD-derived; remove the derivationPath from accounts.json.");
572
+ }
573
+ }
574
+ function keypairFromExpandedSecret(secret) {
575
+ return {
576
+ publicKey: getPublicKey(secret),
577
+ sign: (msg) => sign(secret, msg)
578
+ };
579
+ }
580
+ function keypairFromSecret(secret, derivationPath = "") {
581
+ if (isExpandedSecret(secret)) {
582
+ assertNoPathForExpandedSecret(derivationPath);
583
+ return keypairFromExpandedSecret(hexToBytes(secret));
584
+ }
585
+ return isHexSeed(secret) ? deriveFromHexSeed(secret, derivationPath) : deriveFromMnemonic(secret, derivationPath);
586
+ }
587
+ function expandedSecretFromStored(secret, derivationPath = "") {
588
+ if (isExpandedSecret(secret)) {
589
+ assertNoPathForExpandedSecret(derivationPath);
590
+ return hexToBytes(secret);
591
+ }
592
+ return deriveExpandedSecret(miniSecretFromSecret(secret), derivationPath);
593
+ }
501
594
  function parseDerivations(path) {
502
595
  const out = [];
503
596
  for (const [, type, code] of path.matchAll(DERIVATION_RE)) {
@@ -528,17 +621,11 @@ function deriveExpandedSecret(miniSecret, path) {
528
621
  return parseDerivations(path).reduce((sk, [type, code]) => type === "hard" ? HDKD.secretHard(sk, createChainCode(code)) : HDKD.secretSoft(sk, createChainCode(code)), secretFromSeed(miniSecret));
529
622
  }
530
623
  function miniSecretFromSecret(secret) {
531
- const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
532
- if (isHexSeed) {
533
- const clean = secret.slice(2);
534
- const bytes = new Uint8Array(32);
535
- for (let i = 0;i < clean.length; i += 2) {
536
- bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
537
- }
538
- return bytes;
624
+ if (isHexSeed(secret)) {
625
+ return hexToBytes(secret);
539
626
  }
540
627
  if (!validateMnemonic(secret)) {
541
- throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
628
+ throw new Error("Invalid secret. Expected a BIP39 mnemonic or a 0x-prefixed 32-byte hex seed.");
542
629
  }
543
630
  return entropyToMiniSecret(mnemonicToEntropy(secret));
544
631
  }
@@ -555,13 +642,19 @@ function createNewAccount(path = "") {
555
642
  return { mnemonic, publicKey: keypair.publicKey };
556
643
  }
557
644
  function importAccount(secret, path = "") {
558
- const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
559
- if (isHexSeed) {
645
+ if (isExpandedSecret(secret)) {
646
+ if (path) {
647
+ throw new Error("Derivation paths are not supported for raw private key (64-byte expanded secret) import. An expanded secret cannot be HD-derived; omit --path.");
648
+ }
649
+ const keypair2 = keypairFromExpandedSecret(hexToBytes(secret));
650
+ return { publicKey: keypair2.publicKey };
651
+ }
652
+ if (isHexSeed(secret)) {
560
653
  const keypair2 = deriveFromHexSeed(secret, path);
561
654
  return { publicKey: keypair2.publicKey };
562
655
  }
563
656
  if (!validateMnemonic(secret)) {
564
- throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
657
+ throw new Error("Invalid secret. Expected a BIP39 mnemonic, a 0x-prefixed 32-byte hex seed, or a 0x-prefixed 64-byte sr25519 expanded secret.");
565
658
  }
566
659
  const keypair = deriveFromMnemonic(secret, path);
567
660
  return { publicKey: keypair.publicKey };
@@ -608,6 +701,16 @@ function tryDerivePublicKey(envVarName, path = "") {
608
701
  return null;
609
702
  }
610
703
  }
704
+ function unknownAccountError(name, accountsFile) {
705
+ const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
706
+ const suggestions = findClosest(name, available);
707
+ const hint = suggestions.length > 0 ? `
708
+ Did you mean: ${suggestions.join(", ")}?` : "";
709
+ const list = available.map((a) => `
710
+ - ${a}`).join("");
711
+ return new Error(`Unknown account "${name}" in ${describeConfigDir()}.${hint}
712
+ Available accounts:${list}`);
713
+ }
611
714
  async function resolveAccountKeypair(name) {
612
715
  if (isDevAccount(name)) {
613
716
  return getDevKeypair(name);
@@ -615,21 +718,12 @@ async function resolveAccountKeypair(name) {
615
718
  const accountsFile = await loadAccounts();
616
719
  const account = findAccount(accountsFile, name);
617
720
  if (!account) {
618
- const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
619
- const suggestions = findClosest(name, available);
620
- const hint = suggestions.length > 0 ? `
621
- Did you mean: ${suggestions.join(", ")}?` : "";
622
- const list = available.map((a) => `
623
- - ${a}`).join("");
624
- throw new Error(`Unknown account "${name}".${hint}
625
- Available accounts:${list}`);
721
+ throw unknownAccountError(name, accountsFile);
626
722
  }
627
723
  if (account.secret === undefined) {
628
724
  throw new Error(`Account "${name}" is watch-only (no secret). Cannot sign. Import with --secret or --env.`);
629
725
  }
630
- const secret = resolveSecret(account.secret);
631
- const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
632
- return isHexSeed ? deriveFromHexSeed(secret, account.derivationPath) : deriveFromMnemonic(secret, account.derivationPath);
726
+ return keypairFromSecret(resolveSecret(account.secret), account.derivationPath);
633
727
  }
634
728
  async function resolveAccountSigner(name) {
635
729
  const keypair = await resolveAccountKeypair(name);
@@ -637,34 +731,29 @@ async function resolveAccountSigner(name) {
637
731
  }
638
732
  async function resolveAccountExpandedSecret(name) {
639
733
  if (isDevAccount(name)) {
640
- const miniSecret2 = entropyToMiniSecret(mnemonicToEntropy(DEV_PHRASE));
641
- return deriveExpandedSecret(miniSecret2, devDerivationPath(name));
734
+ const miniSecret = entropyToMiniSecret(mnemonicToEntropy(DEV_PHRASE));
735
+ return deriveExpandedSecret(miniSecret, devDerivationPath(name));
642
736
  }
643
737
  const accountsFile = await loadAccounts();
644
738
  const account = findAccount(accountsFile, name);
645
739
  if (!account) {
646
- const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
647
- const suggestions = findClosest(name, available);
648
- const hint = suggestions.length > 0 ? `
649
- Did you mean: ${suggestions.join(", ")}?` : "";
650
- const list = available.map((a) => `
651
- - ${a}`).join("");
652
- throw new Error(`Unknown account "${name}".${hint}
653
- Available accounts:${list}`);
740
+ throw unknownAccountError(name, accountsFile);
654
741
  }
655
742
  if (account.secret === undefined) {
656
743
  throw new Error(`Account "${name}" is watch-only (no secret). Cannot derive private key. Import with --secret or --env.`);
657
744
  }
658
- const miniSecret = miniSecretFromSecret(resolveSecret(account.secret));
659
- return deriveExpandedSecret(miniSecret, account.derivationPath);
745
+ return expandedSecretFromStored(resolveSecret(account.secret), account.derivationPath);
660
746
  }
661
747
  function bytesToHex(bytes) {
662
748
  return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
663
749
  }
664
- var DEV_NAMES, DERIVATION_RE;
750
+ var DEV_NAMES, EXPANDED_SECRET_RE, HEX_SEED_RE, DERIVATION_RE;
665
751
  var init_accounts = __esm(() => {
666
752
  init_accounts_store();
753
+ init_store();
667
754
  DEV_NAMES = ["alice", "bob", "charlie", "dave", "eve", "ferdie"];
755
+ EXPANDED_SECRET_RE = /^0x[0-9a-fA-F]{128}$/;
756
+ HEX_SEED_RE = /^0x[0-9a-fA-F]{64}$/;
668
757
  DERIVATION_RE = /(\/{1,2})([^/]+)/g;
669
758
  });
670
759
 
@@ -723,8 +812,8 @@ function isJsonOutput(opts) {
723
812
  return opts.json === true || opts.output === "json";
724
813
  }
725
814
  function writeStdout(text) {
726
- return new Promise((resolve) => {
727
- process.stdout.write(text, () => resolve());
815
+ return new Promise((resolve2) => {
816
+ process.stdout.write(text, () => resolve2());
728
817
  });
729
818
  }
730
819
  function printJsonLine(data) {
@@ -828,24 +917,130 @@ var init_output = __esm(() => {
828
917
  SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
829
918
  });
830
919
 
831
- // src/core/bandersnatch.ts
832
- var exports_bandersnatch = {};
833
- __export(exports_bandersnatch, {
834
- deriveBandersnatchMember: () => deriveBandersnatchMember
920
+ // src/features/verifiable/lib.ts
921
+ var exports_lib = {};
922
+ __export(exports_lib, {
923
+ verifyRingProof: () => verifyRingProof,
924
+ verifyBandersnatchSig: () => verifyBandersnatchSig,
925
+ ringRoot: () => ringRoot,
926
+ ringProve: () => ringProve,
927
+ resolveEntropyKey: () => resolveEntropyKey,
928
+ isRingExponent: () => isRingExponent,
929
+ encodeMembers: () => encodeMembers,
930
+ encodeContext: () => encodeContext,
931
+ deriveMemberKey: () => deriveMemberKey,
932
+ deriveMemberEntropy: () => deriveMemberEntropy,
933
+ deriveBandersnatchMember: () => deriveBandersnatchMember,
934
+ deriveAlias: () => deriveAlias,
935
+ compactEncode: () => compactEncode,
936
+ bandersnatchSign: () => bandersnatchSign,
937
+ DEFAULT_RING_EXPONENT: () => DEFAULT_RING_EXPONENT
835
938
  });
836
939
  import { blake2b } from "@noble/hashes/blake2.js";
940
+ import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
941
+ import { compact } from "@polkadot-api/substrate-bindings";
837
942
  import { mnemonicToEntropy as mnemonicToEntropy2 } from "@polkadot-labs/hdkd-helpers";
838
- import { member_from_entropy } from "verifiablejs/nodejs";
839
- function deriveBandersnatchMember(mnemonic, context) {
943
+ import {
944
+ alias_in_context,
945
+ member_from_entropy,
946
+ members_root,
947
+ one_shot,
948
+ sign as sign2,
949
+ validate,
950
+ validate_with_commitment,
951
+ verify_signature
952
+ } from "verifiablejs/nodejs";
953
+ function isRingExponent(n) {
954
+ return RING_EXPONENTS.includes(n);
955
+ }
956
+ function textOrHexBytes(value, label) {
957
+ if (value.startsWith("0x")) {
958
+ const hex = value.slice(2);
959
+ if (hex.length % 2 !== 0) {
960
+ throw new Error(`Invalid hex ${label}: odd number of characters`);
961
+ }
962
+ return hexToBytes3(hex);
963
+ }
964
+ return new TextEncoder().encode(value);
965
+ }
966
+ function resolveEntropyKey(value) {
967
+ if (value === undefined || value === "")
968
+ return;
969
+ return textOrHexBytes(value, "entropy-key");
970
+ }
971
+ function deriveMemberEntropy(mnemonic, entropyKey) {
840
972
  const entropy = mnemonicToEntropy2(mnemonic);
841
973
  const opts = { dkLen: 32 };
842
- if (context) {
843
- opts.key = new TextEncoder().encode(context);
974
+ if (entropyKey !== undefined && entropyKey.length > 0) {
975
+ opts.key = entropyKey;
976
+ }
977
+ return blake2b(entropy, opts);
978
+ }
979
+ function deriveMemberKey(entropy) {
980
+ return member_from_entropy(entropy);
981
+ }
982
+ function deriveBandersnatchMember(mnemonic, entropyKey) {
983
+ return deriveMemberKey(deriveMemberEntropy(mnemonic, resolveEntropyKey(entropyKey)));
984
+ }
985
+ function deriveAlias(entropy, context) {
986
+ return alias_in_context(entropy, context);
987
+ }
988
+ function bandersnatchSign(entropy, message) {
989
+ return sign2(entropy, message);
990
+ }
991
+ function verifyBandersnatchSig(signature, message, member) {
992
+ return verify_signature(signature, message, member);
993
+ }
994
+ function ringProve(ringExp, entropy, members, context, message) {
995
+ const result = one_shot(ringExp, entropy, members, context, message);
996
+ return { proof: result.proof, alias: result.alias };
997
+ }
998
+ function verifyRingProof(ringExp, proof, source, context, message) {
999
+ if (source.commitment !== undefined) {
1000
+ return validate_with_commitment(ringExp, proof, source.commitment, context, message);
1001
+ }
1002
+ if (source.members !== undefined) {
1003
+ return validate(ringExp, proof, source.members, context, message);
1004
+ }
1005
+ throw new Error("verifyRingProof requires either `members` or `commitment`");
1006
+ }
1007
+ function ringRoot(ringExp, members) {
1008
+ return members_root(ringExp, members);
1009
+ }
1010
+ function compactEncode(n) {
1011
+ if (!Number.isInteger(n) || n < 0)
1012
+ throw new Error("compactEncode: non-negative integer required");
1013
+ return compact.enc(n);
1014
+ }
1015
+ function encodeMembers(members) {
1016
+ for (const m of members) {
1017
+ if (m.length !== 32) {
1018
+ throw new Error(`member key must be 32 bytes (got ${m.length})`);
1019
+ }
1020
+ }
1021
+ const prefix = compactEncode(members.length);
1022
+ const out = new Uint8Array(prefix.length + members.length * 32);
1023
+ out.set(prefix, 0);
1024
+ let offset = prefix.length;
1025
+ for (const m of members) {
1026
+ out.set(m, offset);
1027
+ offset += 32;
1028
+ }
1029
+ return out;
1030
+ }
1031
+ function encodeContext(input) {
1032
+ const bytes = textOrHexBytes(input, "context");
1033
+ if (bytes.length > 32) {
1034
+ throw new Error(`Context must be at most 32 bytes (got ${bytes.length})`);
844
1035
  }
845
- const hashed = blake2b(entropy, opts);
846
- return member_from_entropy(hashed);
1036
+ const out = new Uint8Array(32);
1037
+ out.set(bytes, 0);
1038
+ return out;
847
1039
  }
848
- var init_bandersnatch = () => {};
1040
+ var RING_EXPONENTS, DEFAULT_RING_EXPONENT = 9;
1041
+ var init_lib = __esm(() => {
1042
+ RING_EXPONENTS = [9, 10, 14];
1043
+ });
849
1044
 
850
1045
  // src/core/client.ts
851
1046
  import { createClient } from "polkadot-api";
@@ -971,9 +1166,9 @@ function compactEntry(entry, color) {
971
1166
  }
972
1167
  }
973
1168
  function expandEntry(entry, indent, width, color, prefix = 0) {
974
- const compact = compactEntry(entry, color);
975
- if (visualWidth(compact) + indent + prefix <= width)
976
- return compact;
1169
+ const compact2 = compactEntry(entry, color);
1170
+ if (visualWidth(compact2) + indent + prefix <= width)
1171
+ return compact2;
977
1172
  switch (entry.type) {
978
1173
  case "struct":
979
1174
  return expandStruct(entry.value, indent, width, color);
@@ -1007,7 +1202,7 @@ ${closePadding}>`;
1007
1202
  case "lookupEntry":
1008
1203
  return expandEntry(entry.value, indent, width, color);
1009
1204
  default:
1010
- return compact;
1205
+ return compact2;
1011
1206
  }
1012
1207
  }
1013
1208
  function expandStruct(fields, indent, width, color) {
@@ -1066,9 +1261,9 @@ ${closePadding}${close}`;
1066
1261
  }
1067
1262
  function prettyType(entry, opts = {}) {
1068
1263
  const { indent, prefix, width, color } = resolveOpts(opts);
1069
- const compact = compactEntry(entry, color);
1070
- if (visualWidth(compact) + indent + prefix <= width)
1071
- return compact;
1264
+ const compact2 = compactEntry(entry, color);
1265
+ if (visualWidth(compact2) + indent + prefix <= width)
1266
+ return compact2;
1072
1267
  return expandEntry(entry, indent, width, color);
1073
1268
  }
1074
1269
  function prettyTypeById(lookup, typeId, opts = {}) {
@@ -1172,15 +1367,15 @@ function renderArgsFromFields(fields, opts) {
1172
1367
  case "void":
1173
1368
  return "()";
1174
1369
  case "named": {
1175
- const compact = `(${fields.fields.map(([k, v]) => `${paint(color, CYAN2, k)}: ${compactEntry(v, color)}`).join(", ")})`;
1176
- if (visualWidth(compact) + lead <= width)
1177
- return compact;
1370
+ const compact2 = `(${fields.fields.map(([k, v]) => `${paint(color, CYAN2, k)}: ${compactEntry(v, color)}`).join(", ")})`;
1371
+ if (visualWidth(compact2) + lead <= width)
1372
+ return compact2;
1178
1373
  return renderFieldList(fields.fields, "(", ")", indent, width, color);
1179
1374
  }
1180
1375
  case "positional": {
1181
- const compact = `(${fields.types.map((t) => compactEntry(t, color)).join(", ")})`;
1182
- if (visualWidth(compact) + lead <= width)
1183
- return compact;
1376
+ const compact2 = `(${fields.types.map((t) => compactEntry(t, color)).join(", ")})`;
1377
+ if (visualWidth(compact2) + lead <= width)
1378
+ return compact2;
1184
1379
  const innerIndent = indent + 2;
1185
1380
  const padding = " ".repeat(innerIndent);
1186
1381
  const closePadding = " ".repeat(indent);
@@ -1191,9 +1386,9 @@ ${lines.join(`,
1191
1386
  ${closePadding})`;
1192
1387
  }
1193
1388
  case "single": {
1194
- const compact = `(${compactEntry(fields.type, color)})`;
1195
- if (visualWidth(compact) + lead <= width)
1196
- return compact;
1389
+ const compact2 = `(${compactEntry(fields.type, color)})`;
1390
+ if (visualWidth(compact2) + lead <= width)
1391
+ return compact2;
1197
1392
  const inner = expandEntry(fields.type, indent + 2, width, color);
1198
1393
  return `(
1199
1394
  ${" ".repeat(indent + 2)}${inner},
@@ -1264,7 +1459,7 @@ async function fetchMetadataFromChain(clientHandle, chainName) {
1264
1459
  let bytes;
1265
1460
  try {
1266
1461
  const hex = await withTimeout(client._request("state_call", ["Metadata_metadata_at_version", v15Arg]), chainName);
1267
- const raw = hexToBytes2(hex);
1462
+ const raw = hexToBytes4(hex);
1268
1463
  const decoded = optionalOpaqueBytes.dec(raw);
1269
1464
  if (decoded !== undefined) {
1270
1465
  bytes = new Uint8Array(decoded);
@@ -1273,7 +1468,7 @@ async function fetchMetadataFromChain(clientHandle, chainName) {
1273
1468
  if (!bytes) {
1274
1469
  try {
1275
1470
  const hex = await withTimeout(client._request("state_getMetadata", []), chainName);
1276
- bytes = hexToBytes2(hex);
1471
+ bytes = hexToBytes4(hex);
1277
1472
  } catch (err) {
1278
1473
  if (err instanceof ConnectionError)
1279
1474
  throw err;
@@ -1469,7 +1664,7 @@ function describeCallArgs(meta, palletName, callName) {
1469
1664
  function describeEventFields(meta, palletName, eventName) {
1470
1665
  return compactArgsString(getEventFields(meta, palletName, eventName));
1471
1666
  }
1472
- function hexToBytes2(hex) {
1667
+ function hexToBytes4(hex) {
1473
1668
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1474
1669
  const bytes = new Uint8Array(clean.length / 2);
1475
1670
  for (let i = 0;i < clean.length; i += 2) {
@@ -3198,7 +3393,7 @@ var init_xxh64 = __esm(() => {
3198
3393
  import { blake2b as blake2b2 } from "@noble/hashes/blake2.js";
3199
3394
  import { sha256 } from "@noble/hashes/sha2.js";
3200
3395
  import { keccak_256 as keccak_2562 } from "@noble/hashes/sha3.js";
3201
- import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
3396
+ import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes5 } from "@noble/hashes/utils.js";
3202
3397
  function computeHash(algorithm, data) {
3203
3398
  const algo = ALGORITHMS[algorithm];
3204
3399
  if (!algo) {
@@ -3212,7 +3407,7 @@ function parseInputData(input) {
3212
3407
  if (hex.length % 2 !== 0) {
3213
3408
  throw new Error(`Invalid hex input: odd number of characters`);
3214
3409
  }
3215
- return hexToBytes3(hex);
3410
+ return hexToBytes5(hex);
3216
3411
  }
3217
3412
  return new TextEncoder().encode(input);
3218
3413
  }
@@ -3267,6 +3462,349 @@ var init_hash = __esm(() => {
3267
3462
  };
3268
3463
  });
3269
3464
 
3465
+ // src/core/input.ts
3466
+ import { readFile as readFile5 } from "node:fs/promises";
3467
+ async function resolveDataInput(inline, opts, messages) {
3468
+ const sources = [inline !== undefined, !!opts.file, !!opts.stdin].filter(Boolean).length;
3469
+ if (sources > 1) {
3470
+ throw new CliError(messages?.conflict ?? "Provide only one of: inline data, --file, or --stdin");
3471
+ }
3472
+ if (sources === 0) {
3473
+ throw new CliError(messages?.missing ?? "No input provided. Pass data as argument, or use --file or --stdin");
3474
+ }
3475
+ if (opts.file) {
3476
+ const buf = await readFile5(opts.file);
3477
+ return new Uint8Array(buf);
3478
+ }
3479
+ if (opts.stdin) {
3480
+ const chunks = [];
3481
+ for await (const chunk of process.stdin) {
3482
+ chunks.push(chunk);
3483
+ }
3484
+ return new Uint8Array(Buffer.concat(chunks));
3485
+ }
3486
+ return parseInputData(inline);
3487
+ }
3488
+ var init_input = __esm(() => {
3489
+ init_errors();
3490
+ init_hash();
3491
+ });
3492
+
3493
+ // src/platform/cli.ts
3494
+ function readRawOptionValue(name, argv = process.argv) {
3495
+ const flag = `--${name}`;
3496
+ const prefix = `${flag}=`;
3497
+ let value;
3498
+ for (let i = 0;i < argv.length; i++) {
3499
+ const arg = argv[i];
3500
+ if (arg === "--")
3501
+ break;
3502
+ if (arg === flag && i + 1 < argv.length)
3503
+ value = argv[i + 1];
3504
+ else if (arg.startsWith(prefix))
3505
+ value = arg.slice(prefix.length);
3506
+ }
3507
+ return value;
3508
+ }
3509
+
3510
+ // src/platform/index.ts
3511
+ var init_platform = __esm(() => {
3512
+ init_accounts_store();
3513
+ init_accounts();
3514
+ init_hash();
3515
+ init_input();
3516
+ init_output();
3517
+ init_errors();
3518
+ });
3519
+
3520
+ // src/features/verifiable/commands.ts
3521
+ var exports_commands = {};
3522
+ __export(exports_commands, {
3523
+ runVerifiable: () => runVerifiable
3524
+ });
3525
+ import { readFile as readFile8 } from "node:fs/promises";
3526
+ import { DEV_PHRASE as DEV_PHRASE2 } from "@polkadot-labs/hdkd-helpers";
3527
+ async function runVerifiable(action, rest, opts) {
3528
+ switch (action) {
3529
+ case "member":
3530
+ return deriveMember(rest[0], opts);
3531
+ case "alias":
3532
+ return deriveAliasCmd(rest[0], opts);
3533
+ case "sign":
3534
+ return signCmd(rest[0], opts);
3535
+ case "prove":
3536
+ return proveCmd(rest[0], opts);
3537
+ case "verify":
3538
+ return verifyCmd(opts);
3539
+ case "verify-sig":
3540
+ return verifySigCmd(opts);
3541
+ case "members":
3542
+ return membersCmd(rest, opts);
3543
+ default:
3544
+ return deriveMember(action, opts);
3545
+ }
3546
+ }
3547
+ function mnemonicFromStored(stored, account, accountsFile) {
3548
+ if (!stored) {
3549
+ const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
3550
+ const suggestions = findClosest(account, available);
3551
+ const hint = suggestions.length > 0 ? `
3552
+ Did you mean: ${suggestions.join(", ")}?` : "";
3553
+ const list = available.map((a) => `
3554
+ - ${a}`).join("");
3555
+ throw new Error(`Unknown account "${account}".${hint}
3556
+ Available accounts:${list}`);
3557
+ }
3558
+ if (isWatchOnly(stored)) {
3559
+ throw new Error(`Account "${account}" is watch-only (no secret). Cannot derive Bandersnatch key.`);
3560
+ }
3561
+ const secret = resolveSecret(stored.secret);
3562
+ if (isHexPublicKey(`0x${secret.replace(/^0x/, "")}`)) {
3563
+ throw new Error(`Account "${account}" uses a hex seed. Bandersnatch derivation requires a BIP39 mnemonic.`);
3564
+ }
3565
+ return secret;
3566
+ }
3567
+ async function resolveMnemonic(account) {
3568
+ if (isDevAccount(account)) {
3569
+ return DEV_PHRASE2;
3570
+ }
3571
+ const accountsFile = await loadAccounts();
3572
+ return mnemonicFromStored(findAccount(accountsFile, account), account, accountsFile);
3573
+ }
3574
+ async function resolveEntropy(account, entropyKey) {
3575
+ const mnemonic = await resolveMnemonic(account);
3576
+ return deriveMemberEntropy(mnemonic, resolveEntropyKey(entropyKey));
3577
+ }
3578
+ function requireAccount(account, action) {
3579
+ if (!account) {
3580
+ throw new CliError(`dot verifiable ${action} requires an account. See "dot verifiable".`);
3581
+ }
3582
+ return account;
3583
+ }
3584
+ function resolveMessage(opts) {
3585
+ return resolveDataInput(opts.message, opts, {
3586
+ conflict: "Provide only one of: --message, --file, or --stdin",
3587
+ missing: "No message provided. Use --message, --file, or --stdin"
3588
+ });
3589
+ }
3590
+ async function resolveBytesArg(value, name, allowFile = false) {
3591
+ if (value.startsWith("0x")) {
3592
+ return parseInputData(value);
3593
+ }
3594
+ if (allowFile) {
3595
+ const buf = await readFile8(value);
3596
+ const text = buf.toString("utf8").trim();
3597
+ return text.startsWith("0x") ? parseInputData(text) : new Uint8Array(buf);
3598
+ }
3599
+ throw new CliError(`${name} must be 0x-prefixed hex`);
3600
+ }
3601
+ function resolveRingExponent(opts) {
3602
+ if (opts.ringExponent === undefined)
3603
+ return DEFAULT_RING_EXPONENT;
3604
+ const n = Number(opts.ringExponent);
3605
+ if (!isRingExponent(n)) {
3606
+ throw new CliError(`Invalid --ring-exponent "${opts.ringExponent}". Supported: 9, 10, 14.`);
3607
+ }
3608
+ return n;
3609
+ }
3610
+ function requireOption(value, flag, action) {
3611
+ if (value === undefined) {
3612
+ throw new CliError(`dot verifiable ${action} requires ${flag}.`);
3613
+ }
3614
+ return value;
3615
+ }
3616
+ async function deriveMember(accountArg, opts) {
3617
+ const account = requireAccount(accountArg, "member");
3618
+ const usedDeprecatedContext = opts.entropyKey === undefined && opts.context !== undefined;
3619
+ if (usedDeprecatedContext) {
3620
+ if (opts.context.startsWith("0x")) {
3621
+ throw new CliError(`"--context" on "dot verifiable" now means the 32-byte ring context, and hex ` + `entropy keys changed meaning in this release. Pass "--entropy-key ${opts.context}" explicitly.`);
3622
+ }
3623
+ process.stderr.write(`Warning: "--context" on "dot verifiable" now refers to the 32-byte ring context. ` + `For member-key derivation use "--entropy-key". Treating "--context ${opts.context}" ` + `as the entropy key for now.
3624
+ `);
3625
+ }
3626
+ const entropyKeyStr = opts.entropyKey ?? opts.context;
3627
+ let mnemonic;
3628
+ let accountsFile;
3629
+ let stored;
3630
+ if (isDevAccount(account)) {
3631
+ mnemonic = DEV_PHRASE2;
3632
+ } else {
3633
+ accountsFile = await loadAccounts();
3634
+ stored = findAccount(accountsFile, account);
3635
+ mnemonic = mnemonicFromStored(stored, account, accountsFile);
3636
+ }
3637
+ const memberKeyHex = publicKeyToHex(deriveBandersnatchMember(mnemonic, entropyKeyStr));
3638
+ if (stored && accountsFile) {
3639
+ if (!stored.bandersnatch)
3640
+ stored.bandersnatch = {};
3641
+ const entryKey = entropyKeyStr ?? "";
3642
+ if (stored.bandersnatch[entryKey] !== memberKeyHex) {
3643
+ stored.bandersnatch[entryKey] = memberKeyHex;
3644
+ await saveAccounts(accountsFile);
3645
+ }
3646
+ }
3647
+ const fieldKey = usedDeprecatedContext ? "context" : "entropyKey";
3648
+ if (isJsonOutput(opts)) {
3649
+ const result = { account, memberKey: memberKeyHex };
3650
+ if (entropyKeyStr)
3651
+ result[fieldKey] = entropyKeyStr;
3652
+ console.log(formatJson(result));
3653
+ } else {
3654
+ printHeading("Bandersnatch Member Key");
3655
+ console.log(` ${BOLD}Account:${RESET} ${account}`);
3656
+ if (entropyKeyStr) {
3657
+ const line = usedDeprecatedContext ? ` ${BOLD}Context:${RESET} ${entropyKeyStr}` : ` ${BOLD}Entropy Key:${RESET} ${entropyKeyStr}`;
3658
+ console.log(line);
3659
+ }
3660
+ console.log(` ${BOLD}Member Key:${RESET} ${memberKeyHex}`);
3661
+ console.log();
3662
+ }
3663
+ }
3664
+ async function deriveAliasCmd(accountArg, opts) {
3665
+ const account = requireAccount(accountArg, "alias");
3666
+ const contextStr = requireOption(opts.context, "--context", "alias");
3667
+ const entropy = await resolveEntropy(account, opts.entropyKey);
3668
+ const context = encodeContext(contextStr);
3669
+ const aliasHex = toHex2(deriveAlias(entropy, context));
3670
+ if (isJsonOutput(opts)) {
3671
+ console.log(formatJson({ account, context: contextStr, alias: aliasHex }));
3672
+ } else {
3673
+ printHeading("Verifiable Alias");
3674
+ console.log(` ${BOLD}Account:${RESET} ${account}`);
3675
+ console.log(` ${BOLD}Context:${RESET} ${contextStr}`);
3676
+ console.log(` ${BOLD}Alias:${RESET} ${aliasHex}`);
3677
+ console.log();
3678
+ }
3679
+ }
3680
+ async function signCmd(accountArg, opts) {
3681
+ const account = requireAccount(accountArg, "sign");
3682
+ const message = await resolveMessage(opts);
3683
+ const entropy = await resolveEntropy(account, opts.entropyKey);
3684
+ const signature = bandersnatchSign(entropy, message);
3685
+ const member = deriveMemberKey(entropy);
3686
+ const sigHex = toHex2(signature);
3687
+ const result = {
3688
+ type: "Bandersnatch",
3689
+ account,
3690
+ message: toHex2(message),
3691
+ signature: sigHex,
3692
+ member: toHex2(member),
3693
+ enum: `Bandersnatch(${sigHex})`
3694
+ };
3695
+ if (isJsonOutput(opts)) {
3696
+ console.log(formatJson(result));
3697
+ } else {
3698
+ console.log(` ${BOLD}Type:${RESET} ${result.type}`);
3699
+ console.log(` ${BOLD}Message:${RESET} ${result.message}`);
3700
+ console.log(` ${BOLD}Signature:${RESET} ${result.signature}`);
3701
+ console.log(` ${BOLD}Member:${RESET} ${result.member}`);
3702
+ console.log(` ${BOLD}Enum:${RESET} ${result.enum}`);
3703
+ }
3704
+ }
3705
+ async function proveCmd(accountArg, opts) {
3706
+ const account = requireAccount(accountArg, "prove");
3707
+ const contextStr = requireOption(opts.context, "--context", "prove");
3708
+ const membersArg = requireOption(opts.members, "--members", "prove");
3709
+ const message = await resolveMessage(opts);
3710
+ const ringExponent = resolveRingExponent(opts);
3711
+ const entropy = await resolveEntropy(account, opts.entropyKey);
3712
+ const context = encodeContext(contextStr);
3713
+ const members = await resolveBytesArg(membersArg, "--members", true);
3714
+ const { proof, alias } = ringProve(ringExponent, entropy, members, context, message);
3715
+ const result = {
3716
+ account,
3717
+ context: contextStr,
3718
+ ringExponent,
3719
+ alias: toHex2(alias),
3720
+ proof: toHex2(proof)
3721
+ };
3722
+ if (isJsonOutput(opts)) {
3723
+ console.log(formatJson(result));
3724
+ } else {
3725
+ printHeading("Ring-VRF Proof");
3726
+ console.log(` ${BOLD}Account:${RESET} ${account}`);
3727
+ console.log(` ${BOLD}Context:${RESET} ${contextStr}`);
3728
+ console.log(` ${BOLD}Ring exponent:${RESET} ${ringExponent}`);
3729
+ console.log(` ${BOLD}Alias:${RESET} ${result.alias}`);
3730
+ console.log(` ${BOLD}Proof:${RESET} ${result.proof}`);
3731
+ console.log();
3732
+ }
3733
+ }
3734
+ async function verifyCmd(opts) {
3735
+ const proofArg = requireOption(opts.proof, "--proof", "verify");
3736
+ const contextStr = requireOption(opts.context, "--context", "verify");
3737
+ if (!opts.members && !opts.root) {
3738
+ throw new CliError("dot verifiable verify requires --members or --root.");
3739
+ }
3740
+ if (opts.members && opts.root) {
3741
+ throw new CliError("Provide either --members or --root, not both.");
3742
+ }
3743
+ const message = await resolveMessage(opts);
3744
+ const ringExponent = resolveRingExponent(opts);
3745
+ const proof = await resolveBytesArg(proofArg, "--proof", true);
3746
+ const context = encodeContext(contextStr);
3747
+ const source = opts.root ? { commitment: await resolveBytesArg(opts.root, "--root", true) } : { members: await resolveBytesArg(opts.members, "--members", true) };
3748
+ let aliasHex;
3749
+ try {
3750
+ aliasHex = toHex2(verifyRingProof(ringExponent, proof, source, context, message));
3751
+ } catch (err) {
3752
+ const exponentHint = opts.ringExponent === undefined ? ` (assumed ring exponent ${ringExponent} — pass --ring-exponent if the ring uses 10 or 14)` : "";
3753
+ throw new CliError(`Ring-VRF proof is invalid: ${err instanceof Error ? err.message : String(err)}${exponentHint}`);
3754
+ }
3755
+ if (isJsonOutput(opts)) {
3756
+ console.log(formatJson({ valid: true, alias: aliasHex, ringExponent }));
3757
+ } else {
3758
+ printHeading("Ring-VRF Verification");
3759
+ console.log(` ${BOLD}Valid:${RESET} yes`);
3760
+ console.log(` ${BOLD}Alias:${RESET} ${aliasHex}`);
3761
+ console.log();
3762
+ }
3763
+ }
3764
+ async function verifySigCmd(opts) {
3765
+ const sigArg = requireOption(opts.signature, "--signature", "verify-sig");
3766
+ const memberArg = requireOption(opts.member, "--member", "verify-sig");
3767
+ const message = await resolveMessage(opts);
3768
+ const signature = await resolveBytesArg(sigArg, "--signature", true);
3769
+ const member = await resolveBytesArg(memberArg, "--member");
3770
+ const valid = verifyBandersnatchSig(signature, message, member);
3771
+ if (!valid) {
3772
+ throw new CliError("Bandersnatch signature is invalid.");
3773
+ }
3774
+ if (isJsonOutput(opts)) {
3775
+ console.log(formatJson({ valid: true }));
3776
+ } else {
3777
+ printHeading("Bandersnatch Signature Verification");
3778
+ console.log(` ${BOLD}Valid:${RESET} yes`);
3779
+ console.log();
3780
+ }
3781
+ }
3782
+ async function membersCmd(rest, opts) {
3783
+ if (rest.length === 0) {
3784
+ throw new CliError("dot verifiable members requires one or more 0x-hex member keys.");
3785
+ }
3786
+ const memberBytes = rest.map((k) => {
3787
+ const bytes = parseInputData(k);
3788
+ if (bytes.length !== 32) {
3789
+ throw new CliError(`member key must be 32 bytes (got ${bytes.length}): ${k}`);
3790
+ }
3791
+ return bytes;
3792
+ });
3793
+ const encoded = toHex2(encodeMembers(memberBytes));
3794
+ if (isJsonOutput(opts)) {
3795
+ console.log(formatJson({ count: rest.length, members: encoded }));
3796
+ } else {
3797
+ printHeading("Encoded Members");
3798
+ console.log(` ${BOLD}Count:${RESET} ${rest.length}`);
3799
+ console.log(` ${BOLD}Members:${RESET} ${encoded}`);
3800
+ console.log();
3801
+ }
3802
+ }
3803
+ var init_commands = __esm(() => {
3804
+ init_platform();
3805
+ init_lib();
3806
+ });
3807
+
3270
3808
  // src/completions/complete.ts
3271
3809
  var exports_complete = {};
3272
3810
  __export(exports_complete, {
@@ -3628,7 +4166,16 @@ var init_complete = __esm(() => {
3628
4166
  ext: "extensions",
3629
4167
  rpc: "rpc"
3630
4168
  };
3631
- NAMED_COMMANDS = ["chain", "account", "inspect", "hash", "sign", "completions"];
4169
+ NAMED_COMMANDS = [
4170
+ "chain",
4171
+ "account",
4172
+ "inspect",
4173
+ "hash",
4174
+ "sign",
4175
+ "completions",
4176
+ "init",
4177
+ "which"
4178
+ ];
3632
4179
  CHAIN_SUBCOMMANDS = ["add", "info", "list", "remove", "update"];
3633
4180
  ACCOUNT_SUBCOMMANDS = [
3634
4181
  "add",
@@ -3660,17 +4207,17 @@ var init_complete = __esm(() => {
3660
4207
  // src/cli.ts
3661
4208
  import cac from "cac";
3662
4209
  // package.json
3663
- var version = "1.19.0";
4210
+ var version = "1.21.0";
3664
4211
 
3665
4212
  // src/commands/account.ts
3666
4213
  init_accounts_store();
3667
4214
  init_accounts();
3668
4215
  import { readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
3669
- import { hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js";
4216
+ import { hexToBytes as nobleHexToBytes2 } from "@noble/hashes/utils.js";
3670
4217
 
3671
4218
  // src/core/h160.ts
3672
4219
  import { keccak_256 } from "@noble/hashes/sha3.js";
3673
- import { bytesToHex as bytesToHex2, hexToBytes } from "@noble/hashes/utils.js";
4220
+ import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
3674
4221
  var ETH_DERIVED_SUFFIX_BYTE = 238;
3675
4222
  var H160_LEN = 20;
3676
4223
  var ACCOUNT_ID_LEN = 32;
@@ -3722,7 +4269,7 @@ function h160FromHex(input) {
3722
4269
  if (!isH160Hex(input)) {
3723
4270
  throw new Error(`Not a valid 0x-prefixed 20-byte hex string: ${input}`);
3724
4271
  }
3725
- return hexToBytes(input.slice(2));
4272
+ return hexToBytes2(input.slice(2));
3726
4273
  }
3727
4274
 
3728
4275
  // src/commands/account.ts
@@ -3797,7 +4344,8 @@ function isValidParaId(value) {
3797
4344
  var ACCOUNT_HELP = `
3798
4345
  ${BOLD}Usage:${RESET}
3799
4346
  $ dot account add <name> <ss58|hex> Add a watch-only address (no secret)
3800
- $ dot account add <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic
4347
+ $ dot account add <name> --secret <s> [--path <derivation>] Import from BIP39 mnemonic or 32-byte hex seed
4348
+ $ dot account add <name> --secret 0x<128 hex> Import a raw 64-byte sr25519 private key (no --path)
3801
4349
  $ dot account add <name> --env <VAR> [--path <derivation>] Import account backed by env variable
3802
4350
  $ dot account add <name> --parachain <id> --parachain-type <t> Derive a parachain sovereign (t = child|sibling)
3803
4351
  $ dot account add <name> --pallet-id <8 chars or 0x hex> Derive a pallet sovereign (e.g. py/trsry)
@@ -3814,6 +4362,7 @@ ${BOLD}Usage:${RESET}
3814
4362
  ${BOLD}Examples:${RESET}
3815
4363
  $ dot account add treasury 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
3816
4364
  $ dot account add treasury --secret "word1 word2 ... word12"
4365
+ $ dot account add raw-key --secret 0x<128-hex-char sr25519 expanded secret>
3817
4366
  $ dot account add ci-signer --env MY_SECRET --path //ci
3818
4367
  $ dot account add Treasury --pallet-id py/trsry
3819
4368
  $ dot account add Bounties --pallet-id 0x70792f626f756e74
@@ -3841,16 +4390,21 @@ ${BOLD}Examples:${RESET}
3841
4390
 
3842
4391
  ${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
3843
4392
  Use --env to keep secrets off disk entirely.
3844
- Hex seed import (0x...) is not supported via CLI.${RESET}
4393
+ --secret accepts a BIP39 mnemonic, a 0x 32-byte hex seed, or a
4394
+ 0x 64-byte raw sr25519 private key (the value --show-secret prints).
4395
+ Raw private keys cannot be HD-derived, so --path is rejected for them.${RESET}
3845
4396
  `.trimStart();
3846
4397
  function registerAccountCommands(cli) {
3847
- cli.command("account [action] [...names]", "Manage local accounts (create, import, list, remove, export)").alias("accounts").option("--secret <value>", "Secret key (mnemonic or hex seed) for import").option("--env <varName>", "Environment variable name holding the secret").option("--path <derivation>", "Derivation path (e.g. //staking, //polkadot//0/wallet)").option("--parachain <id>", "Derive a parachain sovereign account (requires --parachain-type)").option("--parachain-type <type>", "Parachain sovereign type: child or sibling").option("--pallet-id <id>", "Derive a pallet sovereign account from an 8-byte PalletId").option("--prefix <number>", "SS58 prefix for address encoding (default: 42)").option("--file <path>", "Input/output file for batch import/export").option("--overwrite", "Overwrite existing accounts on batch import").option("--dry-run", "Preview batch import without applying changes").option("--include-secrets", "Include secrets in export (redacted by default)").option("--watch-only", "Export only watch-only accounts").option("--show-secret", "Reveal the 64-byte sr25519 expanded private key (inspect only)").action(async (action, names, opts) => {
4398
+ cli.command("account [action] [...names]", "Manage local accounts (create, import, list, remove, export)").alias("accounts").option("--secret <value>", "Secret for import: BIP39 mnemonic, 0x 32-byte hex seed, or 0x 64-byte raw private key").option("--env <varName>", "Environment variable name holding the secret").option("--path <derivation>", "Derivation path (e.g. //staking, //polkadot//0/wallet)").option("--parachain <id>", "Derive a parachain sovereign account (requires --parachain-type)").option("--parachain-type <type>", "Parachain sovereign type: child or sibling").option("--pallet-id <id>", "Derive a pallet sovereign account from an 8-byte PalletId").option("--prefix <number>", "SS58 prefix for address encoding (default: 42)").option("--file <path>", "Input/output file for batch import/export").option("--overwrite", "Overwrite existing accounts on batch import").option("--dry-run", "Preview batch import without applying changes").option("--include-secrets", "Include secrets in export (redacted by default)").option("--watch-only", "Export only watch-only accounts").option("--show-secret", "Reveal the 64-byte sr25519 expanded private key (inspect only)").action(async (action, names, opts) => {
3848
4399
  if (!action) {
3849
4400
  if (process.argv[2] === "accounts")
3850
4401
  return accountList(opts);
3851
4402
  console.log(ACCOUNT_HELP);
3852
4403
  return;
3853
4404
  }
4405
+ const rawSecret = rawArgValue("--secret");
4406
+ if (rawSecret != null)
4407
+ opts.secret = rawSecret;
3854
4408
  switch (action) {
3855
4409
  case "new":
3856
4410
  case "create":
@@ -3900,7 +4454,7 @@ async function accountCreate(name, opts) {
3900
4454
  const { mnemonic, publicKey } = createNewAccount(path);
3901
4455
  const hexPub = publicKeyToHex(publicKey);
3902
4456
  const address = toSs58(publicKey);
3903
- const { deriveBandersnatchMember: deriveBandersnatchMember2 } = await Promise.resolve().then(() => (init_bandersnatch(), exports_bandersnatch));
4457
+ const { deriveBandersnatchMember: deriveBandersnatchMember2 } = await Promise.resolve().then(() => (init_lib(), exports_lib));
3904
4458
  const bandersnatch = {};
3905
4459
  bandersnatch[""] = publicKeyToHex(deriveBandersnatchMember2(mnemonic));
3906
4460
  bandersnatch.candidate = publicKeyToHex(deriveBandersnatchMember2(mnemonic, "candidate"));
@@ -4492,8 +5046,9 @@ async function accountInspect(input, opts) {
4492
5046
  }
4493
5047
  }
4494
5048
  const ss58 = toSs58(publicKeyHex, prefix);
4495
- const h160Hex = toEip55(accountIdToH160(nobleHexToBytes(publicKeyHex.slice(2))));
5049
+ const h160Hex = toEip55(accountIdToH160(nobleHexToBytes2(publicKeyHex.slice(2))));
4496
5050
  let privateKeyHex;
5051
+ let revealedSecret;
4497
5052
  if (opts.showSecret) {
4498
5053
  if (!name) {
4499
5054
  console.error("--show-secret requires an account name; raw addresses and hex keys have no secret to reveal.");
@@ -4509,6 +5064,14 @@ async function accountInspect(input, opts) {
4509
5064
  console.error(err.message);
4510
5065
  process.exit(1);
4511
5066
  }
5067
+ if (storedAccount?.secret !== undefined && !isEnvSecret(storedAccount.secret)) {
5068
+ const kind = secretKind(storedAccount.secret);
5069
+ if (kind === "mnemonic") {
5070
+ revealedSecret = { label: "Mnemonic", field: "mnemonic", value: storedAccount.secret };
5071
+ } else if (kind === "seed") {
5072
+ revealedSecret = { label: "Seed", field: "seed", value: storedAccount.secret };
5073
+ }
5074
+ }
4512
5075
  }
4513
5076
  let kindLabel;
4514
5077
  let sourceLine;
@@ -4587,6 +5150,8 @@ async function accountInspect(input, opts) {
4587
5150
  result.env = envLine.replace(/^\$/, "");
4588
5151
  if (bandersnatch && Object.keys(bandersnatch).length > 0)
4589
5152
  result.bandersnatch = bandersnatch;
5153
+ if (revealedSecret)
5154
+ result[revealedSecret.field] = revealedSecret.value;
4590
5155
  if (privateKeyHex)
4591
5156
  result.privateKey = privateKeyHex;
4592
5157
  console.log(formatJson(result));
@@ -4618,6 +5183,12 @@ async function accountInspect(input, opts) {
4618
5183
  }
4619
5184
  }
4620
5185
  console.log(` ${BOLD}Prefix:${RESET} ${prefix}`);
5186
+ if (revealedSecret) {
5187
+ console.log(` ${BOLD}${`${revealedSecret.label}:`.padEnd(13)}${RESET}${revealedSecret.value}`);
5188
+ if (derivationLine) {
5189
+ console.log(` ${YELLOW}(derived with ${derivationLine} — re-import needs --path ${derivationLine})${RESET}`);
5190
+ }
5191
+ }
4621
5192
  if (privateKeyHex) {
4622
5193
  console.log(` ${BOLD}Private Key:${RESET} ${privateKeyHex}`);
4623
5194
  console.log(` ${YELLOW}(sr25519 expanded, 64 bytes — never share)${RESET}`);
@@ -5540,7 +6111,7 @@ function registerCompletionsCommand(cli) {
5540
6111
  process.exit(1);
5541
6112
  }
5542
6113
  const instructions = SETUP_INSTRUCTIONS[shell];
5543
- if (instructions) {
6114
+ if (instructions && process.stdout.isTTY) {
5544
6115
  process.stderr.write(`${instructions}
5545
6116
  `);
5546
6117
  }
@@ -6321,30 +6892,9 @@ async function handleExtensions(target, opts) {
6321
6892
 
6322
6893
  // src/commands/hash.ts
6323
6894
  init_hash();
6895
+ init_input();
6324
6896
  init_output();
6325
6897
  init_errors();
6326
- import { readFile as readFile5 } from "node:fs/promises";
6327
- async function resolveInput(data, opts) {
6328
- const sources = [data !== undefined, !!opts.file, !!opts.stdin].filter(Boolean).length;
6329
- if (sources > 1) {
6330
- throw new CliError("Provide only one of: inline data, --file, or --stdin");
6331
- }
6332
- if (sources === 0) {
6333
- throw new CliError("No input provided. Pass data as argument, or use --file or --stdin");
6334
- }
6335
- if (opts.file) {
6336
- const buf = await readFile5(opts.file);
6337
- return new Uint8Array(buf);
6338
- }
6339
- if (opts.stdin) {
6340
- const chunks = [];
6341
- for await (const chunk of process.stdin) {
6342
- chunks.push(chunk);
6343
- }
6344
- return new Uint8Array(Buffer.concat(chunks));
6345
- }
6346
- return parseInputData(data);
6347
- }
6348
6898
  function printAlgorithmHelp() {
6349
6899
  console.log(`${BOLD}Usage:${RESET} dot hash <algorithm> <data> [options]
6350
6900
  `);
@@ -6373,7 +6923,7 @@ function registerHashCommand(cli) {
6373
6923
  if (!isValidAlgorithm(algorithm)) {
6374
6924
  throw new CliError(suggestMessage("algorithm", algorithm, getAlgorithmNames()));
6375
6925
  }
6376
- const input = await resolveInput(data, opts);
6926
+ const input = await resolveDataInput(data, opts);
6377
6927
  const hash = computeHash(algorithm, input);
6378
6928
  const hexHash = toHex2(hash);
6379
6929
  if (isJsonOutput(opts)) {
@@ -7279,9 +7829,9 @@ async function handleRpc(method, args, opts) {
7279
7829
  // src/commands/sign.ts
7280
7830
  init_accounts();
7281
7831
  init_hash();
7832
+ init_input();
7282
7833
  init_output();
7283
7834
  init_errors();
7284
- import { readFile as readFile6 } from "node:fs/promises";
7285
7835
  var SUPPORTED_TYPES = ["sr25519"];
7286
7836
  function isSupportedType(type) {
7287
7837
  return SUPPORTED_TYPES.includes(type.toLowerCase());
@@ -7292,27 +7842,6 @@ function variantName(type) {
7292
7842
  return "Sr25519";
7293
7843
  }
7294
7844
  }
7295
- async function resolveInput2(data, opts) {
7296
- const sources = [data !== undefined, !!opts.file, !!opts.stdin].filter(Boolean).length;
7297
- if (sources > 1) {
7298
- throw new CliError("Provide only one of: inline data, --file, or --stdin");
7299
- }
7300
- if (sources === 0) {
7301
- throw new CliError("No input provided. Pass data as argument, or use --file or --stdin");
7302
- }
7303
- if (opts.file) {
7304
- const buf = await readFile6(opts.file);
7305
- return new Uint8Array(buf);
7306
- }
7307
- if (opts.stdin) {
7308
- const chunks = [];
7309
- for await (const chunk of process.stdin) {
7310
- chunks.push(chunk);
7311
- }
7312
- return new Uint8Array(Buffer.concat(chunks));
7313
- }
7314
- return parseInputData(data);
7315
- }
7316
7845
  function printSignHelp() {
7317
7846
  console.log(`${BOLD}Usage:${RESET} dot sign <message> --from <account> [options]
7318
7847
  `);
@@ -7346,7 +7875,7 @@ function registerSignCommand(cli) {
7346
7875
  if (!isSupportedType(type)) {
7347
7876
  throw new CliError(`Unsupported signature type "${opts.type}". Supported: ${SUPPORTED_TYPES.join(", ")}`);
7348
7877
  }
7349
- const input = await resolveInput2(message, opts);
7878
+ const input = await resolveDataInput(message, opts);
7350
7879
  const keypair = await resolveAccountKeypair(opts.from);
7351
7880
  const signature = keypair.sign(input);
7352
7881
  const hexMessage = toHex2(input);
@@ -8718,7 +9247,7 @@ function buildGeneralTx(meta, callData, userExtOverrides) {
8718
9247
  }
8719
9248
  function watchTransaction(observable, level, options) {
8720
9249
  const spinner = new Spinner;
8721
- return new Promise((resolve, reject) => {
9250
+ return new Promise((resolve2, reject) => {
8722
9251
  let settled = false;
8723
9252
  spinner.start(options?.unsigned ? "Submitting..." : "Signing...");
8724
9253
  const subscription = observable.subscribe({
@@ -8738,7 +9267,7 @@ function watchTransaction(observable, level, options) {
8738
9267
  spinner.succeed("Broadcasted");
8739
9268
  settled = true;
8740
9269
  subscription.unsubscribe();
8741
- resolve(event);
9270
+ resolve2(event);
8742
9271
  } else {
8743
9272
  spinner.succeed("Broadcasted");
8744
9273
  spinner.start("In best block...");
@@ -8750,7 +9279,7 @@ function watchTransaction(observable, level, options) {
8750
9279
  spinner.succeed(`In best block #${event.block.number}`);
8751
9280
  settled = true;
8752
9281
  subscription.unsubscribe();
8753
- resolve(event);
9282
+ resolve2(event);
8754
9283
  } else {
8755
9284
  spinner.succeed(`In best block #${event.block.number}`);
8756
9285
  spinner.start("Finalizing...");
@@ -8762,7 +9291,7 @@ function watchTransaction(observable, level, options) {
8762
9291
  case "finalized":
8763
9292
  spinner.succeed(`Finalized in block #${event.block.number}`);
8764
9293
  settled = true;
8765
- resolve(event);
9294
+ resolve2(event);
8766
9295
  break;
8767
9296
  }
8768
9297
  },
@@ -8776,7 +9305,7 @@ function watchTransaction(observable, level, options) {
8776
9305
  });
8777
9306
  }
8778
9307
  function watchTransactionJson(observable, level, options) {
8779
- return new Promise((resolve, reject) => {
9308
+ return new Promise((resolve2, reject) => {
8780
9309
  let settled = false;
8781
9310
  const subscription = observable.subscribe({
8782
9311
  next(event) {
@@ -8793,7 +9322,7 @@ function watchTransactionJson(observable, level, options) {
8793
9322
  if (level === "broadcast") {
8794
9323
  settled = true;
8795
9324
  subscription.unsubscribe();
8796
- resolve(event);
9325
+ resolve2(event);
8797
9326
  }
8798
9327
  break;
8799
9328
  case "txBestBlocksState":
@@ -8802,13 +9331,13 @@ function watchTransactionJson(observable, level, options) {
8802
9331
  if (level === "best-block") {
8803
9332
  settled = true;
8804
9333
  subscription.unsubscribe();
8805
- resolve(event);
9334
+ resolve2(event);
8806
9335
  }
8807
9336
  }
8808
9337
  break;
8809
9338
  case "finalized":
8810
9339
  settled = true;
8811
- resolve(event);
9340
+ resolve2(event);
8812
9341
  break;
8813
9342
  }
8814
9343
  },
@@ -8821,119 +9350,91 @@ function watchTransactionJson(observable, level, options) {
8821
9350
  });
8822
9351
  }
8823
9352
 
8824
- // src/commands/verifiable.ts
8825
- init_accounts_store();
8826
- init_accounts();
8827
- init_bandersnatch();
9353
+ // src/commands/workspace.ts
9354
+ init_store();
9355
+ init_workspace();
8828
9356
  init_output();
8829
- var DEV_PHRASE2 = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
8830
- var VERIFIABLE_HELP = `
8831
- ${BOLD}Usage:${RESET}
8832
- $ dot verifiable <account> [--context <value>]
8833
-
8834
- ${BOLD}Arguments:${RESET}
8835
- account Account name (stored or dev account)
8836
-
8837
- ${BOLD}Options:${RESET}
8838
- --context <value> Blake2b context for keyed derivation (e.g. "candidate")
8839
-
8840
- ${BOLD}Examples:${RESET}
8841
- $ dot verifiable alice Unkeyed derivation (lite person)
8842
- $ dot verifiable alice --context candidate Keyed with "candidate" (full person)
8843
- $ dot verifiable my-account --context candidate
8844
-
8845
- ${BOLD}How it works:${RESET}
8846
-
8847
- Mnemonic (12/24 words)
8848
- │ mnemonicToEntropy() (raw BIP39 entropy, NOT miniSecret)
8849
-
8850
- blake2b256(entropy, context?) keyed or unkeyed
8851
-
8852
- member_from_entropy() verifiablejs WASM (Bandersnatch curve)
8853
-
8854
- 32-byte member key for on-chain member set registration
8855
- `.trimStart();
8856
- function registerVerifiableCommands(cli) {
8857
- 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) => {
8858
- if (!account) {
8859
- console.log(VERIFIABLE_HELP);
8860
- return;
8861
- }
8862
- return deriveVerifiable(account, opts);
8863
- });
8864
- }
8865
- async function deriveVerifiable(account, opts) {
8866
- const mnemonic = await resolveMnemonic(account);
8867
- const memberKey = deriveBandersnatchMember(mnemonic, opts.context);
8868
- const memberKeyHex = publicKeyToHex(memberKey);
8869
- if (!isDevAccount(account)) {
8870
- const accountsFile = await loadAccounts();
8871
- const stored = findAccount(accountsFile, account);
8872
- if (stored) {
8873
- if (!stored.bandersnatch)
8874
- stored.bandersnatch = {};
8875
- stored.bandersnatch[opts.context ?? ""] = memberKeyHex;
8876
- await saveAccounts(accountsFile);
8877
- }
9357
+ init_errors();
9358
+ import { mkdir as mkdir3, stat } from "node:fs/promises";
9359
+ import { homedir as homedir3 } from "node:os";
9360
+ import { join as join4 } from "node:path";
9361
+ async function initWorkspace(cwd, home = homedir3()) {
9362
+ const dir = canonicalPath(cwd);
9363
+ if (dir === canonicalPath(home)) {
9364
+ throw new CliError(`Cannot initialize a workspace in your home directory — ${join4(dir, WORKSPACE_DIR_NAME)} is the global config root.`);
8878
9365
  }
8879
- if (isJsonOutput(opts)) {
8880
- const result = {
8881
- account,
8882
- memberKey: memberKeyHex
8883
- };
8884
- if (opts.context)
8885
- result.context = opts.context;
8886
- console.log(formatJson(result));
8887
- } else {
8888
- printHeading("Bandersnatch Member Key");
8889
- console.log(` ${BOLD}Account:${RESET} ${account}`);
8890
- if (opts.context)
8891
- console.log(` ${BOLD}Context:${RESET} ${opts.context}`);
8892
- console.log(` ${BOLD}Member Key:${RESET} ${memberKeyHex}`);
8893
- console.log();
9366
+ const workspacePath = join4(dir, WORKSPACE_DIR_NAME);
9367
+ const exists = await stat(workspacePath).then(() => true).catch(() => false);
9368
+ if (exists) {
9369
+ throw new CliError(`A workspace already exists at ${workspacePath}.`);
8894
9370
  }
8895
- }
8896
- async function resolveMnemonic(account) {
8897
- if (isDevAccount(account)) {
8898
- return DEV_PHRASE2;
9371
+ const warnings = [];
9372
+ const parentWorkspace = findWorkspace(dir, home);
9373
+ if (parentWorkspace) {
9374
+ warnings.push(`This workspace shadows ${parentWorkspace} for commands run below ${dir}.`);
8899
9375
  }
8900
- const accountsFile = await loadAccounts();
8901
- const stored = findAccount(accountsFile, account);
8902
- if (!stored) {
8903
- const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)].sort((a, b) => a.localeCompare(b));
8904
- const suggestions = findClosest(account, available);
8905
- const hint = suggestions.length > 0 ? `
8906
- Did you mean: ${suggestions.join(", ")}?` : "";
8907
- const list = available.map((a) => `
8908
- - ${a}`).join("");
8909
- throw new Error(`Unknown account "${account}".${hint}
8910
- Available accounts:${list}`);
9376
+ const dotHome = process.env.DOT_HOME;
9377
+ if (dotHome && dotHome.length > 0) {
9378
+ warnings.push(`DOT_HOME is set (${dotHome}) and takes precedence — this workspace will not be picked up until you unset it.`);
8911
9379
  }
8912
- if (isWatchOnly(stored)) {
8913
- throw new Error(`Account "${account}" is watch-only (no secret). Cannot derive Bandersnatch key.`);
9380
+ await mkdir3(workspacePath, { recursive: true });
9381
+ return { workspacePath, warnings };
9382
+ }
9383
+ var SOURCE_LABELS = {
9384
+ env: "DOT_HOME environment variable",
9385
+ workspace: "local workspace (discovered from current directory)",
9386
+ global: "global config"
9387
+ };
9388
+ async function handleInit(cwd = process.cwd()) {
9389
+ const result = await initWorkspace(cwd);
9390
+ for (const warning of result.warnings) {
9391
+ process.stderr.write(`Warning: ${warning}
9392
+ `);
8914
9393
  }
8915
- const secret = resolveSecret(stored.secret);
8916
- if (isHexPublicKey(`0x${secret.replace(/^0x/, "")}`)) {
8917
- throw new Error(`Account "${account}" uses a hex seed. Bandersnatch derivation requires a BIP39 mnemonic.`);
9394
+ await writeStdout(`Initialized empty dot workspace at ${result.workspacePath}
9395
+ ` + `Check which workspace is active with: dot which
9396
+ `);
9397
+ }
9398
+ async function handleWhich(opts, cwd = process.cwd()) {
9399
+ const resolved = resolveConfigDir(cwd);
9400
+ if (isJsonOutput(opts)) {
9401
+ await writeStdout(`${JSON.stringify({ path: resolved.path, source: resolved.source })}
9402
+ `);
9403
+ return;
8918
9404
  }
8919
- return secret;
9405
+ await writeStdout(`${resolved.path}
9406
+ Source: ${SOURCE_LABELS[resolved.source]}
9407
+ `);
9408
+ }
9409
+ function registerWorkspaceCommands(cli) {
9410
+ cli.command("init", "Initialize a local .polkadot workspace in the current directory").action(() => handleInit());
9411
+ cli.command("which", "Show the active config root (workspace, DOT_HOME, or global)").action((opts) => handleWhich(opts));
8920
9412
  }
8921
9413
 
8922
9414
  // src/config/store.ts
8923
9415
  init_errors();
8924
9416
  init_types();
8925
- import { access as access3, mkdir as mkdir3, readFile as readFile7, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
8926
- import { homedir as homedir2 } from "node:os";
8927
- import { join as join3 } from "node:path";
8928
- function getConfigDir2() {
9417
+ init_workspace();
9418
+ import { access as access3, mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
9419
+ import { homedir as homedir4 } from "node:os";
9420
+ import { join as join5 } from "node:path";
9421
+ function resolveConfigDir2(cwd = process.cwd()) {
8929
9422
  const override = process.env.DOT_HOME;
8930
- return override && override.length > 0 ? override : join3(homedir2(), ".polkadot");
9423
+ if (override && override.length > 0)
9424
+ return { path: override, source: "env" };
9425
+ const workspace = findWorkspace(cwd);
9426
+ if (workspace)
9427
+ return { path: workspace, source: "workspace" };
9428
+ return { path: join5(homedir4(), ".polkadot"), source: "global" };
9429
+ }
9430
+ function getConfigDir2() {
9431
+ return resolveConfigDir2().path;
8931
9432
  }
8932
9433
  function getConfigPath2() {
8933
- return join3(getConfigDir2(), "config.json");
9434
+ return join5(getConfigDir2(), "config.json");
8934
9435
  }
8935
9436
  async function ensureDir3(dir) {
8936
- await mkdir3(dir, { recursive: true });
9437
+ await mkdir4(dir, { recursive: true });
8937
9438
  }
8938
9439
  async function fileExists3(path) {
8939
9440
  try {
@@ -8947,7 +9448,7 @@ async function loadConfig2() {
8947
9448
  await ensureDir3(getConfigDir2());
8948
9449
  const configPath = getConfigPath2();
8949
9450
  if (await fileExists3(configPath)) {
8950
- const saved = JSON.parse(await readFile7(configPath, "utf-8"));
9451
+ const saved = JSON.parse(await readFile6(configPath, "utf-8"));
8951
9452
  const chains = {};
8952
9453
  for (const [name, defaultConfig] of Object.entries(DEFAULT_CONFIG.chains)) {
8953
9454
  chains[name] = saved.chains[name] ? { ...defaultConfig, ...saved.chains[name] } : defaultConfig;
@@ -8970,7 +9471,7 @@ async function saveConfig2(config) {
8970
9471
 
8971
9472
  // src/core/file-loader.ts
8972
9473
  init_errors();
8973
- import { access as access4, readFile as readFile8 } from "node:fs/promises";
9474
+ import { access as access4, readFile as readFile7 } from "node:fs/promises";
8974
9475
  import { parse as parseYaml } from "yaml";
8975
9476
  var CATEGORIES = ["tx", "query", "const", "apis"];
8976
9477
  var FILE_EXTENSIONS = [".json", ".yaml", ".yml"];
@@ -9035,7 +9536,7 @@ async function loadCommandFile(filePath, cliVars) {
9035
9536
  } catch {
9036
9537
  throw new CliError(`File not found: ${filePath}`);
9037
9538
  }
9038
- const rawText = await readFile8(filePath, "utf-8");
9539
+ const rawText = await readFile7(filePath, "utf-8");
9039
9540
  if (!rawText.trim()) {
9040
9541
  throw new CliError(`File is empty: ${filePath}`);
9041
9542
  }
@@ -9107,8 +9608,8 @@ async function loadCommandFile(filePath, cliVars) {
9107
9608
  // src/core/update-notifier.ts
9108
9609
  init_store();
9109
9610
  import { readFileSync } from "node:fs";
9110
- import { mkdir as mkdir4, writeFile as writeFile6 } from "node:fs/promises";
9111
- import { join as join4 } from "node:path";
9611
+ import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
9612
+ import { join as join6 } from "node:path";
9112
9613
  var CACHE_FILE = "update-check.json";
9113
9614
  var STALE_MS = 24 * 60 * 60 * 1000;
9114
9615
  var FETCH_TIMEOUT_MS = 5000;
@@ -9164,7 +9665,7 @@ function buildNotificationBox(current, latest) {
9164
9665
  `);
9165
9666
  }
9166
9667
  function getCachePath() {
9167
- return join4(getConfigDir(), CACHE_FILE);
9668
+ return join6(getConfigDir(), CACHE_FILE);
9168
9669
  }
9169
9670
  function readCache() {
9170
9671
  try {
@@ -9176,7 +9677,7 @@ function readCache() {
9176
9677
  }
9177
9678
  async function writeCache(cache) {
9178
9679
  try {
9179
- await mkdir4(getConfigDir(), { recursive: true });
9680
+ await mkdir5(getConfigDir(), { recursive: true });
9180
9681
  await writeFile6(getCachePath(), `${JSON.stringify(cache)}
9181
9682
  `);
9182
9683
  } catch {}
@@ -9204,7 +9705,7 @@ function startBackgroundCheck(currentVersion) {
9204
9705
  async function waitForPendingCheck() {
9205
9706
  if (!pendingCheck)
9206
9707
  return;
9207
- const timeout = new Promise((resolve) => setTimeout(resolve, EXIT_WAIT_TIMEOUT_MS));
9708
+ const timeout = new Promise((resolve2) => setTimeout(resolve2, EXIT_WAIT_TIMEOUT_MS));
9208
9709
  await Promise.race([pendingCheck.catch(() => {}), timeout]);
9209
9710
  pendingCheck = null;
9210
9711
  }
@@ -9224,6 +9725,117 @@ function getUpdateNotification(currentVersion) {
9224
9725
  return null;
9225
9726
  }
9226
9727
 
9728
+ // src/features/verifiable/register.ts
9729
+ init_platform();
9730
+ var VERIFIABLE_HELP = `
9731
+ ${BOLD}Usage:${RESET}
9732
+ $ dot verifiable [account] [--entropy-key <key>] Derive the member key (default action)
9733
+ $ dot verifiable <action> [account] [options]
9734
+
9735
+ ${BOLD}Actions:${RESET}
9736
+ member <account> Derive the Bandersnatch member key (default if omitted)
9737
+ alias <account> Derive the alias for a 32-byte ring context
9738
+ sign <account> Standalone Bandersnatch signature (64 bytes)
9739
+ prove <account> Ring-VRF proof (one_shot) over a members set
9740
+ verify Locally verify a ring-VRF proof against members/root
9741
+ verify-sig Verify a standalone Bandersnatch signature
9742
+ members <key…> SCALE-encode member keys as Vec<[u8;32]>
9743
+
9744
+ verify / verify-sig exit non-zero on failure (the verdict is the exit code);
9745
+ on success they print the recovered alias / {"valid":true}.
9746
+
9747
+ ${BOLD}Key concepts (do not conflate these):${RESET}
9748
+ --entropy-key <text|0xhex>
9749
+ Key mixed into the keyed-blake2b that turns your mnemonic into the
9750
+ Bandersnatch member entropy. Omit for a ${BOLD}lite${RESET} person (unkeyed);
9751
+ use "candidate" for a ${BOLD}full${RESET} person. Must match the key used when the
9752
+ member was recognised on-chain, or you derive a different (unrecognised)
9753
+ member key. It is NOT a derivation path and NOT the ring --context.
9754
+ --context <text|0xhex>
9755
+ The 32-byte ring/proof namespace (e.g. "dotns"), zero-padded right to 32
9756
+ bytes like Solidity bytes32(). Determines the alias. Used by alias/prove/verify.
9757
+
9758
+ ${BOLD}Options:${RESET}
9759
+ --entropy-key <key> Entropy-derivation key (see above)
9760
+ --context <value> 32-byte ring context (alias/prove/verify)
9761
+ --message <data> Message to sign / bind / verify (text or 0x hex)
9762
+ --file <path> Read the message from a file (raw bytes)
9763
+ --stdin Read the message from stdin
9764
+ --members <hex|file> SCALE-encoded Vec<[u8;32]> ring (prove/verify)
9765
+ --root <hex> 768-byte ring root / commitment (verify)
9766
+ --proof <hex> Ring-VRF proof bytes (verify)
9767
+ --signature <hex> Bandersnatch signature (verify-sig)
9768
+ --member <hex> 32-byte member public key (verify-sig)
9769
+ --ring-exponent <n> Ring exponent: 9 (default), 10, or 14
9770
+ --output json Output as JSON
9771
+
9772
+ ${BOLD}Examples:${RESET}
9773
+ $ dot verifiable alice Lite member key
9774
+ $ dot verifiable alice --entropy-key candidate Full member key
9775
+ $ dot verifiable alias alice --entropy-key candidate --context dotns
9776
+ $ dot verifiable sign alice --message "hello" --entropy-key candidate
9777
+ $ dot verifiable prove alice --entropy-key candidate --context dotns \\
9778
+ --message 0x… --members 0x…
9779
+ $ dot verifiable verify --proof 0x… --context dotns --message 0x… --members 0x…
9780
+ $ dot verifiable members 0x… 0x…
9781
+
9782
+ ${BOLD}Derivation flow:${RESET}
9783
+
9784
+ Mnemonic ─BIP39─▶ entropy ─keyed blake2b─▶ member entropy ─▶ member key / secret
9785
+ (key = --entropy-key) │
9786
+ ring proof: one_shot(…, --context, --message)
9787
+ `.trimStart();
9788
+ var RAW_STRING_FLAGS = [
9789
+ ["entropy-key", "entropyKey"],
9790
+ ["context", "context"],
9791
+ ["message", "message"],
9792
+ ["members", "members"],
9793
+ ["root", "root"],
9794
+ ["proof", "proof"],
9795
+ ["signature", "signature"],
9796
+ ["member", "member"]
9797
+ ];
9798
+ function registerVerifiableCommands(cli) {
9799
+ cli.command("verifiable [action] [...rest]", "Bandersnatch member keys, ring-VRF proofs, signing and verification").option("--entropy-key <key>", "Entropy-derivation key (omit = lite, 'candidate' = full)").option("--context <value>", "32-byte ring/proof context (alias/prove/verify)").option("--message <data>", "Message to sign/bind/verify (text or 0x hex)").option("--file <path>", "Read message from a file (raw bytes)").option("--stdin", "Read message from stdin").option("--members <hex|file>", "SCALE-encoded Vec<[u8;32]> ring (prove/verify)").option("--root <hex>", "768-byte ring root/commitment (verify)").option("--proof <hex>", "Ring-VRF proof bytes (verify)").option("--signature <hex>", "Bandersnatch signature (verify-sig)").option("--member <hex>", "32-byte member public key (verify-sig)").option("--ring-exponent <n>", "Ring exponent: 9 (default), 10, or 14").action(async (action, rest, opts) => {
9800
+ if (!action) {
9801
+ console.log(VERIFIABLE_HELP);
9802
+ return;
9803
+ }
9804
+ for (const [flag, key] of RAW_STRING_FLAGS) {
9805
+ const raw = readRawOptionValue(flag);
9806
+ if (raw !== undefined)
9807
+ opts[key] = raw;
9808
+ }
9809
+ const { runVerifiable: runVerifiable2 } = await Promise.resolve().then(() => (init_commands(), exports_commands));
9810
+ return runVerifiable2(action, rest, opts);
9811
+ });
9812
+ }
9813
+
9814
+ // src/platform/cli.ts
9815
+ function registerGlobalOptions(cli) {
9816
+ cli.option("--chain <name>", "Target chain (required)");
9817
+ cli.option("--rpc <url>", "Override RPC endpoint for this call");
9818
+ cli.option("--output <format>", "Output format: pretty or json", {
9819
+ default: "pretty"
9820
+ });
9821
+ cli.option("--json", "Output as JSON (shorthand for --output json)");
9822
+ }
9823
+ function readRawOptionValue2(name, argv = process.argv) {
9824
+ const flag = `--${name}`;
9825
+ const prefix = `${flag}=`;
9826
+ let value;
9827
+ for (let i = 0;i < argv.length; i++) {
9828
+ const arg = argv[i];
9829
+ if (arg === "--")
9830
+ break;
9831
+ if (arg === flag && i + 1 < argv.length)
9832
+ value = argv[i + 1];
9833
+ else if (arg.startsWith(prefix))
9834
+ value = arg.slice(prefix.length);
9835
+ }
9836
+ return value;
9837
+ }
9838
+
9227
9839
  // src/utils/errors.ts
9228
9840
  class CliError2 extends Error {
9229
9841
  constructor(message) {
@@ -9346,15 +9958,7 @@ if (process.argv[2] === "__complete") {
9346
9958
  process.exit(0);
9347
9959
  })();
9348
9960
  } else {
9349
- let readAtFromArgv = function(argv) {
9350
- for (let i = 0;i < argv.length; i++) {
9351
- if (argv[i] === "--at" && i + 1 < argv.length)
9352
- return argv[i + 1];
9353
- if (argv[i].startsWith("--at="))
9354
- return argv[i].slice(5);
9355
- }
9356
- return;
9357
- }, collectVarFlags = function(argv) {
9961
+ let collectVarFlags = function(argv) {
9358
9962
  const vars = [];
9359
9963
  for (let i = 0;i < argv.length; i++) {
9360
9964
  if (argv[i] === "--var" && i + 1 < argv.length) {
@@ -9410,8 +10014,10 @@ if (process.argv[2] === "__complete") {
9410
10014
  console.log(" hash Hash utilities");
9411
10015
  console.log(" sign Sign a message with an account keypair");
9412
10016
  console.log(" parachain Derive parachain sovereign accounts (deprecated \u2014 use `account inspect --parachain`)");
9413
- console.log(" verifiable Derive Bandersnatch member key from mnemonic");
10017
+ console.log(" verifiable Bandersnatch member keys, ring-VRF proofs, sign/verify");
9414
10018
  console.log(" completions <sh> Generate shell completions (zsh, bash, fish)");
10019
+ console.log(" init Initialize a local .polkadot workspace in this directory");
10020
+ console.log(" which Show the active config root (workspace, DOT_HOME, or global)");
9415
10021
  console.log();
9416
10022
  console.log("Global options:");
9417
10023
  console.log(" --chain <name> Target chain (required)");
@@ -9423,12 +10029,7 @@ if (process.argv[2] === "__complete") {
9423
10029
  };
9424
10030
  startBackgroundCheck(version);
9425
10031
  const cli = cac("dot");
9426
- cli.option("--chain <name>", "Target chain (required)");
9427
- cli.option("--rpc <url>", "Override RPC endpoint for this call");
9428
- cli.option("--output <format>", "Output format: pretty or json", {
9429
- default: "pretty"
9430
- });
9431
- cli.option("--json", "Output as JSON (shorthand for --output json)");
10032
+ registerGlobalOptions(cli);
9432
10033
  registerChainCommands(cli);
9433
10034
  registerInspectCommand(cli);
9434
10035
  registerMetadataCommand(cli);
@@ -9438,6 +10039,7 @@ if (process.argv[2] === "__complete") {
9438
10039
  registerParachainCommand(cli);
9439
10040
  registerCompletionsCommand(cli);
9440
10041
  registerVerifiableCommands(cli);
10042
+ registerWorkspaceCommands(cli);
9441
10043
  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("--to-yaml", "Decode call to YAML file format (for tx)").option("--to-json", "Decode call to JSON file format (for tx)").option("--ext <json>", "Custom signed extension values as JSON (for tx)").option("--asset <json>", "Pay fees in an alternative asset (XCM location JSON, for tx)").option("-w, --wait <level>", "Resolve at: broadcast, best-block (or best), finalized (for tx)", {
9442
10044
  default: "finalized"
9443
10045
  }).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 read/validate against (tx, query, apis)').option("--unsigned", "Submit as unsigned/bare transaction (no signer required, for tx)").option("--refresh", "Refresh the cached RPC method list from the node (for rpc)").action(async (dotpath, args, opts) => {
@@ -9445,7 +10047,7 @@ if (process.argv[2] === "__complete") {
9445
10047
  printHelp();
9446
10048
  return;
9447
10049
  }
9448
- const atRaw = readAtFromArgv(process.argv);
10050
+ const atRaw = readRawOptionValue2("at");
9449
10051
  if (isFilePath(dotpath)) {
9450
10052
  const cliVars = collectVarFlags(process.argv);
9451
10053
  const cmd = await loadCommandFile(dotpath, cliVars);