polkadot-cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -1
- package/dist/cli.mjs +262 -25
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# polkadot-cli
|
|
2
2
|
|
|
3
|
-
A command-line tool for interacting with Polkadot-ecosystem chains. Manage chains and accounts, query storage, look up constants, inspect metadata,
|
|
3
|
+
A command-line tool for interacting with Polkadot-ecosystem chains. Manage chains and accounts, query storage, look up constants, inspect metadata, submit extrinsics, and compute hashes — all from your terminal.
|
|
4
4
|
|
|
5
5
|
Ships with Polkadot as the default chain. Add any Substrate-based chain by pointing to its RPC endpoint.
|
|
6
6
|
|
|
@@ -115,6 +115,21 @@ dot tx 0x0503008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48
|
|
|
115
115
|
dot tx Utility.batchAll '[{"type":"Balances","value":{"type":"transfer_keep_alive","value":{"dest":"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty","value":1000000000000}}},{"type":"Balances","value":{"type":"transfer_keep_alive","value":{"dest":"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y","value":2000000000000}}}]' --from alice
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
#### Encode call data
|
|
119
|
+
|
|
120
|
+
Encode a call to hex without signing or submitting. Useful for preparing calls to pass to `Sudo.sudo`, multisig proposals, or governance. Works offline from cached metadata and does not require `--from`.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Encode a remark call
|
|
124
|
+
dot tx System.remark 0xdeadbeef --encode
|
|
125
|
+
|
|
126
|
+
# Encode a transfer (use the hex output in a batch or sudo call)
|
|
127
|
+
dot tx Balances.transfer_keep_alive 5FHneW46... 1000000000000 --encode
|
|
128
|
+
|
|
129
|
+
# Use encoded output with Sudo.sudo
|
|
130
|
+
dot tx Sudo.sudo $(dot tx System.remark 0xcafe --encode) --from alice
|
|
131
|
+
```
|
|
132
|
+
|
|
118
133
|
Both dry-run and submission display the encoded call hex and a decoded human-readable form:
|
|
119
134
|
|
|
120
135
|
```
|
|
@@ -139,6 +154,29 @@ For manual override, use `--ext` with a JSON object:
|
|
|
139
154
|
dot tx System.remark 0xdeadbeef --from alice --ext '{"MyExtension":{"value":"..."}}'
|
|
140
155
|
```
|
|
141
156
|
|
|
157
|
+
### Compute hashes
|
|
158
|
+
|
|
159
|
+
Compute cryptographic hashes commonly used in Substrate. Supports BLAKE2b-256, BLAKE2b-128, Keccak-256, and SHA-256.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Hash hex-encoded data
|
|
163
|
+
dot hash blake2b256 0xdeadbeef
|
|
164
|
+
|
|
165
|
+
# Hash plain text (UTF-8 encoded)
|
|
166
|
+
dot hash sha256 hello
|
|
167
|
+
|
|
168
|
+
# Hash file contents
|
|
169
|
+
dot hash keccak256 --file ./data.bin
|
|
170
|
+
|
|
171
|
+
# Read from stdin
|
|
172
|
+
echo -n "hello" | dot hash sha256 --stdin
|
|
173
|
+
|
|
174
|
+
# JSON output
|
|
175
|
+
dot hash blake2b256 0xdeadbeef --output json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Run `dot hash` with no arguments to see all available algorithms.
|
|
179
|
+
|
|
142
180
|
### Global options
|
|
143
181
|
|
|
144
182
|
| Flag | Description |
|
|
@@ -149,6 +187,21 @@ dot tx System.remark 0xdeadbeef --from alice --ext '{"MyExtension":{"value":"...
|
|
|
149
187
|
| `--output json` | Raw JSON output (default: pretty) |
|
|
150
188
|
| `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
|
|
151
189
|
|
|
190
|
+
## How it compares
|
|
191
|
+
|
|
192
|
+
| | polkadot-cli | @polkadot/api-cli | subxt-cli | Pop CLI |
|
|
193
|
+
|---|---|---|---|---|
|
|
194
|
+
| **Query storage** | SS58 keys, map iteration | yes (full `--ws` URL required) | yes (keys as SCALE tuples, no SS58) | — |
|
|
195
|
+
| **Read constants** | yes | yes | yes | — |
|
|
196
|
+
| **Submit extrinsics** | yes, with dry-run | yes (via `--seed`) | — | ink! contract calls only |
|
|
197
|
+
| **Inspect metadata** | yes | — | yes (excellent browser) | — |
|
|
198
|
+
| **Chain presets** | built-in aliases (`--chain kusama`) | — (manual `--ws` every call) | — | parachain templates |
|
|
199
|
+
| **Tx tracking + explorer links** | spinner progress, block + explorer link | basic events | — | — |
|
|
200
|
+
|
|
201
|
+
polkadot-cli aims to be the single tool for day-to-day chain interaction: storage reads, constant lookups, transaction submission, and metadata browsing with a polished terminal UX. @polkadot/api-cli covers similar ground but is in maintenance mode and requires verbose flags. subxt-cli has an excellent metadata explorer but cannot sign or submit transactions. Pop CLI targets a different workflow — scaffolding parachains and deploying ink! contracts rather than end-user chain queries.
|
|
202
|
+
|
|
203
|
+
Outside Polkadot, the closest comparable in terms of interactive UX is [near-cli-rs](https://github.com/near/near-cli-rs) (NEAR).
|
|
204
|
+
|
|
152
205
|
## Configuration
|
|
153
206
|
|
|
154
207
|
Config and metadata caches live in `~/.polkadot/`:
|
package/dist/cli.mjs
CHANGED
|
@@ -1092,27 +1092,54 @@ var papiLink = (rpc, hash) => `https://dev.papi.how/explorer/${hash}#networkId=c
|
|
|
1092
1092
|
|
|
1093
1093
|
// src/commands/tx.ts
|
|
1094
1094
|
function registerTxCommand(cli) {
|
|
1095
|
-
cli.command("tx [target] [...args]", "Submit an extrinsic (e.g. Balances.transferKeepAlive <dest> <amount>)").option("--from <name>", "Account to sign with (required)").option("--dry-run", "Estimate fees without submitting").option("--ext <json>", `Custom signed extension values as JSON, e.g. '{"ExtName":{"value":...}}'`).action(async (target, args, opts) => {
|
|
1096
|
-
if (!target
|
|
1097
|
-
console.log("Usage: dot tx <Pallet.Call|0xCallHex> [...args] --from <account> [--dry-run]");
|
|
1095
|
+
cli.command("tx [target] [...args]", "Submit an extrinsic (e.g. Balances.transferKeepAlive <dest> <amount>)").option("--from <name>", "Account to sign with (required)").option("--dry-run", "Estimate fees without submitting").option("--encode", "Encode call to hex without signing or submitting").option("--ext <json>", `Custom signed extension values as JSON, e.g. '{"ExtName":{"value":...}}'`).action(async (target, args, opts) => {
|
|
1096
|
+
if (!target) {
|
|
1097
|
+
console.log("Usage: dot tx <Pallet.Call|0xCallHex> [...args] --from <account> [--dry-run] [--encode]");
|
|
1098
1098
|
console.log("");
|
|
1099
1099
|
console.log("Examples:");
|
|
1100
1100
|
console.log(" $ dot tx Balances.transferKeepAlive 5FHn... 1000000000000 --from alice");
|
|
1101
1101
|
console.log(" $ dot tx System.remark 0xdeadbeef --from alice --dry-run");
|
|
1102
1102
|
console.log(" $ dot tx 0x0001076465616462656566 --from alice");
|
|
1103
|
+
console.log(" $ dot tx Assets.force_create 4 owner true 10 --encode --chain people");
|
|
1103
1104
|
return;
|
|
1104
1105
|
}
|
|
1106
|
+
if (!opts.from && !opts.encode) {
|
|
1107
|
+
throw new Error("--from is required (or use --encode to output hex without signing)");
|
|
1108
|
+
}
|
|
1109
|
+
if (opts.encode && opts.dryRun) {
|
|
1110
|
+
throw new Error("--encode and --dry-run are mutually exclusive");
|
|
1111
|
+
}
|
|
1105
1112
|
const isRawCall = /^0x[0-9a-fA-F]+$/.test(target);
|
|
1113
|
+
if (opts.encode && isRawCall) {
|
|
1114
|
+
throw new Error("--encode cannot be used with raw call hex (already encoded)");
|
|
1115
|
+
}
|
|
1106
1116
|
const config = await loadConfig();
|
|
1107
1117
|
const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
|
|
1108
|
-
const signer = await resolveAccountSigner(opts.from);
|
|
1109
|
-
|
|
1118
|
+
const signer = opts.encode ? undefined : await resolveAccountSigner(opts.from);
|
|
1119
|
+
let clientHandle;
|
|
1120
|
+
if (!opts.encode) {
|
|
1121
|
+
clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
1122
|
+
}
|
|
1110
1123
|
try {
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1124
|
+
let meta;
|
|
1125
|
+
if (clientHandle) {
|
|
1126
|
+
meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
1127
|
+
} else {
|
|
1128
|
+
try {
|
|
1129
|
+
meta = await getOrFetchMetadata(chainName);
|
|
1130
|
+
} catch {
|
|
1131
|
+
clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
1132
|
+
meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
let unsafeApi;
|
|
1136
|
+
let txOptions;
|
|
1137
|
+
if (!opts.encode) {
|
|
1138
|
+
const userExtOverrides = parseExtOption(opts.ext);
|
|
1139
|
+
const customSignedExtensions = buildCustomSignedExtensions(meta, userExtOverrides);
|
|
1140
|
+
txOptions = Object.keys(customSignedExtensions).length > 0 ? { customSignedExtensions } : undefined;
|
|
1141
|
+
unsafeApi = clientHandle.client.getUnsafeApi();
|
|
1142
|
+
}
|
|
1116
1143
|
let tx;
|
|
1117
1144
|
let callHex;
|
|
1118
1145
|
if (isRawCall) {
|
|
@@ -1136,6 +1163,17 @@ function registerTxCommand(cli) {
|
|
|
1136
1163
|
throw new Error(suggestMessage(`call in ${palletInfo.name}`, callName, callNames));
|
|
1137
1164
|
}
|
|
1138
1165
|
const callData = parseCallArgs(meta, palletInfo.name, callInfo.name, args);
|
|
1166
|
+
if (opts.encode) {
|
|
1167
|
+
const { codec, location } = meta.builder.buildCall(palletInfo.name, callInfo.name);
|
|
1168
|
+
const encodedArgs = codec.enc(callData);
|
|
1169
|
+
const fullCall = new Uint8Array([
|
|
1170
|
+
location[0],
|
|
1171
|
+
location[1],
|
|
1172
|
+
...encodedArgs
|
|
1173
|
+
]);
|
|
1174
|
+
console.log(Binary.fromBytes(fullCall).asHex());
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1139
1177
|
tx = unsafeApi.tx[palletInfo.name][callInfo.name](callData);
|
|
1140
1178
|
const encodedCall = await tx.getEncodedData();
|
|
1141
1179
|
callHex = encodedCall.asHex();
|
|
@@ -1189,7 +1227,7 @@ function registerTxCommand(cli) {
|
|
|
1189
1227
|
}
|
|
1190
1228
|
console.log();
|
|
1191
1229
|
} finally {
|
|
1192
|
-
clientHandle
|
|
1230
|
+
clientHandle?.destroy();
|
|
1193
1231
|
}
|
|
1194
1232
|
});
|
|
1195
1233
|
}
|
|
@@ -1312,33 +1350,33 @@ function parseCallArgs(meta, palletName, callName, args) {
|
|
|
1312
1350
|
return;
|
|
1313
1351
|
}
|
|
1314
1352
|
if (variant.type === "struct") {
|
|
1315
|
-
return parseStructArgs(meta
|
|
1353
|
+
return parseStructArgs(meta, variant.value, args, `${palletName}.${callName}`);
|
|
1316
1354
|
}
|
|
1317
1355
|
if (variant.type === "lookupEntry") {
|
|
1318
1356
|
const inner = variant.value;
|
|
1319
1357
|
if (inner.type === "struct") {
|
|
1320
|
-
return parseStructArgs(meta
|
|
1358
|
+
return parseStructArgs(meta, inner.value, args, `${palletName}.${callName}`);
|
|
1321
1359
|
}
|
|
1322
1360
|
if (inner.type === "void")
|
|
1323
1361
|
return;
|
|
1324
1362
|
if (args.length !== 1) {
|
|
1325
1363
|
throw new Error(`${palletName}.${callName} takes 1 argument (${describeType(meta.lookup, inner.id)}), but ${args.length} provided.`);
|
|
1326
1364
|
}
|
|
1327
|
-
return parseTypedArg(meta
|
|
1365
|
+
return parseTypedArg(meta, inner, args[0]);
|
|
1328
1366
|
}
|
|
1329
1367
|
if (variant.type === "tuple") {
|
|
1330
1368
|
const entries = variant.value;
|
|
1331
1369
|
if (args.length !== entries.length) {
|
|
1332
1370
|
throw new Error(`${palletName}.${callName} takes ${entries.length} arguments, but ${args.length} provided.`);
|
|
1333
1371
|
}
|
|
1334
|
-
return entries.map((entry, i) => parseTypedArg(meta
|
|
1372
|
+
return entries.map((entry, i) => parseTypedArg(meta, entry, args[i]));
|
|
1335
1373
|
}
|
|
1336
1374
|
return args.length === 0 ? undefined : args.map(parseValue);
|
|
1337
1375
|
}
|
|
1338
|
-
function parseStructArgs(
|
|
1376
|
+
function parseStructArgs(meta, fields, args, callLabel) {
|
|
1339
1377
|
const fieldNames = Object.keys(fields);
|
|
1340
1378
|
if (args.length !== fieldNames.length) {
|
|
1341
|
-
const expected = fieldNames.map((name) => `${name}: ${describeType(lookup, fields[name].id)}`).join(", ");
|
|
1379
|
+
const expected = fieldNames.map((name) => `${name}: ${describeType(meta.lookup, fields[name].id)}`).join(", ");
|
|
1342
1380
|
throw new Error(`${callLabel} takes ${fieldNames.length} argument(s): ${expected}
|
|
1343
1381
|
` + ` Got ${args.length} argument(s).`);
|
|
1344
1382
|
}
|
|
@@ -1346,11 +1384,78 @@ function parseStructArgs(lookup, fields, args, callLabel) {
|
|
|
1346
1384
|
for (let i = 0;i < fieldNames.length; i++) {
|
|
1347
1385
|
const name = fieldNames[i];
|
|
1348
1386
|
const entry = fields[name];
|
|
1349
|
-
result[name] = parseTypedArg(
|
|
1387
|
+
result[name] = parseTypedArg(meta, entry, args[i]);
|
|
1350
1388
|
}
|
|
1351
1389
|
return result;
|
|
1352
1390
|
}
|
|
1353
|
-
function
|
|
1391
|
+
function normalizeValue(lookup, entry, value) {
|
|
1392
|
+
let resolved = entry;
|
|
1393
|
+
while (resolved.type === "lookupEntry") {
|
|
1394
|
+
resolved = resolved.value;
|
|
1395
|
+
}
|
|
1396
|
+
switch (resolved.type) {
|
|
1397
|
+
case "enum": {
|
|
1398
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && "type" in value) {
|
|
1399
|
+
const enumValue = value;
|
|
1400
|
+
const variant = resolved.value[enumValue.type];
|
|
1401
|
+
if (variant) {
|
|
1402
|
+
let innerEntry = variant;
|
|
1403
|
+
while (innerEntry.type === "lookupEntry") {
|
|
1404
|
+
innerEntry = innerEntry.value;
|
|
1405
|
+
}
|
|
1406
|
+
let normalizedInner = enumValue.value;
|
|
1407
|
+
if (innerEntry.type !== "array" && innerEntry.type !== "sequence" && innerEntry.type !== "void" && Array.isArray(normalizedInner) && normalizedInner.length === 1) {
|
|
1408
|
+
normalizedInner = normalizedInner[0];
|
|
1409
|
+
}
|
|
1410
|
+
if (normalizedInner !== undefined && innerEntry.type !== "void") {
|
|
1411
|
+
normalizedInner = normalizeValue(lookup, innerEntry, normalizedInner);
|
|
1412
|
+
}
|
|
1413
|
+
return { type: enumValue.type, value: normalizedInner };
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return value;
|
|
1417
|
+
}
|
|
1418
|
+
case "struct": {
|
|
1419
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1420
|
+
const fields = resolved.value;
|
|
1421
|
+
const result = {};
|
|
1422
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1423
|
+
if (key in fields) {
|
|
1424
|
+
result[key] = normalizeValue(lookup, fields[key], val);
|
|
1425
|
+
} else {
|
|
1426
|
+
result[key] = val;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return result;
|
|
1430
|
+
}
|
|
1431
|
+
return value;
|
|
1432
|
+
}
|
|
1433
|
+
case "array":
|
|
1434
|
+
case "sequence": {
|
|
1435
|
+
if (Array.isArray(value)) {
|
|
1436
|
+
const innerEntry = resolved.value;
|
|
1437
|
+
return value.map((item) => normalizeValue(lookup, innerEntry, item));
|
|
1438
|
+
}
|
|
1439
|
+
return value;
|
|
1440
|
+
}
|
|
1441
|
+
case "tuple": {
|
|
1442
|
+
if (Array.isArray(value)) {
|
|
1443
|
+
const entries = resolved.value;
|
|
1444
|
+
return value.map((item, i) => i < entries.length ? normalizeValue(lookup, entries[i], item) : item);
|
|
1445
|
+
}
|
|
1446
|
+
return value;
|
|
1447
|
+
}
|
|
1448
|
+
case "option": {
|
|
1449
|
+
if (value !== null && value !== undefined) {
|
|
1450
|
+
return normalizeValue(lookup, resolved.value, value);
|
|
1451
|
+
}
|
|
1452
|
+
return value;
|
|
1453
|
+
}
|
|
1454
|
+
default:
|
|
1455
|
+
return value;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
function parseTypedArg(meta, entry, arg) {
|
|
1354
1459
|
switch (entry.type) {
|
|
1355
1460
|
case "primitive":
|
|
1356
1461
|
return parsePrimitive(entry.value, arg);
|
|
@@ -1363,12 +1468,16 @@ function parseTypedArg(lookup, entry, arg) {
|
|
|
1363
1468
|
if (arg === "null" || arg === "undefined" || arg === "none") {
|
|
1364
1469
|
return;
|
|
1365
1470
|
}
|
|
1366
|
-
return parseTypedArg(
|
|
1471
|
+
return parseTypedArg(meta, entry.value, arg);
|
|
1367
1472
|
}
|
|
1368
1473
|
case "enum": {
|
|
1474
|
+
if (/^0x[0-9a-fA-F]+$/.test(arg) && meta.lookup.call != null && entry.id === meta.lookup.call) {
|
|
1475
|
+
const callCodec = meta.builder.buildDefinition(meta.lookup.call);
|
|
1476
|
+
return callCodec.dec(Binary.fromHex(arg).asBytes());
|
|
1477
|
+
}
|
|
1369
1478
|
if (arg.startsWith("{")) {
|
|
1370
1479
|
try {
|
|
1371
|
-
return JSON.parse(arg);
|
|
1480
|
+
return normalizeValue(meta.lookup, entry, JSON.parse(arg));
|
|
1372
1481
|
} catch {}
|
|
1373
1482
|
}
|
|
1374
1483
|
const variants = Object.keys(entry.value);
|
|
@@ -1398,7 +1507,7 @@ function parseTypedArg(lookup, entry, arg) {
|
|
|
1398
1507
|
}
|
|
1399
1508
|
if (arg.startsWith("[")) {
|
|
1400
1509
|
try {
|
|
1401
|
-
return JSON.parse(arg);
|
|
1510
|
+
return normalizeValue(meta.lookup, entry, JSON.parse(arg));
|
|
1402
1511
|
} catch {}
|
|
1403
1512
|
}
|
|
1404
1513
|
if (/^0x[0-9a-fA-F]*$/.test(arg))
|
|
@@ -1408,14 +1517,14 @@ function parseTypedArg(lookup, entry, arg) {
|
|
|
1408
1517
|
case "struct":
|
|
1409
1518
|
if (arg.startsWith("{")) {
|
|
1410
1519
|
try {
|
|
1411
|
-
return JSON.parse(arg);
|
|
1520
|
+
return normalizeValue(meta.lookup, entry, JSON.parse(arg));
|
|
1412
1521
|
} catch {}
|
|
1413
1522
|
}
|
|
1414
1523
|
return parseValue(arg);
|
|
1415
1524
|
case "tuple":
|
|
1416
1525
|
if (arg.startsWith("[")) {
|
|
1417
1526
|
try {
|
|
1418
|
-
return JSON.parse(arg);
|
|
1527
|
+
return normalizeValue(meta.lookup, entry, JSON.parse(arg));
|
|
1419
1528
|
} catch {}
|
|
1420
1529
|
}
|
|
1421
1530
|
return parseValue(arg);
|
|
@@ -1553,6 +1662,133 @@ function watchTransaction(observable) {
|
|
|
1553
1662
|
});
|
|
1554
1663
|
}
|
|
1555
1664
|
|
|
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
|
+
|
|
1556
1792
|
// src/utils/errors.ts
|
|
1557
1793
|
class CliError2 extends Error {
|
|
1558
1794
|
constructor(message) {
|
|
@@ -1561,7 +1797,7 @@ class CliError2 extends Error {
|
|
|
1561
1797
|
}
|
|
1562
1798
|
}
|
|
1563
1799
|
// package.json
|
|
1564
|
-
var version = "0.
|
|
1800
|
+
var version = "0.6.0";
|
|
1565
1801
|
|
|
1566
1802
|
// src/cli.ts
|
|
1567
1803
|
var cli = cac("dot");
|
|
@@ -1577,6 +1813,7 @@ registerQueryCommand(cli);
|
|
|
1577
1813
|
registerConstCommand(cli);
|
|
1578
1814
|
registerAccountCommands(cli);
|
|
1579
1815
|
registerTxCommand(cli);
|
|
1816
|
+
registerHashCommand(cli);
|
|
1580
1817
|
cli.help();
|
|
1581
1818
|
cli.version(version);
|
|
1582
1819
|
function handleError(err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polkadot-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "CLI tool for querying Polkadot-ecosystem on-chain state",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"url": "git+https://github.com/peetzweg/polkadot-cli.git"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@noble/hashes": "^2.0.1",
|
|
35
36
|
"@polkadot-api/metadata-builders": "^0.13.9",
|
|
36
37
|
"@polkadot-api/substrate-bindings": "^0.17.0",
|
|
37
38
|
"@polkadot-api/view-builder": "^0.4.17",
|