polkadot-cli 0.1.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 +107 -0
- package/dist/cli.mjs +745 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# polkadot-cli
|
|
2
|
+
|
|
3
|
+
A command-line tool for querying Polkadot-ecosystem on-chain state. Query storage, look up constants, and inspect chain metadata — all from your terminal.
|
|
4
|
+
|
|
5
|
+
Ships with Polkadot as the default chain. Add any Substrate-based chain by pointing to its RPC endpoint.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g polkadot-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This installs the `dot` command globally.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Query storage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Plain storage value
|
|
21
|
+
dot query System.Number
|
|
22
|
+
|
|
23
|
+
# Map entry by key
|
|
24
|
+
dot query System.Account 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
|
|
25
|
+
|
|
26
|
+
# All map entries (default limit: 100)
|
|
27
|
+
dot query System.Account --limit 10
|
|
28
|
+
|
|
29
|
+
# Pipe to jq (colors disabled automatically)
|
|
30
|
+
dot query System.Account --limit 5 | jq '.[0].value.data.free'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Look up constants
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
dot const Balances.ExistentialDeposit
|
|
37
|
+
dot const System.SS58Prefix --chain kusama
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Inspect metadata
|
|
41
|
+
|
|
42
|
+
Works offline from cached metadata after the first fetch.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# List all pallets
|
|
46
|
+
dot inspect
|
|
47
|
+
|
|
48
|
+
# List a pallet's storage items and constants
|
|
49
|
+
dot inspect System
|
|
50
|
+
|
|
51
|
+
# Detailed type info for a specific item
|
|
52
|
+
dot inspect System.Account
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Manage chains
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Add a chain
|
|
59
|
+
dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
|
|
60
|
+
dot chain add westend --light-client
|
|
61
|
+
|
|
62
|
+
# List configured chains
|
|
63
|
+
dot chain list
|
|
64
|
+
|
|
65
|
+
# Set default chain
|
|
66
|
+
dot chain default kusama
|
|
67
|
+
|
|
68
|
+
# Remove a chain
|
|
69
|
+
dot chain remove westend
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Global options
|
|
73
|
+
|
|
74
|
+
| Flag | Description |
|
|
75
|
+
|------|-------------|
|
|
76
|
+
| `--chain <name>` | Target chain (default from config) |
|
|
77
|
+
| `--rpc <url>` | Override RPC endpoint for this call |
|
|
78
|
+
| `--light-client` | Use Smoldot light client |
|
|
79
|
+
| `--output json` | Raw JSON output (default: pretty) |
|
|
80
|
+
| `--limit <n>` | Max entries for map queries (0 = unlimited, default: 100) |
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
Config and metadata caches live in `~/.polkadot/`:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
~/.polkadot/
|
|
88
|
+
├── config.json # chains and default chain
|
|
89
|
+
└── chains/
|
|
90
|
+
└── polkadot/
|
|
91
|
+
└── metadata.bin # cached SCALE-encoded metadata
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
Requires [Bun](https://bun.sh).
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bun install
|
|
100
|
+
bun run dev -- query System.Number
|
|
101
|
+
bun run build
|
|
102
|
+
bun test
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
// src/cli.ts
|
|
22
|
+
import cac from "cac";
|
|
23
|
+
|
|
24
|
+
// src/config/store.ts
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { homedir } from "node:os";
|
|
27
|
+
import { mkdir, readFile, writeFile, rm, access } from "node:fs/promises";
|
|
28
|
+
|
|
29
|
+
// src/config/types.ts
|
|
30
|
+
var DEFAULT_CONFIG = {
|
|
31
|
+
defaultChain: "polkadot",
|
|
32
|
+
chains: {
|
|
33
|
+
polkadot: {
|
|
34
|
+
rpc: "wss://rpc.polkadot.io"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/config/store.ts
|
|
40
|
+
var DOT_DIR = join(homedir(), ".polkadot");
|
|
41
|
+
var CONFIG_PATH = join(DOT_DIR, "config.json");
|
|
42
|
+
var CHAINS_DIR = join(DOT_DIR, "chains");
|
|
43
|
+
function getChainDir(chainName) {
|
|
44
|
+
return join(CHAINS_DIR, chainName);
|
|
45
|
+
}
|
|
46
|
+
function getMetadataPath(chainName) {
|
|
47
|
+
return join(CHAINS_DIR, chainName, "metadata.bin");
|
|
48
|
+
}
|
|
49
|
+
async function ensureDir(dir) {
|
|
50
|
+
await mkdir(dir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
async function fileExists(path) {
|
|
53
|
+
try {
|
|
54
|
+
await access(path);
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function loadConfig() {
|
|
61
|
+
await ensureDir(DOT_DIR);
|
|
62
|
+
if (await fileExists(CONFIG_PATH)) {
|
|
63
|
+
const data = await readFile(CONFIG_PATH, "utf-8");
|
|
64
|
+
return JSON.parse(data);
|
|
65
|
+
}
|
|
66
|
+
await saveConfig(DEFAULT_CONFIG);
|
|
67
|
+
return DEFAULT_CONFIG;
|
|
68
|
+
}
|
|
69
|
+
async function saveConfig(config) {
|
|
70
|
+
await ensureDir(DOT_DIR);
|
|
71
|
+
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
async function loadMetadata(chainName) {
|
|
75
|
+
const path = getMetadataPath(chainName);
|
|
76
|
+
if (await fileExists(path)) {
|
|
77
|
+
return await readFile(path);
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
async function saveMetadata(chainName, data) {
|
|
82
|
+
const dir = getChainDir(chainName);
|
|
83
|
+
await ensureDir(dir);
|
|
84
|
+
await writeFile(getMetadataPath(chainName), data);
|
|
85
|
+
}
|
|
86
|
+
async function removeChainData(chainName) {
|
|
87
|
+
const dir = getChainDir(chainName);
|
|
88
|
+
await rm(dir, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
function resolveChain(config, chainFlag) {
|
|
91
|
+
const name = chainFlag ?? config.defaultChain;
|
|
92
|
+
const chain = config.chains[name];
|
|
93
|
+
if (!chain) {
|
|
94
|
+
const available = Object.keys(config.chains).join(", ");
|
|
95
|
+
throw new Error(`Unknown chain "${name}". Available chains: ${available}`);
|
|
96
|
+
}
|
|
97
|
+
return { name, chain };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/core/client.ts
|
|
101
|
+
import { createClient } from "polkadot-api";
|
|
102
|
+
import { getWsProvider } from "polkadot-api/ws-provider";
|
|
103
|
+
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
|
|
104
|
+
|
|
105
|
+
// src/utils/errors.ts
|
|
106
|
+
class CliError extends Error {
|
|
107
|
+
constructor(message) {
|
|
108
|
+
super(message);
|
|
109
|
+
this.name = "CliError";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class ConnectionError extends CliError {
|
|
114
|
+
constructor(message) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.name = "ConnectionError";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class MetadataError extends CliError {
|
|
121
|
+
constructor(message) {
|
|
122
|
+
super(message);
|
|
123
|
+
this.name = "MetadataError";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/core/client.ts
|
|
128
|
+
var KNOWN_CHAIN_SPECS = {
|
|
129
|
+
polkadot: "polkadot-api/chains/polkadot",
|
|
130
|
+
kusama: "polkadot-api/chains/ksmcc3",
|
|
131
|
+
westend: "polkadot-api/chains/westend2",
|
|
132
|
+
paseo: "polkadot-api/chains/paseo"
|
|
133
|
+
};
|
|
134
|
+
async function createChainClient(chainName, chainConfig, rpcOverride) {
|
|
135
|
+
const useLight = !rpcOverride && chainConfig.lightClient;
|
|
136
|
+
let provider;
|
|
137
|
+
if (useLight) {
|
|
138
|
+
provider = await createSmoldotProvider(chainName);
|
|
139
|
+
} else {
|
|
140
|
+
const rpc = rpcOverride ?? chainConfig.rpc;
|
|
141
|
+
if (!rpc) {
|
|
142
|
+
throw new ConnectionError(`No RPC endpoint configured for chain "${chainName}". Use --rpc or configure one with: dot chain add ${chainName} --rpc <url>`);
|
|
143
|
+
}
|
|
144
|
+
provider = withPolkadotSdkCompat(getWsProvider(rpc));
|
|
145
|
+
}
|
|
146
|
+
const client = createClient(provider, {
|
|
147
|
+
getMetadata: async () => loadMetadata(chainName),
|
|
148
|
+
setMetadata: async (_codeHash, metadata) => {
|
|
149
|
+
await saveMetadata(chainName, metadata);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
client,
|
|
154
|
+
destroy: () => client.destroy()
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async function createSmoldotProvider(chainName) {
|
|
158
|
+
const { start } = await import("polkadot-api/smoldot");
|
|
159
|
+
const { getSmProvider } = await import("polkadot-api/sm-provider");
|
|
160
|
+
const specPath = KNOWN_CHAIN_SPECS[chainName];
|
|
161
|
+
if (!specPath) {
|
|
162
|
+
throw new ConnectionError(`Light client is only supported for known chains: ${Object.keys(KNOWN_CHAIN_SPECS).join(", ")}. Use --rpc to connect to "${chainName}" instead.`);
|
|
163
|
+
}
|
|
164
|
+
const { chainSpec } = await import(specPath);
|
|
165
|
+
const smoldot = start();
|
|
166
|
+
const chain = await smoldot.addChain({ chainSpec });
|
|
167
|
+
return getSmProvider(chain);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/core/metadata.ts
|
|
171
|
+
import {
|
|
172
|
+
decAnyMetadata,
|
|
173
|
+
unifyMetadata
|
|
174
|
+
} from "@polkadot-api/substrate-bindings";
|
|
175
|
+
import { getLookupFn, getDynamicBuilder } from "@polkadot-api/metadata-builders";
|
|
176
|
+
function parseMetadata(raw) {
|
|
177
|
+
const decoded = decAnyMetadata(raw);
|
|
178
|
+
const unified = unifyMetadata(decoded);
|
|
179
|
+
const lookup = getLookupFn(unified);
|
|
180
|
+
const builder = getDynamicBuilder(lookup);
|
|
181
|
+
return { unified, lookup, builder };
|
|
182
|
+
}
|
|
183
|
+
async function fetchMetadataFromChain(clientHandle, chainName) {
|
|
184
|
+
const { client } = clientHandle;
|
|
185
|
+
const hex = await client._request("state_getMetadata", []);
|
|
186
|
+
const bytes = hexToBytes(hex);
|
|
187
|
+
await saveMetadata(chainName, bytes);
|
|
188
|
+
return bytes;
|
|
189
|
+
}
|
|
190
|
+
async function getOrFetchMetadata(chainName, clientHandle) {
|
|
191
|
+
let raw = await loadMetadata(chainName);
|
|
192
|
+
if (!raw) {
|
|
193
|
+
if (!clientHandle) {
|
|
194
|
+
throw new MetadataError(`No cached metadata for chain "${chainName}". Run a command that connects to the chain first, ` + `e.g.: dot chain add ${chainName} --rpc <url>`);
|
|
195
|
+
}
|
|
196
|
+
raw = await fetchMetadataFromChain(clientHandle, chainName);
|
|
197
|
+
}
|
|
198
|
+
return parseMetadata(raw);
|
|
199
|
+
}
|
|
200
|
+
function listPallets(meta) {
|
|
201
|
+
return meta.unified.pallets.map((p) => ({
|
|
202
|
+
name: p.name,
|
|
203
|
+
index: p.index,
|
|
204
|
+
docs: p.docs ?? [],
|
|
205
|
+
storage: (p.storage?.items ?? []).map((s) => ({
|
|
206
|
+
name: s.name,
|
|
207
|
+
docs: s.docs ?? [],
|
|
208
|
+
type: s.type.tag,
|
|
209
|
+
keyTypeId: s.type.tag === "map" ? s.type.value.key : null,
|
|
210
|
+
valueTypeId: s.type.tag === "plain" ? s.type.value : s.type.value.value
|
|
211
|
+
})),
|
|
212
|
+
constants: (p.constants ?? []).map((c) => ({
|
|
213
|
+
name: c.name,
|
|
214
|
+
docs: c.docs ?? [],
|
|
215
|
+
typeId: c.type
|
|
216
|
+
}))
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
function findPallet(meta, palletName) {
|
|
220
|
+
const pallets = listPallets(meta);
|
|
221
|
+
return pallets.find((p) => p.name.toLowerCase() === palletName.toLowerCase());
|
|
222
|
+
}
|
|
223
|
+
function getPalletNames(meta) {
|
|
224
|
+
return meta.unified.pallets.map((p) => p.name);
|
|
225
|
+
}
|
|
226
|
+
function describeType(lookup, typeId) {
|
|
227
|
+
try {
|
|
228
|
+
const entry = lookup(typeId);
|
|
229
|
+
return formatLookupEntry(entry);
|
|
230
|
+
} catch {
|
|
231
|
+
return `type(${typeId})`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function formatLookupEntry(entry) {
|
|
235
|
+
switch (entry.type) {
|
|
236
|
+
case "primitive":
|
|
237
|
+
return entry.value;
|
|
238
|
+
case "compact":
|
|
239
|
+
return `Compact<${formatLookupEntry(entry.isBig ? { type: "primitive", value: "u128" } : { type: "primitive", value: "u64" })}>`;
|
|
240
|
+
case "AccountId32":
|
|
241
|
+
return "AccountId32";
|
|
242
|
+
case "bitSequence":
|
|
243
|
+
return "BitSequence";
|
|
244
|
+
case "sequence":
|
|
245
|
+
return `Vec<${formatLookupEntry(entry.value)}>`;
|
|
246
|
+
case "array":
|
|
247
|
+
return `[${formatLookupEntry(entry.value)}; ${entry.len}]`;
|
|
248
|
+
case "tuple":
|
|
249
|
+
return `(${entry.value.map(formatLookupEntry).join(", ")})`;
|
|
250
|
+
case "struct":
|
|
251
|
+
return `{ ${Object.entries(entry.value).map(([k, v]) => `${k}: ${formatLookupEntry(v)}`).join(", ")} }`;
|
|
252
|
+
case "option":
|
|
253
|
+
return `Option<${formatLookupEntry(entry.value)}>`;
|
|
254
|
+
case "result":
|
|
255
|
+
return `Result<${formatLookupEntry(entry.value.ok)}, ${formatLookupEntry(entry.value.ko)}>`;
|
|
256
|
+
case "enum": {
|
|
257
|
+
const variants = Object.keys(entry.value);
|
|
258
|
+
if (variants.length <= 4)
|
|
259
|
+
return variants.join(" | ");
|
|
260
|
+
return `enum(${variants.length} variants)`;
|
|
261
|
+
}
|
|
262
|
+
default:
|
|
263
|
+
return "unknown";
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function hexToBytes(hex) {
|
|
267
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
268
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
269
|
+
for (let i = 0;i < clean.length; i += 2) {
|
|
270
|
+
bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
|
|
271
|
+
}
|
|
272
|
+
return bytes;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/core/output.ts
|
|
276
|
+
var isTTY = process.stdout.isTTY ?? false;
|
|
277
|
+
var RESET = isTTY ? "\x1B[0m" : "";
|
|
278
|
+
var CYAN = isTTY ? "\x1B[36m" : "";
|
|
279
|
+
var GREEN = isTTY ? "\x1B[32m" : "";
|
|
280
|
+
var YELLOW = isTTY ? "\x1B[33m" : "";
|
|
281
|
+
var MAGENTA = isTTY ? "\x1B[35m" : "";
|
|
282
|
+
var DIM = isTTY ? "\x1B[2m" : "";
|
|
283
|
+
var BOLD = isTTY ? "\x1B[1m" : "";
|
|
284
|
+
function replacer(_key, value) {
|
|
285
|
+
if (typeof value === "bigint")
|
|
286
|
+
return value.toString();
|
|
287
|
+
if (value instanceof Uint8Array)
|
|
288
|
+
return "0x" + Buffer.from(value).toString("hex");
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
function formatJson(data) {
|
|
292
|
+
return JSON.stringify(data, replacer, 2);
|
|
293
|
+
}
|
|
294
|
+
function formatPretty(data) {
|
|
295
|
+
const json = JSON.stringify(data, replacer, 2);
|
|
296
|
+
if (!json)
|
|
297
|
+
return "undefined";
|
|
298
|
+
return colorizeJson(json);
|
|
299
|
+
}
|
|
300
|
+
function colorizeJson(json) {
|
|
301
|
+
return json.replace(/("(?:\\.|[^"\\])*")\s*:/g, `${CYAN}$1${RESET}:`).replace(/:\s*("(?:\\.|[^"\\])*")/g, (match, str) => match.replace(str, `${GREEN}${str}${RESET}`)).replace(/:\s*(\d+(?:\.\d+)?)/g, (match, num) => match.replace(num, `${YELLOW}${num}${RESET}`)).replace(/:\s*(true|false)/g, (match, bool) => match.replace(bool, `${MAGENTA}${bool}${RESET}`)).replace(/:\s*(null)/g, (match, n) => match.replace(n, `${DIM}${n}${RESET}`));
|
|
302
|
+
}
|
|
303
|
+
function printResult(data, format = "pretty") {
|
|
304
|
+
if (format === "json") {
|
|
305
|
+
console.log(formatJson(data));
|
|
306
|
+
} else {
|
|
307
|
+
console.log(formatPretty(data));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function printHeading(text) {
|
|
311
|
+
console.log(`
|
|
312
|
+
${BOLD}${text}${RESET}
|
|
313
|
+
`);
|
|
314
|
+
}
|
|
315
|
+
function printItem(name, description) {
|
|
316
|
+
if (description) {
|
|
317
|
+
console.log(` ${CYAN}${name}${RESET} ${DIM}${description}${RESET}`);
|
|
318
|
+
} else {
|
|
319
|
+
console.log(` ${CYAN}${name}${RESET}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function printDocs(docs) {
|
|
323
|
+
const text = docs.join(`
|
|
324
|
+
`).trim();
|
|
325
|
+
if (text) {
|
|
326
|
+
console.log(` ${DIM}${text}${RESET}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/commands/chain.ts
|
|
331
|
+
var CHAIN_HELP = `
|
|
332
|
+
${BOLD}Usage:${RESET}
|
|
333
|
+
$ dot chain add <name> --rpc <url> Add a chain via WebSocket RPC
|
|
334
|
+
$ dot chain add <name> --light-client Add a chain via Smoldot light client
|
|
335
|
+
$ dot chain remove <name> Remove a chain
|
|
336
|
+
$ dot chain list List configured chains
|
|
337
|
+
$ dot chain default <name> Set the default chain
|
|
338
|
+
|
|
339
|
+
${BOLD}Examples:${RESET}
|
|
340
|
+
$ dot chain add kusama --rpc wss://kusama-rpc.polkadot.io
|
|
341
|
+
$ dot chain add westend --light-client
|
|
342
|
+
$ dot chain default kusama
|
|
343
|
+
$ dot chain list
|
|
344
|
+
$ dot chain remove kusama
|
|
345
|
+
`.trimStart();
|
|
346
|
+
function registerChainCommands(cli) {
|
|
347
|
+
cli.command("chain [action] [name]", "Manage chains (add, remove, list, default)").action(async (action, name, opts) => {
|
|
348
|
+
if (!action) {
|
|
349
|
+
console.log(CHAIN_HELP);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
switch (action) {
|
|
353
|
+
case "add":
|
|
354
|
+
return chainAdd(name, opts);
|
|
355
|
+
case "remove":
|
|
356
|
+
return chainRemove(name);
|
|
357
|
+
case "list":
|
|
358
|
+
return chainList();
|
|
359
|
+
case "default":
|
|
360
|
+
return chainDefault(name);
|
|
361
|
+
default:
|
|
362
|
+
console.error(`Unknown action "${action}".
|
|
363
|
+
`);
|
|
364
|
+
console.log(CHAIN_HELP);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
async function chainAdd(name, opts) {
|
|
370
|
+
if (!name) {
|
|
371
|
+
console.error(`Chain name is required.
|
|
372
|
+
`);
|
|
373
|
+
console.error("Usage: dot chain add <name> --rpc <url>");
|
|
374
|
+
console.error(" dot chain add <name> --light-client");
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
if (!opts.rpc && !opts.lightClient) {
|
|
378
|
+
console.error(`Must provide either --rpc <url> or --light-client.
|
|
379
|
+
`);
|
|
380
|
+
console.error("Usage: dot chain add <name> --rpc <url>");
|
|
381
|
+
console.error(" dot chain add <name> --light-client");
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
const config = await loadConfig();
|
|
385
|
+
config.chains[name] = {
|
|
386
|
+
rpc: opts.rpc ?? "",
|
|
387
|
+
...opts.lightClient ? { lightClient: true } : {}
|
|
388
|
+
};
|
|
389
|
+
await saveConfig(config);
|
|
390
|
+
console.log(`Connecting to ${name}...`);
|
|
391
|
+
const clientHandle = await createChainClient(name, config.chains[name], opts.rpc);
|
|
392
|
+
try {
|
|
393
|
+
console.log("Fetching metadata...");
|
|
394
|
+
await fetchMetadataFromChain(clientHandle, name);
|
|
395
|
+
console.log(`Chain "${name}" added successfully.`);
|
|
396
|
+
} finally {
|
|
397
|
+
clientHandle.destroy();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function chainRemove(name) {
|
|
401
|
+
if (!name) {
|
|
402
|
+
console.error("Usage: dot chain remove <name>");
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
const config = await loadConfig();
|
|
406
|
+
if (!config.chains[name]) {
|
|
407
|
+
throw new Error(`Chain "${name}" not found.`);
|
|
408
|
+
}
|
|
409
|
+
if (name === "polkadot") {
|
|
410
|
+
throw new Error('Cannot remove the built-in "polkadot" chain.');
|
|
411
|
+
}
|
|
412
|
+
delete config.chains[name];
|
|
413
|
+
if (config.defaultChain === name) {
|
|
414
|
+
config.defaultChain = "polkadot";
|
|
415
|
+
console.log(`Default chain reset to "polkadot".`);
|
|
416
|
+
}
|
|
417
|
+
await saveConfig(config);
|
|
418
|
+
await removeChainData(name);
|
|
419
|
+
console.log(`Chain "${name}" removed.`);
|
|
420
|
+
}
|
|
421
|
+
async function chainList() {
|
|
422
|
+
const config = await loadConfig();
|
|
423
|
+
printHeading("Configured Chains");
|
|
424
|
+
for (const [name, chainConfig] of Object.entries(config.chains)) {
|
|
425
|
+
const isDefault = name === config.defaultChain;
|
|
426
|
+
const marker = isDefault ? ` ${BOLD}(default)${RESET}` : "";
|
|
427
|
+
const provider = chainConfig.lightClient ? `${DIM}light-client${RESET}` : `${DIM}${chainConfig.rpc}${RESET}`;
|
|
428
|
+
console.log(` ${CYAN}${name}${RESET}${marker} ${provider}`);
|
|
429
|
+
}
|
|
430
|
+
console.log();
|
|
431
|
+
}
|
|
432
|
+
async function chainDefault(name) {
|
|
433
|
+
if (!name) {
|
|
434
|
+
console.error("Usage: dot chain default <name>");
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
const config = await loadConfig();
|
|
438
|
+
if (!config.chains[name]) {
|
|
439
|
+
const available = Object.keys(config.chains).join(", ");
|
|
440
|
+
throw new Error(`Chain "${name}" not found. Available: ${available}`);
|
|
441
|
+
}
|
|
442
|
+
config.defaultChain = name;
|
|
443
|
+
await saveConfig(config);
|
|
444
|
+
console.log(`Default chain set to "${name}".`);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/utils/parse-target.ts
|
|
448
|
+
function parseTarget(input) {
|
|
449
|
+
const parts = input.split(".");
|
|
450
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
451
|
+
throw new Error(`Invalid target "${input}". Expected format: Pallet.Item (e.g. System.Account)`);
|
|
452
|
+
}
|
|
453
|
+
return { pallet: parts[0], item: parts[1] };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/utils/fuzzy-match.ts
|
|
457
|
+
function levenshtein(a, b) {
|
|
458
|
+
const la = a.length;
|
|
459
|
+
const lb = b.length;
|
|
460
|
+
const dp = Array.from({ length: la + 1 }, () => Array(lb + 1).fill(0));
|
|
461
|
+
for (let i = 0;i <= la; i++)
|
|
462
|
+
dp[i][0] = i;
|
|
463
|
+
for (let j = 0;j <= lb; j++)
|
|
464
|
+
dp[0][j] = j;
|
|
465
|
+
for (let i = 1;i <= la; i++) {
|
|
466
|
+
for (let j = 1;j <= lb; j++) {
|
|
467
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
468
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return dp[la][lb];
|
|
472
|
+
}
|
|
473
|
+
function findClosest(input, candidates, maxDistance = 3) {
|
|
474
|
+
const lower = input.toLowerCase();
|
|
475
|
+
const exact = candidates.find((c) => c.toLowerCase() === lower);
|
|
476
|
+
if (exact)
|
|
477
|
+
return [exact];
|
|
478
|
+
const scored = candidates.map((c) => ({ name: c, dist: levenshtein(lower, c.toLowerCase()) })).filter((s) => s.dist <= maxDistance).sort((a, b) => a.dist - b.dist);
|
|
479
|
+
return scored.slice(0, 3).map((s) => s.name);
|
|
480
|
+
}
|
|
481
|
+
function suggestMessage(kind, input, candidates) {
|
|
482
|
+
const suggestions = findClosest(input, candidates);
|
|
483
|
+
if (suggestions.length === 0) {
|
|
484
|
+
return `Unknown ${kind} "${input}".`;
|
|
485
|
+
}
|
|
486
|
+
if (suggestions.length === 1 && suggestions[0].toLowerCase() === input.toLowerCase()) {
|
|
487
|
+
return suggestions[0];
|
|
488
|
+
}
|
|
489
|
+
return `Unknown ${kind} "${input}". Did you mean: ${suggestions.join(", ")}?`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/commands/inspect.ts
|
|
493
|
+
function registerInspectCommand(cli) {
|
|
494
|
+
cli.command("inspect [target]", "Inspect chain metadata (pallets, storage, constants)").option("--chain <name>", "Target chain").option("--rpc <url>", "Override RPC endpoint").action(async (target, opts) => {
|
|
495
|
+
const config = await loadConfig();
|
|
496
|
+
const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
|
|
497
|
+
let meta;
|
|
498
|
+
try {
|
|
499
|
+
meta = await getOrFetchMetadata(chainName);
|
|
500
|
+
} catch {
|
|
501
|
+
console.log(`Fetching metadata from ${chainName}...`);
|
|
502
|
+
const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
503
|
+
try {
|
|
504
|
+
meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
505
|
+
} finally {
|
|
506
|
+
clientHandle.destroy();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (!target) {
|
|
510
|
+
const pallets = listPallets(meta);
|
|
511
|
+
printHeading(`Pallets on ${chainName} (${pallets.length})`);
|
|
512
|
+
for (const p of pallets) {
|
|
513
|
+
const counts = [];
|
|
514
|
+
if (p.storage.length)
|
|
515
|
+
counts.push(`${p.storage.length} storage`);
|
|
516
|
+
if (p.constants.length)
|
|
517
|
+
counts.push(`${p.constants.length} constants`);
|
|
518
|
+
printItem(p.name, counts.join(", "));
|
|
519
|
+
}
|
|
520
|
+
console.log();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (!target.includes(".")) {
|
|
524
|
+
const palletNames2 = getPalletNames(meta);
|
|
525
|
+
const pallet2 = findPallet(meta, target);
|
|
526
|
+
if (!pallet2) {
|
|
527
|
+
throw new Error(suggestMessage("pallet", target, palletNames2));
|
|
528
|
+
}
|
|
529
|
+
printHeading(`${pallet2.name} Pallet`);
|
|
530
|
+
if (pallet2.docs.length) {
|
|
531
|
+
printDocs(pallet2.docs);
|
|
532
|
+
console.log();
|
|
533
|
+
}
|
|
534
|
+
if (pallet2.storage.length) {
|
|
535
|
+
console.log(` ${BOLD}Storage Items:${RESET}`);
|
|
536
|
+
for (const s of pallet2.storage) {
|
|
537
|
+
const doc = s.docs[0] ? ` — ${s.docs[0].slice(0, 80)}` : "";
|
|
538
|
+
console.log(` ${CYAN}${s.name}${RESET}${DIM}${doc}${RESET}`);
|
|
539
|
+
}
|
|
540
|
+
console.log();
|
|
541
|
+
}
|
|
542
|
+
if (pallet2.constants.length) {
|
|
543
|
+
console.log(` ${BOLD}Constants:${RESET}`);
|
|
544
|
+
for (const c of pallet2.constants) {
|
|
545
|
+
const doc = c.docs[0] ? ` — ${c.docs[0].slice(0, 80)}` : "";
|
|
546
|
+
console.log(` ${CYAN}${c.name}${RESET}${DIM}${doc}${RESET}`);
|
|
547
|
+
}
|
|
548
|
+
console.log();
|
|
549
|
+
}
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const { pallet: palletName, item: itemName } = parseTarget(target);
|
|
553
|
+
const palletNames = getPalletNames(meta);
|
|
554
|
+
const pallet = findPallet(meta, palletName);
|
|
555
|
+
if (!pallet) {
|
|
556
|
+
throw new Error(suggestMessage("pallet", palletName, palletNames));
|
|
557
|
+
}
|
|
558
|
+
const storageItem = pallet.storage.find((s) => s.name.toLowerCase() === itemName.toLowerCase());
|
|
559
|
+
if (storageItem) {
|
|
560
|
+
printHeading(`${pallet.name}.${storageItem.name} (Storage)`);
|
|
561
|
+
console.log(` ${BOLD}Type:${RESET} ${storageItem.type}`);
|
|
562
|
+
console.log(` ${BOLD}Value:${RESET} ${describeType(meta.lookup, storageItem.valueTypeId)}`);
|
|
563
|
+
if (storageItem.keyTypeId != null) {
|
|
564
|
+
console.log(` ${BOLD}Key:${RESET} ${describeType(meta.lookup, storageItem.keyTypeId)}`);
|
|
565
|
+
}
|
|
566
|
+
if (storageItem.docs.length) {
|
|
567
|
+
console.log();
|
|
568
|
+
printDocs(storageItem.docs);
|
|
569
|
+
}
|
|
570
|
+
console.log();
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const constantItem = pallet.constants.find((c) => c.name.toLowerCase() === itemName.toLowerCase());
|
|
574
|
+
if (constantItem) {
|
|
575
|
+
printHeading(`${pallet.name}.${constantItem.name} (Constant)`);
|
|
576
|
+
console.log(` ${BOLD}Type:${RESET} ${describeType(meta.lookup, constantItem.typeId)}`);
|
|
577
|
+
if (constantItem.docs.length) {
|
|
578
|
+
console.log();
|
|
579
|
+
printDocs(constantItem.docs);
|
|
580
|
+
}
|
|
581
|
+
console.log();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const allItems = [
|
|
585
|
+
...pallet.storage.map((s) => s.name),
|
|
586
|
+
...pallet.constants.map((c) => c.name)
|
|
587
|
+
];
|
|
588
|
+
throw new Error(suggestMessage(`item in ${pallet.name}`, itemName, allItems));
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/commands/query.ts
|
|
593
|
+
var DEFAULT_LIMIT = 100;
|
|
594
|
+
function registerQueryCommand(cli) {
|
|
595
|
+
cli.command("query [target] [...keys]", "Query on-chain storage (e.g. System.Number, System.Account <addr>)").option("--limit <n>", "Max entries to return for map queries (0 = unlimited)", {
|
|
596
|
+
default: DEFAULT_LIMIT
|
|
597
|
+
}).action(async (target, keys, opts) => {
|
|
598
|
+
if (!target) {
|
|
599
|
+
console.log("Usage: dot query <Pallet.Item> [...keys] [--chain <name>] [--output json]");
|
|
600
|
+
console.log("");
|
|
601
|
+
console.log("Examples:");
|
|
602
|
+
console.log(" $ dot query System.Number # plain storage value");
|
|
603
|
+
console.log(" $ dot query System.Account 5Grw... # single map entry");
|
|
604
|
+
console.log(" $ dot query System.Account # all entries (limit 100)");
|
|
605
|
+
console.log(" $ dot query System.Account --limit 10 # first 10 entries");
|
|
606
|
+
console.log(" $ dot query System.Account --limit 0 # all entries (no limit)");
|
|
607
|
+
console.log(" $ dot query Assets.Metadata 42 --chain asset-hub");
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const config = await loadConfig();
|
|
611
|
+
const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
|
|
612
|
+
const { pallet, item } = parseTarget(target);
|
|
613
|
+
const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
614
|
+
try {
|
|
615
|
+
const meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
616
|
+
const palletNames = getPalletNames(meta);
|
|
617
|
+
const palletInfo = findPallet(meta, pallet);
|
|
618
|
+
if (!palletInfo) {
|
|
619
|
+
throw new Error(suggestMessage("pallet", pallet, palletNames));
|
|
620
|
+
}
|
|
621
|
+
const storageItem = palletInfo.storage.find((s) => s.name.toLowerCase() === item.toLowerCase());
|
|
622
|
+
if (!storageItem) {
|
|
623
|
+
const storageNames = palletInfo.storage.map((s) => s.name);
|
|
624
|
+
throw new Error(suggestMessage(`storage item in ${palletInfo.name}`, item, storageNames));
|
|
625
|
+
}
|
|
626
|
+
const unsafeApi = clientHandle.client.getUnsafeApi();
|
|
627
|
+
const storageApi = unsafeApi.query[palletInfo.name][storageItem.name];
|
|
628
|
+
const parsedKeys = keys.map(parseKeyArg);
|
|
629
|
+
const format = opts.output ?? "pretty";
|
|
630
|
+
if (storageItem.type === "map" && parsedKeys.length === 0) {
|
|
631
|
+
const entries = await storageApi.getEntries();
|
|
632
|
+
const limit = Number(opts.limit);
|
|
633
|
+
const truncated = limit > 0 && entries.length > limit;
|
|
634
|
+
const display = truncated ? entries.slice(0, limit) : entries;
|
|
635
|
+
printResult(display.map((e) => ({
|
|
636
|
+
keys: e.keyArgs,
|
|
637
|
+
value: e.value
|
|
638
|
+
})), format);
|
|
639
|
+
if (truncated) {
|
|
640
|
+
console.error(`
|
|
641
|
+
${DIM}Showing ${limit} of ${entries.length} entries. Use --limit 0 for all.${RESET}`);
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
const result = await storageApi.getValue(...parsedKeys);
|
|
645
|
+
printResult(result, format);
|
|
646
|
+
}
|
|
647
|
+
} finally {
|
|
648
|
+
clientHandle.destroy();
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
function parseKeyArg(arg) {
|
|
653
|
+
if (/^\d+$/.test(arg))
|
|
654
|
+
return parseInt(arg, 10);
|
|
655
|
+
if (/^\d{16,}$/.test(arg))
|
|
656
|
+
return BigInt(arg);
|
|
657
|
+
if (/^0x[0-9a-fA-F]+$/.test(arg))
|
|
658
|
+
return arg;
|
|
659
|
+
if (arg === "true")
|
|
660
|
+
return true;
|
|
661
|
+
if (arg === "false")
|
|
662
|
+
return false;
|
|
663
|
+
if (arg.startsWith("{") || arg.startsWith("[")) {
|
|
664
|
+
try {
|
|
665
|
+
return JSON.parse(arg);
|
|
666
|
+
} catch {}
|
|
667
|
+
}
|
|
668
|
+
return arg;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/commands/const.ts
|
|
672
|
+
function registerConstCommand(cli) {
|
|
673
|
+
cli.command("const [target]", "Look up a pallet constant (e.g. Balances.ExistentialDeposit)").action(async (target, opts) => {
|
|
674
|
+
if (!target) {
|
|
675
|
+
console.log("Usage: dot const <Pallet.Constant> [--chain <name>] [--output json]");
|
|
676
|
+
console.log("");
|
|
677
|
+
console.log("Examples:");
|
|
678
|
+
console.log(" $ dot const Balances.ExistentialDeposit");
|
|
679
|
+
console.log(" $ dot const System.SS58Prefix --chain kusama");
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const config = await loadConfig();
|
|
683
|
+
const { name: chainName, chain: chainConfig } = resolveChain(config, opts.chain);
|
|
684
|
+
const { pallet, item } = parseTarget(target);
|
|
685
|
+
const clientHandle = await createChainClient(chainName, chainConfig, opts.rpc);
|
|
686
|
+
try {
|
|
687
|
+
const meta = await getOrFetchMetadata(chainName, clientHandle);
|
|
688
|
+
const palletNames = getPalletNames(meta);
|
|
689
|
+
const palletInfo = findPallet(meta, pallet);
|
|
690
|
+
if (!palletInfo) {
|
|
691
|
+
throw new Error(suggestMessage("pallet", pallet, palletNames));
|
|
692
|
+
}
|
|
693
|
+
const constantItem = palletInfo.constants.find((c) => c.name.toLowerCase() === item.toLowerCase());
|
|
694
|
+
if (!constantItem) {
|
|
695
|
+
const constNames = palletInfo.constants.map((c) => c.name);
|
|
696
|
+
throw new Error(suggestMessage(`constant in ${palletInfo.name}`, item, constNames));
|
|
697
|
+
}
|
|
698
|
+
const unsafeApi = clientHandle.client.getUnsafeApi();
|
|
699
|
+
const runtimeToken = await unsafeApi.runtimeToken;
|
|
700
|
+
const result = unsafeApi.constants[palletInfo.name][constantItem.name](runtimeToken);
|
|
701
|
+
printResult(result, opts.output ?? "pretty");
|
|
702
|
+
} finally {
|
|
703
|
+
clientHandle.destroy();
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/utils/errors.ts
|
|
709
|
+
class CliError2 extends Error {
|
|
710
|
+
constructor(message) {
|
|
711
|
+
super(message);
|
|
712
|
+
this.name = "CliError";
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/cli.ts
|
|
717
|
+
var cli = cac("dot");
|
|
718
|
+
cli.option("--chain <name>", "Target chain (default from config)");
|
|
719
|
+
cli.option("--rpc <url>", "Override RPC endpoint for this call");
|
|
720
|
+
cli.option("--light-client", "Use Smoldot light client instead of WebSocket");
|
|
721
|
+
cli.option("--output <format>", "Output format: pretty or json", {
|
|
722
|
+
default: "pretty"
|
|
723
|
+
});
|
|
724
|
+
registerChainCommands(cli);
|
|
725
|
+
registerInspectCommand(cli);
|
|
726
|
+
registerQueryCommand(cli);
|
|
727
|
+
registerConstCommand(cli);
|
|
728
|
+
cli.help();
|
|
729
|
+
cli.version("0.1.0");
|
|
730
|
+
function handleError(err) {
|
|
731
|
+
if (err instanceof CliError2) {
|
|
732
|
+
console.error(`Error: ${err.message}`);
|
|
733
|
+
} else if (err instanceof Error) {
|
|
734
|
+
console.error(`Error: ${err.message}`);
|
|
735
|
+
} else {
|
|
736
|
+
console.error("An unexpected error occurred:", err);
|
|
737
|
+
}
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
process.on("unhandledRejection", handleError);
|
|
741
|
+
try {
|
|
742
|
+
cli.parse();
|
|
743
|
+
} catch (err) {
|
|
744
|
+
handleError(err);
|
|
745
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "polkadot-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool for querying Polkadot-ecosystem on-chain state",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dot": "./dist/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "bun src/cli.ts",
|
|
14
|
+
"build": "bun build src/cli.ts --outfile dist/cli.mjs --target node --packages external && bun run build:shebang",
|
|
15
|
+
"build:shebang": "bun -e \"const f='dist/cli.mjs'; const s=await Bun.file(f).text(); await Bun.write(f, s.replace('#!/usr/bin/env bun', '#!/usr/bin/env node').replace('// @bun\\n',''))\"",
|
|
16
|
+
"prepublishOnly": "bun run build",
|
|
17
|
+
"test": "bun test",
|
|
18
|
+
"changeset": "changeset",
|
|
19
|
+
"version": "changeset version",
|
|
20
|
+
"release": "changeset publish"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"polkadot",
|
|
25
|
+
"substrate",
|
|
26
|
+
"blockchain",
|
|
27
|
+
"cli",
|
|
28
|
+
"web3"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/peetzweg/polkadot-cli"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"cac": "^6.7.14",
|
|
36
|
+
"polkadot-api": "^1.23.3",
|
|
37
|
+
"@polkadot-api/metadata-builders": "^0.13.9",
|
|
38
|
+
"@polkadot-api/substrate-bindings": "^0.17.0",
|
|
39
|
+
"@polkadot-api/view-builder": "^0.4.17"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@changesets/cli": "^2.29.4",
|
|
43
|
+
"@types/bun": "latest"
|
|
44
|
+
}
|
|
45
|
+
}
|