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.
Files changed (2) hide show
  1. package/dist/cli.mjs +224 -21
  2. 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 palletNames = getPalletNames(meta);
1081
- const palletInfo = findPallet(meta, pallet);
1082
- if (!palletInfo) {
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
- const tx = unsafeApi.tx[palletInfo.name][callInfo.name](callData);
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} ${opts.from} (${signerAddress})`);
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.2.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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {