polkadot-cli 0.2.0 → 0.4.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/dist/cli.mjs +224 -21
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -275,6 +275,13 @@ function findPallet(meta, palletName) {
|
|
|
275
275
|
const pallets = listPallets(meta);
|
|
276
276
|
return pallets.find((p) => p.name.toLowerCase() === palletName.toLowerCase());
|
|
277
277
|
}
|
|
278
|
+
function getSignedExtensions(meta) {
|
|
279
|
+
const byVersion = meta.unified.extrinsic.signedExtensions;
|
|
280
|
+
const versionKeys = Object.keys(byVersion);
|
|
281
|
+
if (versionKeys.length === 0)
|
|
282
|
+
return [];
|
|
283
|
+
return byVersion[Number(versionKeys[0])] ?? [];
|
|
284
|
+
}
|
|
278
285
|
function getPalletNames(meta) {
|
|
279
286
|
return meta.unified.pallets.map((p) => p.name);
|
|
280
287
|
}
|
|
@@ -1060,41 +1067,65 @@ async function accountRemove(name) {
|
|
|
1060
1067
|
}
|
|
1061
1068
|
|
|
1062
1069
|
// src/commands/tx.ts
|
|
1070
|
+
import { Binary } from "polkadot-api";
|
|
1071
|
+
import { getViewBuilder } from "@polkadot-api/view-builder";
|
|
1063
1072
|
function registerTxCommand(cli) {
|
|
1064
|
-
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").action(async (target, args, opts) => {
|
|
1073
|
+
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) => {
|
|
1065
1074
|
if (!target || !opts.from) {
|
|
1066
|
-
console.log("Usage: dot tx <Pallet.Call> [...args] --from <account> [--dry-run]");
|
|
1075
|
+
console.log("Usage: dot tx <Pallet.Call|0xCallHex> [...args] --from <account> [--dry-run]");
|
|
1067
1076
|
console.log("");
|
|
1068
1077
|
console.log("Examples:");
|
|
1069
1078
|
console.log(" $ dot tx Balances.transferKeepAlive 5FHn... 1000000000000 --from alice");
|
|
1070
1079
|
console.log(" $ dot tx System.remark 0xdeadbeef --from alice --dry-run");
|
|
1080
|
+
console.log(" $ dot tx 0x0001076465616462656566 --from alice");
|
|
1071
1081
|
return;
|
|
1072
1082
|
}
|
|
1083
|
+
const isRawCall = /^0x[0-9a-fA-F]+$/.test(target);
|
|
1073
1084
|
const config = await loadConfig();
|
|
1074
1085
|
const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
|
|
1075
|
-
const { pallet, item: callName } = parseTarget(target);
|
|
1076
1086
|
const signer = await resolveAccountSigner(opts.from);
|
|
1077
1087
|
const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
1078
1088
|
try {
|
|
1079
1089
|
const meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
1080
|
-
const
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
throw new Error(suggestMessage("pallet", pallet, palletNames));
|
|
1084
|
-
}
|
|
1085
|
-
const callInfo = palletInfo.calls.find((c) => c.name.toLowerCase() === callName.toLowerCase());
|
|
1086
|
-
if (!callInfo) {
|
|
1087
|
-
const callNames = palletInfo.calls.map((c) => c.name);
|
|
1088
|
-
throw new Error(suggestMessage(`call in ${palletInfo.name}`, callName, callNames));
|
|
1089
|
-
}
|
|
1090
|
-
const callData = parseCallArgs(meta, palletInfo.name, callInfo.name, args);
|
|
1090
|
+
const userExtOverrides = parseExtOption(opts.ext);
|
|
1091
|
+
const customSignedExtensions = buildCustomSignedExtensions(meta, userExtOverrides);
|
|
1092
|
+
const txOptions = Object.keys(customSignedExtensions).length > 0 ? { customSignedExtensions } : undefined;
|
|
1091
1093
|
const unsafeApi = clientHandle.client.getUnsafeApi();
|
|
1092
|
-
|
|
1094
|
+
let tx;
|
|
1095
|
+
let callHex;
|
|
1096
|
+
if (isRawCall) {
|
|
1097
|
+
if (args.length > 0) {
|
|
1098
|
+
throw new Error(`Extra arguments are not allowed when submitting a raw call hex.
|
|
1099
|
+
` + "Usage: dot tx 0x<call_hex> --from <account>");
|
|
1100
|
+
}
|
|
1101
|
+
const callBinary = Binary.fromHex(target);
|
|
1102
|
+
tx = await unsafeApi.txFromCallData(callBinary);
|
|
1103
|
+
callHex = target;
|
|
1104
|
+
} else {
|
|
1105
|
+
const { pallet, item: callName } = parseTarget(target);
|
|
1106
|
+
const palletNames = getPalletNames(meta);
|
|
1107
|
+
const palletInfo = findPallet(meta, pallet);
|
|
1108
|
+
if (!palletInfo) {
|
|
1109
|
+
throw new Error(suggestMessage("pallet", pallet, palletNames));
|
|
1110
|
+
}
|
|
1111
|
+
const callInfo = palletInfo.calls.find((c) => c.name.toLowerCase() === callName.toLowerCase());
|
|
1112
|
+
if (!callInfo) {
|
|
1113
|
+
const callNames = palletInfo.calls.map((c) => c.name);
|
|
1114
|
+
throw new Error(suggestMessage(`call in ${palletInfo.name}`, callName, callNames));
|
|
1115
|
+
}
|
|
1116
|
+
const callData = parseCallArgs(meta, palletInfo.name, callInfo.name, args);
|
|
1117
|
+
tx = unsafeApi.tx[palletInfo.name][callInfo.name](callData);
|
|
1118
|
+
const encodedCall = await tx.getEncodedData();
|
|
1119
|
+
callHex = encodedCall.asHex();
|
|
1120
|
+
}
|
|
1121
|
+
const decodedStr = decodeCall(meta, callHex);
|
|
1093
1122
|
if (opts.dryRun) {
|
|
1094
1123
|
const signerAddress = toSs58(signer.publicKey);
|
|
1095
|
-
console.log(` ${BOLD}From:${RESET}
|
|
1124
|
+
console.log(` ${BOLD}From:${RESET} ${opts.from} (${signerAddress})`);
|
|
1125
|
+
console.log(` ${BOLD}Call:${RESET} ${callHex}`);
|
|
1126
|
+
console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
|
|
1096
1127
|
try {
|
|
1097
|
-
const fees = await tx.getEstimatedFees(signer.publicKey);
|
|
1128
|
+
const fees = await tx.getEstimatedFees(signer.publicKey, txOptions);
|
|
1098
1129
|
console.log(` ${BOLD}Estimated fees:${RESET} ${fees}`);
|
|
1099
1130
|
} catch (err) {
|
|
1100
1131
|
console.log(` ${BOLD}Estimated fees:${RESET} ${YELLOW}unable to estimate${RESET}`);
|
|
@@ -1103,8 +1134,10 @@ function registerTxCommand(cli) {
|
|
|
1103
1134
|
return;
|
|
1104
1135
|
}
|
|
1105
1136
|
console.log("Signing and submitting...");
|
|
1106
|
-
const result = await tx.signAndSubmit(signer);
|
|
1137
|
+
const result = await tx.signAndSubmit(signer, txOptions);
|
|
1107
1138
|
console.log();
|
|
1139
|
+
console.log(` ${BOLD}Call:${RESET} ${callHex}`);
|
|
1140
|
+
console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
|
|
1108
1141
|
console.log(` ${BOLD}Tx:${RESET} ${result.txHash}`);
|
|
1109
1142
|
if (result.block) {
|
|
1110
1143
|
console.log(` ${BOLD}Block:${RESET} #${result.block.number} (${result.block.hash})`);
|
|
@@ -1134,6 +1167,95 @@ function registerTxCommand(cli) {
|
|
|
1134
1167
|
}
|
|
1135
1168
|
});
|
|
1136
1169
|
}
|
|
1170
|
+
function decodeCall(meta, callHex) {
|
|
1171
|
+
try {
|
|
1172
|
+
const viewBuilder = getViewBuilder(meta.lookup);
|
|
1173
|
+
const decoded = viewBuilder.callDecoder(callHex);
|
|
1174
|
+
const palletName = decoded.pallet.value.name;
|
|
1175
|
+
const callName = decoded.call.value.name;
|
|
1176
|
+
const argsStr = formatDecodedArgs(decoded.args.value);
|
|
1177
|
+
return `${palletName}.${callName}${argsStr}`;
|
|
1178
|
+
} catch {
|
|
1179
|
+
return "(unable to decode)";
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
function formatDecodedArgs(decoded) {
|
|
1183
|
+
return formatDecoded(decoded);
|
|
1184
|
+
}
|
|
1185
|
+
function formatDecoded(d) {
|
|
1186
|
+
switch (d.codec) {
|
|
1187
|
+
case "_void":
|
|
1188
|
+
return "";
|
|
1189
|
+
case "bool":
|
|
1190
|
+
return d.value.toString();
|
|
1191
|
+
case "str":
|
|
1192
|
+
case "char":
|
|
1193
|
+
return d.value;
|
|
1194
|
+
case "u8":
|
|
1195
|
+
case "u16":
|
|
1196
|
+
case "u32":
|
|
1197
|
+
case "i8":
|
|
1198
|
+
case "i16":
|
|
1199
|
+
case "i32":
|
|
1200
|
+
case "compactNumber":
|
|
1201
|
+
return d.value.toString();
|
|
1202
|
+
case "u64":
|
|
1203
|
+
case "u128":
|
|
1204
|
+
case "u256":
|
|
1205
|
+
case "i64":
|
|
1206
|
+
case "i128":
|
|
1207
|
+
case "i256":
|
|
1208
|
+
case "compactBn":
|
|
1209
|
+
return d.value.toString();
|
|
1210
|
+
case "bitSequence":
|
|
1211
|
+
return `0x${Buffer.from(d.value.bytes).toString("hex")}`;
|
|
1212
|
+
case "AccountId":
|
|
1213
|
+
return d.value.address;
|
|
1214
|
+
case "ethAccount":
|
|
1215
|
+
return d.value;
|
|
1216
|
+
case "Bytes":
|
|
1217
|
+
return d.value;
|
|
1218
|
+
case "BytesArray":
|
|
1219
|
+
return d.value;
|
|
1220
|
+
case "Enum": {
|
|
1221
|
+
const inner = formatDecoded(d.value.value);
|
|
1222
|
+
if (!inner)
|
|
1223
|
+
return d.value.type;
|
|
1224
|
+
return `${d.value.type}(${inner})`;
|
|
1225
|
+
}
|
|
1226
|
+
case "Struct": {
|
|
1227
|
+
const entries = Object.entries(d.value);
|
|
1228
|
+
if (entries.length === 0)
|
|
1229
|
+
return " {}";
|
|
1230
|
+
const fields = entries.map(([k, v]) => `${k}: ${formatDecoded(v)}`).join(", ");
|
|
1231
|
+
return ` { ${fields} }`;
|
|
1232
|
+
}
|
|
1233
|
+
case "Tuple": {
|
|
1234
|
+
const items = d.value.map(formatDecoded).join(", ");
|
|
1235
|
+
return `(${items})`;
|
|
1236
|
+
}
|
|
1237
|
+
case "Option": {
|
|
1238
|
+
if (d.value.codec === "_void")
|
|
1239
|
+
return "None";
|
|
1240
|
+
return formatDecoded(d.value);
|
|
1241
|
+
}
|
|
1242
|
+
case "Result": {
|
|
1243
|
+
if (d.value.success) {
|
|
1244
|
+
return `Ok(${formatDecoded(d.value.value)})`;
|
|
1245
|
+
}
|
|
1246
|
+
return `Err(${formatDecoded(d.value.value)})`;
|
|
1247
|
+
}
|
|
1248
|
+
case "Sequence":
|
|
1249
|
+
case "Array": {
|
|
1250
|
+
const items = d.value.map(formatDecoded);
|
|
1251
|
+
if (items.length === 0)
|
|
1252
|
+
return "[]";
|
|
1253
|
+
return `[${items.join(", ")}]`;
|
|
1254
|
+
}
|
|
1255
|
+
default:
|
|
1256
|
+
return String(d.value ?? "");
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1137
1259
|
function formatEventValue(v) {
|
|
1138
1260
|
if (typeof v === "bigint")
|
|
1139
1261
|
return v.toString();
|
|
@@ -1224,6 +1346,13 @@ function parseTypedArg(lookup, entry, arg) {
|
|
|
1224
1346
|
} catch {}
|
|
1225
1347
|
}
|
|
1226
1348
|
const variants = Object.keys(entry.value);
|
|
1349
|
+
if (variants.includes("Id")) {
|
|
1350
|
+
const idVariant = entry.value["Id"];
|
|
1351
|
+
const innerType = idVariant.type === "lookupEntry" ? idVariant.value : idVariant;
|
|
1352
|
+
if (innerType.type === "AccountId32" && !arg.startsWith("{")) {
|
|
1353
|
+
return { type: "Id", value: arg };
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1227
1356
|
const matched = variants.find((v) => v.toLowerCase() === arg.toLowerCase());
|
|
1228
1357
|
if (matched) {
|
|
1229
1358
|
const variant = entry.value[matched];
|
|
@@ -1234,15 +1363,22 @@ function parseTypedArg(lookup, entry, arg) {
|
|
|
1234
1363
|
return parseValue(arg);
|
|
1235
1364
|
}
|
|
1236
1365
|
case "sequence":
|
|
1237
|
-
case "array":
|
|
1366
|
+
case "array": {
|
|
1367
|
+
const inner = entry.value;
|
|
1368
|
+
if (inner.type === "primitive" && inner.value === "u8") {
|
|
1369
|
+
if (/^0x[0-9a-fA-F]*$/.test(arg))
|
|
1370
|
+
return Binary.fromHex(arg);
|
|
1371
|
+
return Binary.fromText(arg);
|
|
1372
|
+
}
|
|
1238
1373
|
if (arg.startsWith("[")) {
|
|
1239
1374
|
try {
|
|
1240
1375
|
return JSON.parse(arg);
|
|
1241
1376
|
} catch {}
|
|
1242
1377
|
}
|
|
1243
1378
|
if (/^0x[0-9a-fA-F]*$/.test(arg))
|
|
1244
|
-
return arg;
|
|
1379
|
+
return Binary.fromHex(arg);
|
|
1245
1380
|
return parseValue(arg);
|
|
1381
|
+
}
|
|
1246
1382
|
case "struct":
|
|
1247
1383
|
if (arg.startsWith("{")) {
|
|
1248
1384
|
try {
|
|
@@ -1286,6 +1422,73 @@ function parsePrimitive(prim, arg) {
|
|
|
1286
1422
|
return parseValue(arg);
|
|
1287
1423
|
}
|
|
1288
1424
|
}
|
|
1425
|
+
var PAPI_BUILTIN_EXTENSIONS = new Set([
|
|
1426
|
+
"CheckNonZeroSender",
|
|
1427
|
+
"CheckSpecVersion",
|
|
1428
|
+
"CheckTxVersion",
|
|
1429
|
+
"CheckGenesis",
|
|
1430
|
+
"CheckMortality",
|
|
1431
|
+
"CheckNonce",
|
|
1432
|
+
"CheckWeight",
|
|
1433
|
+
"ChargeTransactionPayment",
|
|
1434
|
+
"ChargeAssetTxPayment",
|
|
1435
|
+
"CheckMetadataHash",
|
|
1436
|
+
"StorageWeightReclaim",
|
|
1437
|
+
"PrevalidateAttests"
|
|
1438
|
+
]);
|
|
1439
|
+
function parseExtOption(ext) {
|
|
1440
|
+
if (!ext)
|
|
1441
|
+
return {};
|
|
1442
|
+
try {
|
|
1443
|
+
const parsed = JSON.parse(ext);
|
|
1444
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1445
|
+
throw new Error(`--ext must be a JSON object, e.g. '{"ExtName":{"value":...}}'`);
|
|
1446
|
+
}
|
|
1447
|
+
return parsed;
|
|
1448
|
+
} catch (err) {
|
|
1449
|
+
if (err.message?.startsWith("--ext"))
|
|
1450
|
+
throw err;
|
|
1451
|
+
throw new Error(`Failed to parse --ext JSON: ${err.message}
|
|
1452
|
+
` + `Expected format: '{"ExtName":{"value":...}}'`);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
var NO_DEFAULT = Symbol("no-default");
|
|
1456
|
+
function buildCustomSignedExtensions(meta, userOverrides) {
|
|
1457
|
+
const result = {};
|
|
1458
|
+
const extensions = getSignedExtensions(meta);
|
|
1459
|
+
for (const ext of extensions) {
|
|
1460
|
+
if (PAPI_BUILTIN_EXTENSIONS.has(ext.identifier))
|
|
1461
|
+
continue;
|
|
1462
|
+
if (ext.identifier in userOverrides) {
|
|
1463
|
+
result[ext.identifier] = userOverrides[ext.identifier];
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const valueEntry = meta.lookup(ext.type);
|
|
1467
|
+
const addEntry = meta.lookup(ext.additionalSigned);
|
|
1468
|
+
const value = autoDefaultForType(valueEntry);
|
|
1469
|
+
const add = autoDefaultForType(addEntry);
|
|
1470
|
+
if (value !== NO_DEFAULT || add !== NO_DEFAULT) {
|
|
1471
|
+
result[ext.identifier] = {
|
|
1472
|
+
...value !== NO_DEFAULT ? { value } : {},
|
|
1473
|
+
...add !== NO_DEFAULT ? { additionalSigned: add } : {}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
return result;
|
|
1478
|
+
}
|
|
1479
|
+
function autoDefaultForType(entry) {
|
|
1480
|
+
if (entry.type === "void")
|
|
1481
|
+
return new Uint8Array([]);
|
|
1482
|
+
if (entry.type === "option")
|
|
1483
|
+
return;
|
|
1484
|
+
if (entry.type === "enum") {
|
|
1485
|
+
const variants = entry.value;
|
|
1486
|
+
if ("Disabled" in variants) {
|
|
1487
|
+
return { type: "Disabled", value: undefined };
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return NO_DEFAULT;
|
|
1491
|
+
}
|
|
1289
1492
|
|
|
1290
1493
|
// src/utils/errors.ts
|
|
1291
1494
|
class CliError2 extends Error {
|
|
@@ -1310,7 +1513,7 @@ registerConstCommand(cli);
|
|
|
1310
1513
|
registerAccountCommands(cli);
|
|
1311
1514
|
registerTxCommand(cli);
|
|
1312
1515
|
cli.help();
|
|
1313
|
-
cli.version("0.
|
|
1516
|
+
cli.version("0.3.0");
|
|
1314
1517
|
function handleError(err) {
|
|
1315
1518
|
if (err instanceof CliError2) {
|
|
1316
1519
|
console.error(`Error: ${err.message}`);
|