polkadot-cli 1.6.1 → 1.8.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 +229 -0
  2. package/dist/cli.mjs +438 -23
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -20,6 +20,8 @@ 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
24
+ - ✅ Parachain sovereign accounts — derive child and sibling addresses from a parachain ID
23
25
 
24
26
  ### Preconfigured chains
25
27
 
@@ -495,6 +497,28 @@ dot tx Balances.transfer_keep_alive 5FHneW46... 1000000000000 --encode
495
497
  dot tx Sudo.sudo $(dot tx System.remark 0xcafe --encode) --from alice
496
498
  ```
497
499
 
500
+ #### Decode call data to YAML / JSON
501
+
502
+ Decode a hex-encoded call into a YAML or JSON file that is compatible with [file-based commands](#file-based-commands). This is useful for inspecting opaque call data, sharing human-readable transaction definitions, or editing parameters before re-submitting. Works offline from cached metadata and does not require `--from`.
503
+
504
+ ```bash
505
+ # Decode a raw hex call to YAML
506
+ dot tx.0x0001076465616462656566 --yaml
507
+
508
+ # Decode a raw hex call to JSON
509
+ dot tx.0x0001076465616462656566 --json
510
+
511
+ # Encode a named call and output as YAML
512
+ dot tx.System.remark 0xdeadbeef --yaml
513
+
514
+ # Round-trip: encode to hex, decode to YAML, re-encode from file
515
+ dot tx.System.remark 0xdeadbeef --encode # 0x0001076465616462656566
516
+ dot tx.0x0001076465616462656566 --yaml > remark.yaml
517
+ dot ./remark.yaml --encode # same hex
518
+ ```
519
+
520
+ `--yaml` / `--json` are mutually exclusive with each other and with `--encode` and `--dry-run`.
521
+
498
522
  Both dry-run and submission display the encoded call hex and a decoded human-readable form:
499
523
 
500
524
  ```
@@ -572,6 +596,185 @@ For manual override, use `--ext` with a JSON object:
572
596
  dot tx System.remark 0xdeadbeef --from alice --ext '{"MyExtension":{"value":"..."}}'
573
597
  ```
574
598
 
599
+ ### File-based commands
600
+
601
+ Run any `dot` command from a YAML or JSON file. Especially useful for complex calls like XCM messages that are hard to construct inline.
602
+
603
+ **Teleport DOT** from Asset Hub to the relay chain:
604
+
605
+ ```yaml
606
+ # teleport-dot.xcm.yaml
607
+ chain: polkadot-asset-hub
608
+ tx:
609
+ PolkadotXcm:
610
+ limited_teleport_assets:
611
+ dest:
612
+ type: V4
613
+ value:
614
+ parents: 1
615
+ interior:
616
+ type: Here
617
+ beneficiary:
618
+ type: V4
619
+ value:
620
+ parents: 0
621
+ interior:
622
+ type: X1
623
+ value:
624
+ - type: AccountId32
625
+ value:
626
+ network: null
627
+ id: "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
628
+ assets:
629
+ type: V4
630
+ value:
631
+ - id:
632
+ parents: 1
633
+ interior:
634
+ type: Here
635
+ fun:
636
+ type: Fungible
637
+ value: 10000000000
638
+ fee_asset_item: 0
639
+ weight_limit:
640
+ type: Unlimited
641
+ ```
642
+
643
+ **Reserve transfer USDC** (asset 1337, 6 decimals) from Asset Hub to Hydration:
644
+
645
+ ```yaml
646
+ # reserve-transfer-usdc.xcm.yaml
647
+ chain: polkadot-asset-hub
648
+ tx:
649
+ PolkadotXcm:
650
+ limited_reserve_transfer_assets:
651
+ dest:
652
+ type: V4
653
+ value:
654
+ parents: 1
655
+ interior:
656
+ type: X1
657
+ value:
658
+ - type: Parachain
659
+ value: 2034
660
+ beneficiary:
661
+ type: V4
662
+ value:
663
+ parents: 0
664
+ interior:
665
+ type: X1
666
+ value:
667
+ - type: AccountId32
668
+ value:
669
+ network: null
670
+ id: "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
671
+ assets:
672
+ type: V4
673
+ value:
674
+ - id:
675
+ parents: 0
676
+ interior:
677
+ type: X2
678
+ value:
679
+ - type: PalletInstance
680
+ value: 50
681
+ - type: GeneralIndex
682
+ value: 1337
683
+ fun:
684
+ type: Fungible
685
+ value: 10000000
686
+ fee_asset_item: 0
687
+ weight_limit:
688
+ type: Unlimited
689
+ ```
690
+
691
+ The same teleport in JSON:
692
+
693
+ ```json
694
+ {
695
+ "chain": "polkadot-asset-hub",
696
+ "tx": {
697
+ "PolkadotXcm": {
698
+ "limited_teleport_assets": {
699
+ "dest": { "type": "V4", "value": { "parents": 1, "interior": { "type": "Here" } } },
700
+ "beneficiary": {
701
+ "type": "V4",
702
+ "value": {
703
+ "parents": 0,
704
+ "interior": {
705
+ "type": "X1",
706
+ "value": [{ "type": "AccountId32", "value": { "network": null, "id": "0xd435...a27d" } }]
707
+ }
708
+ }
709
+ },
710
+ "assets": {
711
+ "type": "V4",
712
+ "value": [{
713
+ "id": { "parents": 1, "interior": { "type": "Here" } },
714
+ "fun": { "type": "Fungible", "value": 10000000000 }
715
+ }]
716
+ },
717
+ "fee_asset_item": 0,
718
+ "weight_limit": { "type": "Unlimited" }
719
+ }
720
+ }
721
+ }
722
+ }
723
+ ```
724
+
725
+ ```bash
726
+ # Run from file
727
+ dot ./teleport-dot.xcm.yaml --from alice --dry-run
728
+
729
+ # Encode only
730
+ dot ./reserve-transfer-usdc.xcm.yaml --encode
731
+
732
+ # Override variables
733
+ dot ./transfer.xcm.yaml --var AMOUNT=2000000000000 --from alice
734
+ ```
735
+
736
+ The file format uses a required category wrapper (`tx`, `query`, `const`, or `apis`) with the structure `category > Pallet > Item > args`:
737
+
738
+ ```yaml
739
+ # Simple transaction
740
+ tx:
741
+ System:
742
+ remark:
743
+ - "0xdeadbeef"
744
+ ```
745
+
746
+ ```yaml
747
+ # Storage query
748
+ chain: polkadot
749
+ query:
750
+ System:
751
+ Account:
752
+ - "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
753
+ ```
754
+
755
+ ```yaml
756
+ # Constant lookup
757
+ chain: polkadot
758
+ const:
759
+ Balances:
760
+ ExistentialDeposit:
761
+ ```
762
+
763
+ **Variable substitution** uses shell-style `${VAR}` with optional defaults `${VAR:-default}`. Variables are resolved in order: `--var` flags > environment variables > `vars:` section defaults.
764
+
765
+ ```yaml
766
+ chain: ${CHAIN:-polkadot}
767
+ vars:
768
+ AMOUNT: "1000000000000"
769
+ tx:
770
+ Balances:
771
+ transfer_keep_alive:
772
+ dest: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
773
+ value: ${AMOUNT}
774
+ ```
775
+
776
+ All existing flags work with file input — `--chain` overrides the file's `chain:` field, `--from`, `--dry-run`, `--encode`, `--yaml`, `--json`, `--output`, etc. behave identically to inline commands.
777
+
575
778
  ### Compute hashes
576
779
 
577
780
  Compute cryptographic hashes commonly used in Substrate. Supports BLAKE2b-256, BLAKE2b-128, Keccak-256, and SHA-256.
@@ -595,6 +798,32 @@ dot hash blake2b256 0xdeadbeef --output json
595
798
 
596
799
  Run `dot hash` with no arguments to see all available algorithms.
597
800
 
801
+ ### Parachain sovereign accounts
802
+
803
+ Derive the sovereign account addresses for a parachain. These are deterministic accounts derived from a parachain ID — no chain connection required.
804
+
805
+ - **Child** accounts represent a parachain on the relay chain (prefix `"para"`)
806
+ - **Sibling** accounts represent a parachain on another parachain (prefix `"sibl"`)
807
+
808
+ ```bash
809
+ # Show both child and sibling accounts
810
+ dot parachain 1000
811
+
812
+ # Show only the child (relay chain) account
813
+ dot parachain 2004 --type child
814
+
815
+ # Show only the sibling (parachain-to-parachain) account
816
+ dot parachain 2004 --type sibling
817
+
818
+ # Use Polkadot SS58 prefix (default: 42)
819
+ dot parachain 1000 --prefix 0
820
+
821
+ # JSON output
822
+ dot parachain 1000 --output json
823
+ ```
824
+
825
+ Run `dot parachain` with no arguments to see usage and examples.
826
+
598
827
  ### Getting help
599
828
 
600
829
  Every command supports `--help` to show its detailed usage, available actions, and examples:
package/dist/cli.mjs CHANGED
@@ -966,6 +966,7 @@ function parseValue(arg) {
966
966
  // src/commands/tx.ts
967
967
  import { getViewBuilder } from "@polkadot-api/view-builder";
968
968
  import { Binary as Binary2 } from "polkadot-api";
969
+ import { stringify as stringifyYaml } from "yaml";
969
970
  async function parseStructArgs(meta, fields, args, callLabel) {
970
971
  const fieldNames = Object.keys(fields);
971
972
  if (args.length !== fieldNames.length) {
@@ -1080,8 +1081,8 @@ function normalizeValue(lookup, entry, value) {
1080
1081
  return;
1081
1082
  }
1082
1083
  case "primitive": {
1084
+ const prim = resolved.value;
1083
1085
  if (typeof value === "string") {
1084
- const prim = resolved.value;
1085
1086
  switch (prim) {
1086
1087
  case "bool":
1087
1088
  return value === "true";
@@ -1101,12 +1102,26 @@ function normalizeValue(lookup, entry, value) {
1101
1102
  return parseInt(value, 10);
1102
1103
  }
1103
1104
  }
1105
+ if (typeof value === "number") {
1106
+ switch (prim) {
1107
+ case "u64":
1108
+ case "u128":
1109
+ case "u256":
1110
+ case "i64":
1111
+ case "i128":
1112
+ case "i256":
1113
+ return BigInt(value);
1114
+ }
1115
+ }
1104
1116
  return value;
1105
1117
  }
1106
1118
  case "compact": {
1107
1119
  if (typeof value === "string") {
1108
1120
  return resolved.isBig ? BigInt(value) : parseInt(value, 10);
1109
1121
  }
1122
+ if (typeof value === "number" && resolved.isBig) {
1123
+ return BigInt(value);
1124
+ }
1110
1125
  return value;
1111
1126
  }
1112
1127
  default:
@@ -1330,7 +1345,10 @@ async function handleApis(target, args, opts) {
1330
1345
  const names = api.methods.map((m) => m.name);
1331
1346
  throw new Error(suggestMessage(`method in ${api.name}`, methodName, names));
1332
1347
  }
1333
- const parsedArgs = await parseRuntimeApiArgs(meta, method, args);
1348
+ 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)) : [
1349
+ typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
1350
+ ];
1351
+ const parsedArgs = await parseRuntimeApiArgs(meta, method, effectiveArgs);
1334
1352
  const unsafeApi = clientHandle.client.getUnsafeApi();
1335
1353
  const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
1336
1354
  const format = opts.output ?? "pretty";
@@ -1950,6 +1968,12 @@ async function generateCompletions(currentWord, precedingWords) {
1950
1968
  if (firstArg === "hash") {
1951
1969
  return filterPrefix(getAlgorithmNames(), currentWord);
1952
1970
  }
1971
+ if (firstArg === "parachain") {
1972
+ if (prevWord === "--type") {
1973
+ return filterPrefix(["child", "sibling"], currentWord);
1974
+ }
1975
+ return [];
1976
+ }
1953
1977
  return completeDotpath(currentWord, config, knownChains, precedingWords);
1954
1978
  }
1955
1979
  function detectCategory(words, _knownChains) {
@@ -1973,7 +1997,7 @@ async function completeDotpath(currentWord, config, knownChains, precedingWords)
1973
1997
  const numComplete = completeSegments.length;
1974
1998
  if (numComplete === 0 && !endsWithDot) {
1975
1999
  const candidates = [
1976
- ...CATEGORIES.map((c) => `${c}.`),
2000
+ ...CATEGORIES2.map((c) => `${c}.`),
1977
2001
  ...knownChains.map((c) => `${c}.`),
1978
2002
  ...NAMED_COMMANDS
1979
2003
  ];
@@ -2024,11 +2048,11 @@ async function completeDotpath(currentWord, config, knownChains, precedingWords)
2024
2048
  if (firstIsChain) {
2025
2049
  const chainName = first;
2026
2050
  if (numComplete === 1 && endsWithDot) {
2027
- const candidates = CATEGORIES.map((c) => `${first}.${c}.`);
2051
+ const candidates = CATEGORIES2.map((c) => `${first}.${c}.`);
2028
2052
  return filterPrefix(candidates, currentWord.slice(0, -1));
2029
2053
  }
2030
2054
  if (numComplete === 1 && !endsWithDot) {
2031
- const candidates = CATEGORIES.map((c) => `${first}.${c}.`);
2055
+ const candidates = CATEGORIES2.map((c) => `${first}.${c}.`);
2032
2056
  return filterPrefix(candidates, currentWord);
2033
2057
  }
2034
2058
  if (numComplete === 2) {
@@ -2092,14 +2116,14 @@ async function completeApisCategory(prefix, numComplete, endsWithDot, segments,
2092
2116
  }
2093
2117
  return [];
2094
2118
  }
2095
- var CATEGORIES, CATEGORY_ALIASES2, NAMED_COMMANDS, CHAIN_SUBCOMMANDS, ACCOUNT_SUBCOMMANDS, GLOBAL_OPTIONS, TX_OPTIONS, QUERY_OPTIONS;
2119
+ var CATEGORIES2, CATEGORY_ALIASES2, NAMED_COMMANDS, CHAIN_SUBCOMMANDS, ACCOUNT_SUBCOMMANDS, GLOBAL_OPTIONS, TX_OPTIONS, QUERY_OPTIONS;
2096
2120
  var init_complete = __esm(() => {
2097
2121
  init_accounts_store();
2098
2122
  init_store();
2099
2123
  init_accounts();
2100
2124
  init_hash();
2101
2125
  init_metadata();
2102
- CATEGORIES = ["query", "tx", "const", "events", "errors", "apis"];
2126
+ CATEGORIES2 = ["query", "tx", "const", "events", "errors", "apis"];
2103
2127
  CATEGORY_ALIASES2 = {
2104
2128
  query: "query",
2105
2129
  tx: "tx",
@@ -2113,7 +2137,7 @@ var init_complete = __esm(() => {
2113
2137
  apis: "apis",
2114
2138
  api: "apis"
2115
2139
  };
2116
- NAMED_COMMANDS = ["chain", "account", "inspect", "hash", "completions"];
2140
+ NAMED_COMMANDS = ["chain", "account", "inspect", "hash", "parachain", "completions"];
2117
2141
  CHAIN_SUBCOMMANDS = ["add", "remove", "update", "list", "default"];
2118
2142
  ACCOUNT_SUBCOMMANDS = [
2119
2143
  "add",
@@ -2134,7 +2158,7 @@ var init_complete = __esm(() => {
2134
2158
  // src/cli.ts
2135
2159
  import cac from "cac";
2136
2160
  // package.json
2137
- var version = "1.6.1";
2161
+ var version = "1.8.0";
2138
2162
 
2139
2163
  // src/commands/account.ts
2140
2164
  init_accounts_store();
@@ -2621,7 +2645,10 @@ async function handleApis2(target, args, opts) {
2621
2645
  const names = api.methods.map((m) => m.name);
2622
2646
  throw new Error(suggestMessage(`method in ${api.name}`, methodName, names));
2623
2647
  }
2624
- const parsedArgs = await parseRuntimeApiArgs2(meta, method, args);
2648
+ 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)) : [
2649
+ typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
2650
+ ];
2651
+ const parsedArgs = await parseRuntimeApiArgs2(meta, method, effectiveArgs);
2625
2652
  const unsafeApi = clientHandle.client.getUnsafeApi();
2626
2653
  const result = await unsafeApi.apis[api.name][method.name](...parsedArgs);
2627
2654
  const format = opts.output ?? "pretty";
@@ -3745,6 +3772,97 @@ function registerInspectCommand(cli) {
3745
3772
  });
3746
3773
  }
3747
3774
 
3775
+ // src/commands/parachain.ts
3776
+ init_accounts();
3777
+ init_output();
3778
+
3779
+ // src/core/parachain.ts
3780
+ var SOVEREIGN_ACCOUNT_TYPES = ["child", "sibling"];
3781
+ var PREFIXES = {
3782
+ child: new Uint8Array([112, 97, 114, 97]),
3783
+ sibling: new Uint8Array([115, 105, 98, 108])
3784
+ };
3785
+ function deriveSovereignAccount(paraId, type) {
3786
+ const result = new Uint8Array(32);
3787
+ result.set(PREFIXES[type], 0);
3788
+ new DataView(result.buffer).setUint32(4, paraId, true);
3789
+ return result;
3790
+ }
3791
+ function isValidParaId(value) {
3792
+ return Number.isInteger(value) && value >= 0 && value <= 4294967295;
3793
+ }
3794
+
3795
+ // src/commands/parachain.ts
3796
+ init_errors();
3797
+ function printParachainHelp() {
3798
+ console.log(`${BOLD}Usage:${RESET} dot parachain <paraId> [options]
3799
+ `);
3800
+ console.log(`${BOLD}Description:${RESET}`);
3801
+ console.log(` Derive sovereign account addresses for a parachain.
3802
+ `);
3803
+ console.log(` ${DIM}Child accounts represent a parachain on the relay chain.${RESET}`);
3804
+ console.log(` ${DIM}Sibling accounts represent a parachain on another parachain.${RESET}
3805
+ `);
3806
+ console.log(`${BOLD}Options:${RESET}`);
3807
+ console.log(` ${CYAN}--type <child|sibling>${RESET} ${DIM}Account type (default: both)${RESET}`);
3808
+ console.log(` ${CYAN}--prefix <N>${RESET} ${DIM}SS58 prefix (default: 42)${RESET}`);
3809
+ console.log(` ${CYAN}--output json${RESET} ${DIM}Output as JSON${RESET}`);
3810
+ console.log(`
3811
+ ${BOLD}Examples:${RESET}`);
3812
+ console.log(` ${DIM}$ dot parachain 1000${RESET}`);
3813
+ console.log(` ${DIM}$ dot parachain 2004 --prefix 0${RESET}`);
3814
+ console.log(` ${DIM}$ dot parachain 1000 --type sibling${RESET}`);
3815
+ console.log(` ${DIM}$ dot parachain 2000 --output json${RESET}`);
3816
+ }
3817
+ function validateType(type) {
3818
+ const lower = type.toLowerCase();
3819
+ if (lower === "child" || lower === "sibling")
3820
+ return lower;
3821
+ throw new CliError(`Unknown account type "${type}". Valid types: child, sibling.`);
3822
+ }
3823
+ function registerParachainCommand(cli) {
3824
+ cli.command("parachain [paraId]", "Derive parachain sovereign accounts").option("--type <type>", "Account type: child, sibling (default: both)").option("--prefix <number>", "SS58 prefix for address encoding (default: 42)").action(async (paraIdStr, opts) => {
3825
+ if (!paraIdStr) {
3826
+ printParachainHelp();
3827
+ return;
3828
+ }
3829
+ const paraId = Number(paraIdStr);
3830
+ if (!isValidParaId(paraId)) {
3831
+ throw new CliError(`Invalid parachain ID "${paraIdStr}". Must be a non-negative integer (0 to 4294967295).`);
3832
+ }
3833
+ const prefix = opts.prefix != null ? Number(opts.prefix) : 42;
3834
+ if (Number.isNaN(prefix) || prefix < 0) {
3835
+ throw new CliError(`Invalid prefix "${opts.prefix}". Must be a non-negative integer.`);
3836
+ }
3837
+ const types = opts.type ? [validateType(opts.type)] : SOVEREIGN_ACCOUNT_TYPES;
3838
+ const format = opts.output ?? "pretty";
3839
+ if (format === "json") {
3840
+ const result = { paraId, prefix };
3841
+ for (const type of types) {
3842
+ const accountId = deriveSovereignAccount(paraId, type);
3843
+ result[type] = {
3844
+ publicKey: publicKeyToHex(accountId),
3845
+ ss58: toSs58(accountId, prefix)
3846
+ };
3847
+ }
3848
+ console.log(formatJson(result));
3849
+ } else {
3850
+ printHeading(`Parachain ${paraId} — Sovereign Accounts`);
3851
+ for (const type of types) {
3852
+ const accountId = deriveSovereignAccount(paraId, type);
3853
+ const hex = publicKeyToHex(accountId);
3854
+ const ss58 = toSs58(accountId, prefix);
3855
+ const label = type.charAt(0).toUpperCase() + type.slice(1);
3856
+ console.log(` ${BOLD}${label}:${RESET}`);
3857
+ console.log(` ${BOLD}Public Key:${RESET} ${hex}`);
3858
+ console.log(` ${BOLD}SS58:${RESET} ${ss58}`);
3859
+ console.log(` ${BOLD}Prefix:${RESET} ${prefix}`);
3860
+ console.log();
3861
+ }
3862
+ }
3863
+ });
3864
+ }
3865
+
3748
3866
  // src/commands/query.ts
3749
3867
  init_store();
3750
3868
  init_client();
@@ -3814,7 +3932,10 @@ async function handleQuery(target, keys, opts) {
3814
3932
  }
3815
3933
  const unsafeApi = clientHandle.client.getUnsafeApi();
3816
3934
  const storageApi = unsafeApi.query[palletInfo.name][storageItem.name];
3817
- const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, keys);
3935
+ 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)) : [
3936
+ typeof opts.parsedArgs === "object" ? JSON.stringify(opts.parsedArgs) : String(opts.parsedArgs)
3937
+ ];
3938
+ const parsedKeys = await parseStorageKeys(meta, palletInfo.name, storageItem, effectiveKeys);
3818
3939
  const format = opts.output ?? "pretty";
3819
3940
  if (storageItem.type === "map" && parsedKeys.length === 0) {
3820
3941
  if (!opts.dump) {
@@ -3895,6 +4016,7 @@ init_errors();
3895
4016
  init_focused_inspect();
3896
4017
  import { getViewBuilder as getViewBuilder2 } from "@polkadot-api/view-builder";
3897
4018
  import { Binary as Binary3 } from "polkadot-api";
4019
+ import { stringify as stringifyYaml2 } from "yaml";
3898
4020
  function parseWaitLevel(raw) {
3899
4021
  switch (raw) {
3900
4022
  case "broadcast":
@@ -3945,7 +4067,7 @@ async function handleTx(target, args, opts) {
3945
4067
  console.log();
3946
4068
  return;
3947
4069
  }
3948
- if (!opts.from && !opts.encode) {
4070
+ if (!opts.from && !opts.encode && !opts.yaml && !opts.json) {
3949
4071
  if (isRawCall) {
3950
4072
  throw new Error("--from is required (or use --encode to output hex without signing)");
3951
4073
  }
@@ -3958,6 +4080,15 @@ async function handleTx(target, args, opts) {
3958
4080
  if (opts.encode && isRawCall) {
3959
4081
  throw new Error("--encode cannot be used with raw call hex (already encoded)");
3960
4082
  }
4083
+ if ((opts.yaml || opts.json) && opts.encode) {
4084
+ throw new Error("--yaml/--json and --encode are mutually exclusive");
4085
+ }
4086
+ if ((opts.yaml || opts.json) && opts.dryRun) {
4087
+ throw new Error("--yaml/--json and --dry-run are mutually exclusive");
4088
+ }
4089
+ if (opts.yaml && opts.json) {
4090
+ throw new Error("--yaml and --json are mutually exclusive");
4091
+ }
3961
4092
  const config = await loadConfig();
3962
4093
  const effectiveChain = opts.chain;
3963
4094
  let pallet;
@@ -3968,9 +4099,10 @@ async function handleTx(target, args, opts) {
3968
4099
  callName = target.slice(dotIdx + 1);
3969
4100
  }
3970
4101
  const { name: chainName, chain: chainConfig } = resolveChain(config, effectiveChain);
3971
- const signer = opts.encode ? undefined : await resolveAccountSigner(opts.from);
4102
+ const decodeOnly = opts.encode || opts.yaml || opts.json;
4103
+ const signer = decodeOnly ? undefined : await resolveAccountSigner(opts.from);
3972
4104
  let clientHandle;
3973
- if (!opts.encode) {
4105
+ if (!decodeOnly) {
3974
4106
  clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
3975
4107
  }
3976
4108
  try {
@@ -3987,7 +4119,7 @@ async function handleTx(target, args, opts) {
3987
4119
  }
3988
4120
  let unsafeApi;
3989
4121
  let txOptions;
3990
- if (!opts.encode) {
4122
+ if (!decodeOnly) {
3991
4123
  const userExtOverrides = parseExtOption(opts.ext);
3992
4124
  const customSignedExtensions = buildCustomSignedExtensions(meta, userExtOverrides);
3993
4125
  txOptions = Object.keys(customSignedExtensions).length > 0 ? { customSignedExtensions } : undefined;
@@ -4000,9 +4132,14 @@ async function handleTx(target, args, opts) {
4000
4132
  throw new Error(`Extra arguments are not allowed when submitting a raw call hex.
4001
4133
  ` + "Usage: dot tx 0x<call_hex> --from <account>");
4002
4134
  }
4135
+ callHex = target;
4136
+ if (opts.yaml || opts.json) {
4137
+ const fileObj = decodeCallToFileFormat(meta, callHex, chainName);
4138
+ outputFileFormat(fileObj, !!opts.yaml);
4139
+ return;
4140
+ }
4003
4141
  const callBinary = Binary3.fromHex(target);
4004
4142
  tx = await unsafeApi.txFromCallData(callBinary);
4005
- callHex = target;
4006
4143
  } else {
4007
4144
  const palletNames = getPalletNames(meta);
4008
4145
  const palletInfo = findPallet(meta, pallet);
@@ -4014,12 +4151,19 @@ async function handleTx(target, args, opts) {
4014
4151
  const callNames = palletInfo.calls.map((c) => c.name);
4015
4152
  throw new Error(suggestMessage(`call in ${palletInfo.name}`, callName, callNames));
4016
4153
  }
4017
- const callData = await parseCallArgs(meta, palletInfo.name, callInfo.name, args);
4018
- if (opts.encode) {
4154
+ const effectiveArgs = opts.parsedArgs !== undefined ? fileArgsToStrings(opts.parsedArgs) : args;
4155
+ const callData = await parseCallArgs(meta, palletInfo.name, callInfo.name, effectiveArgs);
4156
+ if (opts.encode || opts.yaml || opts.json) {
4019
4157
  const { codec, location } = meta.builder.buildCall(palletInfo.name, callInfo.name);
4020
4158
  const encodedArgs = codec.enc(callData);
4021
4159
  const fullCall = new Uint8Array([location[0], location[1], ...encodedArgs]);
4022
- console.log(Binary3.fromBytes(fullCall).asHex());
4160
+ const hex = Binary3.fromBytes(fullCall).asHex();
4161
+ if (opts.encode) {
4162
+ console.log(hex);
4163
+ return;
4164
+ }
4165
+ const fileObj = decodeCallToFileFormat(meta, hex, chainName);
4166
+ outputFileFormat(fileObj, !!opts.yaml);
4023
4167
  return;
4024
4168
  }
4025
4169
  tx = unsafeApi.tx[palletInfo.name][callInfo.name](callData);
@@ -4140,6 +4284,58 @@ function decodeCallFallback(meta, callHex) {
4140
4284
  const argsStr = formatRawDecoded(args);
4141
4285
  return `${palletName2}.${callName} ${argsStr}`;
4142
4286
  }
4287
+ function decodeCallToFileFormat(meta, callHex, chainName) {
4288
+ const callTypeId = meta.lookup.call;
4289
+ if (callTypeId == null)
4290
+ throw new Error("No RuntimeCall type ID in metadata");
4291
+ const codec = meta.builder.buildDefinition(callTypeId);
4292
+ const decoded = codec.dec(Binary3.fromHex(callHex).asBytes());
4293
+ const palletName2 = decoded.type;
4294
+ const call = decoded.value;
4295
+ const callName = call.type;
4296
+ const args = call.value;
4297
+ return {
4298
+ chain: chainName,
4299
+ tx: {
4300
+ [palletName2]: {
4301
+ [callName]: sanitizeForSerialization(args) ?? null
4302
+ }
4303
+ }
4304
+ };
4305
+ }
4306
+ function sanitizeForSerialization(value) {
4307
+ if (value === undefined || value === null)
4308
+ return null;
4309
+ if (value instanceof Binary3)
4310
+ return value.asHex();
4311
+ if (typeof value === "bigint") {
4312
+ if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) {
4313
+ return Number(value);
4314
+ }
4315
+ return value.toString();
4316
+ }
4317
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4318
+ return value;
4319
+ }
4320
+ if (Array.isArray(value)) {
4321
+ return value.map(sanitizeForSerialization);
4322
+ }
4323
+ if (typeof value === "object") {
4324
+ const result = {};
4325
+ for (const [k, v] of Object.entries(value)) {
4326
+ result[k] = sanitizeForSerialization(v);
4327
+ }
4328
+ return result;
4329
+ }
4330
+ return String(value);
4331
+ }
4332
+ function outputFileFormat(obj, asYaml) {
4333
+ if (asYaml) {
4334
+ process.stdout.write(stringifyYaml2(obj));
4335
+ } else {
4336
+ console.log(JSON.stringify(obj, null, 2));
4337
+ }
4338
+ }
4143
4339
  function formatRawDecoded(value) {
4144
4340
  if (value === undefined || value === null)
4145
4341
  return "null";
@@ -4437,8 +4633,8 @@ function normalizeValue2(lookup, entry, value) {
4437
4633
  return;
4438
4634
  }
4439
4635
  case "primitive": {
4636
+ const prim = resolved.value;
4440
4637
  if (typeof value === "string") {
4441
- const prim = resolved.value;
4442
4638
  switch (prim) {
4443
4639
  case "bool":
4444
4640
  return value === "true";
@@ -4458,18 +4654,52 @@ function normalizeValue2(lookup, entry, value) {
4458
4654
  return parseInt(value, 10);
4459
4655
  }
4460
4656
  }
4657
+ if (typeof value === "number") {
4658
+ switch (prim) {
4659
+ case "u64":
4660
+ case "u128":
4661
+ case "u256":
4662
+ case "i64":
4663
+ case "i128":
4664
+ case "i256":
4665
+ return BigInt(value);
4666
+ }
4667
+ }
4461
4668
  return value;
4462
4669
  }
4463
4670
  case "compact": {
4464
4671
  if (typeof value === "string") {
4465
4672
  return resolved.isBig ? BigInt(value) : parseInt(value, 10);
4466
4673
  }
4674
+ if (typeof value === "number" && resolved.isBig) {
4675
+ return BigInt(value);
4676
+ }
4467
4677
  return value;
4468
4678
  }
4469
4679
  default:
4470
4680
  return value;
4471
4681
  }
4472
4682
  }
4683
+ function fileArgsToStrings(args) {
4684
+ if (args == null)
4685
+ return [];
4686
+ if (typeof args === "object" && !Array.isArray(args)) {
4687
+ return Object.values(args).map(serializeForCli);
4688
+ }
4689
+ if (Array.isArray(args)) {
4690
+ return args.map(serializeForCli);
4691
+ }
4692
+ return [serializeForCli(args)];
4693
+ }
4694
+ function serializeForCli(v) {
4695
+ if (typeof v === "string")
4696
+ return v;
4697
+ if (typeof v === "number" || typeof v === "bigint")
4698
+ return String(v);
4699
+ if (typeof v === "boolean" || v === null)
4700
+ return String(v);
4701
+ return JSON.stringify(v);
4702
+ }
4473
4703
  function parseEnumShorthand2(arg) {
4474
4704
  if (arg.startsWith("{") || arg.startsWith("[") || arg.startsWith("0x"))
4475
4705
  return null;
@@ -4763,6 +4993,130 @@ async function saveConfig2(config) {
4763
4993
  `);
4764
4994
  }
4765
4995
 
4996
+ // src/core/file-loader.ts
4997
+ init_errors();
4998
+ import { parse as parseYaml } from "yaml";
4999
+ var CATEGORIES = ["tx", "query", "const", "apis"];
5000
+ var FILE_EXTENSIONS = [".json", ".yaml", ".yml"];
5001
+ function isFilePath(dotpath) {
5002
+ if (FILE_EXTENSIONS.some((ext) => dotpath.endsWith(ext)))
5003
+ return true;
5004
+ if (dotpath.startsWith("./") || dotpath.startsWith("/"))
5005
+ return true;
5006
+ return false;
5007
+ }
5008
+ function parseVarFlags(varFlags) {
5009
+ if (!varFlags)
5010
+ return {};
5011
+ const flags = Array.isArray(varFlags) ? varFlags : [varFlags];
5012
+ const vars = {};
5013
+ for (const flag of flags) {
5014
+ const eqIdx = flag.indexOf("=");
5015
+ if (eqIdx === -1) {
5016
+ throw new CliError(`Invalid --var format "${flag}". Expected KEY=VALUE.`);
5017
+ }
5018
+ const key = flag.slice(0, eqIdx);
5019
+ const value = flag.slice(eqIdx + 1);
5020
+ if (!key) {
5021
+ throw new CliError(`Invalid --var format "${flag}". Key cannot be empty.`);
5022
+ }
5023
+ vars[key] = value;
5024
+ }
5025
+ return vars;
5026
+ }
5027
+ function substituteVars(text, vars) {
5028
+ return text.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
5029
+ const defaultSep = expr.indexOf(":-");
5030
+ let varName;
5031
+ let defaultValue;
5032
+ if (defaultSep !== -1) {
5033
+ varName = expr.slice(0, defaultSep);
5034
+ defaultValue = expr.slice(defaultSep + 2);
5035
+ } else {
5036
+ varName = expr;
5037
+ }
5038
+ if (varName in vars)
5039
+ return vars[varName];
5040
+ const envVal = process.env[varName];
5041
+ if (envVal !== undefined)
5042
+ return envVal;
5043
+ if (defaultValue !== undefined)
5044
+ return defaultValue;
5045
+ 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}.`);
5046
+ });
5047
+ }
5048
+ async function loadCommandFile(filePath, cliVars) {
5049
+ const file = Bun.file(filePath);
5050
+ const exists = await file.exists();
5051
+ if (!exists) {
5052
+ throw new CliError(`File not found: ${filePath}`);
5053
+ }
5054
+ const rawText = await file.text();
5055
+ if (!rawText.trim()) {
5056
+ throw new CliError(`File is empty: ${filePath}`);
5057
+ }
5058
+ const isJson = filePath.endsWith(".json");
5059
+ const fileVars = {};
5060
+ try {
5061
+ const preParsed = isJson ? JSON.parse(rawText) : parseYaml(rawText);
5062
+ if (preParsed && typeof preParsed === "object" && !Array.isArray(preParsed) && preParsed.vars) {
5063
+ const varsSection = preParsed.vars;
5064
+ if (typeof varsSection === "object" && !Array.isArray(varsSection)) {
5065
+ for (const [key, val] of Object.entries(varsSection)) {
5066
+ fileVars[key] = String(val);
5067
+ }
5068
+ }
5069
+ }
5070
+ } catch {}
5071
+ const mergedVars = { ...fileVars, ...cliVars };
5072
+ const substituted = substituteVars(rawText, mergedVars);
5073
+ let parsed;
5074
+ try {
5075
+ parsed = isJson ? JSON.parse(substituted) : parseYaml(substituted);
5076
+ } catch (err) {
5077
+ const format = isJson ? "JSON" : "YAML";
5078
+ const msg = err instanceof Error ? err.message : String(err);
5079
+ throw new CliError(`Failed to parse ${format} file "${filePath}": ${msg}`);
5080
+ }
5081
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
5082
+ throw new CliError(`File "${filePath}" must contain a YAML/JSON object (not an array or scalar).`);
5083
+ }
5084
+ const doc = parsed;
5085
+ const chain = doc.chain != null ? String(doc.chain) : undefined;
5086
+ const foundCategories = CATEGORIES.filter((c) => (c in doc));
5087
+ if (foundCategories.length === 0) {
5088
+ throw new CliError(`File "${filePath}" must contain exactly one category key: ${CATEGORIES.join(", ")}. None found.`);
5089
+ }
5090
+ if (foundCategories.length > 1) {
5091
+ throw new CliError(`File "${filePath}" contains multiple category keys: ${foundCategories.join(", ")}. Only one is allowed.`);
5092
+ }
5093
+ const category = foundCategories[0];
5094
+ const categoryObj = doc[category];
5095
+ if (!categoryObj || typeof categoryObj !== "object" || Array.isArray(categoryObj)) {
5096
+ throw new CliError(`"${category}" in file "${filePath}" must be an object with a pallet name as key.`);
5097
+ }
5098
+ const palletEntries = Object.entries(categoryObj);
5099
+ if (palletEntries.length !== 1) {
5100
+ throw new CliError(`"${category}" in file "${filePath}" must contain exactly one pallet. Found: ${palletEntries.length === 0 ? "none" : palletEntries.map(([k]) => k).join(", ")}.`);
5101
+ }
5102
+ const [pallet, palletObj] = palletEntries[0];
5103
+ if (!palletObj || typeof palletObj !== "object" || Array.isArray(palletObj)) {
5104
+ throw new CliError(`"${category}.${pallet}" in file "${filePath}" must be an object with a call/item name as key.`);
5105
+ }
5106
+ const itemEntries = Object.entries(palletObj);
5107
+ if (itemEntries.length !== 1) {
5108
+ throw new CliError(`"${category}.${pallet}" in file "${filePath}" must contain exactly one item. Found: ${itemEntries.length === 0 ? "none" : itemEntries.map(([k]) => k).join(", ")}.`);
5109
+ }
5110
+ const [item, args] = itemEntries[0];
5111
+ return {
5112
+ chain,
5113
+ category,
5114
+ pallet,
5115
+ item,
5116
+ args: args ?? undefined
5117
+ };
5118
+ }
5119
+
4766
5120
  // src/core/update-notifier.ts
4767
5121
  init_store();
4768
5122
  import { readFileSync } from "node:fs";
@@ -4973,12 +5327,24 @@ if (process.argv[2] === "__complete") {
4973
5327
  process.exit(0);
4974
5328
  })();
4975
5329
  } else {
4976
- let printHelp = function() {
5330
+ let collectVarFlags = function(argv) {
5331
+ const vars = [];
5332
+ for (let i = 0;i < argv.length; i++) {
5333
+ if (argv[i] === "--var" && i + 1 < argv.length) {
5334
+ vars.push(argv[i + 1]);
5335
+ i++;
5336
+ } else if (argv[i].startsWith("--var=")) {
5337
+ vars.push(argv[i].slice(6));
5338
+ }
5339
+ }
5340
+ return parseVarFlags(vars);
5341
+ }, printHelp = function() {
4977
5342
  console.log(`dot/${version} \u2014 Polkadot CLI`);
4978
5343
  console.log();
4979
5344
  console.log("Usage:");
4980
5345
  console.log(" dot <category>[.Pallet[.Item]] [args] [options]");
4981
5346
  console.log(" dot [Chain.]<category>[.Pallet[.Item]] [args] [options]");
5347
+ console.log(" dot <file.yaml|file.json> [options]");
4982
5348
  console.log();
4983
5349
  console.log("Categories:");
4984
5350
  console.log(" query Query on-chain storage");
@@ -4996,12 +5362,16 @@ if (process.argv[2] === "__complete") {
4996
5362
  console.log(" dot events.Balances List events in Balances");
4997
5363
  console.log(" dot apis.Core.version Call a runtime API");
4998
5364
  console.log(" dot polkadot.query.System.Number With chain prefix");
5365
+ console.log(" dot ./transfer.yaml --from alice Run from file");
5366
+ console.log(" dot tx.0x1f0003... --yaml Decode hex call to YAML");
5367
+ console.log(" dot tx.System.remark 0xdead --json Encode & output as JSON file format");
4999
5368
  console.log();
5000
5369
  console.log("Commands:");
5001
5370
  console.log(" inspect [target] Inspect chain metadata (alias: explore)");
5002
5371
  console.log(" chain Manage chain configurations");
5003
5372
  console.log(" account Manage accounts");
5004
5373
  console.log(" hash Hash utilities");
5374
+ console.log(" parachain Derive parachain sovereign accounts");
5005
5375
  console.log(" completions <sh> Generate shell completions (zsh, bash, fish)");
5006
5376
  console.log();
5007
5377
  console.log("Global options:");
@@ -5024,16 +5394,57 @@ if (process.argv[2] === "__complete") {
5024
5394
  registerInspectCommand(cli);
5025
5395
  registerAccountCommands(cli);
5026
5396
  registerHashCommand(cli);
5397
+ registerParachainCommand(cli);
5027
5398
  registerCompletionsCommand(cli);
5028
- cli.command("[dotpath] [...args]").option("--from <name>", "Account to sign with (for tx)").option("--dry-run", "Estimate fees without submitting (for tx)").option("--encode", "Encode call to hex without signing (for tx)").option("--ext <json>", "Custom signed extension values as JSON (for tx)").option("-w, --wait <level>", "Resolve at: broadcast, best-block (or best), finalized (for tx)", {
5399
+ cli.command("[dotpath] [...args]").option("--from <name>", "Account to sign with (for tx)").option("--dry-run", "Estimate fees without submitting (for tx)").option("--encode", "Encode call to hex without signing (for tx)").option("--yaml", "Decode call to YAML file format (for tx)").option("--json", "Decode call to JSON file format (for tx)").option("--ext <json>", "Custom signed extension values as JSON (for tx)").option("-w, --wait <level>", "Resolve at: broadcast, best-block (or best), finalized (for tx)", {
5029
5400
  default: "finalized"
5030
5401
  }).option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
5031
5402
  default: 100
5032
- }).option("--dump", "Dump all entries of a storage map (without specifying a key)").action(async (dotpath, args, opts) => {
5403
+ }).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
5404
  if (!dotpath) {
5034
5405
  printHelp();
5035
5406
  return;
5036
5407
  }
5408
+ if (isFilePath(dotpath)) {
5409
+ const cliVars = collectVarFlags(process.argv);
5410
+ const cmd = await loadCommandFile(dotpath, cliVars);
5411
+ const effectiveChain2 = opts.chain ?? cmd.chain;
5412
+ const handlerOpts2 = { chain: effectiveChain2, rpc: opts.rpc, output: opts.output };
5413
+ const target2 = `${cmd.pallet}.${cmd.item}`;
5414
+ switch (cmd.category) {
5415
+ case "tx":
5416
+ await handleTx(target2, args, {
5417
+ ...handlerOpts2,
5418
+ from: opts.from,
5419
+ dryRun: opts.dryRun,
5420
+ encode: opts.encode,
5421
+ yaml: opts.yaml,
5422
+ json: opts.json,
5423
+ ext: opts.ext,
5424
+ wait: opts.wait,
5425
+ parsedArgs: cmd.args
5426
+ });
5427
+ break;
5428
+ case "query":
5429
+ await handleQuery(target2, args, {
5430
+ ...handlerOpts2,
5431
+ limit: opts.limit,
5432
+ dump: opts.dump,
5433
+ parsedArgs: cmd.args
5434
+ });
5435
+ break;
5436
+ case "const":
5437
+ await handleConst(target2, handlerOpts2);
5438
+ break;
5439
+ case "apis":
5440
+ await handleApis2(target2, args, {
5441
+ ...handlerOpts2,
5442
+ parsedArgs: cmd.args
5443
+ });
5444
+ break;
5445
+ }
5446
+ return;
5447
+ }
5037
5448
  const config = await loadConfig2();
5038
5449
  const knownChains = Object.keys(config.chains);
5039
5450
  let parsed;
@@ -5069,6 +5480,8 @@ if (process.argv[2] === "__complete") {
5069
5480
  from: opts.from,
5070
5481
  dryRun: opts.dryRun,
5071
5482
  encode: opts.encode,
5483
+ yaml: opts.yaml,
5484
+ json: opts.json,
5072
5485
  ext: opts.ext,
5073
5486
  wait: opts.wait
5074
5487
  });
@@ -5078,6 +5491,8 @@ if (process.argv[2] === "__complete") {
5078
5491
  from: opts.from,
5079
5492
  dryRun: opts.dryRun,
5080
5493
  encode: opts.encode,
5494
+ yaml: opts.yaml,
5495
+ json: opts.json,
5081
5496
  ext: opts.ext,
5082
5497
  wait: opts.wait
5083
5498
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polkadot-cli",
3
- "version": "1.6.1",
3
+ "version": "1.8.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",