@alchemy/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 +336 -0
- package/dist/chunk-F7KTEZFZ.js +282 -0
- package/dist/chunk-PH4BPYSY.js +1145 -0
- package/dist/chunk-QKXQW4OF.js +1113 -0
- package/dist/index.js +1897 -0
- package/dist/interactive-2ITFWH3B.js +406 -0
- package/dist/onboarding-3J4EXZMG.js +178 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1897 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
|
+
import {
|
|
4
|
+
adminClientFromFlags,
|
|
5
|
+
clientFromFlags,
|
|
6
|
+
fetchWithTimeout,
|
|
7
|
+
readStdinArg,
|
|
8
|
+
registerConfig,
|
|
9
|
+
registerWallet,
|
|
10
|
+
resolveAPIKey,
|
|
11
|
+
resolveAppId,
|
|
12
|
+
resolveConfiguredNetworkSlugs,
|
|
13
|
+
resolveNetwork,
|
|
14
|
+
splitCommaList,
|
|
15
|
+
validateAddress,
|
|
16
|
+
validateTxHash
|
|
17
|
+
} from "./chunk-PH4BPYSY.js";
|
|
18
|
+
import {
|
|
19
|
+
getRPCNetworks,
|
|
20
|
+
getSetupStatus,
|
|
21
|
+
isSetupComplete,
|
|
22
|
+
shouldRunOnboarding
|
|
23
|
+
} from "./chunk-F7KTEZFZ.js";
|
|
24
|
+
import {
|
|
25
|
+
bold,
|
|
26
|
+
brand,
|
|
27
|
+
brandedHelp,
|
|
28
|
+
debug,
|
|
29
|
+
dim,
|
|
30
|
+
emptyState,
|
|
31
|
+
errAuthRequired,
|
|
32
|
+
errInvalidAPIKey,
|
|
33
|
+
errInvalidArgs,
|
|
34
|
+
errNetwork,
|
|
35
|
+
errNotFound,
|
|
36
|
+
errRateLimited,
|
|
37
|
+
errSetupRequired,
|
|
38
|
+
esc,
|
|
39
|
+
etherscanTxURL,
|
|
40
|
+
exitWithError,
|
|
41
|
+
failBadge,
|
|
42
|
+
formatCommanderError,
|
|
43
|
+
green,
|
|
44
|
+
identity,
|
|
45
|
+
isInteractiveAllowed,
|
|
46
|
+
isJSONMode,
|
|
47
|
+
load,
|
|
48
|
+
maskIf,
|
|
49
|
+
noColor,
|
|
50
|
+
printHuman,
|
|
51
|
+
printJSON,
|
|
52
|
+
printKeyValueBox,
|
|
53
|
+
printSyntaxJSON,
|
|
54
|
+
printTable,
|
|
55
|
+
promptSelect,
|
|
56
|
+
quiet,
|
|
57
|
+
setFlags,
|
|
58
|
+
successBadge,
|
|
59
|
+
timeAgo,
|
|
60
|
+
verbose,
|
|
61
|
+
weiToEth,
|
|
62
|
+
withSpinner
|
|
63
|
+
} from "./chunk-QKXQW4OF.js";
|
|
64
|
+
|
|
65
|
+
// src/index.ts
|
|
66
|
+
import { Command, Help } from "commander";
|
|
67
|
+
|
|
68
|
+
// src/commands/rpc.ts
|
|
69
|
+
function registerRPC(program2) {
|
|
70
|
+
program2.command("rpc <method> [params...]").description("Make a raw JSON-RPC call").addHelpText(
|
|
71
|
+
"after",
|
|
72
|
+
`
|
|
73
|
+
Examples:
|
|
74
|
+
alchemy rpc eth_blockNumber
|
|
75
|
+
alchemy rpc eth_getBalance "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" "latest"
|
|
76
|
+
alchemy rpc eth_getBlockByNumber "0x1" true`
|
|
77
|
+
).action(async (method, params) => {
|
|
78
|
+
try {
|
|
79
|
+
const client = clientFromFlags(program2);
|
|
80
|
+
const parsed = params.map((p) => {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(p);
|
|
83
|
+
} catch {
|
|
84
|
+
return p;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
debug(`rpc ${method} %o`, parsed);
|
|
88
|
+
const result = await withSpinner(
|
|
89
|
+
`Calling ${method}\u2026`,
|
|
90
|
+
`Called ${method}`,
|
|
91
|
+
() => client.call(method, parsed)
|
|
92
|
+
);
|
|
93
|
+
printSyntaxJSON(result);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
exitWithError(err);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/commands/balance.ts
|
|
101
|
+
function registerBalance(program2) {
|
|
102
|
+
program2.command("balance [address]").alias("bal").description("Get the ETH balance of an address").addHelpText(
|
|
103
|
+
"after",
|
|
104
|
+
`
|
|
105
|
+
Examples:
|
|
106
|
+
alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
107
|
+
alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n polygon-mainnet
|
|
108
|
+
echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy balance`
|
|
109
|
+
).action(async (addressArg) => {
|
|
110
|
+
try {
|
|
111
|
+
const address = addressArg ?? await readStdinArg("address");
|
|
112
|
+
validateAddress(address);
|
|
113
|
+
const client = clientFromFlags(program2);
|
|
114
|
+
const result = await withSpinner(
|
|
115
|
+
"Fetching balance\u2026",
|
|
116
|
+
"Balance fetched",
|
|
117
|
+
() => client.call("eth_getBalance", [address, "latest"])
|
|
118
|
+
);
|
|
119
|
+
const wei = BigInt(result);
|
|
120
|
+
const network = resolveNetwork(program2);
|
|
121
|
+
if (isJSONMode()) {
|
|
122
|
+
printJSON({
|
|
123
|
+
address,
|
|
124
|
+
wei: wei.toString(),
|
|
125
|
+
eth: weiToEth(wei),
|
|
126
|
+
network
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
printKeyValueBox([
|
|
130
|
+
["Address", address],
|
|
131
|
+
["Balance", green(weiToEth(wei) + " ETH")],
|
|
132
|
+
["Network", network]
|
|
133
|
+
]);
|
|
134
|
+
if (verbose) {
|
|
135
|
+
console.log("");
|
|
136
|
+
printJSON({
|
|
137
|
+
rpcMethod: "eth_getBalance",
|
|
138
|
+
rpcParams: [address, "latest"],
|
|
139
|
+
rpcResult: result
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
exitWithError(err);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/commands/tx.ts
|
|
150
|
+
function registerTx(program2) {
|
|
151
|
+
program2.command("tx [hash]").description("Get transaction details by hash").addHelpText(
|
|
152
|
+
"after",
|
|
153
|
+
`
|
|
154
|
+
Examples:
|
|
155
|
+
alchemy tx 0xabc123...
|
|
156
|
+
echo 0xabc123... | alchemy tx`
|
|
157
|
+
).action(async (hashArg) => {
|
|
158
|
+
try {
|
|
159
|
+
const hash = hashArg ?? await readStdinArg("hash");
|
|
160
|
+
validateTxHash(hash);
|
|
161
|
+
const client = clientFromFlags(program2);
|
|
162
|
+
const [tx, receipt] = await withSpinner("Fetching transaction\u2026", "Transaction fetched", async () => {
|
|
163
|
+
const t = await client.call("eth_getTransactionByHash", [
|
|
164
|
+
hash
|
|
165
|
+
]);
|
|
166
|
+
if (!t) throw errNotFound(`transaction ${hash}`);
|
|
167
|
+
const r = await client.call("eth_getTransactionReceipt", [
|
|
168
|
+
hash
|
|
169
|
+
]);
|
|
170
|
+
return [t, r];
|
|
171
|
+
});
|
|
172
|
+
if (isJSONMode()) {
|
|
173
|
+
printJSON({ transaction: tx, receipt });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const pairs = [["Hash", hash]];
|
|
177
|
+
if (tx.from) pairs.push(["From", String(tx.from)]);
|
|
178
|
+
if (tx.to) pairs.push(["To", String(tx.to)]);
|
|
179
|
+
if (tx.value) {
|
|
180
|
+
const wei = BigInt(tx.value);
|
|
181
|
+
pairs.push(["Value", green(weiToEth(wei) + " ETH")]);
|
|
182
|
+
}
|
|
183
|
+
if (tx.blockNumber) pairs.push(["Block", String(tx.blockNumber)]);
|
|
184
|
+
if (receipt) {
|
|
185
|
+
if (receipt.status === "0x1") {
|
|
186
|
+
pairs.push(["Status", `${successBadge()} Success`]);
|
|
187
|
+
} else if (receipt.status) {
|
|
188
|
+
pairs.push(["Status", `${failBadge()} Failed`]);
|
|
189
|
+
}
|
|
190
|
+
if (receipt.gasUsed) pairs.push(["Gas Used", String(receipt.gasUsed)]);
|
|
191
|
+
}
|
|
192
|
+
const network = resolveNetwork(program2);
|
|
193
|
+
const explorerURL = etherscanTxURL(hash, network);
|
|
194
|
+
if (explorerURL) {
|
|
195
|
+
pairs.push(["Explorer", explorerURL]);
|
|
196
|
+
}
|
|
197
|
+
printKeyValueBox(pairs);
|
|
198
|
+
if (verbose) {
|
|
199
|
+
console.log("");
|
|
200
|
+
printJSON({ transaction: tx, receipt });
|
|
201
|
+
}
|
|
202
|
+
} catch (err) {
|
|
203
|
+
exitWithError(err);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/lib/block-format.ts
|
|
209
|
+
function parseHexQuantity(value) {
|
|
210
|
+
if (typeof value !== "string" || !/^0x[0-9a-f]+$/i.test(value)) {
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
return BigInt(value);
|
|
215
|
+
} catch {
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function formatWithCommas(value) {
|
|
220
|
+
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
221
|
+
}
|
|
222
|
+
function formatHexQuantity(value) {
|
|
223
|
+
const parsed = parseHexQuantity(value);
|
|
224
|
+
if (parsed === void 0) return void 0;
|
|
225
|
+
return formatWithCommas(parsed);
|
|
226
|
+
}
|
|
227
|
+
function formatBlockTimestamp(value) {
|
|
228
|
+
if (typeof value !== "string") return void 0;
|
|
229
|
+
const parsed = parseHexQuantity(value);
|
|
230
|
+
if (parsed === void 0) return void 0;
|
|
231
|
+
const millis = Number(parsed) * 1e3;
|
|
232
|
+
if (!Number.isFinite(millis)) return void 0;
|
|
233
|
+
const d = new Date(millis);
|
|
234
|
+
if (Number.isNaN(d.getTime())) return void 0;
|
|
235
|
+
const iso = d.toISOString().replace(".000Z", "Z");
|
|
236
|
+
return `${iso} ${dim("(" + timeAgo(value) + ")")}`;
|
|
237
|
+
}
|
|
238
|
+
function formatGasSummary(gasUsed, gasLimit, options) {
|
|
239
|
+
const used = parseHexQuantity(gasUsed);
|
|
240
|
+
const limit = parseHexQuantity(gasLimit);
|
|
241
|
+
if (used === void 0 || limit === void 0) return void 0;
|
|
242
|
+
const usedFormatted = formatWithCommas(used);
|
|
243
|
+
const limitFormatted = formatWithCommas(limit);
|
|
244
|
+
if (limit === 0n) return `${usedFormatted} / ${limitFormatted}`;
|
|
245
|
+
const bps = used * 10000n / limit;
|
|
246
|
+
const percent = Number(bps) / 100;
|
|
247
|
+
const percentText = `${percent.toFixed(2)}%`;
|
|
248
|
+
const percentDisplay = options?.colored ? dim(percentText) : percentText;
|
|
249
|
+
return `${usedFormatted} / ${limitFormatted} (${percentDisplay})`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/commands/block.ts
|
|
253
|
+
function registerBlock(program2) {
|
|
254
|
+
program2.command("block <number>").description("Get block details by number").addHelpText(
|
|
255
|
+
"after",
|
|
256
|
+
`
|
|
257
|
+
Examples:
|
|
258
|
+
alchemy block latest
|
|
259
|
+
alchemy block 17000000
|
|
260
|
+
alchemy block 0x1`
|
|
261
|
+
).action(async (blockId) => {
|
|
262
|
+
try {
|
|
263
|
+
let blockParam;
|
|
264
|
+
if (["latest", "earliest", "pending"].includes(blockId)) {
|
|
265
|
+
blockParam = blockId;
|
|
266
|
+
} else if (blockId.startsWith("0x")) {
|
|
267
|
+
blockParam = blockId;
|
|
268
|
+
} else {
|
|
269
|
+
const num = parseInt(blockId, 10);
|
|
270
|
+
if (isNaN(num)) {
|
|
271
|
+
throw errInvalidArgs(
|
|
272
|
+
"block must be a number, hex, or 'latest'"
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
blockParam = `0x${num.toString(16)}`;
|
|
276
|
+
}
|
|
277
|
+
const client = clientFromFlags(program2);
|
|
278
|
+
const block = await withSpinner(
|
|
279
|
+
"Fetching block\u2026",
|
|
280
|
+
"Block fetched",
|
|
281
|
+
() => client.call("eth_getBlockByNumber", [blockParam, false])
|
|
282
|
+
);
|
|
283
|
+
if (!block) throw errNotFound(`block ${blockId}`);
|
|
284
|
+
if (isJSONMode()) {
|
|
285
|
+
printJSON(block);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const pairs = [];
|
|
289
|
+
if (block.number) {
|
|
290
|
+
const formatted = formatHexQuantity(block.number);
|
|
291
|
+
pairs.push(["Block", bold(formatted ?? String(block.number))]);
|
|
292
|
+
}
|
|
293
|
+
if (block.hash) pairs.push(["Hash", String(block.hash)]);
|
|
294
|
+
if (block.timestamp) {
|
|
295
|
+
const ts = formatBlockTimestamp(block.timestamp);
|
|
296
|
+
if (ts) pairs.push(["Timestamp", ts]);
|
|
297
|
+
}
|
|
298
|
+
if (Array.isArray(block.transactions)) {
|
|
299
|
+
const txCount = block.transactions.length.toLocaleString("en-US");
|
|
300
|
+
pairs.push(["Transactions", txCount]);
|
|
301
|
+
}
|
|
302
|
+
if (block.miner) pairs.push(["Miner", String(block.miner)]);
|
|
303
|
+
const gasSummary = formatGasSummary(block.gasUsed, block.gasLimit, { colored: true });
|
|
304
|
+
if (gasSummary) {
|
|
305
|
+
pairs.push(["Gas", gasSummary]);
|
|
306
|
+
} else {
|
|
307
|
+
if (block.gasUsed) {
|
|
308
|
+
const formatted = formatHexQuantity(block.gasUsed);
|
|
309
|
+
pairs.push(["Gas Used", formatted ?? String(block.gasUsed)]);
|
|
310
|
+
}
|
|
311
|
+
if (block.gasLimit) {
|
|
312
|
+
const formatted = formatHexQuantity(block.gasLimit);
|
|
313
|
+
pairs.push(["Gas Limit", formatted ?? String(block.gasLimit)]);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
printKeyValueBox(pairs);
|
|
317
|
+
if (verbose) {
|
|
318
|
+
console.log("");
|
|
319
|
+
printSyntaxJSON(block);
|
|
320
|
+
} else {
|
|
321
|
+
console.log("");
|
|
322
|
+
console.log(` ${dim("Tip: use --verbose to include the raw block payload.")}`);
|
|
323
|
+
}
|
|
324
|
+
} catch (err) {
|
|
325
|
+
exitWithError(err);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/commands/nfts.ts
|
|
331
|
+
function registerNFTs(program2) {
|
|
332
|
+
const cmd = program2.command("nfts").description("NFT API wrappers").argument("[address]", "Wallet address (default action: list owned NFTs)").option("--limit <n>", "Maximum number of NFTs to return", parseInt).option("--page-key <key>", "Pagination key from a previous response").addHelpText(
|
|
333
|
+
"after",
|
|
334
|
+
`
|
|
335
|
+
Examples:
|
|
336
|
+
alchemy nfts 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
337
|
+
alchemy nfts metadata --contract 0x... --token-id 1
|
|
338
|
+
alchemy nfts contract 0x...
|
|
339
|
+
echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy nfts`
|
|
340
|
+
).action(async (addressArg, opts) => {
|
|
341
|
+
try {
|
|
342
|
+
const address = addressArg ?? await readStdinArg("address");
|
|
343
|
+
validateAddress(address);
|
|
344
|
+
const params = {
|
|
345
|
+
owner: address,
|
|
346
|
+
withMetadata: "true"
|
|
347
|
+
};
|
|
348
|
+
if (opts.limit) params.pageSize = String(opts.limit);
|
|
349
|
+
if (opts.pageKey) params.pageKey = opts.pageKey;
|
|
350
|
+
const client = clientFromFlags(program2);
|
|
351
|
+
const result = await withSpinner(
|
|
352
|
+
"Fetching NFTs\u2026",
|
|
353
|
+
"NFTs fetched",
|
|
354
|
+
() => client.callEnhanced("getNFTsForOwner", params)
|
|
355
|
+
);
|
|
356
|
+
if (isJSONMode()) {
|
|
357
|
+
printJSON(result);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (result.ownedNfts.length === 0) {
|
|
361
|
+
emptyState("No NFTs found.");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const rows = result.ownedNfts.map((nft) => [
|
|
365
|
+
nft.contract.name || dim("unnamed"),
|
|
366
|
+
nft.name || `#${nft.tokenId}`,
|
|
367
|
+
nft.contract.address
|
|
368
|
+
]);
|
|
369
|
+
printTable(["Collection", "Name", "Contract"], rows);
|
|
370
|
+
if (verbose) {
|
|
371
|
+
console.log("");
|
|
372
|
+
printJSON(result);
|
|
373
|
+
}
|
|
374
|
+
if (result.pageKey) {
|
|
375
|
+
console.log(`
|
|
376
|
+
${dim(`More results available. Use --page-key ${result.pageKey} to see the next page.`)}`);
|
|
377
|
+
}
|
|
378
|
+
} catch (err) {
|
|
379
|
+
exitWithError(err);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
cmd.command("metadata").description("Get NFT metadata by contract/token").requiredOption("--contract <address>", "NFT contract address").requiredOption("--token-id <id>", "Token id").action(async (opts) => {
|
|
383
|
+
try {
|
|
384
|
+
validateAddress(opts.contract);
|
|
385
|
+
const client = clientFromFlags(program2);
|
|
386
|
+
const result = await withSpinner(
|
|
387
|
+
"Fetching NFT metadata\u2026",
|
|
388
|
+
"NFT metadata fetched",
|
|
389
|
+
() => client.callEnhanced("getNFTMetadata", {
|
|
390
|
+
contractAddress: opts.contract,
|
|
391
|
+
tokenId: opts.tokenId
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
if (isJSONMode()) printJSON(result);
|
|
395
|
+
else printSyntaxJSON(result);
|
|
396
|
+
} catch (err) {
|
|
397
|
+
exitWithError(err);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
cmd.command("contract <address>").description("Get NFT contract metadata").action(async (address) => {
|
|
401
|
+
try {
|
|
402
|
+
validateAddress(address);
|
|
403
|
+
const client = clientFromFlags(program2);
|
|
404
|
+
const result = await withSpinner(
|
|
405
|
+
"Fetching contract metadata\u2026",
|
|
406
|
+
"Contract metadata fetched",
|
|
407
|
+
() => client.callEnhanced("getContractMetadata", {
|
|
408
|
+
contractAddress: address
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
if (isJSONMode()) printJSON(result);
|
|
412
|
+
else printSyntaxJSON(result);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
exitWithError(err);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/commands/tokens.ts
|
|
420
|
+
function registerTokens(program2) {
|
|
421
|
+
const cmd = program2.command("tokens").description("Token API wrappers").argument("[address]", "Wallet address (default action: list balances)").option("--page-key <key>", "Pagination key from a previous response").addHelpText(
|
|
422
|
+
"after",
|
|
423
|
+
`
|
|
424
|
+
Examples:
|
|
425
|
+
alchemy tokens 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
426
|
+
alchemy tokens metadata 0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eB48
|
|
427
|
+
alchemy tokens allowance --owner 0x... --spender 0x... --contract 0x...
|
|
428
|
+
echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy tokens`
|
|
429
|
+
).action(async (addressArg, opts) => {
|
|
430
|
+
try {
|
|
431
|
+
const address = addressArg ?? await readStdinArg("address");
|
|
432
|
+
validateAddress(address);
|
|
433
|
+
const params = [address];
|
|
434
|
+
if (opts.pageKey) {
|
|
435
|
+
params.push("erc20", { pageKey: opts.pageKey });
|
|
436
|
+
}
|
|
437
|
+
const client = clientFromFlags(program2);
|
|
438
|
+
const result = await withSpinner(
|
|
439
|
+
"Fetching token balances\u2026",
|
|
440
|
+
"Token balances fetched",
|
|
441
|
+
() => client.call("alchemy_getTokenBalances", params)
|
|
442
|
+
);
|
|
443
|
+
if (isJSONMode()) {
|
|
444
|
+
printJSON(result);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const nonZero = result.tokenBalances.filter(
|
|
448
|
+
(tb) => tb.tokenBalance !== "0x0" && tb.tokenBalance !== "0x0000000000000000000000000000000000000000000000000000000000000000"
|
|
449
|
+
);
|
|
450
|
+
if (nonZero.length === 0) {
|
|
451
|
+
emptyState("No token balances found.");
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const rows = nonZero.map((tb) => {
|
|
455
|
+
let decimalBalance = dim("unparseable");
|
|
456
|
+
try {
|
|
457
|
+
decimalBalance = BigInt(tb.tokenBalance).toString();
|
|
458
|
+
} catch {
|
|
459
|
+
}
|
|
460
|
+
return [tb.contractAddress, decimalBalance, tb.tokenBalance];
|
|
461
|
+
});
|
|
462
|
+
printKeyValueBox([
|
|
463
|
+
["Address", address],
|
|
464
|
+
["Network", client.network],
|
|
465
|
+
["Non-zero tokens", String(nonZero.length)]
|
|
466
|
+
]);
|
|
467
|
+
printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
|
|
468
|
+
console.log(`
|
|
469
|
+
${dim(`Showing ${nonZero.length} of ${result.tokenBalances.length} contracts (non-zero only).`)}`);
|
|
470
|
+
if (verbose) {
|
|
471
|
+
console.log("");
|
|
472
|
+
printJSON(result);
|
|
473
|
+
}
|
|
474
|
+
if (result.pageKey) {
|
|
475
|
+
console.log(`
|
|
476
|
+
${dim(`More results available. Use --page-key ${result.pageKey} to see the next page.`)}`);
|
|
477
|
+
}
|
|
478
|
+
} catch (err) {
|
|
479
|
+
exitWithError(err);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
cmd.command("metadata <contract>").description("Get ERC-20 token metadata").action(async (contract) => {
|
|
483
|
+
try {
|
|
484
|
+
validateAddress(contract);
|
|
485
|
+
const client = clientFromFlags(program2);
|
|
486
|
+
const result = await withSpinner(
|
|
487
|
+
"Fetching token metadata\u2026",
|
|
488
|
+
"Token metadata fetched",
|
|
489
|
+
() => client.call("alchemy_getTokenMetadata", [contract])
|
|
490
|
+
);
|
|
491
|
+
if (isJSONMode()) printJSON(result);
|
|
492
|
+
else printSyntaxJSON(result);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
exitWithError(err);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
cmd.command("allowance").description("Get ERC-20 token allowance").requiredOption("--owner <address>", "Owner address").requiredOption("--spender <address>", "Spender address").requiredOption("--contract <address>", "Token contract address").action(async (opts) => {
|
|
498
|
+
try {
|
|
499
|
+
validateAddress(opts.owner);
|
|
500
|
+
validateAddress(opts.spender);
|
|
501
|
+
validateAddress(opts.contract);
|
|
502
|
+
const client = clientFromFlags(program2);
|
|
503
|
+
const result = await withSpinner(
|
|
504
|
+
"Fetching token allowance\u2026",
|
|
505
|
+
"Token allowance fetched",
|
|
506
|
+
() => client.call("alchemy_getTokenAllowance", [
|
|
507
|
+
opts.owner,
|
|
508
|
+
opts.spender,
|
|
509
|
+
opts.contract
|
|
510
|
+
])
|
|
511
|
+
);
|
|
512
|
+
if (isJSONMode()) printJSON(result);
|
|
513
|
+
else printSyntaxJSON(result);
|
|
514
|
+
} catch (err) {
|
|
515
|
+
exitWithError(err);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/commands/network.ts
|
|
521
|
+
function registerNetwork(program2) {
|
|
522
|
+
const cmd = program2.command("network").description("Manage networks");
|
|
523
|
+
cmd.command("list").description("List supported RPC network slugs").option(
|
|
524
|
+
"--configured",
|
|
525
|
+
"List only configured app RPC networks (requires access key and app context)"
|
|
526
|
+
).option(
|
|
527
|
+
"--app-id <id>",
|
|
528
|
+
"App ID for configured network lookups (overrides saved app)"
|
|
529
|
+
).action(async (opts) => {
|
|
530
|
+
try {
|
|
531
|
+
const supported = getRPCNetworks();
|
|
532
|
+
const current = resolveNetwork(program2);
|
|
533
|
+
const configured = opts.configured ? await withSpinner(
|
|
534
|
+
"Fetching configured networks\u2026",
|
|
535
|
+
"Configured networks fetched",
|
|
536
|
+
() => resolveConfiguredNetworkSlugs(program2, opts.appId)
|
|
537
|
+
) : null;
|
|
538
|
+
const configuredSet = new Set(configured ?? []);
|
|
539
|
+
const appId = opts.configured ? opts.appId || resolveAppId(program2) : void 0;
|
|
540
|
+
const display = configured ? supported.filter((network) => configuredSet.has(network.id)) : supported;
|
|
541
|
+
if (isJSONMode()) {
|
|
542
|
+
if (configured) {
|
|
543
|
+
printJSON({
|
|
544
|
+
mode: "configured",
|
|
545
|
+
appId,
|
|
546
|
+
configuredNetworkIds: configured,
|
|
547
|
+
networks: display
|
|
548
|
+
});
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
printJSON(display);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const rows = display.map((network) => {
|
|
555
|
+
const isCurrent = network.id === current;
|
|
556
|
+
const idCell = isCurrent ? green(network.id) : network.id;
|
|
557
|
+
const nameCell = isCurrent ? green(network.name) : network.name;
|
|
558
|
+
const testnetCell = network.isTestnet ? dim("yes") : "no";
|
|
559
|
+
return [idCell, nameCell, network.family, testnetCell];
|
|
560
|
+
});
|
|
561
|
+
printTable(["Network ID", "Name", "Family", "Testnet"], rows);
|
|
562
|
+
if (configured) {
|
|
563
|
+
console.log(
|
|
564
|
+
`
|
|
565
|
+
${dim(`Configured networks for app ${appId}: ${display.length}`)}`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
console.log(`
|
|
569
|
+
Current: ${green(current)}`);
|
|
570
|
+
console.log(
|
|
571
|
+
` ${dim("Need Admin API chain enums instead? Run: alchemy chains list")}`
|
|
572
|
+
);
|
|
573
|
+
if (verbose) {
|
|
574
|
+
console.log("");
|
|
575
|
+
printJSON({
|
|
576
|
+
mode: configured ? "configured" : "all",
|
|
577
|
+
appId: appId ?? null,
|
|
578
|
+
configuredNetworkIds: configured ?? null,
|
|
579
|
+
networks: display,
|
|
580
|
+
currentNetwork: current
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
} catch (err) {
|
|
584
|
+
exitWithError(err);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/commands/version.ts
|
|
590
|
+
function registerVersion(program2) {
|
|
591
|
+
program2.command("version").description("Print the CLI version").action(() => {
|
|
592
|
+
const version = program2.version() || "dev";
|
|
593
|
+
printHuman(` ${brand("\u25C6")} ${bold("alchemy-cli")} ${version}
|
|
594
|
+
`, { version });
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/commands/chains.ts
|
|
599
|
+
function registerChains(program2) {
|
|
600
|
+
const cmd = program2.command("chains").description("Manage Admin API chain enums");
|
|
601
|
+
cmd.command("list").description("List available Admin API chain enums").action(async () => {
|
|
602
|
+
try {
|
|
603
|
+
const admin = adminClientFromFlags(program2);
|
|
604
|
+
const chains = await withSpinner(
|
|
605
|
+
"Fetching chains\u2026",
|
|
606
|
+
"Chains fetched",
|
|
607
|
+
() => admin.listChains()
|
|
608
|
+
);
|
|
609
|
+
if (isJSONMode()) {
|
|
610
|
+
printJSON(chains);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (chains.length === 0) {
|
|
614
|
+
emptyState("No chain networks were returned.");
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const rows = chains.map((c) => [
|
|
618
|
+
c.id,
|
|
619
|
+
c.name,
|
|
620
|
+
c.isTestnet ? dim("yes") : "no",
|
|
621
|
+
c.availability === "public" ? green(c.availability) : dim(c.availability),
|
|
622
|
+
c.currency
|
|
623
|
+
]);
|
|
624
|
+
printTable(["ID", "Name", "Testnet", "Availability", "Currency"], rows);
|
|
625
|
+
if (verbose) {
|
|
626
|
+
console.log("");
|
|
627
|
+
printJSON(chains);
|
|
628
|
+
}
|
|
629
|
+
} catch (err) {
|
|
630
|
+
exitWithError(err);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// src/commands/apps.ts
|
|
636
|
+
function maskAppSecrets(app) {
|
|
637
|
+
return {
|
|
638
|
+
...app,
|
|
639
|
+
...app.apiKey !== void 0 && { apiKey: maskIf(app.apiKey) },
|
|
640
|
+
...app.webhookApiKey !== void 0 && { webhookApiKey: maskIf(app.webhookApiKey) }
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function printFetchSummary(appsCount, pagesCount, opts) {
|
|
644
|
+
const suffix = opts?.suffix ? ` ${opts.suffix}` : "";
|
|
645
|
+
console.log(`
|
|
646
|
+
${dim(`Fetched ${appsCount} apps across ${pagesCount} pages${suffix}`)}`);
|
|
647
|
+
}
|
|
648
|
+
async function promptPaginationAction() {
|
|
649
|
+
const action = await promptSelect({
|
|
650
|
+
message: "More apps available",
|
|
651
|
+
options: [
|
|
652
|
+
{ label: "Load next page", value: "next" },
|
|
653
|
+
{ label: "Load all remaining pages", value: "all" },
|
|
654
|
+
{ label: "Stop here", value: "stop" }
|
|
655
|
+
],
|
|
656
|
+
initialValue: "next",
|
|
657
|
+
cancelMessage: "Stopped pagination."
|
|
658
|
+
});
|
|
659
|
+
if (action === null) {
|
|
660
|
+
return "stop";
|
|
661
|
+
}
|
|
662
|
+
if (action === "next" || action === "all" || action === "stop") {
|
|
663
|
+
return action;
|
|
664
|
+
}
|
|
665
|
+
return "stop";
|
|
666
|
+
}
|
|
667
|
+
function matchesSearch(app, query) {
|
|
668
|
+
const q = query.toLowerCase();
|
|
669
|
+
return app.name.toLowerCase().includes(q) || app.id.toLowerCase().includes(q);
|
|
670
|
+
}
|
|
671
|
+
function appToTableRow(app) {
|
|
672
|
+
return [app.id, app.name, String(app.chainNetworks.length), app.createdAt];
|
|
673
|
+
}
|
|
674
|
+
function handleDryRun(opts, action, payload, humanMsg) {
|
|
675
|
+
if (!opts.dryRun) return false;
|
|
676
|
+
if (isJSONMode()) {
|
|
677
|
+
printJSON({ dryRun: true, action, payload });
|
|
678
|
+
} else {
|
|
679
|
+
console.log(` ${dim("Dry run:")} ${humanMsg}`);
|
|
680
|
+
}
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
function registerApps(program2) {
|
|
684
|
+
const cmd = program2.command("apps").description("Manage Alchemy apps");
|
|
685
|
+
cmd.command("list").description("List all apps").option("--cursor <cursor>", "Pagination cursor").option("--limit <n>", "Max results per page", parseInt).option("--all", "Fetch all pages").option("--search <query>", "Search apps by name or id (client-side)").option("--id <appId>", "Filter by exact app id (client-side)").action(async (opts) => {
|
|
686
|
+
try {
|
|
687
|
+
const admin = adminClientFromFlags(program2);
|
|
688
|
+
const fetchAll = Boolean(opts.all);
|
|
689
|
+
const hasSearch = typeof opts.search === "string";
|
|
690
|
+
const hasId = typeof opts.id === "string";
|
|
691
|
+
const searchQuery = hasSearch ? opts.search.trim() : "";
|
|
692
|
+
const idQuery = hasId ? opts.id.trim() : "";
|
|
693
|
+
if (opts.all && opts.cursor) {
|
|
694
|
+
throw errInvalidArgs("Cannot combine --all with --cursor");
|
|
695
|
+
}
|
|
696
|
+
if (hasSearch && hasId) {
|
|
697
|
+
throw errInvalidArgs("Cannot combine --search with --id");
|
|
698
|
+
}
|
|
699
|
+
if (opts.cursor && (hasSearch || hasId)) {
|
|
700
|
+
throw errInvalidArgs("Cannot combine --cursor with --search or --id");
|
|
701
|
+
}
|
|
702
|
+
if (hasSearch && !searchQuery) {
|
|
703
|
+
throw errInvalidArgs("--search cannot be empty");
|
|
704
|
+
}
|
|
705
|
+
if (hasId && !idQuery) {
|
|
706
|
+
throw errInvalidArgs("--id cannot be empty");
|
|
707
|
+
}
|
|
708
|
+
const isFilteredList = hasSearch || hasId;
|
|
709
|
+
if (fetchAll || isFilteredList) {
|
|
710
|
+
const result2 = await withSpinner(
|
|
711
|
+
"Fetching apps\u2026",
|
|
712
|
+
"Apps fetched",
|
|
713
|
+
() => admin.listAllApps({ limit: opts.limit })
|
|
714
|
+
);
|
|
715
|
+
const filteredApps = hasId ? result2.apps.filter((a) => a.id === idQuery) : hasSearch ? result2.apps.filter((a) => matchesSearch(a, searchQuery)) : result2.apps;
|
|
716
|
+
if (isJSONMode()) {
|
|
717
|
+
printJSON({
|
|
718
|
+
apps: filteredApps.map(maskAppSecrets),
|
|
719
|
+
pageInfo: {
|
|
720
|
+
mode: fetchAll ? "all" : "search",
|
|
721
|
+
pages: result2.pages,
|
|
722
|
+
scannedApps: result2.apps.length,
|
|
723
|
+
...hasSearch && { search: searchQuery },
|
|
724
|
+
...hasId && { id: idQuery }
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (filteredApps.length === 0) {
|
|
730
|
+
emptyState(
|
|
731
|
+
hasId ? `No apps found with id "${idQuery}".` : hasSearch ? `No apps found matching "${searchQuery}".` : "No apps found."
|
|
732
|
+
);
|
|
733
|
+
printFetchSummary(result2.apps.length, result2.pages);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const rows2 = filteredApps.map(appToTableRow);
|
|
737
|
+
printTable(["ID", "Name", "Networks", "Created"], rows2);
|
|
738
|
+
if (isFilteredList) {
|
|
739
|
+
const filterLabel = hasId ? `id "${idQuery}"` : `"${searchQuery}"`;
|
|
740
|
+
console.log(`
|
|
741
|
+
${dim(`Matched ${filteredApps.length} apps for ${filterLabel}`)}`);
|
|
742
|
+
}
|
|
743
|
+
printFetchSummary(result2.apps.length, result2.pages);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const result = await withSpinner(
|
|
747
|
+
"Fetching apps\u2026",
|
|
748
|
+
"Apps fetched",
|
|
749
|
+
() => admin.listApps({ cursor: opts.cursor, limit: opts.limit })
|
|
750
|
+
);
|
|
751
|
+
if (isJSONMode()) {
|
|
752
|
+
printJSON({ ...result, apps: result.apps.map(maskAppSecrets) });
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const interactivePagination = isInteractiveAllowed(program2) && !opts.all;
|
|
756
|
+
if (interactivePagination) {
|
|
757
|
+
let page = result;
|
|
758
|
+
let autoFetchRemaining = false;
|
|
759
|
+
let pagesFetched = 0;
|
|
760
|
+
let appsFetched = 0;
|
|
761
|
+
while (true) {
|
|
762
|
+
if (page.apps.length > 0) {
|
|
763
|
+
pagesFetched += 1;
|
|
764
|
+
appsFetched += page.apps.length;
|
|
765
|
+
const rows2 = page.apps.map(appToTableRow);
|
|
766
|
+
printTable(["ID", "Name", "Networks", "Created"], rows2);
|
|
767
|
+
} else {
|
|
768
|
+
emptyState("No apps found.");
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (!page.cursor) {
|
|
772
|
+
printFetchSummary(appsFetched, pagesFetched);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (!autoFetchRemaining) {
|
|
776
|
+
printFetchSummary(appsFetched, pagesFetched, { suffix: "so far" });
|
|
777
|
+
const action = await promptPaginationAction();
|
|
778
|
+
if (action === "stop") {
|
|
779
|
+
console.log(`
|
|
780
|
+
${dim(`Next cursor: ${page.cursor}`)}`);
|
|
781
|
+
printFetchSummary(appsFetched, pagesFetched);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
if (action === "all") {
|
|
785
|
+
autoFetchRemaining = true;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
page = await withSpinner(
|
|
789
|
+
"Fetching next page\u2026",
|
|
790
|
+
"Page fetched",
|
|
791
|
+
() => admin.listApps({ cursor: page.cursor, limit: opts.limit })
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (result.apps.length === 0) {
|
|
796
|
+
emptyState("No apps found.");
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const rows = result.apps.map(appToTableRow);
|
|
800
|
+
printTable(["ID", "Name", "Networks", "Created"], rows);
|
|
801
|
+
printFetchSummary(result.apps.length, 1);
|
|
802
|
+
if (result.cursor) {
|
|
803
|
+
console.log(`
|
|
804
|
+
${dim(`Next cursor: ${result.cursor}`)}`);
|
|
805
|
+
}
|
|
806
|
+
} catch (err) {
|
|
807
|
+
exitWithError(err);
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
cmd.command("get <id>").description("Get app details").action(async (id) => {
|
|
811
|
+
try {
|
|
812
|
+
const admin = adminClientFromFlags(program2);
|
|
813
|
+
const app = await withSpinner(
|
|
814
|
+
"Fetching app\u2026",
|
|
815
|
+
"App fetched",
|
|
816
|
+
() => admin.getApp(id)
|
|
817
|
+
);
|
|
818
|
+
if (isJSONMode()) {
|
|
819
|
+
printJSON(maskAppSecrets(app));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const networks = app.chainNetworks.map((n) => n.name).join(", ");
|
|
823
|
+
printKeyValueBox([
|
|
824
|
+
["ID", app.id],
|
|
825
|
+
["Name", app.name],
|
|
826
|
+
...app.description ? [["Description", app.description]] : [],
|
|
827
|
+
["API Key", maskIf(app.apiKey)],
|
|
828
|
+
["Networks", networks || dim("none")],
|
|
829
|
+
["Created", app.createdAt]
|
|
830
|
+
]);
|
|
831
|
+
} catch (err) {
|
|
832
|
+
exitWithError(err);
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
cmd.command("create").description("Create a new app").requiredOption("--name <name>", "App name").requiredOption("--networks <networks>", "Comma-separated network IDs").option("--description <desc>", "App description").option("--products <products>", "Comma-separated product IDs").option("--dry-run", "Preview without executing").action(async (opts) => {
|
|
836
|
+
try {
|
|
837
|
+
const networks = splitCommaList(opts.networks);
|
|
838
|
+
const products = opts.products ? splitCommaList(opts.products) : void 0;
|
|
839
|
+
const payload = {
|
|
840
|
+
name: opts.name,
|
|
841
|
+
networks,
|
|
842
|
+
...opts.description && { description: opts.description },
|
|
843
|
+
...products && { products }
|
|
844
|
+
};
|
|
845
|
+
if (handleDryRun(opts, "create", payload, `Would create app "${opts.name}" on networks: ${networks.join(", ")}`)) return;
|
|
846
|
+
const admin = adminClientFromFlags(program2);
|
|
847
|
+
const app = await withSpinner(
|
|
848
|
+
"Creating app\u2026",
|
|
849
|
+
"App created",
|
|
850
|
+
() => admin.createApp({
|
|
851
|
+
name: opts.name,
|
|
852
|
+
networks,
|
|
853
|
+
description: opts.description,
|
|
854
|
+
products
|
|
855
|
+
})
|
|
856
|
+
);
|
|
857
|
+
if (isJSONMode()) {
|
|
858
|
+
printJSON(maskAppSecrets(app));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
printKeyValueBox([
|
|
862
|
+
["ID", app.id],
|
|
863
|
+
["Name", app.name],
|
|
864
|
+
["API Key", maskIf(app.apiKey)]
|
|
865
|
+
]);
|
|
866
|
+
} catch (err) {
|
|
867
|
+
exitWithError(err);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
cmd.command("delete <id>").description("Delete an app").option("--dry-run", "Preview without executing").action(async (id, opts) => {
|
|
871
|
+
try {
|
|
872
|
+
if (handleDryRun(opts, "delete", { id }, `Would delete app ${id}`)) return;
|
|
873
|
+
const admin = adminClientFromFlags(program2);
|
|
874
|
+
await withSpinner(
|
|
875
|
+
"Deleting app\u2026",
|
|
876
|
+
"App deleted",
|
|
877
|
+
() => admin.deleteApp(id)
|
|
878
|
+
);
|
|
879
|
+
if (isJSONMode()) {
|
|
880
|
+
printJSON({ id, status: "deleted" });
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
console.log(` ${green("\u2713")} Deleted app ${id}`);
|
|
884
|
+
} catch (err) {
|
|
885
|
+
exitWithError(err);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
cmd.command("update <id>").description("Update an app").option("--name <name>", "New app name").option("--description <desc>", "New app description").option("--dry-run", "Preview without executing").action(async (id, opts) => {
|
|
889
|
+
try {
|
|
890
|
+
if (!opts.name && !opts.description) {
|
|
891
|
+
throw errInvalidArgs("Provide at least --name or --description");
|
|
892
|
+
}
|
|
893
|
+
const payload = {
|
|
894
|
+
id,
|
|
895
|
+
...opts.name && { name: opts.name },
|
|
896
|
+
...opts.description && { description: opts.description }
|
|
897
|
+
};
|
|
898
|
+
if (handleDryRun(opts, "update", payload, `Would update app ${id}`)) return;
|
|
899
|
+
const admin = adminClientFromFlags(program2);
|
|
900
|
+
const app = await withSpinner(
|
|
901
|
+
"Updating app\u2026",
|
|
902
|
+
"App updated",
|
|
903
|
+
() => admin.updateApp(id, {
|
|
904
|
+
name: opts.name,
|
|
905
|
+
description: opts.description
|
|
906
|
+
})
|
|
907
|
+
);
|
|
908
|
+
if (isJSONMode()) {
|
|
909
|
+
printJSON(maskAppSecrets(app));
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
console.log(` ${green("\u2713")} Updated app ${app.name}`);
|
|
913
|
+
} catch (err) {
|
|
914
|
+
exitWithError(err);
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
cmd.command("networks <id>").description("Update app network allowlist").requiredOption("--networks <networks>", "Comma-separated network IDs").option("--dry-run", "Preview without executing").action(async (id, opts) => {
|
|
918
|
+
try {
|
|
919
|
+
const networks = splitCommaList(opts.networks);
|
|
920
|
+
if (handleDryRun(opts, "networks", { id, networks }, `Would update networks for app ${id}: ${networks.join(", ")}`)) return;
|
|
921
|
+
const admin = adminClientFromFlags(program2);
|
|
922
|
+
const app = await withSpinner(
|
|
923
|
+
"Updating networks\u2026",
|
|
924
|
+
"Networks updated",
|
|
925
|
+
() => admin.updateNetworkAllowlist(id, networks)
|
|
926
|
+
);
|
|
927
|
+
if (isJSONMode()) {
|
|
928
|
+
printJSON(maskAppSecrets(app));
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
console.log(
|
|
932
|
+
` ${green("\u2713")} Updated networks for ${app.name} (${app.chainNetworks.length} networks)`
|
|
933
|
+
);
|
|
934
|
+
} catch (err) {
|
|
935
|
+
exitWithError(err);
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
cmd.command("address-allowlist <id>").description("Update app address allowlist").requiredOption("--addresses <addrs>", "Comma-separated addresses").option("--dry-run", "Preview without executing").action(async (id, opts) => {
|
|
939
|
+
try {
|
|
940
|
+
const entries = splitCommaList(opts.addresses).map((s) => ({ value: s }));
|
|
941
|
+
if (handleDryRun(opts, "address-allowlist", { id, addresses: entries }, `Would update address allowlist for app ${id}`)) return;
|
|
942
|
+
const admin = adminClientFromFlags(program2);
|
|
943
|
+
const app = await withSpinner(
|
|
944
|
+
"Updating address allowlist\u2026",
|
|
945
|
+
"Address allowlist updated",
|
|
946
|
+
() => admin.updateAddressAllowlist(id, entries)
|
|
947
|
+
);
|
|
948
|
+
if (isJSONMode()) {
|
|
949
|
+
printJSON(maskAppSecrets(app));
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
console.log(
|
|
953
|
+
` ${green("\u2713")} Updated address allowlist for ${app.name}`
|
|
954
|
+
);
|
|
955
|
+
} catch (err) {
|
|
956
|
+
exitWithError(err);
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
cmd.command("origin-allowlist <id>").description("Update app origin allowlist").requiredOption("--origins <origins>", "Comma-separated origins").option("--dry-run", "Preview without executing").action(async (id, opts) => {
|
|
960
|
+
try {
|
|
961
|
+
const entries = splitCommaList(opts.origins).map((s) => ({ value: s }));
|
|
962
|
+
if (handleDryRun(opts, "origin-allowlist", { id, origins: entries }, `Would update origin allowlist for app ${id}`)) return;
|
|
963
|
+
const admin = adminClientFromFlags(program2);
|
|
964
|
+
const app = await withSpinner(
|
|
965
|
+
"Updating origin allowlist\u2026",
|
|
966
|
+
"Origin allowlist updated",
|
|
967
|
+
() => admin.updateOriginAllowlist(id, entries)
|
|
968
|
+
);
|
|
969
|
+
if (isJSONMode()) {
|
|
970
|
+
printJSON(maskAppSecrets(app));
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
console.log(
|
|
974
|
+
` ${green("\u2713")} Updated origin allowlist for ${app.name}`
|
|
975
|
+
);
|
|
976
|
+
} catch (err) {
|
|
977
|
+
exitWithError(err);
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
cmd.command("ip-allowlist <id>").description("Update app IP allowlist").requiredOption("--ips <ips>", "Comma-separated IP addresses").option("--dry-run", "Preview without executing").action(async (id, opts) => {
|
|
981
|
+
try {
|
|
982
|
+
const entries = splitCommaList(opts.ips).map((s) => ({ value: s }));
|
|
983
|
+
if (handleDryRun(opts, "ip-allowlist", { id, ips: entries }, `Would update IP allowlist for app ${id}`)) return;
|
|
984
|
+
const admin = adminClientFromFlags(program2);
|
|
985
|
+
const app = await withSpinner(
|
|
986
|
+
"Updating IP allowlist\u2026",
|
|
987
|
+
"IP allowlist updated",
|
|
988
|
+
() => admin.updateIpAllowlist(id, entries)
|
|
989
|
+
);
|
|
990
|
+
if (isJSONMode()) {
|
|
991
|
+
printJSON(maskAppSecrets(app));
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
console.log(
|
|
995
|
+
` ${green("\u2713")} Updated IP allowlist for ${app.name}`
|
|
996
|
+
);
|
|
997
|
+
} catch (err) {
|
|
998
|
+
exitWithError(err);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/commands/setup.ts
|
|
1004
|
+
function registerSetup(program2) {
|
|
1005
|
+
const cmd = program2.command("setup").description("Setup and onboarding utilities");
|
|
1006
|
+
cmd.command("status").description("Show setup status and remediation commands").action(() => {
|
|
1007
|
+
const status = getSetupStatus(load());
|
|
1008
|
+
if (isJSONMode()) {
|
|
1009
|
+
printJSON(status);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
printKeyValueBox([
|
|
1013
|
+
["Complete", status.complete ? "yes" : "no"],
|
|
1014
|
+
["Satisfied by", status.satisfiedBy ?? dim("(none)")]
|
|
1015
|
+
]);
|
|
1016
|
+
if (status.missing.length > 0) {
|
|
1017
|
+
console.log("");
|
|
1018
|
+
console.log(` ${dim("Missing:")}`);
|
|
1019
|
+
for (const item of status.missing) {
|
|
1020
|
+
console.log(` ${dim(`- ${item}`)}`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (status.nextCommands.length > 0) {
|
|
1024
|
+
console.log("");
|
|
1025
|
+
console.log(` ${dim("Next commands:")}`);
|
|
1026
|
+
for (const command of status.nextCommands) {
|
|
1027
|
+
console.log(` ${dim(`- ${command}`)}`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/lib/params.ts
|
|
1034
|
+
function parseCLIParams(params) {
|
|
1035
|
+
return params.map((param) => {
|
|
1036
|
+
try {
|
|
1037
|
+
return JSON.parse(param);
|
|
1038
|
+
} catch {
|
|
1039
|
+
return param;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
function parseRequiredJSON(input, label) {
|
|
1044
|
+
try {
|
|
1045
|
+
return JSON.parse(input);
|
|
1046
|
+
} catch {
|
|
1047
|
+
throw errInvalidArgs(`Invalid JSON for ${label}.`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/commands/trace.ts
|
|
1052
|
+
function normalizeTraceMethod(method) {
|
|
1053
|
+
if (method.startsWith("trace_")) return method;
|
|
1054
|
+
const normalized = method.replace(/-/g, "_").toLowerCase();
|
|
1055
|
+
if (normalized === "tx" || normalized === "transaction") {
|
|
1056
|
+
return "trace_transaction";
|
|
1057
|
+
}
|
|
1058
|
+
return `trace_${normalized}`;
|
|
1059
|
+
}
|
|
1060
|
+
function registerTrace(program2) {
|
|
1061
|
+
program2.command("trace <method> [params...]").description("Call a trace_* method").action(async (method, params) => {
|
|
1062
|
+
try {
|
|
1063
|
+
const client = clientFromFlags(program2);
|
|
1064
|
+
const rpcMethod = normalizeTraceMethod(method);
|
|
1065
|
+
const result = await withSpinner(
|
|
1066
|
+
`Calling ${rpcMethod}\u2026`,
|
|
1067
|
+
`Called ${rpcMethod}`,
|
|
1068
|
+
() => client.call(rpcMethod, parseCLIParams(params))
|
|
1069
|
+
);
|
|
1070
|
+
printSyntaxJSON(result);
|
|
1071
|
+
} catch (err) {
|
|
1072
|
+
exitWithError(err);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/commands/debug.ts
|
|
1078
|
+
function normalizeDebugMethod(method) {
|
|
1079
|
+
if (method.startsWith("debug_")) return method;
|
|
1080
|
+
return `debug_${method.replace(/-/g, "_")}`;
|
|
1081
|
+
}
|
|
1082
|
+
function registerDebug(program2) {
|
|
1083
|
+
program2.command("debug <method> [params...]").description("Call a debug_* method").action(async (method, params) => {
|
|
1084
|
+
try {
|
|
1085
|
+
const client = clientFromFlags(program2);
|
|
1086
|
+
const rpcMethod = normalizeDebugMethod(method);
|
|
1087
|
+
const result = await withSpinner(
|
|
1088
|
+
`Calling ${rpcMethod}\u2026`,
|
|
1089
|
+
`Called ${rpcMethod}`,
|
|
1090
|
+
() => client.call(rpcMethod, parseCLIParams(params))
|
|
1091
|
+
);
|
|
1092
|
+
printSyntaxJSON(result);
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
exitWithError(err);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/commands/transfers.ts
|
|
1100
|
+
function registerTransfers(program2) {
|
|
1101
|
+
program2.command("transfers [address]").description("Get transfer history (alchemy_getAssetTransfers)").option("--from-address <address>", "Filter sender address").option("--to-address <address>", "Filter recipient address").option("--from-block <block>", "Start block (default: 0x0)").option("--to-block <block>", "End block (default: latest)").option("--category <list>", "Comma-separated categories (erc20,erc721,erc1155,external,internal,specialnft)").option("--max-count <hexOrDecimal>", "Max records to return").option("--page-key <key>", "Pagination key").action(async (addressArg, opts) => {
|
|
1102
|
+
try {
|
|
1103
|
+
const client = clientFromFlags(program2);
|
|
1104
|
+
const address = addressArg ?? void 0;
|
|
1105
|
+
if (address) validateAddress(address);
|
|
1106
|
+
if (opts.fromAddress) validateAddress(opts.fromAddress);
|
|
1107
|
+
if (opts.toAddress) validateAddress(opts.toAddress);
|
|
1108
|
+
const filter = {
|
|
1109
|
+
fromBlock: opts.fromBlock ?? "0x0",
|
|
1110
|
+
toBlock: opts.toBlock ?? "latest",
|
|
1111
|
+
withMetadata: true
|
|
1112
|
+
};
|
|
1113
|
+
if (address && !opts.fromAddress && !opts.toAddress) {
|
|
1114
|
+
filter.fromAddress = address;
|
|
1115
|
+
filter.toAddress = address;
|
|
1116
|
+
} else {
|
|
1117
|
+
if (opts.fromAddress) filter.fromAddress = opts.fromAddress;
|
|
1118
|
+
if (opts.toAddress) filter.toAddress = opts.toAddress;
|
|
1119
|
+
}
|
|
1120
|
+
if (opts.category) filter.category = splitCommaList(opts.category);
|
|
1121
|
+
if (opts.maxCount) filter.maxCount = opts.maxCount.startsWith("0x") ? opts.maxCount : `0x${Number.parseInt(opts.maxCount, 10).toString(16)}`;
|
|
1122
|
+
if (opts.pageKey) filter.pageKey = opts.pageKey;
|
|
1123
|
+
const result = await withSpinner(
|
|
1124
|
+
"Fetching transfers\u2026",
|
|
1125
|
+
"Transfers fetched",
|
|
1126
|
+
() => client.call("alchemy_getAssetTransfers", [filter])
|
|
1127
|
+
);
|
|
1128
|
+
if (isJSONMode()) {
|
|
1129
|
+
printJSON(result);
|
|
1130
|
+
} else {
|
|
1131
|
+
printSyntaxJSON(result);
|
|
1132
|
+
}
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
exitWithError(err);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/lib/rest.ts
|
|
1140
|
+
function withQuery(url, query) {
|
|
1141
|
+
if (!query) return url;
|
|
1142
|
+
for (const [key, value] of Object.entries(query)) {
|
|
1143
|
+
if (value !== void 0 && value !== "") {
|
|
1144
|
+
url.searchParams.set(key, value);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return url;
|
|
1148
|
+
}
|
|
1149
|
+
async function requestJSON(url, options) {
|
|
1150
|
+
const method = options.method ?? "GET";
|
|
1151
|
+
const resp = await fetchWithTimeout(url.toString(), {
|
|
1152
|
+
method,
|
|
1153
|
+
headers: {
|
|
1154
|
+
Accept: "application/json",
|
|
1155
|
+
...options.body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
1156
|
+
...options.extraHeaders ?? {}
|
|
1157
|
+
},
|
|
1158
|
+
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {}
|
|
1159
|
+
});
|
|
1160
|
+
if (resp.status === 429) throw errRateLimited();
|
|
1161
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
1162
|
+
const detail = await resp.text().catch(() => "");
|
|
1163
|
+
throw errInvalidAPIKey(detail || void 0);
|
|
1164
|
+
}
|
|
1165
|
+
if (!resp.ok) {
|
|
1166
|
+
const text = await resp.text().catch(() => "");
|
|
1167
|
+
throw errNetwork(`HTTP ${resp.status}: ${text}`);
|
|
1168
|
+
}
|
|
1169
|
+
return resp.json();
|
|
1170
|
+
}
|
|
1171
|
+
async function callApiData(apiKey, path, options = {}) {
|
|
1172
|
+
if (!apiKey) throw errAuthRequired();
|
|
1173
|
+
const base = new URL(`https://api.g.alchemy.com/data/v1/${apiKey}/`);
|
|
1174
|
+
const url = withQuery(new URL(path.replace(/^\//, ""), base), options.query);
|
|
1175
|
+
return requestJSON(url, { ...options, path });
|
|
1176
|
+
}
|
|
1177
|
+
async function callApiPrices(apiKey, path, options = {}) {
|
|
1178
|
+
if (!apiKey) throw errAuthRequired();
|
|
1179
|
+
const base = new URL(`https://api.g.alchemy.com/prices/v1/${apiKey}/`);
|
|
1180
|
+
const url = withQuery(new URL(path.replace(/^\//, ""), base), options.query);
|
|
1181
|
+
return requestJSON(url, { ...options, path });
|
|
1182
|
+
}
|
|
1183
|
+
async function callNotify(token, path, options = {}) {
|
|
1184
|
+
if (!token) {
|
|
1185
|
+
throw errInvalidArgs(
|
|
1186
|
+
"Webhook API key required. Set ALCHEMY_WEBHOOK_API_KEY (or ALCHEMY_NOTIFY_AUTH_TOKEN) or pass --webhook-api-key."
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
const base = new URL("https://dashboard.alchemy.com/api/");
|
|
1190
|
+
const url = withQuery(new URL(path.replace(/^\//, ""), base), options.query);
|
|
1191
|
+
return requestJSON(url, {
|
|
1192
|
+
...options,
|
|
1193
|
+
path,
|
|
1194
|
+
extraHeaders: {
|
|
1195
|
+
...options.extraHeaders ?? {},
|
|
1196
|
+
"X-Alchemy-Token": token
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/commands/prices.ts
|
|
1202
|
+
function registerPrices(program2) {
|
|
1203
|
+
const cmd = program2.command("prices").description("Prices API wrappers");
|
|
1204
|
+
cmd.command("symbol <symbols>").description("Get spot prices by symbol (comma-separated)").action(async (symbols) => {
|
|
1205
|
+
try {
|
|
1206
|
+
const apiKey = resolveAPIKey(program2);
|
|
1207
|
+
const values = splitCommaList(symbols);
|
|
1208
|
+
const query = new URLSearchParams();
|
|
1209
|
+
for (const symbol of values) query.append("symbols", symbol);
|
|
1210
|
+
const result = await withSpinner(
|
|
1211
|
+
"Fetching prices\u2026",
|
|
1212
|
+
"Prices fetched",
|
|
1213
|
+
() => callApiPrices(apiKey, `/tokens/by-symbol?${query.toString()}`)
|
|
1214
|
+
);
|
|
1215
|
+
if (isJSONMode()) printJSON(result);
|
|
1216
|
+
else printSyntaxJSON(result);
|
|
1217
|
+
} catch (err) {
|
|
1218
|
+
exitWithError(err);
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
cmd.command("address").description("Get spot prices by token addresses").requiredOption("--addresses <json>", "JSON array of {network,address}").action(async (opts) => {
|
|
1222
|
+
try {
|
|
1223
|
+
const apiKey = resolveAPIKey(program2);
|
|
1224
|
+
const body = { addresses: JSON.parse(opts.addresses) };
|
|
1225
|
+
const result = await withSpinner(
|
|
1226
|
+
"Fetching prices\u2026",
|
|
1227
|
+
"Prices fetched",
|
|
1228
|
+
() => callApiPrices(apiKey, "/tokens/by-address", { method: "POST", body })
|
|
1229
|
+
);
|
|
1230
|
+
if (isJSONMode()) printJSON(result);
|
|
1231
|
+
else printSyntaxJSON(result);
|
|
1232
|
+
} catch (err) {
|
|
1233
|
+
exitWithError(err);
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
cmd.command("historical").description("Get historical prices").requiredOption("--body <json>", "JSON request payload").action(async (opts) => {
|
|
1237
|
+
try {
|
|
1238
|
+
const apiKey = resolveAPIKey(program2);
|
|
1239
|
+
const payload = JSON.parse(opts.body);
|
|
1240
|
+
const result = await withSpinner(
|
|
1241
|
+
"Fetching historical prices\u2026",
|
|
1242
|
+
"Historical prices fetched",
|
|
1243
|
+
() => callApiPrices(apiKey, "/tokens/historical", { method: "POST", body: payload })
|
|
1244
|
+
);
|
|
1245
|
+
if (isJSONMode()) printJSON(result);
|
|
1246
|
+
else printSyntaxJSON(result);
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
exitWithError(err);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/commands/portfolio.ts
|
|
1254
|
+
async function runDataCall(apiKey, title, path, body) {
|
|
1255
|
+
return withSpinner(
|
|
1256
|
+
`Fetching ${title}\u2026`,
|
|
1257
|
+
`${title} fetched`,
|
|
1258
|
+
() => callApiData(apiKey, path, { method: "POST", body })
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
function registerPortfolio(program2) {
|
|
1262
|
+
const cmd = program2.command("portfolio").description("Portfolio API wrappers");
|
|
1263
|
+
cmd.command("tokens").description("Get token portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/by-address").action(async (opts) => {
|
|
1264
|
+
try {
|
|
1265
|
+
const apiKey = resolveAPIKey(program2);
|
|
1266
|
+
const result = await runDataCall(
|
|
1267
|
+
apiKey,
|
|
1268
|
+
"token portfolio",
|
|
1269
|
+
"/assets/tokens/by-address",
|
|
1270
|
+
JSON.parse(opts.body)
|
|
1271
|
+
);
|
|
1272
|
+
if (isJSONMode()) printJSON(result);
|
|
1273
|
+
else printSyntaxJSON(result);
|
|
1274
|
+
} catch (err) {
|
|
1275
|
+
exitWithError(err);
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
cmd.command("token-balances").description("Get token balances by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/balances/by-address").action(async (opts) => {
|
|
1279
|
+
try {
|
|
1280
|
+
const apiKey = resolveAPIKey(program2);
|
|
1281
|
+
const result = await runDataCall(
|
|
1282
|
+
apiKey,
|
|
1283
|
+
"token balances",
|
|
1284
|
+
"/assets/tokens/balances/by-address",
|
|
1285
|
+
JSON.parse(opts.body)
|
|
1286
|
+
);
|
|
1287
|
+
if (isJSONMode()) printJSON(result);
|
|
1288
|
+
else printSyntaxJSON(result);
|
|
1289
|
+
} catch (err) {
|
|
1290
|
+
exitWithError(err);
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
cmd.command("nfts").description("Get NFT portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/by-address").action(async (opts) => {
|
|
1294
|
+
try {
|
|
1295
|
+
const apiKey = resolveAPIKey(program2);
|
|
1296
|
+
const result = await runDataCall(
|
|
1297
|
+
apiKey,
|
|
1298
|
+
"NFT portfolio",
|
|
1299
|
+
"/assets/nfts/by-address",
|
|
1300
|
+
JSON.parse(opts.body)
|
|
1301
|
+
);
|
|
1302
|
+
if (isJSONMode()) printJSON(result);
|
|
1303
|
+
else printSyntaxJSON(result);
|
|
1304
|
+
} catch (err) {
|
|
1305
|
+
exitWithError(err);
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
cmd.command("nft-contracts").description("Get NFT contracts by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/contracts/by-address").action(async (opts) => {
|
|
1309
|
+
try {
|
|
1310
|
+
const apiKey = resolveAPIKey(program2);
|
|
1311
|
+
const result = await runDataCall(
|
|
1312
|
+
apiKey,
|
|
1313
|
+
"NFT contracts",
|
|
1314
|
+
"/assets/nfts/contracts/by-address",
|
|
1315
|
+
JSON.parse(opts.body)
|
|
1316
|
+
);
|
|
1317
|
+
if (isJSONMode()) printJSON(result);
|
|
1318
|
+
else printSyntaxJSON(result);
|
|
1319
|
+
} catch (err) {
|
|
1320
|
+
exitWithError(err);
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
cmd.command("transactions").description("Get transaction history by address/network pairs").requiredOption("--body <json>", "JSON body for /transactions/history/by-address").action(async (opts) => {
|
|
1324
|
+
try {
|
|
1325
|
+
const apiKey = resolveAPIKey(program2);
|
|
1326
|
+
const result = await runDataCall(
|
|
1327
|
+
apiKey,
|
|
1328
|
+
"transaction history",
|
|
1329
|
+
"/transactions/history/by-address",
|
|
1330
|
+
JSON.parse(opts.body)
|
|
1331
|
+
);
|
|
1332
|
+
if (isJSONMode()) printJSON(result);
|
|
1333
|
+
else printSyntaxJSON(result);
|
|
1334
|
+
} catch (err) {
|
|
1335
|
+
exitWithError(err);
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// src/commands/simulate.ts
|
|
1341
|
+
async function runSimulateCall(program2, options) {
|
|
1342
|
+
const client = clientFromFlags(program2);
|
|
1343
|
+
const result = await withSpinner(
|
|
1344
|
+
`Calling ${options.method}\u2026`,
|
|
1345
|
+
`Called ${options.method}`,
|
|
1346
|
+
() => client.call(options.method, options.params)
|
|
1347
|
+
);
|
|
1348
|
+
printSyntaxJSON(result);
|
|
1349
|
+
}
|
|
1350
|
+
function registerSimulate(program2) {
|
|
1351
|
+
const cmd = program2.command("simulate").description("Simulation API wrappers");
|
|
1352
|
+
cmd.command("asset-changes").description("Call alchemy_simulateAssetChanges").requiredOption("--tx <json>", "Transaction object JSON").option("--block-tag <tag>", "Block tag (default latest)", "latest").action(async (opts) => {
|
|
1353
|
+
try {
|
|
1354
|
+
await runSimulateCall(program2, {
|
|
1355
|
+
method: "alchemy_simulateAssetChanges",
|
|
1356
|
+
params: [parseRequiredJSON(opts.tx, "--tx"), opts.blockTag]
|
|
1357
|
+
});
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
exitWithError(err);
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
cmd.command("execution").description("Call alchemy_simulateExecution").requiredOption("--tx <json>", "Transaction object JSON").option("--block-tag <tag>", "Block tag (default latest)", "latest").action(async (opts) => {
|
|
1363
|
+
try {
|
|
1364
|
+
await runSimulateCall(program2, {
|
|
1365
|
+
method: "alchemy_simulateExecution",
|
|
1366
|
+
params: [parseRequiredJSON(opts.tx, "--tx"), opts.blockTag]
|
|
1367
|
+
});
|
|
1368
|
+
} catch (err) {
|
|
1369
|
+
exitWithError(err);
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
cmd.command("asset-changes-bundle").description("Call alchemy_simulateAssetChangesBundle").requiredOption("--txs <json>", "JSON array of tx objects").option("--block-tag <tag>", "Block tag (default latest)", "latest").action(async (opts) => {
|
|
1373
|
+
try {
|
|
1374
|
+
await runSimulateCall(program2, {
|
|
1375
|
+
method: "alchemy_simulateAssetChangesBundle",
|
|
1376
|
+
params: [parseRequiredJSON(opts.txs, "--txs"), opts.blockTag]
|
|
1377
|
+
});
|
|
1378
|
+
} catch (err) {
|
|
1379
|
+
exitWithError(err);
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
cmd.command("execution-bundle").description("Call alchemy_simulateExecutionBundle").requiredOption("--txs <json>", "JSON array of tx objects").option("--block-tag <tag>", "Block tag (default latest)", "latest").action(async (opts) => {
|
|
1383
|
+
try {
|
|
1384
|
+
await runSimulateCall(program2, {
|
|
1385
|
+
method: "alchemy_simulateExecutionBundle",
|
|
1386
|
+
params: [parseRequiredJSON(opts.txs, "--txs"), opts.blockTag]
|
|
1387
|
+
});
|
|
1388
|
+
} catch (err) {
|
|
1389
|
+
exitWithError(err);
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/commands/webhooks.ts
|
|
1395
|
+
function resolveWebhookApiKey(opts) {
|
|
1396
|
+
const cfg = load();
|
|
1397
|
+
return opts?.webhookApiKey || opts?.notifyToken || process.env.ALCHEMY_WEBHOOK_API_KEY || process.env.ALCHEMY_NOTIFY_AUTH_TOKEN || cfg.webhook_api_key || cfg.app?.webhookApiKey;
|
|
1398
|
+
}
|
|
1399
|
+
function registerWebhooks(program2) {
|
|
1400
|
+
const cmd = program2.command("webhooks").description("Notify API wrappers");
|
|
1401
|
+
cmd.option("--webhook-api-key <key>", "Webhook API key").option("--notify-token <token>", "Deprecated alias for webhook API key");
|
|
1402
|
+
cmd.command("list").description("List team webhooks").action(async () => {
|
|
1403
|
+
try {
|
|
1404
|
+
const token = resolveWebhookApiKey(cmd.opts());
|
|
1405
|
+
const result = await withSpinner(
|
|
1406
|
+
"Fetching webhooks\u2026",
|
|
1407
|
+
"Webhooks fetched",
|
|
1408
|
+
() => callNotify(token, "/team-webhooks")
|
|
1409
|
+
);
|
|
1410
|
+
if (isJSONMode()) printJSON(result);
|
|
1411
|
+
else printSyntaxJSON(result);
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
exitWithError(err);
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
cmd.command("create").description("Create webhook").requiredOption("--body <json>", "Create webhook JSON payload").action(async (opts) => {
|
|
1417
|
+
try {
|
|
1418
|
+
const token = resolveWebhookApiKey(cmd.opts());
|
|
1419
|
+
const result = await withSpinner(
|
|
1420
|
+
"Creating webhook\u2026",
|
|
1421
|
+
"Webhook created",
|
|
1422
|
+
() => callNotify(token, "/create-webhook", {
|
|
1423
|
+
method: "POST",
|
|
1424
|
+
body: parseRequiredJSON(opts.body, "--body")
|
|
1425
|
+
})
|
|
1426
|
+
);
|
|
1427
|
+
if (isJSONMode()) printJSON(result);
|
|
1428
|
+
else printSyntaxJSON(result);
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
exitWithError(err);
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
cmd.command("update").description("Update webhook").requiredOption("--body <json>", "Update webhook JSON payload").action(async (opts) => {
|
|
1434
|
+
try {
|
|
1435
|
+
const token = resolveWebhookApiKey(cmd.opts());
|
|
1436
|
+
const result = await withSpinner(
|
|
1437
|
+
"Updating webhook\u2026",
|
|
1438
|
+
"Webhook updated",
|
|
1439
|
+
() => callNotify(token, "/update-webhook", {
|
|
1440
|
+
method: "PUT",
|
|
1441
|
+
body: parseRequiredJSON(opts.body, "--body")
|
|
1442
|
+
})
|
|
1443
|
+
);
|
|
1444
|
+
if (isJSONMode()) printJSON(result);
|
|
1445
|
+
else printSyntaxJSON(result);
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
exitWithError(err);
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
cmd.command("delete <webhookId>").description("Delete webhook").action(async (webhookId) => {
|
|
1451
|
+
try {
|
|
1452
|
+
const token = resolveWebhookApiKey(cmd.opts());
|
|
1453
|
+
const result = await withSpinner(
|
|
1454
|
+
"Deleting webhook\u2026",
|
|
1455
|
+
"Webhook deleted",
|
|
1456
|
+
() => callNotify(token, "/delete-webhook", {
|
|
1457
|
+
method: "DELETE",
|
|
1458
|
+
query: { webhook_id: webhookId }
|
|
1459
|
+
})
|
|
1460
|
+
);
|
|
1461
|
+
if (isJSONMode()) printJSON(result);
|
|
1462
|
+
else printSyntaxJSON(result);
|
|
1463
|
+
} catch (err) {
|
|
1464
|
+
exitWithError(err);
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
cmd.command("addresses <webhookId>").description("Get address activity webhook addresses").action(async (webhookId) => {
|
|
1468
|
+
try {
|
|
1469
|
+
const token = resolveWebhookApiKey(cmd.opts());
|
|
1470
|
+
const result = await withSpinner(
|
|
1471
|
+
"Fetching webhook addresses\u2026",
|
|
1472
|
+
"Webhook addresses fetched",
|
|
1473
|
+
() => callNotify(token, "/webhook-addresses", { query: { webhook_id: webhookId } })
|
|
1474
|
+
);
|
|
1475
|
+
if (isJSONMode()) printJSON(result);
|
|
1476
|
+
else printSyntaxJSON(result);
|
|
1477
|
+
} catch (err) {
|
|
1478
|
+
exitWithError(err);
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
cmd.command("nft-filters <webhookId>").description("Get NFT activity webhook filters").action(async (webhookId) => {
|
|
1482
|
+
try {
|
|
1483
|
+
const token = resolveWebhookApiKey(cmd.opts());
|
|
1484
|
+
const result = await withSpinner(
|
|
1485
|
+
"Fetching NFT filters\u2026",
|
|
1486
|
+
"NFT filters fetched",
|
|
1487
|
+
() => callNotify(token, "/webhook-nft-filters", { query: { webhook_id: webhookId } })
|
|
1488
|
+
);
|
|
1489
|
+
if (isJSONMode()) printJSON(result);
|
|
1490
|
+
else printSyntaxJSON(result);
|
|
1491
|
+
} catch (err) {
|
|
1492
|
+
exitWithError(err);
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// src/commands/bundler.ts
|
|
1498
|
+
async function runBundlerMethod(program2, method, params) {
|
|
1499
|
+
const client = clientFromFlags(program2);
|
|
1500
|
+
const result = await withSpinner(
|
|
1501
|
+
`Calling ${method}\u2026`,
|
|
1502
|
+
`Called ${method}`,
|
|
1503
|
+
() => client.call(method, params)
|
|
1504
|
+
);
|
|
1505
|
+
printSyntaxJSON(result);
|
|
1506
|
+
}
|
|
1507
|
+
function registerBundler(program2) {
|
|
1508
|
+
const cmd = program2.command("bundler").description("Wallet Bundler API wrappers");
|
|
1509
|
+
cmd.command("send-user-operation").description("Call eth_sendUserOperation").requiredOption("--user-op <json>", "UserOperation JSON").requiredOption("--entry-point <address>", "EntryPoint address").action(async (opts) => {
|
|
1510
|
+
try {
|
|
1511
|
+
await runBundlerMethod(program2, "eth_sendUserOperation", [
|
|
1512
|
+
parseRequiredJSON(opts.userOp, "--user-op"),
|
|
1513
|
+
opts.entryPoint
|
|
1514
|
+
]);
|
|
1515
|
+
} catch (err) {
|
|
1516
|
+
exitWithError(err);
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
cmd.command("estimate-user-operation-gas").description("Call eth_estimateUserOperationGas").requiredOption("--user-op <json>", "UserOperation JSON").requiredOption("--entry-point <address>", "EntryPoint address").option("--state-override <json>", "State override set JSON").action(
|
|
1520
|
+
async (opts) => {
|
|
1521
|
+
try {
|
|
1522
|
+
const params = [
|
|
1523
|
+
parseRequiredJSON(opts.userOp, "--user-op"),
|
|
1524
|
+
opts.entryPoint
|
|
1525
|
+
];
|
|
1526
|
+
if (opts.stateOverride) {
|
|
1527
|
+
params.push(parseRequiredJSON(opts.stateOverride, "--state-override"));
|
|
1528
|
+
}
|
|
1529
|
+
await runBundlerMethod(program2, "eth_estimateUserOperationGas", params);
|
|
1530
|
+
} catch (err) {
|
|
1531
|
+
exitWithError(err);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
);
|
|
1535
|
+
cmd.command("get-user-operation-receipt").description("Call eth_getUserOperationReceipt").requiredOption("--user-op-hash <hash>", "User operation hash").action(async (opts) => {
|
|
1536
|
+
try {
|
|
1537
|
+
await runBundlerMethod(program2, "eth_getUserOperationReceipt", [opts.userOpHash]);
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
exitWithError(err);
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// src/commands/gas-manager.ts
|
|
1545
|
+
async function runGasMethod(program2, method, payload) {
|
|
1546
|
+
const client = clientFromFlags(program2);
|
|
1547
|
+
const result = await withSpinner(
|
|
1548
|
+
`Calling ${method}\u2026`,
|
|
1549
|
+
`Called ${method}`,
|
|
1550
|
+
() => client.call(method, [payload])
|
|
1551
|
+
);
|
|
1552
|
+
printSyntaxJSON(result);
|
|
1553
|
+
}
|
|
1554
|
+
function registerGasManager(program2) {
|
|
1555
|
+
const cmd = program2.command("gas-manager").description("Gas Manager API wrappers");
|
|
1556
|
+
cmd.command("request-gas-and-paymaster").description("Call alchemy_requestGasAndPaymasterAndData").requiredOption("--body <json>", "Request payload JSON").action(async (opts) => {
|
|
1557
|
+
try {
|
|
1558
|
+
await runGasMethod(
|
|
1559
|
+
program2,
|
|
1560
|
+
"alchemy_requestGasAndPaymasterAndData",
|
|
1561
|
+
parseRequiredJSON(opts.body, "--body")
|
|
1562
|
+
);
|
|
1563
|
+
} catch (err) {
|
|
1564
|
+
exitWithError(err);
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
cmd.command("request-paymaster-token-quote").description("Call alchemy_requestPaymasterTokenQuote").requiredOption("--body <json>", "Request payload JSON").action(async (opts) => {
|
|
1568
|
+
try {
|
|
1569
|
+
await runGasMethod(
|
|
1570
|
+
program2,
|
|
1571
|
+
"alchemy_requestPaymasterTokenQuote",
|
|
1572
|
+
parseRequiredJSON(opts.body, "--body")
|
|
1573
|
+
);
|
|
1574
|
+
} catch (err) {
|
|
1575
|
+
exitWithError(err);
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// src/commands/solana.ts
|
|
1581
|
+
function registerSolana(program2) {
|
|
1582
|
+
const cmd = program2.command("solana").description("Solana RPC and DAS wrappers");
|
|
1583
|
+
cmd.command("rpc <method> [params...]").description("Call a Solana JSON-RPC method").action(async (method, params) => {
|
|
1584
|
+
try {
|
|
1585
|
+
const client = clientFromFlags(program2);
|
|
1586
|
+
const result = await withSpinner(
|
|
1587
|
+
`Calling ${method}\u2026`,
|
|
1588
|
+
`Called ${method}`,
|
|
1589
|
+
() => client.call(method, parseCLIParams(params))
|
|
1590
|
+
);
|
|
1591
|
+
printSyntaxJSON(result);
|
|
1592
|
+
} catch (err) {
|
|
1593
|
+
exitWithError(err);
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
const das = cmd.command("das").description("Solana DAS (Digital Asset Standard) wrappers");
|
|
1597
|
+
das.command("<method> [params...]").description("Call DAS method (e.g. getAssetsByOwner)").action(async (method, params) => {
|
|
1598
|
+
try {
|
|
1599
|
+
const client = clientFromFlags(program2);
|
|
1600
|
+
const result = await withSpinner(
|
|
1601
|
+
`Calling ${method}\u2026`,
|
|
1602
|
+
`Called ${method}`,
|
|
1603
|
+
() => client.call(method, parseCLIParams(params))
|
|
1604
|
+
);
|
|
1605
|
+
printSyntaxJSON(result);
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
exitWithError(err);
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// src/index.ts
|
|
1613
|
+
var hBrand = noColor ? identity : (s) => `\x1B[38;2;54;63;249m${s}\x1B[39m`;
|
|
1614
|
+
var hBold = esc("1");
|
|
1615
|
+
var hDim = esc("2");
|
|
1616
|
+
var ROOT_OPTION_GROUPS = [
|
|
1617
|
+
{
|
|
1618
|
+
label: "Auth & Network",
|
|
1619
|
+
matchers: ["--api-key", "--access-key", "--network", "--x402", "--wallet-key-file"]
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
label: "Output & Formatting",
|
|
1623
|
+
matchers: ["--json", "--quiet", "--verbose", "--no-color", "--reveal"]
|
|
1624
|
+
},
|
|
1625
|
+
{
|
|
1626
|
+
label: "Runtime & Behavior",
|
|
1627
|
+
matchers: ["--timeout", "--debug", "--no-interactive"]
|
|
1628
|
+
}
|
|
1629
|
+
];
|
|
1630
|
+
var ROOT_COMMAND_PILLARS = [
|
|
1631
|
+
{
|
|
1632
|
+
label: "Node",
|
|
1633
|
+
commands: ["balance", "tx", "block", "rpc", "trace", "debug"]
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
label: "Data",
|
|
1637
|
+
commands: ["tokens", "nfts", "transfers", "prices", "portfolio", "simulate"]
|
|
1638
|
+
},
|
|
1639
|
+
{
|
|
1640
|
+
label: "Wallets",
|
|
1641
|
+
commands: ["wallet", "bundler", "gas-manager", "webhooks"]
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
label: "Chains",
|
|
1645
|
+
commands: ["network", "chains", "solana"]
|
|
1646
|
+
},
|
|
1647
|
+
{
|
|
1648
|
+
label: "Admin",
|
|
1649
|
+
commands: ["apps", "config", "setup", "version", "help"]
|
|
1650
|
+
}
|
|
1651
|
+
];
|
|
1652
|
+
function formatCommandSignature(sub) {
|
|
1653
|
+
const args = sub.registeredArguments.map((arg) => {
|
|
1654
|
+
const variadic = arg.variadic === true;
|
|
1655
|
+
const name = variadic ? `${arg.name()}...` : arg.name();
|
|
1656
|
+
return arg.required ? `<${name}>` : `[${name}]`;
|
|
1657
|
+
});
|
|
1658
|
+
return [sub.name(), ...args].join(" ");
|
|
1659
|
+
}
|
|
1660
|
+
function rootOptionGroupLabel(flags) {
|
|
1661
|
+
for (const group of ROOT_OPTION_GROUPS) {
|
|
1662
|
+
if (group.matchers.some((matcher) => flags.includes(matcher))) {
|
|
1663
|
+
return group.label;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
return "General";
|
|
1667
|
+
}
|
|
1668
|
+
var program = new Command();
|
|
1669
|
+
var argvTokens = process.argv.slice(2);
|
|
1670
|
+
var isHelpInvocation = argvTokens.some(
|
|
1671
|
+
(token) => token === "help" || token === "--help" || token === "-h"
|
|
1672
|
+
);
|
|
1673
|
+
var findCommandByPath = (root, path) => {
|
|
1674
|
+
let current = root;
|
|
1675
|
+
for (const segment of path) {
|
|
1676
|
+
const next = current.commands.find(
|
|
1677
|
+
(cmd) => cmd.name() === segment || cmd.aliases().includes(segment)
|
|
1678
|
+
);
|
|
1679
|
+
if (!next) return null;
|
|
1680
|
+
current = next;
|
|
1681
|
+
}
|
|
1682
|
+
return current;
|
|
1683
|
+
};
|
|
1684
|
+
program.name("alchemy").description(
|
|
1685
|
+
"The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
|
|
1686
|
+
).version("0.1.0").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option("--access-key <key>", "Alchemy access key (env: ALCHEMY_ACCESS_KEY)").option(
|
|
1687
|
+
"-n, --network <network>",
|
|
1688
|
+
"Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
|
|
1689
|
+
).option("--x402", "Use x402 wallet-based gateway auth").option("--wallet-key-file <path>", "Path to wallet private key file for x402").option("--json", "Force JSON output").option("-q, --quiet", "Suppress non-essential output").option("-v, --verbose", "Enable verbose output").option("--no-color", "Disable color output").option("--reveal", "Show secrets in plain text (TTY only)").option("--timeout <ms>", "Request timeout in milliseconds", parseInt).option("--debug", "Enable debug diagnostics").option("--no-interactive", "Disable REPL and prompt-driven interactions").addHelpCommand(false).configureOutput({
|
|
1690
|
+
outputError(str, write) {
|
|
1691
|
+
write(formatCommanderError(str));
|
|
1692
|
+
}
|
|
1693
|
+
}).configureHelp({
|
|
1694
|
+
formatHelp(cmd, helper) {
|
|
1695
|
+
const defaultHelp = Help.prototype.formatHelp.call(helper, cmd, helper);
|
|
1696
|
+
if (isJSONMode()) {
|
|
1697
|
+
const schema = {
|
|
1698
|
+
name: cmd.name(),
|
|
1699
|
+
description: cmd.description()
|
|
1700
|
+
};
|
|
1701
|
+
const args = cmd.registeredArguments;
|
|
1702
|
+
if (args.length > 0) {
|
|
1703
|
+
schema.arguments = args.map((a) => ({
|
|
1704
|
+
name: a.name(),
|
|
1705
|
+
description: a.description,
|
|
1706
|
+
required: a.required
|
|
1707
|
+
}));
|
|
1708
|
+
}
|
|
1709
|
+
const opts = cmd.options;
|
|
1710
|
+
if (opts.length > 0) {
|
|
1711
|
+
schema.options = opts.map((o) => ({
|
|
1712
|
+
flags: o.flags,
|
|
1713
|
+
description: o.description
|
|
1714
|
+
}));
|
|
1715
|
+
}
|
|
1716
|
+
const subs = cmd.commands;
|
|
1717
|
+
if (subs.length > 0) {
|
|
1718
|
+
schema.subcommands = subs.map((s) => ({
|
|
1719
|
+
name: s.name(),
|
|
1720
|
+
description: s.description()
|
|
1721
|
+
}));
|
|
1722
|
+
}
|
|
1723
|
+
return JSON.stringify(schema, null, 2) + "\n";
|
|
1724
|
+
}
|
|
1725
|
+
const lines = defaultHelp.split("\n");
|
|
1726
|
+
let section = null;
|
|
1727
|
+
const emittedOptionGroups = /* @__PURE__ */ new Set();
|
|
1728
|
+
const out = lines.map((line) => {
|
|
1729
|
+
const sectionMatch = line.match(/^(Usage|Commands|Options|Arguments):$/);
|
|
1730
|
+
if (sectionMatch) {
|
|
1731
|
+
const title = sectionMatch[1];
|
|
1732
|
+
if (title === "Commands" && cmd === program) {
|
|
1733
|
+
section = "commands";
|
|
1734
|
+
const byName = new Map(
|
|
1735
|
+
cmd.commands.map((sub) => [sub.name(), sub])
|
|
1736
|
+
);
|
|
1737
|
+
const groupedRows = ROOT_COMMAND_PILLARS.map((pillar) => {
|
|
1738
|
+
const rows = pillar.commands.map((name) => byName.get(name)).filter((sub) => Boolean(sub)).map((sub) => ({
|
|
1739
|
+
left: formatCommandSignature(sub),
|
|
1740
|
+
right: sub.description()
|
|
1741
|
+
}));
|
|
1742
|
+
return { label: pillar.label, rows };
|
|
1743
|
+
}).filter((group) => group.rows.length > 0);
|
|
1744
|
+
const maxLeft = Math.max(
|
|
1745
|
+
0,
|
|
1746
|
+
...groupedRows.flatMap((group) => group.rows.map((row) => row.left.length))
|
|
1747
|
+
);
|
|
1748
|
+
const groupText = groupedRows.map((group) => {
|
|
1749
|
+
const header = ` ${hBold(group.label)}${hDim(":")}`;
|
|
1750
|
+
const rows = group.rows.map((row) => {
|
|
1751
|
+
const gap = " ".repeat(Math.max(2, maxLeft - row.left.length + 2));
|
|
1752
|
+
return ` ${hBrand(row.left)}${gap}${hDim(row.right)}`;
|
|
1753
|
+
}).join("\n");
|
|
1754
|
+
return `${header}
|
|
1755
|
+
${rows}`;
|
|
1756
|
+
}).join("\n\n");
|
|
1757
|
+
return `${hBrand("\u25C6")} ${hBold("Commands")}
|
|
1758
|
+
${hDim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
1759
|
+
${groupText}`;
|
|
1760
|
+
}
|
|
1761
|
+
section = title.toLowerCase();
|
|
1762
|
+
return `${hBrand("\u25C6")} ${hBold(title)}
|
|
1763
|
+
${hDim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`;
|
|
1764
|
+
}
|
|
1765
|
+
if (line.trim() === "") {
|
|
1766
|
+
section = null;
|
|
1767
|
+
return line;
|
|
1768
|
+
}
|
|
1769
|
+
if (section === "commands" && cmd === program) {
|
|
1770
|
+
return null;
|
|
1771
|
+
}
|
|
1772
|
+
if (section === "options" || section === "commands") {
|
|
1773
|
+
const entryMatch = line.match(/^(\s+)(.+?)(\s{2,})(.+)$/);
|
|
1774
|
+
if (entryMatch) {
|
|
1775
|
+
const [, indent, left, gap, right] = entryMatch;
|
|
1776
|
+
const styledLine = `${indent}${hBrand(left)}${gap}${hDim(right)}`;
|
|
1777
|
+
if (section === "options" && cmd === program) {
|
|
1778
|
+
const groupLabel = rootOptionGroupLabel(left);
|
|
1779
|
+
if (!emittedOptionGroups.has(groupLabel)) {
|
|
1780
|
+
emittedOptionGroups.add(groupLabel);
|
|
1781
|
+
const needsLeadingGap = emittedOptionGroups.size > 1;
|
|
1782
|
+
const groupHeader = `${indent}${hBold(groupLabel)}${hDim(":")}`;
|
|
1783
|
+
return `${needsLeadingGap ? "\n" : ""}${groupHeader}
|
|
1784
|
+
${styledLine}`;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return styledLine;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return line;
|
|
1791
|
+
}).filter((line) => line !== null);
|
|
1792
|
+
return out.join("\n") + "\n";
|
|
1793
|
+
}
|
|
1794
|
+
}).addHelpText("beforeAll", () => isHelpInvocation ? brandedHelp() : "").addHelpText("after", () => {
|
|
1795
|
+
if (isJSONMode()) return "";
|
|
1796
|
+
return [
|
|
1797
|
+
"",
|
|
1798
|
+
`${hBrand("\u25C6")} ${hBold("Exit Codes")}`,
|
|
1799
|
+
` ${hDim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`,
|
|
1800
|
+
` ${hBrand("0")} Success`,
|
|
1801
|
+
` ${hBrand("1")} Internal error`,
|
|
1802
|
+
` ${hBrand("2")} Invalid arguments`,
|
|
1803
|
+
` ${hBrand("3")} Authentication required`,
|
|
1804
|
+
` ${hBrand("4")} Not found`,
|
|
1805
|
+
` ${hBrand("5")} Rate limited`,
|
|
1806
|
+
` ${hBrand("6")} Network error`,
|
|
1807
|
+
` ${hBrand("7")} RPC error`,
|
|
1808
|
+
` ${hBrand("8")} Admin API error`,
|
|
1809
|
+
` ${hBrand("9")} Payment required`,
|
|
1810
|
+
` ${hBrand("130")} Interrupted (SIGINT)`,
|
|
1811
|
+
"",
|
|
1812
|
+
`${hBrand("\u25C6")} ${hBold("Resources")}`,
|
|
1813
|
+
` ${hDim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`,
|
|
1814
|
+
` ${hDim("Docs:")} ${hBrand("https://www.alchemy.com/docs")}`
|
|
1815
|
+
].join("\n");
|
|
1816
|
+
}).hook("preAction", () => {
|
|
1817
|
+
const opts = program.opts();
|
|
1818
|
+
const cfg = load();
|
|
1819
|
+
setFlags({
|
|
1820
|
+
json: opts.json,
|
|
1821
|
+
quiet: opts.quiet,
|
|
1822
|
+
verbose: Boolean(opts.verbose || cfg.verbose),
|
|
1823
|
+
debug: Boolean(opts.debug),
|
|
1824
|
+
reveal: Boolean(opts.reveal),
|
|
1825
|
+
timeout: opts.timeout
|
|
1826
|
+
});
|
|
1827
|
+
}).hook("postAction", () => {
|
|
1828
|
+
if (!isJSONMode() && !quiet) {
|
|
1829
|
+
console.log("");
|
|
1830
|
+
}
|
|
1831
|
+
}).action(async () => {
|
|
1832
|
+
const cfg = load();
|
|
1833
|
+
if (!isSetupComplete(cfg) && !isInteractiveAllowed(program)) {
|
|
1834
|
+
throw errSetupRequired(getSetupStatus(cfg));
|
|
1835
|
+
}
|
|
1836
|
+
if (isInteractiveAllowed(program)) {
|
|
1837
|
+
if (shouldRunOnboarding(program, cfg)) {
|
|
1838
|
+
const { runOnboarding } = await import("./onboarding-3J4EXZMG.js");
|
|
1839
|
+
const completed = await runOnboarding(program);
|
|
1840
|
+
if (!completed) {
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
const { startREPL } = await import("./interactive-2ITFWH3B.js");
|
|
1845
|
+
program.exitOverride();
|
|
1846
|
+
program.configureOutput({
|
|
1847
|
+
writeErr: () => {
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
await startREPL(program);
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
program.help();
|
|
1854
|
+
});
|
|
1855
|
+
registerRPC(program);
|
|
1856
|
+
registerBalance(program);
|
|
1857
|
+
registerTx(program);
|
|
1858
|
+
registerBlock(program);
|
|
1859
|
+
registerTrace(program);
|
|
1860
|
+
registerDebug(program);
|
|
1861
|
+
registerTokens(program);
|
|
1862
|
+
registerNFTs(program);
|
|
1863
|
+
registerTransfers(program);
|
|
1864
|
+
registerPrices(program);
|
|
1865
|
+
registerPortfolio(program);
|
|
1866
|
+
registerSimulate(program);
|
|
1867
|
+
registerWallet(program);
|
|
1868
|
+
registerBundler(program);
|
|
1869
|
+
registerGasManager(program);
|
|
1870
|
+
registerWebhooks(program);
|
|
1871
|
+
registerNetwork(program);
|
|
1872
|
+
registerChains(program);
|
|
1873
|
+
registerApps(program);
|
|
1874
|
+
registerSetup(program);
|
|
1875
|
+
registerConfig(program);
|
|
1876
|
+
registerSolana(program);
|
|
1877
|
+
registerVersion(program);
|
|
1878
|
+
program.command("help [command...]").description("display help for command").action((commandPath) => {
|
|
1879
|
+
if (!commandPath || commandPath.length === 0) {
|
|
1880
|
+
program.outputHelp();
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
const target = findCommandByPath(program, commandPath);
|
|
1884
|
+
if (!target) {
|
|
1885
|
+
program.error(`unknown command '${commandPath.join(" ")}'`);
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
target.outputHelp();
|
|
1889
|
+
});
|
|
1890
|
+
process.on("unhandledRejection", (err) => exitWithError(err));
|
|
1891
|
+
process.on("uncaughtException", (err) => exitWithError(err));
|
|
1892
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
1893
|
+
process.on("SIGINT", () => {
|
|
1894
|
+
if (!isJSONMode()) process.stderr.write("\nInterrupted.\n");
|
|
1895
|
+
process.exit(130);
|
|
1896
|
+
});
|
|
1897
|
+
program.parse();
|