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.
- package/README.md +25 -5
- package/dist/cli.mjs +975 -921
- 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 {
|
|
15
|
+
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
10
16
|
import { homedir } from "node:os";
|
|
11
|
-
import {
|
|
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/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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/
|
|
123
|
+
// src/core/accounts.ts
|
|
124
|
+
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
|
|
174
125
|
import {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return
|
|
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
|
-
|
|
204
|
-
|
|
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
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
250
|
-
|
|
251
|
-
|
|
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
|
|
259
|
-
const
|
|
260
|
-
return
|
|
160
|
+
function getDevAddress(name, prefix = 42) {
|
|
161
|
+
const keypair = getDevKeypair(name);
|
|
162
|
+
return ss58Address(keypair.publicKey, prefix);
|
|
261
163
|
}
|
|
262
|
-
function
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
270
|
-
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
409
|
-
var
|
|
302
|
+
// src/commands/account.ts
|
|
303
|
+
var ACCOUNT_HELP = `
|
|
410
304
|
${BOLD}Usage:${RESET}
|
|
411
|
-
$ dot
|
|
412
|
-
$ dot
|
|
413
|
-
$ dot
|
|
414
|
-
$ dot
|
|
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
|
|
420
|
-
$ dot
|
|
421
|
-
$ dot
|
|
422
|
-
$ dot
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
|
428
|
-
cli.command("
|
|
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(
|
|
322
|
+
console.log(ACCOUNT_HELP);
|
|
431
323
|
return;
|
|
432
324
|
}
|
|
433
325
|
switch (action) {
|
|
434
|
-
case "
|
|
435
|
-
return
|
|
436
|
-
case "
|
|
437
|
-
return
|
|
326
|
+
case "create":
|
|
327
|
+
return accountCreate(name);
|
|
328
|
+
case "import":
|
|
329
|
+
return accountImport(name, opts);
|
|
438
330
|
case "list":
|
|
439
|
-
return
|
|
440
|
-
case "
|
|
441
|
-
return
|
|
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(
|
|
337
|
+
console.log(ACCOUNT_HELP);
|
|
448
338
|
process.exit(1);
|
|
449
339
|
}
|
|
450
340
|
});
|
|
451
341
|
}
|
|
452
|
-
async function
|
|
342
|
+
async function accountCreate(name) {
|
|
453
343
|
if (!name) {
|
|
454
|
-
console.error(`
|
|
344
|
+
console.error(`Account name is required.
|
|
455
345
|
`);
|
|
456
|
-
console.error("Usage: dot
|
|
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 (
|
|
461
|
-
|
|
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
|
|
468
|
-
|
|
469
|
-
|
|
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
|
|
374
|
+
async function accountImport(name, opts) {
|
|
485
375
|
if (!name) {
|
|
486
|
-
console.error(
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
497
|
-
|
|
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
|
|
502
|
-
|
|
503
|
-
|
|
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
|
|
517
|
-
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
|
429
|
+
async function accountRemove(name) {
|
|
530
430
|
if (!name) {
|
|
531
|
-
console.error(
|
|
431
|
+
console.error(`Account name is required.
|
|
432
|
+
`);
|
|
433
|
+
console.error("Usage: dot account remove <name>");
|
|
532
434
|
process.exit(1);
|
|
533
435
|
}
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
444
|
+
accountsFile.accounts.splice(idx, 1);
|
|
445
|
+
await saveAccounts(accountsFile);
|
|
446
|
+
console.log(`Account "${name}" removed.`);
|
|
551
447
|
}
|
|
552
448
|
|
|
553
|
-
// src/
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
461
|
+
|
|
462
|
+
class ConnectionError extends CliError {
|
|
463
|
+
constructor(message) {
|
|
464
|
+
super(message);
|
|
465
|
+
this.name = "ConnectionError";
|
|
582
466
|
}
|
|
583
|
-
|
|
584
|
-
|
|
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/
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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
|
-
|
|
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/
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
|
546
|
+
async function fetchMetadataFromChain(clientHandle, chainName) {
|
|
547
|
+
const { client } = clientHandle;
|
|
815
548
|
try {
|
|
816
|
-
await
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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
|
|
823
|
-
await
|
|
824
|
-
if (
|
|
825
|
-
|
|
826
|
-
|
|
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
|
|
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
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
|
880
|
-
|
|
881
|
-
|
|
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
|
|
884
|
-
const
|
|
885
|
-
return
|
|
617
|
+
function findPallet(meta, palletName) {
|
|
618
|
+
const pallets = listPallets(meta);
|
|
619
|
+
return pallets.find((p) => p.name.toLowerCase() === palletName.toLowerCase());
|
|
886
620
|
}
|
|
887
|
-
function
|
|
888
|
-
const
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
|
896
|
-
|
|
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
|
|
908
|
-
|
|
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
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
-
|
|
667
|
+
default:
|
|
668
|
+
return "unknown";
|
|
918
669
|
}
|
|
919
|
-
return ss58Address(publicKey, prefix);
|
|
920
670
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
-
|
|
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/
|
|
941
|
-
var
|
|
680
|
+
// src/commands/chain.ts
|
|
681
|
+
var CHAIN_HELP = `
|
|
942
682
|
${BOLD}Usage:${RESET}
|
|
943
|
-
$ dot
|
|
944
|
-
$ dot
|
|
945
|
-
$ dot
|
|
946
|
-
$ dot
|
|
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
|
|
950
|
-
$ dot
|
|
951
|
-
$ dot
|
|
952
|
-
$ dot
|
|
953
|
-
$ dot
|
|
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
|
|
956
|
-
cli.command("
|
|
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(
|
|
702
|
+
console.log(CHAIN_HELP);
|
|
959
703
|
return;
|
|
960
704
|
}
|
|
961
705
|
switch (action) {
|
|
962
|
-
case "
|
|
963
|
-
return
|
|
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
|
|
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(
|
|
719
|
+
console.log(CHAIN_HELP);
|
|
974
720
|
process.exit(1);
|
|
975
721
|
}
|
|
976
722
|
});
|
|
977
723
|
}
|
|
978
|
-
async function
|
|
724
|
+
async function chainAdd(name, opts) {
|
|
979
725
|
if (!name) {
|
|
980
|
-
console.error(`
|
|
726
|
+
console.error(`Chain name is required.
|
|
981
727
|
`);
|
|
982
|
-
console.error("Usage: dot
|
|
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 (
|
|
986
|
-
|
|
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
|
|
989
|
-
|
|
990
|
-
|
|
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
|
|
756
|
+
async function chainRemove(name) {
|
|
1011
757
|
if (!name) {
|
|
1012
|
-
console.error(
|
|
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
|
-
|
|
1018
|
-
|
|
1019
|
-
`);
|
|
1020
|
-
|
|
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
|
-
|
|
1024
|
-
|
|
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
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1324
|
+
let dispatchErrorMsg;
|
|
1202
1325
|
if (result.ok) {
|
|
1203
1326
|
console.log(` ${BOLD}Status:${RESET} ${GREEN}ok${RESET}`);
|
|
1204
1327
|
} else {
|
|
1205
|
-
|
|
1206
|
-
console.log(` ${BOLD}
|
|
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
|
|
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.
|
|
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");
|