polkadot-cli 1.6.1 → 1.7.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 (3) hide show
  1. package/README.md +180 -0
  2. package/dist/cli.mjs +247 -14
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -20,6 +20,7 @@ Ships with Polkadot and all system parachains preconfigured with multiple fallba
20
20
  - ✅ Named address resolution across all commands
21
21
  - ✅ Runtime API calls — `dot apis.Core.version`
22
22
  - ✅ Batteries included — all system parachains and testnets already setup to be used
23
+ - ✅ File-based commands — run any command from a YAML/JSON file with variable substitution
23
24
 
24
25
  ### Preconfigured chains
25
26
 
@@ -572,6 +573,185 @@ For manual override, use `--ext` with a JSON object:
572
573
  dot tx System.remark 0xdeadbeef --from alice --ext '{"MyExtension":{"value":"..."}}'
573
574
  ```
574
575
 
576
+ ### File-based commands
577
+
578
+ Run any `dot` command from a YAML or JSON file. Especially useful for complex calls like XCM messages that are hard to construct inline.
579
+
580
+ **Teleport DOT** from Asset Hub to the relay chain:
581
+
582
+ ```yaml
583
+ # teleport-dot.xcm.yaml
584
+ chain: polkadot-asset-hub
585
+ tx:
586
+ PolkadotXcm:
587
+ limited_teleport_assets:
588
+ dest:
589
+ type: V4
590
+ value:
591
+ parents: 1
592
+ interior:
593
+ type: Here
594
+ beneficiary:
595
+ type: V4
596
+ value:
597
+ parents: 0
598
+ interior:
599
+ type: X1
600
+ value:
601
+ - type: AccountId32
602
+ value:
603
+ network: null
604
+ id: "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
605
+ assets:
606
+ type: V4
607
+ value:
608
+ - id:
609
+ parents: 1
610
+ interior:
611
+ type: Here
612
+ fun:
613
+ type: Fungible
614
+ value: 10000000000
615
+ fee_asset_item: 0
616
+ weight_limit:
617
+ type: Unlimited
618
+ ```
619
+
620
+ **Reserve transfer USDC** (asset 1337, 6 decimals) from Asset Hub to Hydration:
621
+
622
+ ```yaml
623
+ # reserve-transfer-usdc.xcm.yaml
624
+ chain: polkadot-asset-hub
625
+ tx:
626
+ PolkadotXcm:
627
+ limited_reserve_transfer_assets:
628
+ dest:
629
+ type: V4
630
+ value:
631
+ parents: 1
632
+ interior:
633
+ type: X1
634
+ value:
635
+ - type: Parachain
636
+ value: 2034
637
+ beneficiary:
638
+ type: V4
639
+ value:
640
+ parents: 0
641
+ interior:
642
+ type: X1
643
+ value:
644
+ - type: AccountId32
645
+ value:
646
+ network: null
647
+ id: "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
648
+ assets:
649
+ type: V4
650
+ value:
651
+ - id:
652
+ parents: 0
653
+ interior:
654
+ type: X2
655
+ value:
656
+ - type: PalletInstance
657
+ value: 50
658
+ - type: GeneralIndex
659
+ value: 1337
660
+ fun:
661
+ type: Fungible
662
+ value: 10000000
663
+ fee_asset_item: 0
664
+ weight_limit:
665
+ type: Unlimited
666
+ ```
667
+
668
+ The same teleport in JSON:
669
+
670
+ ```json
671
+ {
672
+ "chain": "polkadot-asset-hub",
673
+ "tx": {
674
+ "PolkadotXcm": {
675
+ "limited_teleport_assets": {
676
+ "dest": { "type": "V4", "value": { "parents": 1, "interior": { "type": "Here" } } },
677
+ "beneficiary": {
678
+ "type": "V4",
679
+ "value": {
680
+ "parents": 0,
681
+ "interior": {
682
+ "type": "X1",
683
+ "value": [{ "type": "AccountId32", "value": { "network": null, "id": "0xd435...a27d" } }]
684
+ }
685
+ }
686
+ },
687
+ "assets": {
688
+ "type": "V4",
689
+ "value": [{
690
+ "id": { "parents": 1, "interior": { "type": "Here" } },
691
+ "fun": { "type": "Fungible", "value": 10000000000 }
692
+ }]
693
+ },
694
+ "fee_asset_item": 0,
695
+ "weight_limit": { "type": "Unlimited" }
696
+ }
697
+ }
698
+ }
699
+ }
700
+ ```
701
+
702
+ ```bash
703
+ # Run from file
704
+ dot ./teleport-dot.xcm.yaml --from alice --dry-run
705
+
706
+ # Encode only
707
+ dot ./reserve-transfer-usdc.xcm.yaml --encode
708
+
709
+ # Override variables
710
+ dot ./transfer.xcm.yaml --var AMOUNT=2000000000000 --from alice
711
+ ```
712
+
713
+ The file format uses a required category wrapper (`tx`, `query`, `const`, or `apis`) with the structure `category > Pallet > Item > args`:
714
+
715
+ ```yaml
716
+ # Simple transaction
717
+ tx:
718
+ System:
719
+ remark:
720
+ - "0xdeadbeef"
721
+ ```
722
+
723
+ ```yaml
724
+ # Storage query
725
+ chain: polkadot
726
+ query:
727
+ System:
728
+ Account:
729
+ - "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
730
+ ```
731
+
732
+ ```yaml
733
+ # Constant lookup
734
+ chain: polkadot
735
+ const:
736
+ Balances:
737
+ ExistentialDeposit:
738
+ ```
739
+
740
+ **Variable substitution** uses shell-style `${VAR}` with optional defaults `${VAR:-default}`. Variables are resolved in order: `--var` flags > environment variables > `vars:` section defaults.
741
+
742
+ ```yaml
743
+ chain: ${CHAIN:-polkadot}
744
+ vars:
745
+ AMOUNT: "1000000000000"
746
+ tx:
747
+ Balances:
748
+ transfer_keep_alive:
749
+ dest: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
750
+ value: ${AMOUNT}
751
+ ```
752
+
753
+ All existing flags work with file input — `--chain` overrides the file's `chain:` field, `--from`, `--dry-run`, `--encode`, `--output`, etc. behave identically to inline commands.
754
+
575
755
  ### Compute hashes
576
756
 
577
757
  Compute cryptographic hashes commonly used in Substrate. Supports BLAKE2b-256, BLAKE2b-128, Keccak-256, and SHA-256.
package/dist/cli.mjs CHANGED
@@ -1080,8 +1080,8 @@ function normalizeValue(lookup, entry, value) {
1080
1080
  return;
1081
1081
  }
1082
1082
  case "primitive": {
1083
+ const prim = resolved.value;
1083
1084
  if (typeof value === "string") {
1084
- const prim = resolved.value;
1085
1085
  switch (prim) {
1086
1086
  case "bool":
1087
1087
  return value === "true";
@@ -1101,12 +1101,26 @@ function normalizeValue(lookup, entry, value) {
1101
1101
  return parseInt(value, 10);
1102
1102
  }
1103
1103
  }
1104
+ if (typeof value === "number") {
1105
+ switch (prim) {
1106
+ case "u64":
1107
+ case "u128":
1108
+ case "u256":
1109
+ case "i64":
1110
+ case "i128":
1111
+ case "i256":
1112
+ return BigInt(value);
1113
+ }
1114
+ }
1104
1115
  return value;
1105
1116
  }
1106
1117
  case "compact": {
1107
1118
  if (typeof value === "string") {
1108
1119
  return resolved.isBig ? BigInt(value) : parseInt(value, 10);
1109
1120
  }
1121
+ if (typeof value === "number" && resolved.isBig) {
1122
+ return BigInt(value);
1123
+ }
1110
1124
  return value;
1111
1125
  }
1112
1126
  default:
@@ -1330,7 +1344,10 @@ async function handleApis(target, args, opts) {
1330
1344
  const names = api.methods.map((m) => m.name);
1331
1345
  throw new Error(suggestMessage(`method in ${api.name}`, methodName, names));
1332
1346
  }
1333
- const parsedArgs = await parseRuntimeApiArgs(meta, method, args);
1347
+ const effectiveArgs = args.length > 0 || opts.parsedArgs == null ? args : Array.isArray(opts.parsedArgs) ? opts.parsedArgs.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)) : [
1348
+ typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
1349
+ ];
1350
+ const parsedArgs = await parseRuntimeApiArgs(meta, method, effectiveArgs);
1334
1351
  const unsafeApi = clientHandle.client.getUnsafeApi();
1335
1352
  const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
1336
1353
  const format = opts.output ?? "pretty";
@@ -1973,7 +1990,7 @@ async function completeDotpath(currentWord, config, knownChains, precedingWords)
1973
1990
  const numComplete = completeSegments.length;
1974
1991
  if (numComplete === 0 && !endsWithDot) {
1975
1992
  const candidates = [
1976
- ...CATEGORIES.map((c) => `${c}.`),
1993
+ ...CATEGORIES2.map((c) => `${c}.`),
1977
1994
  ...knownChains.map((c) => `${c}.`),
1978
1995
  ...NAMED_COMMANDS
1979
1996
  ];
@@ -2024,11 +2041,11 @@ async function completeDotpath(currentWord, config, knownChains, precedingWords)
2024
2041
  if (firstIsChain) {
2025
2042
  const chainName = first;
2026
2043
  if (numComplete === 1 && endsWithDot) {
2027
- const candidates = CATEGORIES.map((c) => `${first}.${c}.`);
2044
+ const candidates = CATEGORIES2.map((c) => `${first}.${c}.`);
2028
2045
  return filterPrefix(candidates, currentWord.slice(0, -1));
2029
2046
  }
2030
2047
  if (numComplete === 1 && !endsWithDot) {
2031
- const candidates = CATEGORIES.map((c) => `${first}.${c}.`);
2048
+ const candidates = CATEGORIES2.map((c) => `${first}.${c}.`);
2032
2049
  return filterPrefix(candidates, currentWord);
2033
2050
  }
2034
2051
  if (numComplete === 2) {
@@ -2092,14 +2109,14 @@ async function completeApisCategory(prefix, numComplete, endsWithDot, segments,
2092
2109
  }
2093
2110
  return [];
2094
2111
  }
2095
- var CATEGORIES, CATEGORY_ALIASES2, NAMED_COMMANDS, CHAIN_SUBCOMMANDS, ACCOUNT_SUBCOMMANDS, GLOBAL_OPTIONS, TX_OPTIONS, QUERY_OPTIONS;
2112
+ var CATEGORIES2, CATEGORY_ALIASES2, NAMED_COMMANDS, CHAIN_SUBCOMMANDS, ACCOUNT_SUBCOMMANDS, GLOBAL_OPTIONS, TX_OPTIONS, QUERY_OPTIONS;
2096
2113
  var init_complete = __esm(() => {
2097
2114
  init_accounts_store();
2098
2115
  init_store();
2099
2116
  init_accounts();
2100
2117
  init_hash();
2101
2118
  init_metadata();
2102
- CATEGORIES = ["query", "tx", "const", "events", "errors", "apis"];
2119
+ CATEGORIES2 = ["query", "tx", "const", "events", "errors", "apis"];
2103
2120
  CATEGORY_ALIASES2 = {
2104
2121
  query: "query",
2105
2122
  tx: "tx",
@@ -2134,7 +2151,7 @@ var init_complete = __esm(() => {
2134
2151
  // src/cli.ts
2135
2152
  import cac from "cac";
2136
2153
  // package.json
2137
- var version = "1.6.1";
2154
+ var version = "1.7.0";
2138
2155
 
2139
2156
  // src/commands/account.ts
2140
2157
  init_accounts_store();
@@ -2621,7 +2638,10 @@ async function handleApis2(target, args, opts) {
2621
2638
  const names = api.methods.map((m) => m.name);
2622
2639
  throw new Error(suggestMessage(`method in ${api.name}`, methodName, names));
2623
2640
  }
2624
- const parsedArgs = await parseRuntimeApiArgs2(meta, method, args);
2641
+ const effectiveArgs = args.length > 0 || opts.parsedArgs == null ? args : Array.isArray(opts.parsedArgs) ? opts.parsedArgs.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)) : [
2642
+ typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
2643
+ ];
2644
+ const parsedArgs = await parseRuntimeApiArgs2(meta, method, effectiveArgs);
2625
2645
  const unsafeApi = clientHandle.client.getUnsafeApi();
2626
2646
  const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
2627
2647
  const format = opts.output ?? "pretty";
@@ -3814,7 +3834,10 @@ async function handleQuery(target, keys, opts) {
3814
3834
  }
3815
3835
  const unsafeApi = clientHandle.client.getUnsafeApi();
3816
3836
  const storageApi = unsafeApi.query[palletInfo.name][storageItem.name];
3817
- const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, keys);
3837
+ const effectiveKeys = keys.length > 0 || opts.parsedArgs == null ? keys : Array.isArray(opts.parsedArgs) ? opts.parsedArgs.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)) : [
3838
+ typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
3839
+ ];
3840
+ const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, effectiveKeys);
3818
3841
  const format = opts.output ?? "pretty";
3819
3842
  if (storageItem.type === "map" && parsedKeys.length === 0) {
3820
3843
  if (!opts.dump) {
@@ -4014,7 +4037,8 @@ async function handleTx(target, args, opts) {
4014
4037
  const callNames = palletInfo.calls.map((c) => c.name);
4015
4038
  throw new Error(suggestMessage(`call in ${palletInfo.name}`, callName, callNames));
4016
4039
  }
4017
- const callData = await parseCallArgs(meta, palletInfo.name, callInfo.name, args);
4040
+ const effectiveArgs = opts.parsedArgs !== undefined ? fileArgsToStrings(opts.parsedArgs) : args;
4041
+ const callData = await parseCallArgs(meta, palletInfo.name, callInfo.name, effectiveArgs);
4018
4042
  if (opts.encode) {
4019
4043
  const { codec, location } = meta.builder.buildCall(palletInfo.name, callInfo.name);
4020
4044
  const encodedArgs = codec.enc(callData);
@@ -4437,8 +4461,8 @@ function normalizeValue2(lookup, entry, value) {
4437
4461
  return;
4438
4462
  }
4439
4463
  case "primitive": {
4464
+ const prim = resolved.value;
4440
4465
  if (typeof value === "string") {
4441
- const prim = resolved.value;
4442
4466
  switch (prim) {
4443
4467
  case "bool":
4444
4468
  return value === "true";
@@ -4458,18 +4482,52 @@ function normalizeValue2(lookup, entry, value) {
4458
4482
  return parseInt(value, 10);
4459
4483
  }
4460
4484
  }
4485
+ if (typeof value === "number") {
4486
+ switch (prim) {
4487
+ case "u64":
4488
+ case "u128":
4489
+ case "u256":
4490
+ case "i64":
4491
+ case "i128":
4492
+ case "i256":
4493
+ return BigInt(value);
4494
+ }
4495
+ }
4461
4496
  return value;
4462
4497
  }
4463
4498
  case "compact": {
4464
4499
  if (typeof value === "string") {
4465
4500
  return resolved.isBig ? BigInt(value) : parseInt(value, 10);
4466
4501
  }
4502
+ if (typeof value === "number" && resolved.isBig) {
4503
+ return BigInt(value);
4504
+ }
4467
4505
  return value;
4468
4506
  }
4469
4507
  default:
4470
4508
  return value;
4471
4509
  }
4472
4510
  }
4511
+ function fileArgsToStrings(args) {
4512
+ if (args == null)
4513
+ return [];
4514
+ if (typeof args === "object" && !Array.isArray(args)) {
4515
+ return Object.values(args).map(serializeForCli);
4516
+ }
4517
+ if (Array.isArray(args)) {
4518
+ return args.map(serializeForCli);
4519
+ }
4520
+ return [serializeForCli(args)];
4521
+ }
4522
+ function serializeForCli(v) {
4523
+ if (typeof v === "string")
4524
+ return v;
4525
+ if (typeof v === "number" || typeof v === "bigint")
4526
+ return String(v);
4527
+ if (typeof v === "boolean" || v === null)
4528
+ return String(v);
4529
+ return JSON.stringify(v);
4530
+ }
4473
4531
  function parseEnumShorthand2(arg) {
4474
4532
  if (arg.startsWith("{") || arg.startsWith("[") || arg.startsWith("0x"))
4475
4533
  return null;
@@ -4763,6 +4821,130 @@ async function saveConfig2(config) {
4763
4821
  `);
4764
4822
  }
4765
4823
 
4824
+ // src/core/file-loader.ts
4825
+ init_errors();
4826
+ import { parse as parseYaml } from "yaml";
4827
+ var CATEGORIES = ["tx", "query", "const", "apis"];
4828
+ var FILE_EXTENSIONS = [".json", ".yaml", ".yml"];
4829
+ function isFilePath(dotpath) {
4830
+ if (FILE_EXTENSIONS.some((ext) => dotpath.endsWith(ext)))
4831
+ return true;
4832
+ if (dotpath.startsWith("./") || dotpath.startsWith("/"))
4833
+ return true;
4834
+ return false;
4835
+ }
4836
+ function parseVarFlags(varFlags) {
4837
+ if (!varFlags)
4838
+ return {};
4839
+ const flags = Array.isArray(varFlags) ? varFlags : [varFlags];
4840
+ const vars = {};
4841
+ for (const flag of flags) {
4842
+ const eqIdx = flag.indexOf("=");
4843
+ if (eqIdx === -1) {
4844
+ throw new CliError(`Invalid --var format "${flag}". Expected KEY=VALUE.`);
4845
+ }
4846
+ const key = flag.slice(0, eqIdx);
4847
+ const value = flag.slice(eqIdx + 1);
4848
+ if (!key) {
4849
+ throw new CliError(`Invalid --var format "${flag}". Key cannot be empty.`);
4850
+ }
4851
+ vars[key] = value;
4852
+ }
4853
+ return vars;
4854
+ }
4855
+ function substituteVars(text, vars) {
4856
+ return text.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
4857
+ const defaultSep = expr.indexOf(":-");
4858
+ let varName;
4859
+ let defaultValue;
4860
+ if (defaultSep !== -1) {
4861
+ varName = expr.slice(0, defaultSep);
4862
+ defaultValue = expr.slice(defaultSep + 2);
4863
+ } else {
4864
+ varName = expr;
4865
+ }
4866
+ if (varName in vars)
4867
+ return vars[varName];
4868
+ const envVal = process.env[varName];
4869
+ if (envVal !== undefined)
4870
+ return envVal;
4871
+ if (defaultValue !== undefined)
4872
+ return defaultValue;
4873
+ throw new CliError(`Undefined variable "\${${varName}}" in file. ` + `Provide it via --var ${varName}=VALUE, as an environment variable, ` + `or add a default with \${${varName}:-default}.`);
4874
+ });
4875
+ }
4876
+ async function loadCommandFile(filePath, cliVars) {
4877
+ const file = Bun.file(filePath);
4878
+ const exists = await file.exists();
4879
+ if (!exists) {
4880
+ throw new CliError(`File not found: ${filePath}`);
4881
+ }
4882
+ const rawText = await file.text();
4883
+ if (!rawText.trim()) {
4884
+ throw new CliError(`File is empty: ${filePath}`);
4885
+ }
4886
+ const isJson = filePath.endsWith(".json");
4887
+ const fileVars = {};
4888
+ try {
4889
+ const preParsed = isJson ? JSON.parse(rawText) : parseYaml(rawText);
4890
+ if (preParsed && typeof preParsed === "object" && !Array.isArray(preParsed) && preParsed.vars) {
4891
+ const varsSection = preParsed.vars;
4892
+ if (typeof varsSection === "object" && !Array.isArray(varsSection)) {
4893
+ for (const [key, val] of Object.entries(varsSection)) {
4894
+ fileVars[key] = String(val);
4895
+ }
4896
+ }
4897
+ }
4898
+ } catch {}
4899
+ const mergedVars = { ...fileVars, ...cliVars };
4900
+ const substituted = substituteVars(rawText, mergedVars);
4901
+ let parsed;
4902
+ try {
4903
+ parsed = isJson ? JSON.parse(substituted) : parseYaml(substituted);
4904
+ } catch (err) {
4905
+ const format = isJson ? "JSON" : "YAML";
4906
+ const msg = err instanceof Error ? err.message : String(err);
4907
+ throw new CliError(`Failed to parse ${format} file "${filePath}": ${msg}`);
4908
+ }
4909
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4910
+ throw new CliError(`File "${filePath}" must contain a YAML/JSON object (not an array or scalar).`);
4911
+ }
4912
+ const doc = parsed;
4913
+ const chain = doc.chain != null ? String(doc.chain) : undefined;
4914
+ const foundCategories = CATEGORIES.filter((c) => (c in doc));
4915
+ if (foundCategories.length === 0) {
4916
+ throw new CliError(`File "${filePath}" must contain exactly one category key: ${CATEGORIES.join(", ")}. None found.`);
4917
+ }
4918
+ if (foundCategories.length > 1) {
4919
+ throw new CliError(`File "${filePath}" contains multiple category keys: ${foundCategories.join(", ")}. Only one is allowed.`);
4920
+ }
4921
+ const category = foundCategories[0];
4922
+ const categoryObj = doc[category];
4923
+ if (!categoryObj || typeof categoryObj !== "object" || Array.isArray(categoryObj)) {
4924
+ throw new CliError(`"${category}" in file "${filePath}" must be an object with a pallet name as key.`);
4925
+ }
4926
+ const palletEntries = Object.entries(categoryObj);
4927
+ if (palletEntries.length !== 1) {
4928
+ throw new CliError(`"${category}" in file "${filePath}" must contain exactly one pallet. Found: ${palletEntries.length === 0 ? "none" : palletEntries.map(([k]) => k).join(", ")}.`);
4929
+ }
4930
+ const [pallet, palletObj] = palletEntries[0];
4931
+ if (!palletObj || typeof palletObj !== "object" || Array.isArray(palletObj)) {
4932
+ throw new CliError(`"${category}.${pallet}" in file "${filePath}" must be an object with a call/item name as key.`);
4933
+ }
4934
+ const itemEntries = Object.entries(palletObj);
4935
+ if (itemEntries.length !== 1) {
4936
+ throw new CliError(`"${category}.${pallet}" in file "${filePath}" must contain exactly one item. Found: ${itemEntries.length === 0 ? "none" : itemEntries.map(([k]) => k).join(", ")}.`);
4937
+ }
4938
+ const [item, args] = itemEntries[0];
4939
+ return {
4940
+ chain,
4941
+ category,
4942
+ pallet,
4943
+ item,
4944
+ args: args ?? undefined
4945
+ };
4946
+ }
4947
+
4766
4948
  // src/core/update-notifier.ts
4767
4949
  init_store();
4768
4950
  import { readFileSync } from "node:fs";
@@ -4973,12 +5155,24 @@ if (process.argv[2] === "__complete") {
4973
5155
  process.exit(0);
4974
5156
  })();
4975
5157
  } else {
4976
- let printHelp = function() {
5158
+ let collectVarFlags = function(argv) {
5159
+ const vars = [];
5160
+ for (let i = 0;i < argv.length; i++) {
5161
+ if (argv[i] === "--var" && i + 1 < argv.length) {
5162
+ vars.push(argv[i + 1]);
5163
+ i++;
5164
+ } else if (argv[i].startsWith("--var=")) {
5165
+ vars.push(argv[i].slice(6));
5166
+ }
5167
+ }
5168
+ return parseVarFlags(vars);
5169
+ }, printHelp = function() {
4977
5170
  console.log(`dot/${version} \u2014 Polkadot CLI`);
4978
5171
  console.log();
4979
5172
  console.log("Usage:");
4980
5173
  console.log(" dot <category>[.Pallet[.Item]] [args] [options]");
4981
5174
  console.log(" dot [Chain.]<category>[.Pallet[.Item]] [args] [options]");
5175
+ console.log(" dot <file.yaml|file.json> [options]");
4982
5176
  console.log();
4983
5177
  console.log("Categories:");
4984
5178
  console.log(" query Query on-chain storage");
@@ -4996,6 +5190,7 @@ if (process.argv[2] === "__complete") {
4996
5190
  console.log(" dot events.Balances List events in Balances");
4997
5191
  console.log(" dot apis.Core.version Call a runtime API");
4998
5192
  console.log(" dot polkadot.query.System.Number With chain prefix");
5193
+ console.log(" dot ./transfer.yaml --from alice Run from file");
4999
5194
  console.log();
5000
5195
  console.log("Commands:");
5001
5196
  console.log(" inspect [target] Inspect chain metadata (alias: explore)");
@@ -5029,11 +5224,49 @@ if (process.argv[2] === "__complete") {
5029
5224
  default: "finalized"
5030
5225
  }).option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
5031
5226
  default: 100
5032
- }).option("--dump", "Dump all entries of a storage map (without specifying a key)").action(async (dotpath, args, opts) => {
5227
+ }).option("--dump", "Dump all entries of a storage map (without specifying a key)").option("--var <kv>", "Template variable for file input (KEY=VALUE, repeatable)").action(async (dotpath, args, opts) => {
5033
5228
  if (!dotpath) {
5034
5229
  printHelp();
5035
5230
  return;
5036
5231
  }
5232
+ if (isFilePath(dotpath)) {
5233
+ const cliVars = collectVarFlags(process.argv);
5234
+ const cmd = await loadCommandFile(dotpath, cliVars);
5235
+ const effectiveChain2 = opts.chain ?? cmd.chain;
5236
+ const handlerOpts2 = { chain: effectiveChain2, rpc: opts.rpc, output: opts.output };
5237
+ const target2 = `${cmd.pallet}.${cmd.item}`;
5238
+ switch (cmd.category) {
5239
+ case "tx":
5240
+ await handleTx(target2, args, {
5241
+ ...handlerOpts2,
5242
+ from: opts.from,
5243
+ dryRun: opts.dryRun,
5244
+ encode: opts.encode,
5245
+ ext: opts.ext,
5246
+ wait: opts.wait,
5247
+ parsedArgs: cmd.args
5248
+ });
5249
+ break;
5250
+ case "query":
5251
+ await handleQuery(target2, args, {
5252
+ ...handlerOpts2,
5253
+ limit: opts.limit,
5254
+ dump: opts.dump,
5255
+ parsedArgs: cmd.args
5256
+ });
5257
+ break;
5258
+ case "const":
5259
+ await handleConst(target2, handlerOpts2);
5260
+ break;
5261
+ case "apis":
5262
+ await handleApis2(target2, args, {
5263
+ ...handlerOpts2,
5264
+ parsedArgs: cmd.args
5265
+ });
5266
+ break;
5267
+ }
5268
+ return;
5269
+ }
5037
5270
  const config = await loadConfig2();
5038
5271
  const knownChains = Object.keys(config.chains);
5039
5272
  let parsed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "CLI tool for querying Polkadot-ecosystem on-chain state",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,7 +45,8 @@
45
45
  "@polkadot-labs/hdkd-helpers": "^0.0.27",
46
46
  "cac": "^6.7.14",
47
47
  "polkadot-api": "^1.23.3",
48
- "ws": "^8.19.0"
48
+ "ws": "^8.19.0",
49
+ "yaml": "^2.8.3"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@biomejs/biome": "^2.4.5",