polkadot-cli 0.2.1 → 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 +207 -19
- 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
|
}
|
|
@@ -1061,41 +1068,64 @@ async function accountRemove(name) {
|
|
|
1061
1068
|
|
|
1062
1069
|
// src/commands/tx.ts
|
|
1063
1070
|
import { Binary } from "polkadot-api";
|
|
1071
|
+
import { getViewBuilder } from "@polkadot-api/view-builder";
|
|
1064
1072
|
function registerTxCommand(cli) {
|
|
1065
|
-
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) => {
|
|
1066
1074
|
if (!target || !opts.from) {
|
|
1067
|
-
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]");
|
|
1068
1076
|
console.log("");
|
|
1069
1077
|
console.log("Examples:");
|
|
1070
1078
|
console.log(" $ dot tx Balances.transferKeepAlive 5FHn... 1000000000000 --from alice");
|
|
1071
1079
|
console.log(" $ dot tx System.remark 0xdeadbeef --from alice --dry-run");
|
|
1080
|
+
console.log(" $ dot tx 0x0001076465616462656566 --from alice");
|
|
1072
1081
|
return;
|
|
1073
1082
|
}
|
|
1083
|
+
const isRawCall = /^0x[0-9a-fA-F]+$/.test(target);
|
|
1074
1084
|
const config = await loadConfig();
|
|
1075
1085
|
const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
|
|
1076
|
-
const { pallet, item: callName } = parseTarget(target);
|
|
1077
1086
|
const signer = await resolveAccountSigner(opts.from);
|
|
1078
1087
|
const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
1079
1088
|
try {
|
|
1080
1089
|
const meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
1081
|
-
const
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
throw new Error(suggestMessage("pallet", pallet, palletNames));
|
|
1085
|
-
}
|
|
1086
|
-
const callInfo = palletInfo.calls.find((c) => c.name.toLowerCase() === callName.toLowerCase());
|
|
1087
|
-
if (!callInfo) {
|
|
1088
|
-
const callNames = palletInfo.calls.map((c) => c.name);
|
|
1089
|
-
throw new Error(suggestMessage(`call in ${palletInfo.name}`, callName, callNames));
|
|
1090
|
-
}
|
|
1091
|
-
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;
|
|
1092
1093
|
const unsafeApi = clientHandle.client.getUnsafeApi();
|
|
1093
|
-
|
|
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);
|
|
1094
1122
|
if (opts.dryRun) {
|
|
1095
1123
|
const signerAddress = toSs58(signer.publicKey);
|
|
1096
|
-
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}`);
|
|
1097
1127
|
try {
|
|
1098
|
-
const fees = await tx.getEstimatedFees(signer.publicKey);
|
|
1128
|
+
const fees = await tx.getEstimatedFees(signer.publicKey, txOptions);
|
|
1099
1129
|
console.log(` ${BOLD}Estimated fees:${RESET} ${fees}`);
|
|
1100
1130
|
} catch (err) {
|
|
1101
1131
|
console.log(` ${BOLD}Estimated fees:${RESET} ${YELLOW}unable to estimate${RESET}`);
|
|
@@ -1104,8 +1134,10 @@ function registerTxCommand(cli) {
|
|
|
1104
1134
|
return;
|
|
1105
1135
|
}
|
|
1106
1136
|
console.log("Signing and submitting...");
|
|
1107
|
-
const result = await tx.signAndSubmit(signer);
|
|
1137
|
+
const result = await tx.signAndSubmit(signer, txOptions);
|
|
1108
1138
|
console.log();
|
|
1139
|
+
console.log(` ${BOLD}Call:${RESET} ${callHex}`);
|
|
1140
|
+
console.log(` ${BOLD}Decode:${RESET} ${decodedStr}`);
|
|
1109
1141
|
console.log(` ${BOLD}Tx:${RESET} ${result.txHash}`);
|
|
1110
1142
|
if (result.block) {
|
|
1111
1143
|
console.log(` ${BOLD}Block:${RESET} #${result.block.number} (${result.block.hash})`);
|
|
@@ -1135,6 +1167,95 @@ function registerTxCommand(cli) {
|
|
|
1135
1167
|
}
|
|
1136
1168
|
});
|
|
1137
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
|
+
}
|
|
1138
1259
|
function formatEventValue(v) {
|
|
1139
1260
|
if (typeof v === "bigint")
|
|
1140
1261
|
return v.toString();
|
|
@@ -1301,6 +1422,73 @@ function parsePrimitive(prim, arg) {
|
|
|
1301
1422
|
return parseValue(arg);
|
|
1302
1423
|
}
|
|
1303
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
|
+
}
|
|
1304
1492
|
|
|
1305
1493
|
// src/utils/errors.ts
|
|
1306
1494
|
class CliError2 extends Error {
|
|
@@ -1325,7 +1513,7 @@ registerConstCommand(cli);
|
|
|
1325
1513
|
registerAccountCommands(cli);
|
|
1326
1514
|
registerTxCommand(cli);
|
|
1327
1515
|
cli.help();
|
|
1328
|
-
cli.version("0.
|
|
1516
|
+
cli.version("0.3.0");
|
|
1329
1517
|
function handleError(err) {
|
|
1330
1518
|
if (err instanceof CliError2) {
|
|
1331
1519
|
console.error(`Error: ${err.message}`);
|