polkadot-cli 0.6.0 → 0.6.2

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 +25 -5
  2. package/dist/cli.mjs +975 -921
  3. package/package.json +8 -2
package/dist/cli.mjs CHANGED
@@ -4,11 +4,17 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/cli.ts
6
6
  import cac from "cac";
7
+ // package.json
8
+ var version = "0.6.2";
9
+
10
+ // src/config/accounts-store.ts
11
+ import { access as access2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
12
+ import { join as join2 } from "node:path";
7
13
 
8
14
  // src/config/store.ts
9
- import { join } from "node:path";
15
+ import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
10
16
  import { homedir } from "node:os";
11
- import { mkdir, readFile, writeFile, rm, access } from "node:fs/promises";
17
+ import { join } from "node:path";
12
18
 
13
19
  // src/config/types.ts
14
20
  var DEFAULT_CONFIG = {
@@ -55,7 +61,7 @@ async function loadConfig() {
55
61
  }
56
62
  async function saveConfig(config) {
57
63
  await ensureDir(DOT_DIR);
58
- await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + `
64
+ await writeFile(CONFIG_PATH, `${JSON.stringify(config, null, 2)}
59
65
  `);
60
66
  }
61
67
  async function loadMetadata(chainName) {
@@ -84,238 +90,125 @@ function resolveChain(config, chainFlag) {
84
90
  return { name, chain };
85
91
  }
86
92
 
87
- // src/core/client.ts
88
- import { createClient } from "polkadot-api";
89
- import { getWsProvider } from "polkadot-api/ws-provider";
90
- import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
91
-
92
- // src/utils/errors.ts
93
- class CliError extends Error {
94
- constructor(message) {
95
- super(message);
96
- this.name = "CliError";
97
- }
93
+ // src/config/accounts-store.ts
94
+ var ACCOUNTS_PATH = join2(getConfigDir(), "accounts.json");
95
+ async function ensureDir2(dir) {
96
+ await mkdir2(dir, { recursive: true });
98
97
  }
99
-
100
- class ConnectionError extends CliError {
101
- constructor(message) {
102
- super(message);
103
- this.name = "ConnectionError";
98
+ async function fileExists2(path) {
99
+ try {
100
+ await access2(path);
101
+ return true;
102
+ } catch {
103
+ return false;
104
104
  }
105
105
  }
106
-
107
- class MetadataError extends CliError {
108
- constructor(message) {
109
- super(message);
110
- this.name = "MetadataError";
106
+ async function loadAccounts() {
107
+ await ensureDir2(getConfigDir());
108
+ if (await fileExists2(ACCOUNTS_PATH)) {
109
+ const data = await readFile2(ACCOUNTS_PATH, "utf-8");
110
+ return JSON.parse(data);
111
111
  }
112
+ return { accounts: [] };
112
113
  }
113
-
114
- // src/core/client.ts
115
- var KNOWN_CHAIN_SPECS = {
116
- polkadot: "polkadot-api/chains/polkadot",
117
- kusama: "polkadot-api/chains/ksmcc3",
118
- westend: "polkadot-api/chains/westend2",
119
- paseo: "polkadot-api/chains/paseo"
120
- };
121
- function suppressWsNoise() {
122
- const orig = console.error;
123
- console.error = (...args) => {
124
- if (typeof args[0] === "string" && args[0].includes("Unable to connect"))
125
- return;
126
- orig(...args);
127
- };
128
- return () => {
129
- console.error = orig;
130
- };
131
- }
132
- async function createChainClient(chainName, chainConfig, rpcOverride) {
133
- const useLight = !rpcOverride && chainConfig.lightClient;
134
- const restoreConsole = suppressWsNoise();
135
- let provider;
136
- if (useLight) {
137
- provider = await createSmoldotProvider(chainName);
138
- } else {
139
- const rpc = rpcOverride ?? chainConfig.rpc;
140
- if (!rpc) {
141
- restoreConsole();
142
- throw new ConnectionError(`No RPC endpoint configured for chain "${chainName}". Use --rpc or configure one with: dot chain add ${chainName} --rpc <url>`);
143
- }
144
- provider = withPolkadotSdkCompat(getWsProvider(rpc, { timeout: 1e4 }));
145
- }
146
- const client = createClient(provider, {
147
- getMetadata: async () => loadMetadata(chainName),
148
- setMetadata: async (_codeHash, metadata) => {
149
- await saveMetadata(chainName, metadata);
150
- }
151
- });
152
- return {
153
- client,
154
- destroy: () => {
155
- client.destroy();
156
- restoreConsole();
157
- }
158
- };
114
+ async function saveAccounts(file) {
115
+ await ensureDir2(getConfigDir());
116
+ await writeFile2(ACCOUNTS_PATH, `${JSON.stringify(file, null, 2)}
117
+ `);
159
118
  }
160
- async function createSmoldotProvider(chainName) {
161
- const { start } = await import("polkadot-api/smoldot");
162
- const { getSmProvider } = await import("polkadot-api/sm-provider");
163
- const specPath = KNOWN_CHAIN_SPECS[chainName];
164
- if (!specPath) {
165
- throw new ConnectionError(`Light client is only supported for known chains: ${Object.keys(KNOWN_CHAIN_SPECS).join(", ")}. Use --rpc to connect to "${chainName}" instead.`);
166
- }
167
- const { chainSpec } = await import(specPath);
168
- const smoldot = start();
169
- const chain = await smoldot.addChain({ chainSpec });
170
- return getSmProvider(chain);
119
+ function findAccount(file, name) {
120
+ return file.accounts.find((a) => a.name.toLowerCase() === name.toLowerCase());
171
121
  }
172
122
 
173
- // src/core/metadata.ts
123
+ // src/core/accounts.ts
124
+ import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
174
125
  import {
175
- decAnyMetadata,
176
- unifyMetadata
177
- } from "@polkadot-api/substrate-bindings";
178
- import { getLookupFn, getDynamicBuilder } from "@polkadot-api/metadata-builders";
179
- var METADATA_TIMEOUT_MS = 15000;
180
- function parseMetadata(raw) {
181
- const decoded = decAnyMetadata(raw);
182
- const unified = unifyMetadata(decoded);
183
- const lookup = getLookupFn(unified);
184
- const builder = getDynamicBuilder(lookup);
185
- return { unified, lookup, builder };
186
- }
187
- async function fetchMetadataFromChain(clientHandle, chainName) {
188
- const { client } = clientHandle;
189
- try {
190
- const hex = await Promise.race([
191
- client._request("state_getMetadata", []),
192
- new Promise((_, reject) => setTimeout(() => reject(new ConnectionError(`Timed out fetching metadata for "${chainName}" after ${METADATA_TIMEOUT_MS / 1000}s. ` + "Check that the RPC endpoint is correct and reachable.")), METADATA_TIMEOUT_MS))
193
- ]);
194
- const bytes = hexToBytes(hex);
195
- await saveMetadata(chainName, bytes);
196
- return bytes;
197
- } catch (err) {
198
- if (err instanceof ConnectionError)
199
- throw err;
200
- throw new ConnectionError(`Failed to fetch metadata for "${chainName}": ${err instanceof Error ? err.message : err}. ` + "Check that the RPC endpoint is correct and reachable.");
201
- }
126
+ DEV_PHRASE,
127
+ entropyToMiniSecret,
128
+ generateMnemonic,
129
+ mnemonicToEntropy,
130
+ ss58Address,
131
+ validateMnemonic
132
+ } from "@polkadot-labs/hdkd-helpers";
133
+ import { getPolkadotSigner } from "polkadot-api/signer";
134
+ var DEV_NAMES = ["alice", "bob", "charlie", "dave", "eve", "ferdie"];
135
+ function isDevAccount(name) {
136
+ return DEV_NAMES.includes(name.toLowerCase());
202
137
  }
203
- async function getOrFetchMetadata(chainName, clientHandle) {
204
- let raw = await loadMetadata(chainName);
205
- if (!raw) {
206
- if (!clientHandle) {
207
- throw new MetadataError(`No cached metadata for chain "${chainName}". Run a command that connects to the chain first, ` + `e.g.: dot chain add ${chainName} --rpc <url>`);
208
- }
209
- raw = await fetchMetadataFromChain(clientHandle, chainName);
210
- }
211
- return parseMetadata(raw);
138
+ function devDerivationPath(name) {
139
+ return `//${name.charAt(0).toUpperCase()}${name.slice(1).toLowerCase()}`;
212
140
  }
213
- function listPallets(meta) {
214
- return meta.unified.pallets.map((p) => ({
215
- name: p.name,
216
- index: p.index,
217
- docs: p.docs ?? [],
218
- storage: (p.storage?.items ?? []).map((s) => ({
219
- name: s.name,
220
- docs: s.docs ?? [],
221
- type: s.type.tag,
222
- keyTypeId: s.type.tag === "map" ? s.type.value.key : null,
223
- valueTypeId: s.type.tag === "plain" ? s.type.value : s.type.value.value
224
- })),
225
- constants: (p.constants ?? []).map((c) => ({
226
- name: c.name,
227
- docs: c.docs ?? [],
228
- typeId: c.type
229
- })),
230
- calls: extractCalls(meta, p.calls)
231
- }));
141
+ function deriveFromMnemonic(mnemonic, path) {
142
+ const entropy = mnemonicToEntropy(mnemonic);
143
+ const miniSecret = entropyToMiniSecret(entropy);
144
+ const derive = sr25519CreateDerive(miniSecret);
145
+ return derive(path);
232
146
  }
233
- function extractCalls(meta, callsRef) {
234
- if (!callsRef)
235
- return [];
236
- try {
237
- const entry = meta.lookup(callsRef.type);
238
- if (entry.type !== "enum")
239
- return [];
240
- return Object.entries(entry.value).map(([name, variant]) => ({
241
- name,
242
- docs: variant.docs ?? [],
243
- typeId: resolveCallTypeId(variant)
244
- }));
245
- } catch {
246
- return [];
147
+ function deriveFromHexSeed(hexSeed, path) {
148
+ const clean = hexSeed.startsWith("0x") ? hexSeed.slice(2) : hexSeed;
149
+ const seed = new Uint8Array(clean.length / 2);
150
+ for (let i = 0;i < clean.length; i += 2) {
151
+ seed[i / 2] = parseInt(clean.substring(i, i + 2), 16);
247
152
  }
153
+ const derive = sr25519CreateDerive(seed);
154
+ return derive(path);
248
155
  }
249
- function resolveCallTypeId(variant) {
250
- if (variant.type === "lookupEntry")
251
- return variant.value?.id ?? null;
252
- if (variant.type === "struct")
253
- return null;
254
- if (variant.type === "void" || variant.type === "empty")
255
- return null;
256
- return null;
156
+ function getDevKeypair(name) {
157
+ const path = devDerivationPath(name);
158
+ return deriveFromMnemonic(DEV_PHRASE, path);
257
159
  }
258
- function findPallet(meta, palletName) {
259
- const pallets = listPallets(meta);
260
- return pallets.find((p) => p.name.toLowerCase() === palletName.toLowerCase());
160
+ function getDevAddress(name, prefix = 42) {
161
+ const keypair = getDevKeypair(name);
162
+ return ss58Address(keypair.publicKey, prefix);
261
163
  }
262
- function getSignedExtensions(meta) {
263
- const byVersion = meta.unified.extrinsic.signedExtensions;
264
- const versionKeys = Object.keys(byVersion);
265
- if (versionKeys.length === 0)
266
- return [];
267
- return byVersion[Number(versionKeys[0])] ?? [];
164
+ function createNewAccount() {
165
+ const mnemonic = generateMnemonic();
166
+ const entropy = mnemonicToEntropy(mnemonic);
167
+ const miniSecret = entropyToMiniSecret(entropy);
168
+ const derive = sr25519CreateDerive(miniSecret);
169
+ const keypair = derive("");
170
+ return { mnemonic, publicKey: keypair.publicKey };
268
171
  }
269
- function getPalletNames(meta) {
270
- return meta.unified.pallets.map((p) => p.name);
172
+ function importAccount(secret) {
173
+ const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
174
+ if (isHexSeed) {
175
+ const keypair2 = deriveFromHexSeed(secret, "");
176
+ return { publicKey: keypair2.publicKey };
177
+ }
178
+ if (!validateMnemonic(secret)) {
179
+ throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
180
+ }
181
+ const keypair = deriveFromMnemonic(secret, "");
182
+ return { publicKey: keypair.publicKey };
271
183
  }
272
- function describeType(lookup, typeId) {
273
- try {
274
- const entry = lookup(typeId);
275
- return formatLookupEntry(entry);
276
- } catch {
277
- return `type(${typeId})`;
184
+ function publicKeyToHex(publicKey) {
185
+ return "0x" + Array.from(publicKey).map((b) => b.toString(16).padStart(2, "0")).join("");
186
+ }
187
+ function toSs58(publicKey, prefix = 42) {
188
+ if (typeof publicKey === "string") {
189
+ const clean = publicKey.startsWith("0x") ? publicKey.slice(2) : publicKey;
190
+ const bytes = new Uint8Array(clean.length / 2);
191
+ for (let i = 0;i < clean.length; i += 2) {
192
+ bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
193
+ }
194
+ return ss58Address(bytes, prefix);
278
195
  }
196
+ return ss58Address(publicKey, prefix);
279
197
  }
280
- function formatLookupEntry(entry) {
281
- switch (entry.type) {
282
- case "primitive":
283
- return entry.value;
284
- case "compact":
285
- return `Compact<${formatLookupEntry(entry.isBig ? { type: "primitive", value: "u128" } : { type: "primitive", value: "u64" })}>`;
286
- case "AccountId32":
287
- return "AccountId32";
288
- case "bitSequence":
289
- return "BitSequence";
290
- case "sequence":
291
- return `Vec<${formatLookupEntry(entry.value)}>`;
292
- case "array":
293
- return `[${formatLookupEntry(entry.value)}; ${entry.len}]`;
294
- case "tuple":
295
- return `(${entry.value.map(formatLookupEntry).join(", ")})`;
296
- case "struct":
297
- return `{ ${Object.entries(entry.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ")} }`;
298
- case "option":
299
- return `Option<${formatLookupEntry(entry.value)}>`;
300
- case "result":
301
- return `Result<${formatLookupEntry(entry.value.ok)}, ${formatLookupEntry(entry.value.ko)}>`;
302
- case "enum": {
303
- const variants = Object.keys(entry.value);
304
- if (variants.length <= 4)
305
- return variants.join(" | ");
306
- return `enum(${variants.length} variants)`;
307
- }
308
- default:
309
- return "unknown";
198
+ async function resolveAccountSigner(name) {
199
+ if (isDevAccount(name)) {
200
+ const keypair2 = getDevKeypair(name);
201
+ return getPolkadotSigner(keypair2.publicKey, "Sr25519", keypair2.sign);
310
202
  }
311
- }
312
- function hexToBytes(hex) {
313
- const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
314
- const bytes = new Uint8Array(clean.length / 2);
315
- for (let i = 0;i < clean.length; i += 2) {
316
- bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
203
+ const accountsFile = await loadAccounts();
204
+ const account = findAccount(accountsFile, name);
205
+ if (!account) {
206
+ const available = [...DEV_NAMES, ...accountsFile.accounts.map((a) => a.name)];
207
+ throw new Error(`Unknown account "${name}". Available accounts: ${available.join(", ")}`);
317
208
  }
318
- return bytes;
209
+ const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(account.secret);
210
+ const keypair = isHexSeed ? deriveFromHexSeed(account.secret, account.derivationPath) : deriveFromMnemonic(account.secret, account.derivationPath);
211
+ return getPolkadotSigner(keypair.publicKey, "Sr25519", keypair.sign);
319
212
  }
320
213
 
321
214
  // src/core/output.ts
@@ -323,6 +216,7 @@ var isTTY = process.stdout.isTTY ?? false;
323
216
  var RESET = isTTY ? "\x1B[0m" : "";
324
217
  var CYAN = isTTY ? "\x1B[36m" : "";
325
218
  var GREEN = isTTY ? "\x1B[32m" : "";
219
+ var RED = isTTY ? "\x1B[31m" : "";
326
220
  var YELLOW = isTTY ? "\x1B[33m" : "";
327
221
  var MAGENTA = isTTY ? "\x1B[35m" : "";
328
222
  var DIM = isTTY ? "\x1B[2m" : "";
@@ -331,7 +225,7 @@ function replacer(_key, value) {
331
225
  if (typeof value === "bigint")
332
226
  return value.toString();
333
227
  if (value instanceof Uint8Array)
334
- return "0x" + Buffer.from(value).toString("hex");
228
+ return `0x${Buffer.from(value).toString("hex")}`;
335
229
  return value;
336
230
  }
337
231
  function formatJson(data) {
@@ -405,686 +299,917 @@ class Spinner {
405
299
  }
406
300
  }
407
301
 
408
- // src/commands/chain.ts
409
- var CHAIN_HELP = `
302
+ // src/commands/account.ts
303
+ var ACCOUNT_HELP = `
410
304
  ${BOLD}Usage:${RESET}
411
- $ dot chain add <name> --rpc <url> Add a chain via WebSocket RPC
412
- $ dot chain add <name> --light-client Add a chain via Smoldot light client
413
- $ dot chain remove <name> Remove a chain
414
- $ dot chain update [name] Re-fetch metadata (default chain if omitted)
415
- $ dot chain list List configured chains
416
- $ dot chain default <name> Set the default chain
305
+ $ dot account create <name> Create a new account
306
+ $ dot account import <name> --secret <s> Import from BIP39 mnemonic
307
+ $ dot account list List all accounts
308
+ $ dot account remove <name> Remove a stored account
417
309
 
418
310
  ${BOLD}Examples:${RESET}
419
- $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
420
- $ dot chain add westend --light-client
421
- $ dot chain default kusama
422
- $ dot chain list
423
- $ dot chain update
424
- $ dot chain update kusama
425
- $ dot chain remove kusama
311
+ $ dot account create my-validator
312
+ $ dot account import treasury --secret "word1 word2 ... word12"
313
+ $ dot account list
314
+ $ dot account remove my-validator
315
+
316
+ ${YELLOW}Note: Secrets are stored unencrypted in ~/.polkadot/accounts.json.
317
+ Hex seed import (0x...) is not supported via CLI.${RESET}
426
318
  `.trimStart();
427
- function registerChainCommands(cli) {
428
- cli.command("chain [action] [name]", "Manage chains (add, remove, update, list, default)").action(async (action, name, opts) => {
319
+ function registerAccountCommands(cli) {
320
+ cli.command("account [action] [name]", "Manage local accounts (create, import, list, remove)").option("--secret <value>", "Secret key (mnemonic or hex seed) for import").action(async (action, name, opts) => {
429
321
  if (!action) {
430
- console.log(CHAIN_HELP);
322
+ console.log(ACCOUNT_HELP);
431
323
  return;
432
324
  }
433
325
  switch (action) {
434
- case "add":
435
- return chainAdd(name, opts);
436
- case "remove":
437
- return chainRemove(name);
326
+ case "create":
327
+ return accountCreate(name);
328
+ case "import":
329
+ return accountImport(name, opts);
438
330
  case "list":
439
- return chainList();
440
- case "update":
441
- return chainUpdate(name, opts);
442
- case "default":
443
- return chainDefault(name);
331
+ return accountList();
332
+ case "remove":
333
+ return accountRemove(name);
444
334
  default:
445
335
  console.error(`Unknown action "${action}".
446
336
  `);
447
- console.log(CHAIN_HELP);
337
+ console.log(ACCOUNT_HELP);
448
338
  process.exit(1);
449
339
  }
450
340
  });
451
341
  }
452
- async function chainAdd(name, opts) {
342
+ async function accountCreate(name) {
453
343
  if (!name) {
454
- console.error(`Chain name is required.
344
+ console.error(`Account name is required.
455
345
  `);
456
- console.error("Usage: dot chain add <name> --rpc <url>");
457
- console.error(" dot chain add <name> --light-client");
346
+ console.error("Usage: dot account create <name>");
458
347
  process.exit(1);
459
348
  }
460
- if (!opts.rpc && !opts.lightClient) {
461
- console.error(`Must provide either --rpc <url> or --light-client.
462
- `);
463
- console.error("Usage: dot chain add <name> --rpc <url>");
464
- console.error(" dot chain add <name> --light-client");
465
- process.exit(1);
349
+ if (isDevAccount(name)) {
350
+ throw new Error(`"${name}" is a built-in dev account and cannot be used as a custom account name.`);
466
351
  }
467
- const chainConfig = {
468
- rpc: opts.rpc ?? "",
469
- ...opts.lightClient ? { lightClient: true } : {}
470
- };
471
- console.log(`Connecting to ${name}...`);
472
- const clientHandle = await createChainClient(name, chainConfig, opts.rpc);
473
- try {
474
- console.log("Fetching metadata...");
475
- await fetchMetadataFromChain(clientHandle, name);
476
- const config = await loadConfig();
477
- config.chains[name] = chainConfig;
478
- await saveConfig(config);
479
- console.log(`Chain "${name}" added successfully.`);
480
- } finally {
481
- clientHandle.destroy();
352
+ const accountsFile = await loadAccounts();
353
+ if (findAccount(accountsFile, name)) {
354
+ throw new Error(`Account "${name}" already exists.`);
482
355
  }
356
+ const { mnemonic, publicKey } = createNewAccount();
357
+ const hexPub = publicKeyToHex(publicKey);
358
+ const address = toSs58(publicKey);
359
+ accountsFile.accounts.push({
360
+ name,
361
+ secret: mnemonic,
362
+ publicKey: hexPub,
363
+ derivationPath: ""
364
+ });
365
+ await saveAccounts(accountsFile);
366
+ printHeading("Account Created");
367
+ console.log(` ${BOLD}Name:${RESET} ${name}`);
368
+ console.log(` ${BOLD}Address:${RESET} ${address}`);
369
+ console.log(` ${BOLD}Mnemonic:${RESET} ${mnemonic}`);
370
+ console.log();
371
+ console.log(` ${YELLOW}Save this mnemonic phrase! It is the only way to recover this account.${RESET}`);
372
+ console.log();
483
373
  }
484
- async function chainRemove(name) {
374
+ async function accountImport(name, opts) {
485
375
  if (!name) {
486
- console.error("Usage: dot chain remove <name>");
376
+ console.error(`Account name is required.
377
+ `);
378
+ console.error('Usage: dot account import <name> --secret "mnemonic or hex seed"');
487
379
  process.exit(1);
488
380
  }
489
- const config = await loadConfig();
490
- if (!config.chains[name]) {
491
- throw new Error(`Chain "${name}" not found.`);
492
- }
493
- if (name === "polkadot") {
494
- throw new Error('Cannot remove the built-in "polkadot" chain.');
381
+ if (!opts.secret) {
382
+ console.error(`--secret is required.
383
+ `);
384
+ console.error('Usage: dot account import <name> --secret "mnemonic or hex seed"');
385
+ process.exit(1);
495
386
  }
496
- delete config.chains[name];
497
- if (config.defaultChain === name) {
498
- config.defaultChain = "polkadot";
499
- console.log(`Default chain reset to "polkadot".`);
387
+ if (isDevAccount(name)) {
388
+ throw new Error(`"${name}" is a built-in dev account and cannot be used as a custom account name.`);
500
389
  }
501
- await saveConfig(config);
502
- await removeChainData(name);
503
- console.log(`Chain "${name}" removed.`);
504
- }
505
- async function chainList() {
506
- const config = await loadConfig();
507
- printHeading("Configured Chains");
508
- for (const [name, chainConfig] of Object.entries(config.chains)) {
509
- const isDefault = name === config.defaultChain;
510
- const marker = isDefault ? ` ${BOLD}(default)${RESET}` : "";
511
- const provider = chainConfig.lightClient ? `${DIM}light-client${RESET}` : `${DIM}${chainConfig.rpc}${RESET}`;
512
- console.log(` ${CYAN}${name}${RESET}${marker} ${provider}`);
390
+ const accountsFile = await loadAccounts();
391
+ if (findAccount(accountsFile, name)) {
392
+ throw new Error(`Account "${name}" already exists.`);
513
393
  }
394
+ const { publicKey } = importAccount(opts.secret);
395
+ const hexPub = publicKeyToHex(publicKey);
396
+ const address = toSs58(publicKey);
397
+ accountsFile.accounts.push({
398
+ name,
399
+ secret: opts.secret,
400
+ publicKey: hexPub,
401
+ derivationPath: ""
402
+ });
403
+ await saveAccounts(accountsFile);
404
+ printHeading("Account Imported");
405
+ console.log(` ${BOLD}Name:${RESET} ${name}`);
406
+ console.log(` ${BOLD}Address:${RESET} ${address}`);
514
407
  console.log();
515
408
  }
516
- async function chainUpdate(name, opts) {
517
- const config = await loadConfig();
518
- const { name: chainName, chain: chainConfig } = resolveChain(config, name);
519
- console.log(`Connecting to ${chainName}...`);
520
- const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
521
- try {
522
- console.log("Fetching metadata...");
523
- await fetchMetadataFromChain(clientHandle, chainName);
524
- console.log(`Metadata for "${chainName}" updated.`);
525
- } finally {
526
- clientHandle.destroy();
409
+ async function accountList() {
410
+ printHeading("Dev Accounts");
411
+ for (const name of DEV_NAMES) {
412
+ const display = name.charAt(0).toUpperCase() + name.slice(1);
413
+ const address = getDevAddress(name);
414
+ printItem(display, address);
415
+ }
416
+ const accountsFile = await loadAccounts();
417
+ if (accountsFile.accounts.length > 0) {
418
+ printHeading("Stored Accounts");
419
+ for (const account of accountsFile.accounts) {
420
+ const address = toSs58(account.publicKey);
421
+ printItem(account.name, address);
422
+ }
423
+ } else {
424
+ printHeading("Stored Accounts");
425
+ console.log(" (none)");
527
426
  }
427
+ console.log();
528
428
  }
529
- async function chainDefault(name) {
429
+ async function accountRemove(name) {
530
430
  if (!name) {
531
- console.error("Usage: dot chain default <name>");
431
+ console.error(`Account name is required.
432
+ `);
433
+ console.error("Usage: dot account remove <name>");
532
434
  process.exit(1);
533
435
  }
534
- const config = await loadConfig();
535
- if (!config.chains[name]) {
536
- const available = Object.keys(config.chains).join(", ");
537
- throw new Error(`Chain "${name}" not found. Available: ${available}`);
436
+ if (isDevAccount(name)) {
437
+ throw new Error("Cannot remove built-in dev accounts.");
538
438
  }
539
- config.defaultChain = name;
540
- await saveConfig(config);
541
- console.log(`Default chain set to "${name}".`);
542
- }
543
-
544
- // src/utils/parse-target.ts
545
- function parseTarget(input) {
546
- const parts = input.split(".");
547
- if (parts.length !== 2 || !parts[0] || !parts[1]) {
548
- throw new Error(`Invalid target "${input}". Expected format: Pallet.Item (e.g. System.Account)`);
439
+ const accountsFile = await loadAccounts();
440
+ const idx = accountsFile.accounts.findIndex((a) => a.name.toLowerCase() === name.toLowerCase());
441
+ if (idx === -1) {
442
+ throw new Error(`Account "${name}" not found.`);
549
443
  }
550
- return { pallet: parts[0], item: parts[1] };
444
+ accountsFile.accounts.splice(idx, 1);
445
+ await saveAccounts(accountsFile);
446
+ console.log(`Account "${name}" removed.`);
551
447
  }
552
448
 
553
- // src/utils/fuzzy-match.ts
554
- function levenshtein(a, b) {
555
- const la = a.length;
556
- const lb = b.length;
557
- const dp = Array.from({ length: la + 1 }, () => Array(lb + 1).fill(0));
558
- for (let i = 0;i <= la; i++)
559
- dp[i][0] = i;
560
- for (let j = 0;j <= lb; j++)
561
- dp[0][j] = j;
562
- for (let i = 1;i <= la; i++) {
563
- for (let j = 1;j <= lb; j++) {
564
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
565
- dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
566
- }
449
+ // src/core/client.ts
450
+ import { createClient } from "polkadot-api";
451
+ import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
452
+ import { getWsProvider } from "polkadot-api/ws-provider";
453
+
454
+ // src/utils/errors.ts
455
+ class CliError extends Error {
456
+ constructor(message) {
457
+ super(message);
458
+ this.name = "CliError";
567
459
  }
568
- return dp[la][lb];
569
- }
570
- function findClosest(input, candidates, maxDistance = 3) {
571
- const lower = input.toLowerCase();
572
- const exact = candidates.find((c) => c.toLowerCase() === lower);
573
- if (exact)
574
- return [exact];
575
- const scored = candidates.map((c) => ({ name: c, dist: levenshtein(lower, c.toLowerCase()) })).filter((s) => s.dist <= maxDistance).sort((a, b) => a.dist - b.dist);
576
- return scored.slice(0, 3).map((s) => s.name);
577
460
  }
578
- function suggestMessage(kind, input, candidates) {
579
- const suggestions = findClosest(input, candidates);
580
- if (suggestions.length === 0) {
581
- return `Unknown ${kind} "${input}".`;
461
+
462
+ class ConnectionError extends CliError {
463
+ constructor(message) {
464
+ super(message);
465
+ this.name = "ConnectionError";
582
466
  }
583
- if (suggestions.length === 1 && suggestions[0].toLowerCase() === input.toLowerCase()) {
584
- return suggestions[0];
467
+ }
468
+
469
+ class MetadataError extends CliError {
470
+ constructor(message) {
471
+ super(message);
472
+ this.name = "MetadataError";
585
473
  }
586
- return `Unknown ${kind} "${input}". Did you mean: ${suggestions.join(", ")}?`;
587
474
  }
588
475
 
589
- // src/commands/inspect.ts
590
- function registerInspectCommand(cli) {
591
- cli.command("inspect [target]", "Inspect chain metadata (pallets, storage, constants)").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
592
- const config = await loadConfig();
593
- const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
594
- let meta;
595
- try {
596
- meta = await getOrFetchMetadata(chainName);
597
- } catch {
598
- console.log(`Fetching metadata from ${chainName}...`);
599
- const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
600
- try {
601
- meta = await getOrFetchMetadata(chainName, clientHandle);
602
- } finally {
603
- clientHandle.destroy();
604
- }
605
- }
606
- if (!target) {
607
- const pallets = listPallets(meta);
608
- printHeading(`Pallets on ${chainName} (${pallets.length})`);
609
- for (const p of pallets) {
610
- const counts = [];
611
- if (p.storage.length)
612
- counts.push(`${p.storage.length} storage`);
613
- if (p.constants.length)
614
- counts.push(`${p.constants.length} constants`);
615
- printItem(p.name, counts.join(", "));
616
- }
617
- console.log();
618
- return;
619
- }
620
- if (!target.includes(".")) {
621
- const palletNames2 = getPalletNames(meta);
622
- const pallet2 = findPallet(meta, target);
623
- if (!pallet2) {
624
- throw new Error(suggestMessage("pallet", target, palletNames2));
625
- }
626
- printHeading(`${pallet2.name} Pallet`);
627
- if (pallet2.docs.length) {
628
- printDocs(pallet2.docs);
629
- console.log();
630
- }
631
- if (pallet2.storage.length) {
632
- console.log(` ${BOLD}Storage Items:${RESET}`);
633
- for (const s of pallet2.storage) {
634
- const doc = s.docs[0] ? ` — ${s.docs[0].slice(0, 80)}` : "";
635
- console.log(` ${CYAN}${s.name}${RESET}${DIM}${doc}${RESET}`);
636
- }
637
- console.log();
638
- }
639
- if (pallet2.constants.length) {
640
- console.log(` ${BOLD}Constants:${RESET}`);
641
- for (const c of pallet2.constants) {
642
- const doc = c.docs[0] ? ` — ${c.docs[0].slice(0, 80)}` : "";
643
- console.log(` ${CYAN}${c.name}${RESET}${DIM}${doc}${RESET}`);
644
- }
645
- console.log();
646
- }
647
- return;
648
- }
649
- const { pallet: palletName, item: itemName } = parseTarget(target);
650
- const palletNames = getPalletNames(meta);
651
- const pallet = findPallet(meta, palletName);
652
- if (!pallet) {
653
- throw new Error(suggestMessage("pallet", palletName, palletNames));
654
- }
655
- const storageItem = pallet.storage.find((s) => s.name.toLowerCase() === itemName.toLowerCase());
656
- if (storageItem) {
657
- printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
658
- console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
659
- console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
660
- if (storageItem.keyTypeId != null) {
661
- console.log(` ${BOLD}Key:${RESET} ${describeType(meta.lookup, storageItem.keyTypeId)}`);
662
- }
663
- if (storageItem.docs.length) {
664
- console.log();
665
- printDocs(storageItem.docs);
666
- }
667
- console.log();
476
+ // src/core/client.ts
477
+ var KNOWN_CHAIN_SPECS = {
478
+ polkadot: "polkadot-api/chains/polkadot",
479
+ kusama: "polkadot-api/chains/ksmcc3",
480
+ westend: "polkadot-api/chains/westend2",
481
+ paseo: "polkadot-api/chains/paseo"
482
+ };
483
+ function suppressWsNoise() {
484
+ const orig = console.error;
485
+ console.error = (...args) => {
486
+ if (typeof args[0] === "string" && args[0].includes("Unable to connect"))
668
487
  return;
488
+ orig(...args);
489
+ };
490
+ return () => {
491
+ console.error = orig;
492
+ };
493
+ }
494
+ async function createChainClient(chainName, chainConfig, rpcOverride) {
495
+ const useLight = !rpcOverride && chainConfig.lightClient;
496
+ const restoreConsole = suppressWsNoise();
497
+ let provider;
498
+ if (useLight) {
499
+ provider = await createSmoldotProvider(chainName);
500
+ } else {
501
+ const rpc = rpcOverride ?? chainConfig.rpc;
502
+ if (!rpc) {
503
+ restoreConsole();
504
+ throw new ConnectionError(`No RPC endpoint configured for chain "${chainName}". Use --rpc or configure one with: dot chain add ${chainName} --rpc <url>`);
669
505
  }
670
- const constantItem = pallet.constants.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
671
- if (constantItem) {
672
- printHeading(`${pallet.name}.${constantItem.name} (Constant)`);
673
- console.log(` ${BOLD}Type:${RESET} ${describeType(meta.lookup, constantItem.typeId)}`);
674
- if (constantItem.docs.length) {
675
- console.log();
676
- printDocs(constantItem.docs);
677
- }
678
- console.log();
679
- return;
506
+ provider = withPolkadotSdkCompat(getWsProvider(rpc, { timeout: 1e4 }));
507
+ }
508
+ const client = createClient(provider, {
509
+ getMetadata: async () => loadMetadata(chainName),
510
+ setMetadata: async (_codeHash, metadata) => {
511
+ await saveMetadata(chainName, metadata);
680
512
  }
681
- const allItems = [
682
- ...pallet.storage.map((s) => s.name),
683
- ...pallet.constants.map((c) => c.name)
684
- ];
685
- throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems));
686
513
  });
514
+ return {
515
+ client,
516
+ destroy: () => {
517
+ client.destroy();
518
+ restoreConsole();
519
+ }
520
+ };
687
521
  }
688
-
689
- // src/utils/parse-value.ts
690
- function parseValue(arg) {
691
- if (/^\d+$/.test(arg))
692
- return parseInt(arg, 10);
693
- if (/^\d{16,}$/.test(arg))
694
- return BigInt(arg);
695
- if (/^0x[0-9a-fA-F]+$/.test(arg))
696
- return arg;
697
- if (arg === "true")
698
- return true;
699
- if (arg === "false")
700
- return false;
701
- if (arg.startsWith("{") || arg.startsWith("[")) {
702
- try {
703
- return JSON.parse(arg);
704
- } catch {}
522
+ async function createSmoldotProvider(chainName) {
523
+ const { start } = await import("polkadot-api/smoldot");
524
+ const { getSmProvider } = await import("polkadot-api/sm-provider");
525
+ const specPath = KNOWN_CHAIN_SPECS[chainName];
526
+ if (!specPath) {
527
+ throw new ConnectionError(`Light client is only supported for known chains: ${Object.keys(KNOWN_CHAIN_SPECS).join(", ")}. Use --rpc to connect to "${chainName}" instead.`);
705
528
  }
706
- return arg;
529
+ const { chainSpec } = await import(specPath);
530
+ const smoldot = start();
531
+ const chain = await smoldot.addChain({ chainSpec });
532
+ return getSmProvider(chain);
707
533
  }
708
534
 
709
- // src/commands/query.ts
710
- var DEFAULT_LIMIT = 100;
711
- function registerQueryCommand(cli) {
712
- cli.command("query [target] [...keys]", "Query on-chain storage (e.g. System.Number, System.Account <addr>)").option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
713
- default: DEFAULT_LIMIT
714
- }).action(async (target, keys, opts) => {
715
- if (!target) {
716
- console.log("Usage: dot query <Pallet.Item> [...keys] [--chain <name>] [--output json]");
717
- console.log("");
718
- console.log("Examples:");
719
- console.log(" $ dot query System.Number # plain storage value");
720
- console.log(" $ dot query System.Account 5Grw... # single map entry");
721
- console.log(" $ dot query System.Account # all entries (limit 100)");
722
- console.log(" $ dot query System.Account --limit 10 # first 10 entries");
723
- console.log(" $ dot query System.Account --limit 0 # all entries (no limit)");
724
- console.log(" $ dot query Assets.Metadata 42 --chain asset-hub");
725
- return;
726
- }
727
- const config = await loadConfig();
728
- const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
729
- const { pallet, item } = parseTarget(target);
730
- const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
731
- try {
732
- const meta = await getOrFetchMetadata(chainName, clientHandle);
733
- const palletNames = getPalletNames(meta);
734
- const palletInfo = findPallet(meta, pallet);
735
- if (!palletInfo) {
736
- throw new Error(suggestMessage("pallet", pallet, palletNames));
737
- }
738
- const storageItem = palletInfo.storage.find((s) => s.name.toLowerCase() === item.toLowerCase());
739
- if (!storageItem) {
740
- const storageNames = palletInfo.storage.map((s) => s.name);
741
- throw new Error(suggestMessage(`storage item in ${palletInfo.name}`, item, storageNames));
742
- }
743
- const unsafeApi = clientHandle.client.getUnsafeApi();
744
- const storageApi = unsafeApi.query[palletInfo.name][storageItem.name];
745
- const parsedKeys = keys.map(parseValue);
746
- const format = opts.output ?? "pretty";
747
- if (storageItem.type === "map" && parsedKeys.length === 0) {
748
- const entries = await storageApi.getEntries();
749
- const limit = Number(opts.limit);
750
- const truncated = limit > 0 && entries.length > limit;
751
- const display = truncated ? entries.slice(0, limit) : entries;
752
- printResult(display.map((e) => ({
753
- keys: e.keyArgs,
754
- value: e.value
755
- })), format);
756
- if (truncated) {
757
- console.error(`
758
- ${DIM}Showing ${limit} of ${entries.length} entries. Use --limit 0 for all.${RESET}`);
759
- }
760
- } else {
761
- const result = await storageApi.getValue(...parsedKeys);
762
- printResult(result, format);
763
- }
764
- } finally {
765
- clientHandle.destroy();
766
- }
767
- });
768
- }
769
-
770
- // src/commands/const.ts
771
- function registerConstCommand(cli) {
772
- cli.command("const [target]", "Look up a pallet constant (e.g. Balances.ExistentialDeposit)").action(async (target, opts) => {
773
- if (!target) {
774
- console.log("Usage: dot const <Pallet.Constant> [--chain <name>] [--output json]");
775
- console.log("");
776
- console.log("Examples:");
777
- console.log(" $ dot const Balances.ExistentialDeposit");
778
- console.log(" $ dot const System.SS58Prefix --chain kusama");
779
- return;
780
- }
781
- const config = await loadConfig();
782
- const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
783
- const { pallet, item } = parseTarget(target);
784
- const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
785
- try {
786
- const meta = await getOrFetchMetadata(chainName, clientHandle);
787
- const palletNames = getPalletNames(meta);
788
- const palletInfo = findPallet(meta, pallet);
789
- if (!palletInfo) {
790
- throw new Error(suggestMessage("pallet", pallet, palletNames));
791
- }
792
- const constantItem = palletInfo.constants.find((c) => c.name.toLowerCase() === item.toLowerCase());
793
- if (!constantItem) {
794
- const constNames = palletInfo.constants.map((c) => c.name);
795
- throw new Error(suggestMessage(`constant in ${palletInfo.name}`, item, constNames));
796
- }
797
- const unsafeApi = clientHandle.client.getUnsafeApi();
798
- const runtimeToken = await unsafeApi.runtimeToken;
799
- const result = unsafeApi.constants[palletInfo.name][constantItem.name](runtimeToken);
800
- printResult(result, opts.output ?? "pretty");
801
- } finally {
802
- clientHandle.destroy();
803
- }
804
- });
805
- }
806
-
807
- // src/config/accounts-store.ts
808
- import { join as join2 } from "node:path";
809
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, access as access2 } from "node:fs/promises";
810
- var ACCOUNTS_PATH = join2(getConfigDir(), "accounts.json");
811
- async function ensureDir2(dir) {
812
- await mkdir2(dir, { recursive: true });
535
+ // src/core/metadata.ts
536
+ import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders";
537
+ import { decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
538
+ var METADATA_TIMEOUT_MS = 15000;
539
+ function parseMetadata(raw) {
540
+ const decoded = decAnyMetadata(raw);
541
+ const unified = unifyMetadata(decoded);
542
+ const lookup = getLookupFn(unified);
543
+ const builder = getDynamicBuilder(lookup);
544
+ return { unified, lookup, builder };
813
545
  }
814
- async function fileExists2(path) {
546
+ async function fetchMetadataFromChain(clientHandle, chainName) {
547
+ const { client } = clientHandle;
815
548
  try {
816
- await access2(path);
817
- return true;
818
- } catch {
819
- return false;
549
+ const hex = await Promise.race([
550
+ client._request("state_getMetadata", []),
551
+ new Promise((_, reject) => setTimeout(() => reject(new ConnectionError(`Timed out fetching metadata for "${chainName}" after ${METADATA_TIMEOUT_MS / 1000}s. ` + "Check that the RPC endpoint is correct and reachable.")), METADATA_TIMEOUT_MS))
552
+ ]);
553
+ const bytes = hexToBytes(hex);
554
+ await saveMetadata(chainName, bytes);
555
+ return bytes;
556
+ } catch (err) {
557
+ if (err instanceof ConnectionError)
558
+ throw err;
559
+ throw new ConnectionError(`Failed to fetch metadata for "${chainName}": ${err instanceof Error ? err.message : err}. ` + "Check that the RPC endpoint is correct and reachable.");
820
560
  }
821
561
  }
822
- async function loadAccounts() {
823
- await ensureDir2(getConfigDir());
824
- if (await fileExists2(ACCOUNTS_PATH)) {
825
- const data = await readFile2(ACCOUNTS_PATH, "utf-8");
826
- return JSON.parse(data);
562
+ async function getOrFetchMetadata(chainName, clientHandle) {
563
+ let raw = await loadMetadata(chainName);
564
+ if (!raw) {
565
+ if (!clientHandle) {
566
+ throw new MetadataError(`No cached metadata for chain "${chainName}". Run a command that connects to the chain first, ` + `e.g.: dot chain add ${chainName} --rpc <url>`);
567
+ }
568
+ raw = await fetchMetadataFromChain(clientHandle, chainName);
827
569
  }
828
- return { accounts: [] };
829
- }
830
- async function saveAccounts(file) {
831
- await ensureDir2(getConfigDir());
832
- await writeFile2(ACCOUNTS_PATH, JSON.stringify(file, null, 2) + `
833
- `);
834
- }
835
- function findAccount(file, name) {
836
- return file.accounts.find((a) => a.name.toLowerCase() === name.toLowerCase());
837
- }
838
-
839
- // src/core/accounts.ts
840
- import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
841
- import {
842
- DEV_PHRASE,
843
- mnemonicToEntropy,
844
- entropyToMiniSecret,
845
- generateMnemonic,
846
- validateMnemonic,
847
- ss58Address
848
- } from "@polkadot-labs/hdkd-helpers";
849
- import { getPolkadotSigner } from "polkadot-api/signer";
850
- var DEV_NAMES = [
851
- "alice",
852
- "bob",
853
- "charlie",
854
- "dave",
855
- "eve",
856
- "ferdie"
857
- ];
858
- function isDevAccount(name) {
859
- return DEV_NAMES.includes(name.toLowerCase());
860
- }
861
- function devDerivationPath(name) {
862
- return "//" + name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
570
+ return parseMetadata(raw);
863
571
  }
864
- function deriveFromMnemonic(mnemonic, path) {
865
- const entropy = mnemonicToEntropy(mnemonic);
866
- const miniSecret = entropyToMiniSecret(entropy);
867
- const derive = sr25519CreateDerive(miniSecret);
868
- return derive(path);
572
+ function listPallets(meta) {
573
+ return meta.unified.pallets.map((p) => ({
574
+ name: p.name,
575
+ index: p.index,
576
+ docs: p.docs ?? [],
577
+ storage: (p.storage?.items ?? []).map((s) => ({
578
+ name: s.name,
579
+ docs: s.docs ?? [],
580
+ type: s.type.tag,
581
+ keyTypeId: s.type.tag === "map" ? s.type.value.key : null,
582
+ valueTypeId: s.type.tag === "plain" ? s.type.value : s.type.value.value
583
+ })),
584
+ constants: (p.constants ?? []).map((c) => ({
585
+ name: c.name,
586
+ docs: c.docs ?? [],
587
+ typeId: c.type
588
+ })),
589
+ calls: extractCalls(meta, p.calls)
590
+ }));
869
591
  }
870
- function deriveFromHexSeed(hexSeed, path) {
871
- const clean = hexSeed.startsWith("0x") ? hexSeed.slice(2) : hexSeed;
872
- const seed = new Uint8Array(clean.length / 2);
873
- for (let i = 0;i < clean.length; i += 2) {
874
- seed[i / 2] = parseInt(clean.substring(i, i + 2), 16);
592
+ function extractCalls(meta, callsRef) {
593
+ if (!callsRef)
594
+ return [];
595
+ try {
596
+ const entry = meta.lookup(callsRef.type);
597
+ if (entry.type !== "enum")
598
+ return [];
599
+ return Object.entries(entry.value).map(([name, variant]) => ({
600
+ name,
601
+ docs: variant.docs ?? [],
602
+ typeId: resolveCallTypeId(variant)
603
+ }));
604
+ } catch {
605
+ return [];
875
606
  }
876
- const derive = sr25519CreateDerive(seed);
877
- return derive(path);
878
607
  }
879
- function getDevKeypair(name) {
880
- const path = devDerivationPath(name);
881
- return deriveFromMnemonic(DEV_PHRASE, path);
608
+ function resolveCallTypeId(variant) {
609
+ if (variant.type === "lookupEntry")
610
+ return variant.value?.id ?? null;
611
+ if (variant.type === "struct")
612
+ return null;
613
+ if (variant.type === "void" || variant.type === "empty")
614
+ return null;
615
+ return null;
882
616
  }
883
- function getDevAddress(name, prefix = 42) {
884
- const keypair = getDevKeypair(name);
885
- return ss58Address(keypair.publicKey, prefix);
617
+ function findPallet(meta, palletName) {
618
+ const pallets = listPallets(meta);
619
+ return pallets.find((p) => p.name.toLowerCase() === palletName.toLowerCase());
886
620
  }
887
- function createNewAccount() {
888
- const mnemonic = generateMnemonic();
889
- const entropy = mnemonicToEntropy(mnemonic);
890
- const miniSecret = entropyToMiniSecret(entropy);
891
- const derive = sr25519CreateDerive(miniSecret);
892
- const keypair = derive("");
893
- return { mnemonic, publicKey: keypair.publicKey };
621
+ function getSignedExtensions(meta) {
622
+ const byVersion = meta.unified.extrinsic.signedExtensions;
623
+ const versionKeys = Object.keys(byVersion);
624
+ if (versionKeys.length === 0)
625
+ return [];
626
+ return byVersion[Number(versionKeys[0])] ?? [];
894
627
  }
895
- function importAccount(secret) {
896
- const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(secret);
897
- if (isHexSeed) {
898
- const keypair2 = deriveFromHexSeed(secret, "");
899
- return { publicKey: keypair2.publicKey };
900
- }
901
- if (!validateMnemonic(secret)) {
902
- throw new Error("Invalid secret. Expected a 0x-prefixed 32-byte hex seed or a valid BIP39 mnemonic.");
903
- }
904
- const keypair = deriveFromMnemonic(secret, "");
905
- return { publicKey: keypair.publicKey };
628
+ function getPalletNames(meta) {
629
+ return meta.unified.pallets.map((p) => p.name);
906
630
  }
907
- function publicKeyToHex(publicKey) {
908
- return "0x" + Array.from(publicKey).map((b) => b.toString(16).padStart(2, "0")).join("");
631
+ function describeType(lookup, typeId) {
632
+ try {
633
+ const entry = lookup(typeId);
634
+ return formatLookupEntry(entry);
635
+ } catch {
636
+ return `type(${typeId})`;
637
+ }
909
638
  }
910
- function toSs58(publicKey, prefix = 42) {
911
- if (typeof publicKey === "string") {
912
- const clean = publicKey.startsWith("0x") ? publicKey.slice(2) : publicKey;
913
- const bytes = new Uint8Array(clean.length / 2);
914
- for (let i = 0;i < clean.length; i += 2) {
915
- bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
639
+ function formatLookupEntry(entry) {
640
+ switch (entry.type) {
641
+ case "primitive":
642
+ return entry.value;
643
+ case "compact":
644
+ return `Compact<${formatLookupEntry(entry.isBig ? { type: "primitive", value: "u128" } : { type: "primitive", value: "u64" })}>`;
645
+ case "AccountId32":
646
+ return "AccountId32";
647
+ case "bitSequence":
648
+ return "BitSequence";
649
+ case "sequence":
650
+ return `Vec<${formatLookupEntry(entry.value)}>`;
651
+ case "array":
652
+ return `[${formatLookupEntry(entry.value)}; ${entry.len}]`;
653
+ case "tuple":
654
+ return `(${entry.value.map(formatLookupEntry).join(", ")})`;
655
+ case "struct":
656
+ return `{ ${Object.entries(entry.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ")} }`;
657
+ case "option":
658
+ return `Option<${formatLookupEntry(entry.value)}>`;
659
+ case "result":
660
+ return `Result<${formatLookupEntry(entry.value.ok)}, ${formatLookupEntry(entry.value.ko)}>`;
661
+ case "enum": {
662
+ const variants = Object.keys(entry.value);
663
+ if (variants.length <= 4)
664
+ return variants.join(" | ");
665
+ return `enum(${variants.length} variants)`;
916
666
  }
917
- return ss58Address(bytes, prefix);
667
+ default:
668
+ return "unknown";
918
669
  }
919
- return ss58Address(publicKey, prefix);
920
670
  }
921
- async function resolveAccountSigner(name) {
922
- if (isDevAccount(name)) {
923
- const keypair2 = getDevKeypair(name);
924
- return getPolkadotSigner(keypair2.publicKey, "Sr25519", keypair2.sign);
925
- }
926
- const accountsFile = await loadAccounts();
927
- const account = findAccount(accountsFile, name);
928
- if (!account) {
929
- const available = [
930
- ...DEV_NAMES,
931
- ...accountsFile.accounts.map((a) => a.name)
932
- ];
933
- throw new Error(`Unknown account "${name}". Available accounts: ${available.join(", ")}`);
671
+ function hexToBytes(hex) {
672
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
673
+ const bytes = new Uint8Array(clean.length / 2);
674
+ for (let i = 0;i < clean.length; i += 2) {
675
+ bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
934
676
  }
935
- const isHexSeed = /^0x[0-9a-fA-F]{64}$/.test(account.secret);
936
- const keypair = isHexSeed ? deriveFromHexSeed(account.secret, account.derivationPath) : deriveFromMnemonic(account.secret, account.derivationPath);
937
- return getPolkadotSigner(keypair.publicKey, "Sr25519", keypair.sign);
677
+ return bytes;
938
678
  }
939
679
 
940
- // src/commands/account.ts
941
- var ACCOUNT_HELP = `
680
+ // src/commands/chain.ts
681
+ var CHAIN_HELP = `
942
682
  ${BOLD}Usage:${RESET}
943
- $ dot account create <name> Create a new account
944
- $ dot account import <name> --secret <s> Import from mnemonic or hex seed
945
- $ dot account list List all accounts
946
- $ dot account remove <name> Remove a stored account
683
+ $ dot chain add <name> --rpc <url> Add a chain via WebSocket RPC
684
+ $ dot chain add <name> --light-client Add a chain via Smoldot light client
685
+ $ dot chain remove <name> Remove a chain
686
+ $ dot chain update [name] Re-fetch metadata (default chain if omitted)
687
+ $ dot chain list List configured chains
688
+ $ dot chain default <name> Set the default chain
947
689
 
948
690
  ${BOLD}Examples:${RESET}
949
- $ dot account create my-validator
950
- $ dot account import treasury --secret "word1 word2 ... word12"
951
- $ dot account import raw-key --secret 0xabcdef...
952
- $ dot account list
953
- $ dot account remove my-validator
691
+ $ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
692
+ $ dot chain add westend --light-client
693
+ $ dot chain default kusama
694
+ $ dot chain list
695
+ $ dot chain update
696
+ $ dot chain update kusama
697
+ $ dot chain remove kusama
954
698
  `.trimStart();
955
- function registerAccountCommands(cli) {
956
- cli.command("account [action] [name]", "Manage local accounts (create, import, list, remove)").option("--secret <value>", "Secret key (mnemonic or hex seed) for import").action(async (action, name, opts) => {
699
+ function registerChainCommands(cli) {
700
+ cli.command("chain [action] [name]", "Manage chains (add, remove, update, list, default)").action(async (action, name, opts) => {
957
701
  if (!action) {
958
- console.log(ACCOUNT_HELP);
702
+ console.log(CHAIN_HELP);
959
703
  return;
960
704
  }
961
705
  switch (action) {
962
- case "create":
963
- return accountCreate(name);
964
- case "import":
965
- return accountImport(name, opts);
966
- case "list":
967
- return accountList();
706
+ case "add":
707
+ return chainAdd(name, opts);
968
708
  case "remove":
969
- return accountRemove(name);
709
+ return chainRemove(name);
710
+ case "list":
711
+ return chainList();
712
+ case "update":
713
+ return chainUpdate(name, opts);
714
+ case "default":
715
+ return chainDefault(name);
970
716
  default:
971
717
  console.error(`Unknown action "${action}".
972
718
  `);
973
- console.log(ACCOUNT_HELP);
719
+ console.log(CHAIN_HELP);
974
720
  process.exit(1);
975
721
  }
976
722
  });
977
723
  }
978
- async function accountCreate(name) {
724
+ async function chainAdd(name, opts) {
979
725
  if (!name) {
980
- console.error(`Account name is required.
726
+ console.error(`Chain name is required.
981
727
  `);
982
- console.error("Usage: dot account create <name>");
728
+ console.error("Usage: dot chain add <name> --rpc <url>");
729
+ console.error(" dot chain add <name> --light-client");
983
730
  process.exit(1);
984
731
  }
985
- if (isDevAccount(name)) {
986
- throw new Error(`"${name}" is a built-in dev account and cannot be used as a custom account name.`);
732
+ if (!opts.rpc && !opts.lightClient) {
733
+ console.error(`Must provide either --rpc <url> or --light-client.
734
+ `);
735
+ console.error("Usage: dot chain add <name> --rpc <url>");
736
+ console.error(" dot chain add <name> --light-client");
737
+ process.exit(1);
987
738
  }
988
- const accountsFile = await loadAccounts();
989
- if (findAccount(accountsFile, name)) {
990
- throw new Error(`Account "${name}" already exists.`);
739
+ const chainConfig = {
740
+ rpc: opts.rpc ?? "",
741
+ ...opts.lightClient ? { lightClient: true } : {}
742
+ };
743
+ console.log(`Connecting to ${name}...`);
744
+ const clientHandle = await createChainClient(name, chainConfig, opts.rpc);
745
+ try {
746
+ console.log("Fetching metadata...");
747
+ await fetchMetadataFromChain(clientHandle, name);
748
+ const config = await loadConfig();
749
+ config.chains[name] = chainConfig;
750
+ await saveConfig(config);
751
+ console.log(`Chain "${name}" added successfully.`);
752
+ } finally {
753
+ clientHandle.destroy();
991
754
  }
992
- const { mnemonic, publicKey } = createNewAccount();
993
- const hexPub = publicKeyToHex(publicKey);
994
- const address = toSs58(publicKey);
995
- accountsFile.accounts.push({
996
- name,
997
- secret: mnemonic,
998
- publicKey: hexPub,
999
- derivationPath: ""
1000
- });
1001
- await saveAccounts(accountsFile);
1002
- printHeading("Account Created");
1003
- console.log(` ${BOLD}Name:${RESET} ${name}`);
1004
- console.log(` ${BOLD}Address:${RESET} ${address}`);
1005
- console.log(` ${BOLD}Mnemonic:${RESET} ${mnemonic}`);
1006
- console.log();
1007
- console.log(` ${YELLOW}Save this mnemonic phrase! It is the only way to recover this account.${RESET}`);
1008
- console.log();
1009
755
  }
1010
- async function accountImport(name, opts) {
756
+ async function chainRemove(name) {
1011
757
  if (!name) {
1012
- console.error(`Account name is required.
1013
- `);
1014
- console.error('Usage: dot account import <name> --secret "mnemonic or hex seed"');
758
+ console.error("Usage: dot chain remove <name>");
1015
759
  process.exit(1);
1016
760
  }
1017
- if (!opts.secret) {
1018
- console.error(`--secret is required.
1019
- `);
1020
- console.error('Usage: dot account import <name> --secret "mnemonic or hex seed"');
761
+ const config = await loadConfig();
762
+ if (!config.chains[name]) {
763
+ throw new Error(`Chain "${name}" not found.`);
764
+ }
765
+ if (name === "polkadot") {
766
+ throw new Error('Cannot remove the built-in "polkadot" chain.');
767
+ }
768
+ delete config.chains[name];
769
+ if (config.defaultChain === name) {
770
+ config.defaultChain = "polkadot";
771
+ console.log(`Default chain reset to "polkadot".`);
772
+ }
773
+ await saveConfig(config);
774
+ await removeChainData(name);
775
+ console.log(`Chain "${name}" removed.`);
776
+ }
777
+ async function chainList() {
778
+ const config = await loadConfig();
779
+ printHeading("Configured Chains");
780
+ for (const [name, chainConfig] of Object.entries(config.chains)) {
781
+ const isDefault = name === config.defaultChain;
782
+ const marker = isDefault ? ` ${BOLD}(default)${RESET}` : "";
783
+ const provider = chainConfig.lightClient ? `${DIM}light-client${RESET}` : `${DIM}${chainConfig.rpc}${RESET}`;
784
+ console.log(` ${CYAN}${name}${RESET}${marker} ${provider}`);
785
+ }
786
+ console.log();
787
+ }
788
+ async function chainUpdate(name, opts) {
789
+ const config = await loadConfig();
790
+ const { name: chainName, chain: chainConfig } = resolveChain(config, name);
791
+ console.log(`Connecting to ${chainName}...`);
792
+ const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
793
+ try {
794
+ console.log("Fetching metadata...");
795
+ await fetchMetadataFromChain(clientHandle, chainName);
796
+ console.log(`Metadata for "${chainName}" updated.`);
797
+ } finally {
798
+ clientHandle.destroy();
799
+ }
800
+ }
801
+ async function chainDefault(name) {
802
+ if (!name) {
803
+ console.error("Usage: dot chain default <name>");
1021
804
  process.exit(1);
1022
805
  }
1023
- if (isDevAccount(name)) {
1024
- throw new Error(`"${name}" is a built-in dev account and cannot be used as a custom account name.`);
806
+ const config = await loadConfig();
807
+ if (!config.chains[name]) {
808
+ const available = Object.keys(config.chains).join(", ");
809
+ throw new Error(`Chain "${name}" not found. Available: ${available}`);
1025
810
  }
1026
- const accountsFile = await loadAccounts();
1027
- if (findAccount(accountsFile, name)) {
1028
- throw new Error(`Account "${name}" already exists.`);
811
+ config.defaultChain = name;
812
+ await saveConfig(config);
813
+ console.log(`Default chain set to "${name}".`);
814
+ }
815
+
816
+ // src/utils/fuzzy-match.ts
817
+ function levenshtein(a, b) {
818
+ const la = a.length;
819
+ const lb = b.length;
820
+ const dp = Array.from({ length: la + 1 }, () => Array(lb + 1).fill(0));
821
+ for (let i = 0;i <= la; i++)
822
+ dp[i][0] = i;
823
+ for (let j = 0;j <= lb; j++)
824
+ dp[0][j] = j;
825
+ for (let i = 1;i <= la; i++) {
826
+ for (let j = 1;j <= lb; j++) {
827
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
828
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
829
+ }
1029
830
  }
1030
- const { publicKey } = importAccount(opts.secret);
1031
- const hexPub = publicKeyToHex(publicKey);
1032
- const address = toSs58(publicKey);
1033
- accountsFile.accounts.push({
1034
- name,
1035
- secret: opts.secret,
1036
- publicKey: hexPub,
1037
- derivationPath: ""
831
+ return dp[la][lb];
832
+ }
833
+ function findClosest(input, candidates, maxDistance = 3) {
834
+ const lower = input.toLowerCase();
835
+ const exact = candidates.find((c) => c.toLowerCase() === lower);
836
+ if (exact)
837
+ return [exact];
838
+ const scored = candidates.map((c) => ({ name: c, dist: levenshtein(lower, c.toLowerCase()) })).filter((s) => s.dist <= maxDistance).sort((a, b) => a.dist - b.dist);
839
+ return scored.slice(0, 3).map((s) => s.name);
840
+ }
841
+ function suggestMessage(kind, input, candidates) {
842
+ const suggestions = findClosest(input, candidates);
843
+ if (suggestions.length === 0) {
844
+ return `Unknown ${kind} "${input}".`;
845
+ }
846
+ if (suggestions.length === 1 && suggestions[0]?.toLowerCase() === input.toLowerCase()) {
847
+ return suggestions[0];
848
+ }
849
+ return `Unknown ${kind} "${input}". Did you mean: ${suggestions.join(", ")}?`;
850
+ }
851
+
852
+ // src/utils/parse-target.ts
853
+ function parseTarget(input) {
854
+ const parts = input.split(".");
855
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
856
+ throw new Error(`Invalid target "${input}". Expected format: Pallet.Item (e.g. System.Account)`);
857
+ }
858
+ return { pallet: parts[0], item: parts[1] };
859
+ }
860
+
861
+ // src/commands/const.ts
862
+ function registerConstCommand(cli) {
863
+ cli.command("const [target]", "Look up a pallet constant (e.g. Balances.ExistentialDeposit)").action(async (target, opts) => {
864
+ if (!target) {
865
+ console.log("Usage: dot const <Pallet.Constant> [--chain <name>] [--output json]");
866
+ console.log("");
867
+ console.log("Examples:");
868
+ console.log(" $ dot const Balances.ExistentialDeposit");
869
+ console.log(" $ dot const System.SS58Prefix --chain kusama");
870
+ return;
871
+ }
872
+ const config = await loadConfig();
873
+ const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
874
+ const { pallet, item } = parseTarget(target);
875
+ const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
876
+ try {
877
+ const meta = await getOrFetchMetadata(chainName, clientHandle);
878
+ const palletNames = getPalletNames(meta);
879
+ const palletInfo = findPallet(meta, pallet);
880
+ if (!palletInfo) {
881
+ throw new Error(suggestMessage("pallet", pallet, palletNames));
882
+ }
883
+ const constantItem = palletInfo.constants.find((c) => c.name.toLowerCase() === item.toLowerCase());
884
+ if (!constantItem) {
885
+ const constNames = palletInfo.constants.map((c) => c.name);
886
+ throw new Error(suggestMessage(`constant in ${palletInfo.name}`, item, constNames));
887
+ }
888
+ const unsafeApi = clientHandle.client.getUnsafeApi();
889
+ const runtimeToken = await unsafeApi.runtimeToken;
890
+ const result = unsafeApi.constants[palletInfo.name][constantItem.name](runtimeToken);
891
+ printResult(result, opts.output ?? "pretty");
892
+ } finally {
893
+ clientHandle.destroy();
894
+ }
1038
895
  });
1039
- await saveAccounts(accountsFile);
1040
- printHeading("Account Imported");
1041
- console.log(` ${BOLD}Name:${RESET} ${name}`);
1042
- console.log(` ${BOLD}Address:${RESET} ${address}`);
1043
- console.log();
1044
896
  }
1045
- async function accountList() {
1046
- printHeading("Dev Accounts");
1047
- for (const name of DEV_NAMES) {
1048
- const display = name.charAt(0).toUpperCase() + name.slice(1);
1049
- const address = getDevAddress(name);
1050
- printItem(display, address);
897
+
898
+ // src/core/hash.ts
899
+ import { blake2b } from "@noble/hashes/blake2.js";
900
+ import { sha256 } from "@noble/hashes/sha2.js";
901
+ import { keccak_256 } from "@noble/hashes/sha3.js";
902
+ import { bytesToHex, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
903
+ var ALGORITHMS = {
904
+ blake2b256: {
905
+ compute: (data) => blake2b(data, { dkLen: 32 }),
906
+ outputLen: 32,
907
+ description: "BLAKE2b with 256-bit output"
908
+ },
909
+ blake2b128: {
910
+ compute: (data) => blake2b(data, { dkLen: 16 }),
911
+ outputLen: 16,
912
+ description: "BLAKE2b with 128-bit output"
913
+ },
914
+ keccak256: {
915
+ compute: (data) => keccak_256(data),
916
+ outputLen: 32,
917
+ description: "Keccak-256 (Ethereum-compatible)"
918
+ },
919
+ sha256: {
920
+ compute: (data) => sha256(data),
921
+ outputLen: 32,
922
+ description: "SHA-256"
1051
923
  }
1052
- const accountsFile = await loadAccounts();
1053
- if (accountsFile.accounts.length > 0) {
1054
- printHeading("Stored Accounts");
1055
- for (const account of accountsFile.accounts) {
1056
- const address = toSs58(account.publicKey);
1057
- printItem(account.name, address);
924
+ };
925
+ function computeHash(algorithm, data) {
926
+ const algo = ALGORITHMS[algorithm];
927
+ if (!algo) {
928
+ throw new Error(`Unknown algorithm: ${algorithm}`);
929
+ }
930
+ return algo.compute(data);
931
+ }
932
+ function parseInputData(input) {
933
+ if (input.startsWith("0x")) {
934
+ const hex = input.slice(2);
935
+ if (hex.length % 2 !== 0) {
936
+ throw new Error(`Invalid hex input: odd number of characters`);
937
+ }
938
+ return hexToBytes2(hex);
939
+ }
940
+ return new TextEncoder().encode(input);
941
+ }
942
+ function toHex(bytes) {
943
+ return `0x${bytesToHex(bytes)}`;
944
+ }
945
+ function isValidAlgorithm(name) {
946
+ return name in ALGORITHMS;
947
+ }
948
+ function getAlgorithmNames() {
949
+ return Object.keys(ALGORITHMS);
950
+ }
951
+
952
+ // src/commands/hash.ts
953
+ async function resolveInput(data, opts) {
954
+ const sources = [data !== undefined, !!opts.file, !!opts.stdin].filter(Boolean).length;
955
+ if (sources > 1) {
956
+ throw new CliError("Provide only one of: inline data, --file, or --stdin");
957
+ }
958
+ if (sources === 0) {
959
+ throw new CliError("No input provided. Pass data as argument, or use --file or --stdin");
960
+ }
961
+ if (opts.file) {
962
+ const buf = await Bun.file(opts.file).arrayBuffer();
963
+ return new Uint8Array(buf);
964
+ }
965
+ if (opts.stdin) {
966
+ const reader = Bun.stdin.stream().getReader();
967
+ const chunks = [];
968
+ while (true) {
969
+ const { done, value } = await reader.read();
970
+ if (done)
971
+ break;
972
+ chunks.push(value);
973
+ }
974
+ const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
975
+ const result = new Uint8Array(totalLen);
976
+ let offset = 0;
977
+ for (const chunk of chunks) {
978
+ result.set(chunk, offset);
979
+ offset += chunk.length;
980
+ }
981
+ return result;
982
+ }
983
+ return parseInputData(data);
984
+ }
985
+ function printAlgorithmHelp() {
986
+ console.log(`${BOLD}Usage:${RESET} dot hash <algorithm> <data> [options]
987
+ `);
988
+ console.log(`${BOLD}Algorithms:${RESET}`);
989
+ for (const [name, algo] of Object.entries(ALGORITHMS)) {
990
+ console.log(` ${CYAN}${name}${RESET} ${DIM}${algo.description} (${algo.outputLen} bytes)${RESET}`);
991
+ }
992
+ console.log(`
993
+ ${BOLD}Options:${RESET}`);
994
+ console.log(` ${CYAN}--file <path>${RESET} ${DIM}Hash file contents${RESET}`);
995
+ console.log(` ${CYAN}--stdin${RESET} ${DIM}Read from stdin${RESET}`);
996
+ console.log(` ${CYAN}--output json${RESET} ${DIM}Output as JSON${RESET}`);
997
+ console.log(`
998
+ ${BOLD}Examples:${RESET}`);
999
+ console.log(` ${DIM}$ dot hash blake2b256 0xdeadbeef${RESET}`);
1000
+ console.log(` ${DIM}$ dot hash sha256 hello${RESET}`);
1001
+ console.log(` ${DIM}$ dot hash keccak256 --file ./data.bin${RESET}`);
1002
+ console.log(` ${DIM}$ echo -n "hello" | dot hash sha256 --stdin${RESET}`);
1003
+ }
1004
+ function registerHashCommand(cli) {
1005
+ cli.command("hash [algorithm] [data]", "Compute cryptographic hashes").option("--file <path>", "Hash file contents (raw bytes)").option("--stdin", "Read data from stdin").action(async (algorithm, data, opts) => {
1006
+ if (!algorithm) {
1007
+ printAlgorithmHelp();
1008
+ return;
1009
+ }
1010
+ if (!isValidAlgorithm(algorithm)) {
1011
+ throw new CliError(suggestMessage("algorithm", algorithm, getAlgorithmNames()));
1012
+ }
1013
+ const input = await resolveInput(data, opts);
1014
+ const hash = computeHash(algorithm, input);
1015
+ const hexHash = toHex(hash);
1016
+ const format = opts.output ?? "pretty";
1017
+ if (format === "json") {
1018
+ printResult({
1019
+ algorithm,
1020
+ input: data ?? (opts.file ? `file:${opts.file}` : "stdin"),
1021
+ hash: hexHash
1022
+ }, "json");
1023
+ } else {
1024
+ console.log(hexHash);
1025
+ }
1026
+ });
1027
+ }
1028
+
1029
+ // src/commands/inspect.ts
1030
+ function registerInspectCommand(cli) {
1031
+ cli.command("inspect [target]", "Inspect chain metadata (pallets, storage, constants)").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
1032
+ const config = await loadConfig();
1033
+ const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
1034
+ let meta;
1035
+ try {
1036
+ meta = await getOrFetchMetadata(chainName);
1037
+ } catch {
1038
+ console.log(`Fetching metadata from ${chainName}...`);
1039
+ const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
1040
+ try {
1041
+ meta = await getOrFetchMetadata(chainName, clientHandle);
1042
+ } finally {
1043
+ clientHandle.destroy();
1044
+ }
1045
+ }
1046
+ if (!target) {
1047
+ const pallets = listPallets(meta);
1048
+ printHeading(`Pallets on ${chainName} (${pallets.length})`);
1049
+ for (const p of pallets) {
1050
+ const counts = [];
1051
+ if (p.storage.length)
1052
+ counts.push(`${p.storage.length} storage`);
1053
+ if (p.constants.length)
1054
+ counts.push(`${p.constants.length} constants`);
1055
+ printItem(p.name, counts.join(", "));
1056
+ }
1057
+ console.log();
1058
+ return;
1059
+ }
1060
+ if (!target.includes(".")) {
1061
+ const palletNames2 = getPalletNames(meta);
1062
+ const pallet2 = findPallet(meta, target);
1063
+ if (!pallet2) {
1064
+ throw new Error(suggestMessage("pallet", target, palletNames2));
1065
+ }
1066
+ printHeading(`${pallet2.name} Pallet`);
1067
+ if (pallet2.docs.length) {
1068
+ printDocs(pallet2.docs);
1069
+ console.log();
1070
+ }
1071
+ if (pallet2.storage.length) {
1072
+ console.log(` ${BOLD}Storage Items:${RESET}`);
1073
+ for (const s of pallet2.storage) {
1074
+ const doc = s.docs[0] ? ` — ${s.docs[0].slice(0, 80)}` : "";
1075
+ console.log(` ${CYAN}${s.name}${RESET}${DIM}${doc}${RESET}`);
1076
+ }
1077
+ console.log();
1078
+ }
1079
+ if (pallet2.constants.length) {
1080
+ console.log(` ${BOLD}Constants:${RESET}`);
1081
+ for (const c of pallet2.constants) {
1082
+ const doc = c.docs[0] ? ` — ${c.docs[0].slice(0, 80)}` : "";
1083
+ console.log(` ${CYAN}${c.name}${RESET}${DIM}${doc}${RESET}`);
1084
+ }
1085
+ console.log();
1086
+ }
1087
+ return;
1088
+ }
1089
+ const { pallet: palletName, item: itemName } = parseTarget(target);
1090
+ const palletNames = getPalletNames(meta);
1091
+ const pallet = findPallet(meta, palletName);
1092
+ if (!pallet) {
1093
+ throw new Error(suggestMessage("pallet", palletName, palletNames));
1094
+ }
1095
+ const storageItem = pallet.storage.find((s) => s.name.toLowerCase() === itemName.toLowerCase());
1096
+ if (storageItem) {
1097
+ printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
1098
+ console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
1099
+ console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
1100
+ if (storageItem.keyTypeId != null) {
1101
+ console.log(` ${BOLD}Key:${RESET} ${describeType(meta.lookup, storageItem.keyTypeId)}`);
1102
+ }
1103
+ if (storageItem.docs.length) {
1104
+ console.log();
1105
+ printDocs(storageItem.docs);
1106
+ }
1107
+ console.log();
1108
+ return;
1109
+ }
1110
+ const constantItem = pallet.constants.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
1111
+ if (constantItem) {
1112
+ printHeading(`${pallet.name}.${constantItem.name} (Constant)`);
1113
+ console.log(` ${BOLD}Type:${RESET} ${describeType(meta.lookup, constantItem.typeId)}`);
1114
+ if (constantItem.docs.length) {
1115
+ console.log();
1116
+ printDocs(constantItem.docs);
1117
+ }
1118
+ console.log();
1119
+ return;
1058
1120
  }
1059
- } else {
1060
- printHeading("Stored Accounts");
1061
- console.log(" (none)");
1062
- }
1063
- console.log();
1121
+ const allItems = [
1122
+ ...pallet.storage.map((s) => s.name),
1123
+ ...pallet.constants.map((c) => c.name)
1124
+ ];
1125
+ throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems));
1126
+ });
1064
1127
  }
1065
- async function accountRemove(name) {
1066
- if (!name) {
1067
- console.error(`Account name is required.
1068
- `);
1069
- console.error("Usage: dot account remove <name>");
1070
- process.exit(1);
1071
- }
1072
- if (isDevAccount(name)) {
1073
- throw new Error("Cannot remove built-in dev accounts.");
1074
- }
1075
- const accountsFile = await loadAccounts();
1076
- const idx = accountsFile.accounts.findIndex((a) => a.name.toLowerCase() === name.toLowerCase());
1077
- if (idx === -1) {
1078
- throw new Error(`Account "${name}" not found.`);
1128
+
1129
+ // src/utils/parse-value.ts
1130
+ function parseValue(arg) {
1131
+ if (/^\d+$/.test(arg))
1132
+ return parseInt(arg, 10);
1133
+ if (/^\d{16,}$/.test(arg))
1134
+ return BigInt(arg);
1135
+ if (/^0x[0-9a-fA-F]+$/.test(arg))
1136
+ return arg;
1137
+ if (arg === "true")
1138
+ return true;
1139
+ if (arg === "false")
1140
+ return false;
1141
+ if (arg.startsWith("{") || arg.startsWith("[")) {
1142
+ try {
1143
+ return JSON.parse(arg);
1144
+ } catch {}
1079
1145
  }
1080
- accountsFile.accounts.splice(idx, 1);
1081
- await saveAccounts(accountsFile);
1082
- console.log(`Account "${name}" removed.`);
1146
+ return arg;
1147
+ }
1148
+
1149
+ // src/commands/query.ts
1150
+ var DEFAULT_LIMIT = 100;
1151
+ function registerQueryCommand(cli) {
1152
+ cli.command("query [target] [...keys]", "Query on-chain storage (e.g. System.Number, System.Account <addr>)").option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
1153
+ default: DEFAULT_LIMIT
1154
+ }).action(async (target, keys, opts) => {
1155
+ if (!target) {
1156
+ console.log("Usage: dot query <Pallet.Item> [...keys] [--chain <name>] [--output json]");
1157
+ console.log("");
1158
+ console.log("Examples:");
1159
+ console.log(" $ dot query System.Number # plain storage value");
1160
+ console.log(" $ dot query System.Account 5Grw... # single map entry");
1161
+ console.log(" $ dot query System.Account # all entries (limit 100)");
1162
+ console.log(" $ dot query System.Account --limit 10 # first 10 entries");
1163
+ console.log(" $ dot query System.Account --limit 0 # all entries (no limit)");
1164
+ console.log(" $ dot query Assets.Metadata 42 --chain asset-hub");
1165
+ return;
1166
+ }
1167
+ const config = await loadConfig();
1168
+ const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
1169
+ const { pallet, item } = parseTarget(target);
1170
+ const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
1171
+ try {
1172
+ const meta = await getOrFetchMetadata(chainName, clientHandle);
1173
+ const palletNames = getPalletNames(meta);
1174
+ const palletInfo = findPallet(meta, pallet);
1175
+ if (!palletInfo) {
1176
+ throw new Error(suggestMessage("pallet", pallet, palletNames));
1177
+ }
1178
+ const storageItem = palletInfo.storage.find((s) => s.name.toLowerCase() === item.toLowerCase());
1179
+ if (!storageItem) {
1180
+ const storageNames = palletInfo.storage.map((s) => s.name);
1181
+ throw new Error(suggestMessage(`storage item in ${palletInfo.name}`, item, storageNames));
1182
+ }
1183
+ const unsafeApi = clientHandle.client.getUnsafeApi();
1184
+ const storageApi = unsafeApi.query[palletInfo.name][storageItem.name];
1185
+ const parsedKeys = keys.map(parseValue);
1186
+ const format = opts.output ?? "pretty";
1187
+ if (storageItem.type === "map" && parsedKeys.length === 0) {
1188
+ const entries = await storageApi.getEntries();
1189
+ const limit = Number(opts.limit);
1190
+ const truncated = limit > 0 && entries.length > limit;
1191
+ const display = truncated ? entries.slice(0, limit) : entries;
1192
+ printResult(display.map((e) => ({
1193
+ keys: e.keyArgs,
1194
+ value: e.value
1195
+ })), format);
1196
+ if (truncated) {
1197
+ console.error(`
1198
+ ${DIM}Showing ${limit} of ${entries.length} entries. Use --limit 0 for all.${RESET}`);
1199
+ }
1200
+ } else {
1201
+ const result = await storageApi.getValue(...parsedKeys);
1202
+ printResult(result, format);
1203
+ }
1204
+ } finally {
1205
+ clientHandle.destroy();
1206
+ }
1207
+ });
1083
1208
  }
1084
1209
 
1085
1210
  // src/commands/tx.ts
1086
- import { Binary } from "polkadot-api";
1087
1211
  import { getViewBuilder } from "@polkadot-api/view-builder";
1212
+ import { Binary } from "polkadot-api";
1088
1213
 
1089
1214
  // src/core/explorers.ts
1090
1215
  var pjsAppsLink = (rpc, hash) => `https://polkadot.js.org/apps/?rpc=${encodeURIComponent(rpc)}#/explorer/query/${hash}`;
@@ -1138,7 +1263,7 @@ function registerTxCommand(cli) {
1138
1263
  const userExtOverrides = parseExtOption(opts.ext);
1139
1264
  const customSignedExtensions = buildCustomSignedExtensions(meta, userExtOverrides);
1140
1265
  txOptions = Object.keys(customSignedExtensions).length > 0 ? { customSignedExtensions } : undefined;
1141
- unsafeApi = clientHandle.client.getUnsafeApi();
1266
+ unsafeApi = clientHandle?.client.getUnsafeApi();
1142
1267
  }
1143
1268
  let tx;
1144
1269
  let callHex;
@@ -1166,11 +1291,7 @@ function registerTxCommand(cli) {
1166
1291
  if (opts.encode) {
1167
1292
  const { codec, location } = meta.builder.buildCall(palletInfo.name, callInfo.name);
1168
1293
  const encodedArgs = codec.enc(callData);
1169
- const fullCall = new Uint8Array([
1170
- location[0],
1171
- location[1],
1172
- ...encodedArgs
1173
- ]);
1294
+ const fullCall = new Uint8Array([location[0], location[1], ...encodedArgs]);
1174
1295
  console.log(Binary.fromBytes(fullCall).asHex());
1175
1296
  return;
1176
1297
  }
@@ -1181,11 +1302,12 @@ function registerTxCommand(cli) {
1181
1302
  const decodedStr = decodeCall(meta, callHex);
1182
1303
  if (opts.dryRun) {
1183
1304
  const signerAddress = toSs58(signer.publicKey);
1305
+ console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
1184
1306
  console.log(` ${BOLD}From:${RESET} ${opts.from} (${signerAddress})`);
1185
1307
  console.log(` ${BOLD}Call:${RESET} ${callHex}`);
1186
1308
  console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
1187
1309
  try {
1188
- const fees = await tx.getEstimatedFees(signer.publicKey, txOptions);
1310
+ const fees = await tx.getEstimatedFees(signer?.publicKey, txOptions);
1189
1311
  console.log(` ${BOLD}Estimated fees:${RESET} ${fees}`);
1190
1312
  } catch (err) {
1191
1313
  console.log(` ${BOLD}Estimated fees:${RESET} ${YELLOW}unable to estimate${RESET}`);
@@ -1195,15 +1317,17 @@ function registerTxCommand(cli) {
1195
1317
  }
1196
1318
  const result = await watchTransaction(tx.signSubmitAndWatch(signer, txOptions));
1197
1319
  console.log();
1320
+ console.log(` ${BOLD}Chain:${RESET} ${chainName}`);
1198
1321
  console.log(` ${BOLD}Call:${RESET} ${callHex}`);
1199
1322
  console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
1200
1323
  console.log(` ${BOLD}Tx:${RESET} ${result.txHash}`);
1201
- console.log(` ${BOLD}Block:${RESET} #${result.block.number} (${result.block.hash})`);
1324
+ let dispatchErrorMsg;
1202
1325
  if (result.ok) {
1203
1326
  console.log(` ${BOLD}Status:${RESET} ${GREEN}ok${RESET}`);
1204
1327
  } else {
1205
- console.log(` ${BOLD}Status:${RESET} ${YELLOW}dispatch error${RESET}`);
1206
- console.log(` ${BOLD}Error:${RESET} ${result.dispatchError.type}${result.dispatchError.value ? ": " + JSON.stringify(result.dispatchError.value) : ""}`);
1328
+ dispatchErrorMsg = formatDispatchError(result.dispatchError);
1329
+ console.log(` ${BOLD}Status:${RESET} ${RED}dispatch error${RESET}`);
1330
+ console.log(` ${BOLD}Error:${RESET} ${dispatchErrorMsg}`);
1207
1331
  }
1208
1332
  if (result.events && result.events.length > 0) {
1209
1333
  console.log(` ${BOLD}Events:${RESET}`);
@@ -1226,11 +1350,31 @@ function registerTxCommand(cli) {
1226
1350
  console.log(` ${DIM}PAPI${RESET} ${papiLink(rpcUrl, blockHash)}`);
1227
1351
  }
1228
1352
  console.log();
1353
+ if (!result.ok) {
1354
+ throw new CliError(`Transaction dispatch error: ${dispatchErrorMsg}`);
1355
+ }
1229
1356
  } finally {
1230
1357
  clientHandle?.destroy();
1231
1358
  }
1232
1359
  });
1233
1360
  }
1361
+ function formatDispatchError(err) {
1362
+ if (err.type === "Module" && err.value && typeof err.value === "object") {
1363
+ const mod = err.value;
1364
+ if (mod.type) {
1365
+ const inner = mod.value;
1366
+ if (inner && typeof inner === "object" && inner.type) {
1367
+ return `${mod.type}.${inner.type}`;
1368
+ }
1369
+ return mod.type;
1370
+ }
1371
+ }
1372
+ if (err.value !== undefined && err.value !== null) {
1373
+ const val = typeof err.value === "string" ? err.value : JSON.stringify(err.value);
1374
+ return `${err.type}: ${val}`;
1375
+ }
1376
+ return err.type;
1377
+ }
1234
1378
  function decodeCall(meta, callHex) {
1235
1379
  try {
1236
1380
  const viewBuilder = getViewBuilder(meta.lookup);
@@ -1432,6 +1576,15 @@ function normalizeValue(lookup, entry, value) {
1432
1576
  }
1433
1577
  case "array":
1434
1578
  case "sequence": {
1579
+ let innerResolved = resolved.value;
1580
+ while (innerResolved.type === "lookupEntry") {
1581
+ innerResolved = innerResolved.value;
1582
+ }
1583
+ if (innerResolved.type === "primitive" && innerResolved.value === "u8" && typeof value === "string") {
1584
+ if (/^0x[0-9a-fA-F]*$/.test(value))
1585
+ return Binary.fromHex(value);
1586
+ return Binary.fromText(value);
1587
+ }
1435
1588
  if (Array.isArray(value)) {
1436
1589
  const innerEntry = resolved.value;
1437
1590
  return value.map((item) => normalizeValue(lookup, innerEntry, item));
@@ -1449,6 +1602,36 @@ function normalizeValue(lookup, entry, value) {
1449
1602
  if (value !== null && value !== undefined) {
1450
1603
  return normalizeValue(lookup, resolved.value, value);
1451
1604
  }
1605
+ return;
1606
+ }
1607
+ case "primitive": {
1608
+ if (typeof value === "string") {
1609
+ const prim = resolved.value;
1610
+ switch (prim) {
1611
+ case "bool":
1612
+ return value === "true";
1613
+ case "u64":
1614
+ case "u128":
1615
+ case "u256":
1616
+ case "i64":
1617
+ case "i128":
1618
+ case "i256":
1619
+ return BigInt(value);
1620
+ case "u8":
1621
+ case "u16":
1622
+ case "u32":
1623
+ case "i8":
1624
+ case "i16":
1625
+ case "i32":
1626
+ return parseInt(value, 10);
1627
+ }
1628
+ }
1629
+ return value;
1630
+ }
1631
+ case "compact": {
1632
+ if (typeof value === "string") {
1633
+ return resolved.isBig ? BigInt(value) : parseInt(value, 10);
1634
+ }
1452
1635
  return value;
1453
1636
  }
1454
1637
  default:
@@ -1482,7 +1665,7 @@ function parseTypedArg(meta, entry, arg) {
1482
1665
  }
1483
1666
  const variants = Object.keys(entry.value);
1484
1667
  if (variants.includes("Id")) {
1485
- const idVariant = entry.value["Id"];
1668
+ const idVariant = entry.value.Id;
1486
1669
  const innerType = idVariant.type === "lookupEntry" ? idVariant.value : idVariant;
1487
1670
  if (innerType.type === "AccountId32" && !arg.startsWith("{")) {
1488
1671
  return { type: "Id", value: arg };
@@ -1649,7 +1832,7 @@ function watchTransaction(observable) {
1649
1832
  }
1650
1833
  break;
1651
1834
  case "finalized":
1652
- spinner.stop();
1835
+ spinner.succeed(`Finalized in block #${event.block.number}`);
1653
1836
  resolve(event);
1654
1837
  break;
1655
1838
  }
@@ -1662,133 +1845,6 @@ function watchTransaction(observable) {
1662
1845
  });
1663
1846
  }
1664
1847
 
1665
- // src/core/hash.ts
1666
- import { blake2b } from "@noble/hashes/blake2.js";
1667
- import { keccak_256 } from "@noble/hashes/sha3.js";
1668
- import { sha256 } from "@noble/hashes/sha2.js";
1669
- import { bytesToHex, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
1670
- var ALGORITHMS = {
1671
- blake2b256: {
1672
- compute: (data) => blake2b(data, { dkLen: 32 }),
1673
- outputLen: 32,
1674
- description: "BLAKE2b with 256-bit output"
1675
- },
1676
- blake2b128: {
1677
- compute: (data) => blake2b(data, { dkLen: 16 }),
1678
- outputLen: 16,
1679
- description: "BLAKE2b with 128-bit output"
1680
- },
1681
- keccak256: {
1682
- compute: (data) => keccak_256(data),
1683
- outputLen: 32,
1684
- description: "Keccak-256 (Ethereum-compatible)"
1685
- },
1686
- sha256: {
1687
- compute: (data) => sha256(data),
1688
- outputLen: 32,
1689
- description: "SHA-256"
1690
- }
1691
- };
1692
- function computeHash(algorithm, data) {
1693
- const algo = ALGORITHMS[algorithm];
1694
- if (!algo) {
1695
- throw new Error(`Unknown algorithm: ${algorithm}`);
1696
- }
1697
- return algo.compute(data);
1698
- }
1699
- function parseInputData(input) {
1700
- if (input.startsWith("0x")) {
1701
- const hex = input.slice(2);
1702
- if (hex.length % 2 !== 0) {
1703
- throw new Error(`Invalid hex input: odd number of characters`);
1704
- }
1705
- return hexToBytes2(hex);
1706
- }
1707
- return new TextEncoder().encode(input);
1708
- }
1709
- function toHex(bytes) {
1710
- return "0x" + bytesToHex(bytes);
1711
- }
1712
- function isValidAlgorithm(name) {
1713
- return name in ALGORITHMS;
1714
- }
1715
- function getAlgorithmNames() {
1716
- return Object.keys(ALGORITHMS);
1717
- }
1718
-
1719
- // src/commands/hash.ts
1720
- async function resolveInput(data, opts) {
1721
- const sources = [data !== undefined, !!opts.file, !!opts.stdin].filter(Boolean).length;
1722
- if (sources > 1) {
1723
- throw new CliError("Provide only one of: inline data, --file, or --stdin");
1724
- }
1725
- if (sources === 0) {
1726
- throw new CliError("No input provided. Pass data as argument, or use --file or --stdin");
1727
- }
1728
- if (opts.file) {
1729
- const buf = await Bun.file(opts.file).arrayBuffer();
1730
- return new Uint8Array(buf);
1731
- }
1732
- if (opts.stdin) {
1733
- const reader = Bun.stdin.stream().getReader();
1734
- const chunks = [];
1735
- while (true) {
1736
- const { done, value } = await reader.read();
1737
- if (done)
1738
- break;
1739
- chunks.push(value);
1740
- }
1741
- const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
1742
- const result = new Uint8Array(totalLen);
1743
- let offset = 0;
1744
- for (const chunk of chunks) {
1745
- result.set(chunk, offset);
1746
- offset += chunk.length;
1747
- }
1748
- return result;
1749
- }
1750
- return parseInputData(data);
1751
- }
1752
- function printAlgorithmHelp() {
1753
- console.log(`${BOLD}Usage:${RESET} dot hash <algorithm> <data> [options]
1754
- `);
1755
- console.log(`${BOLD}Algorithms:${RESET}`);
1756
- for (const [name, algo] of Object.entries(ALGORITHMS)) {
1757
- console.log(` ${CYAN}${name}${RESET} ${DIM}${algo.description} (${algo.outputLen} bytes)${RESET}`);
1758
- }
1759
- console.log(`
1760
- ${BOLD}Options:${RESET}`);
1761
- console.log(` ${CYAN}--file <path>${RESET} ${DIM}Hash file contents${RESET}`);
1762
- console.log(` ${CYAN}--stdin${RESET} ${DIM}Read from stdin${RESET}`);
1763
- console.log(` ${CYAN}--output json${RESET} ${DIM}Output as JSON${RESET}`);
1764
- console.log(`
1765
- ${BOLD}Examples:${RESET}`);
1766
- console.log(` ${DIM}$ dot hash blake2b256 0xdeadbeef${RESET}`);
1767
- console.log(` ${DIM}$ dot hash sha256 hello${RESET}`);
1768
- console.log(` ${DIM}$ dot hash keccak256 --file ./data.bin${RESET}`);
1769
- console.log(` ${DIM}$ echo -n "hello" | dot hash sha256 --stdin${RESET}`);
1770
- }
1771
- function registerHashCommand(cli) {
1772
- cli.command("hash [algorithm] [data]", "Compute cryptographic hashes").option("--file <path>", "Hash file contents (raw bytes)").option("--stdin", "Read data from stdin").action(async (algorithm, data, opts) => {
1773
- if (!algorithm) {
1774
- printAlgorithmHelp();
1775
- return;
1776
- }
1777
- if (!isValidAlgorithm(algorithm)) {
1778
- throw new CliError(suggestMessage("algorithm", algorithm, getAlgorithmNames()));
1779
- }
1780
- const input = await resolveInput(data, opts);
1781
- const hash = computeHash(algorithm, input);
1782
- const hexHash = toHex(hash);
1783
- const format = opts.output ?? "pretty";
1784
- if (format === "json") {
1785
- printResult({ algorithm, input: data ?? (opts.file ? `file:${opts.file}` : "stdin"), hash: hexHash }, "json");
1786
- } else {
1787
- console.log(hexHash);
1788
- }
1789
- });
1790
- }
1791
-
1792
1848
  // src/utils/errors.ts
1793
1849
  class CliError2 extends Error {
1794
1850
  constructor(message) {
@@ -1796,8 +1852,6 @@ class CliError2 extends Error {
1796
1852
  this.name = "CliError";
1797
1853
  }
1798
1854
  }
1799
- // package.json
1800
- var version = "0.6.0";
1801
1855
 
1802
1856
  // src/cli.ts
1803
1857
  var cli = cac("dot");