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.
- package/README.md +180 -0
- package/dist/cli.mjs +247 -14
- 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
|
|
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
|
-
...
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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",
|