@alchemy/cli 0.5.1 → 0.6.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 +25 -74
- package/dist/{auth-7E33EMAI.js → auth-QB3BA7AN.js} +7 -3
- package/dist/{auth-E26YCAJV.js → auth-S4DTOWW3.js} +7 -5
- package/dist/{chunk-Z7J64GJJ.js → chunk-3W4ICF67.js} +2 -2
- package/dist/chunk-ATX65U7J.js +737 -0
- package/dist/chunk-BAAQ7ELR.js +143 -0
- package/dist/{chunk-IGD4NIK7.js → chunk-FFMNT74F.js} +54 -36
- package/dist/chunk-JQRGILIS.js +53 -0
- package/dist/chunk-KDMIWPZH.js +27 -0
- package/dist/chunk-NBDWF4ZQ.js +554 -0
- package/dist/{chunk-5X6YRTPU.js → chunk-T5Z2GJUX.js} +7 -5
- package/dist/{chunk-LYUW7O6X.js → chunk-UMKDYHMO.js} +113 -37
- package/dist/credential-storage-T6FFW7DG.js +14 -0
- package/dist/index.js +726 -44
- package/dist/{interactive-G4ON47AR.js → interactive-OM476LBG.js} +11 -6
- package/dist/onboarding-S3GAP4OV.js +61 -0
- package/dist/resolve-HXKHDOJZ.js +31 -0
- package/package.json +2 -1
- package/dist/chunk-44OGGLN4.js +0 -681
- package/dist/chunk-T2XSNZE3.js +0 -1398
- package/dist/onboarding-CWCVWSUG.js +0 -227
package/dist/index.js
CHANGED
|
@@ -2,64 +2,72 @@
|
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
3
|
import {
|
|
4
4
|
registerAuth
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import {
|
|
8
|
-
readStdinArg,
|
|
9
|
-
readStdinLines,
|
|
10
|
-
registerConfig,
|
|
11
|
-
registerWallet,
|
|
12
|
-
resolveAddress,
|
|
13
|
-
splitCommaList,
|
|
14
|
-
validateAddress,
|
|
15
|
-
validateTxHash
|
|
16
|
-
} from "./chunk-44OGGLN4.js";
|
|
5
|
+
} from "./chunk-UMKDYHMO.js";
|
|
6
|
+
import "./chunk-FFMNT74F.js";
|
|
17
7
|
import {
|
|
18
8
|
getRPCNetworks,
|
|
19
9
|
getSetupStatus,
|
|
20
10
|
isSetupComplete,
|
|
21
11
|
nativeTokenSymbol,
|
|
22
12
|
shouldRunOnboarding
|
|
23
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-T5Z2GJUX.js";
|
|
14
|
+
import {
|
|
15
|
+
isInteractiveAllowed
|
|
16
|
+
} from "./chunk-KDMIWPZH.js";
|
|
17
|
+
import {
|
|
18
|
+
AdminClient,
|
|
19
|
+
adminClientFromFlags,
|
|
20
|
+
clientFromFlags,
|
|
21
|
+
resolveAPIKey,
|
|
22
|
+
resolveAppId,
|
|
23
|
+
resolveNetwork,
|
|
24
|
+
resolveWalletKey,
|
|
25
|
+
resolveX402Client
|
|
26
|
+
} from "./chunk-ATX65U7J.js";
|
|
27
|
+
import "./chunk-JQRGILIS.js";
|
|
24
28
|
import {
|
|
25
29
|
getAvailableUpdate,
|
|
26
30
|
getUpdateStatus,
|
|
27
31
|
printUpdateNotice
|
|
28
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-3W4ICF67.js";
|
|
29
33
|
import {
|
|
30
|
-
adminClientFromFlags,
|
|
31
34
|
bold,
|
|
32
35
|
brand,
|
|
33
36
|
brandedHelp,
|
|
34
|
-
clientFromFlags,
|
|
35
37
|
dim,
|
|
36
38
|
emptyState,
|
|
37
39
|
etherscanTxURL,
|
|
38
40
|
failBadge,
|
|
39
41
|
green,
|
|
40
|
-
isInteractiveAllowed,
|
|
41
|
-
load,
|
|
42
|
-
maskIf,
|
|
43
42
|
printKeyValueBox,
|
|
44
43
|
printSyntaxJSON,
|
|
45
44
|
printTable,
|
|
45
|
+
promptAutocomplete,
|
|
46
46
|
promptConfirm,
|
|
47
|
+
promptMultiselect,
|
|
47
48
|
promptSelect,
|
|
49
|
+
promptText,
|
|
48
50
|
red,
|
|
49
|
-
resolveAPIKey,
|
|
50
|
-
resolveAppId,
|
|
51
|
-
resolveNetwork,
|
|
52
|
-
resolveX402Client,
|
|
53
|
-
save,
|
|
54
51
|
successBadge,
|
|
55
52
|
timeAgo,
|
|
56
53
|
weiToEth,
|
|
57
|
-
withSpinner
|
|
58
|
-
|
|
54
|
+
withSpinner,
|
|
55
|
+
yellow
|
|
56
|
+
} from "./chunk-NBDWF4ZQ.js";
|
|
57
|
+
import {
|
|
58
|
+
KEY_MAP,
|
|
59
|
+
configDir,
|
|
60
|
+
get,
|
|
61
|
+
load,
|
|
62
|
+
maskIf,
|
|
63
|
+
save,
|
|
64
|
+
toMap
|
|
65
|
+
} from "./chunk-BAAQ7ELR.js";
|
|
59
66
|
import {
|
|
60
67
|
EXIT_CODES,
|
|
61
68
|
ErrorCode,
|
|
62
69
|
debug,
|
|
70
|
+
errAccessKeyRequired,
|
|
63
71
|
errAppRequired,
|
|
64
72
|
errAuthRequired,
|
|
65
73
|
errInvalidAPIKey,
|
|
@@ -68,6 +76,7 @@ import {
|
|
|
68
76
|
errNotFound,
|
|
69
77
|
errRateLimited,
|
|
70
78
|
errSetupRequired,
|
|
79
|
+
errWalletKeyRequired,
|
|
71
80
|
esc,
|
|
72
81
|
exitWithError,
|
|
73
82
|
fetchWithTimeout,
|
|
@@ -87,6 +96,557 @@ import {
|
|
|
87
96
|
// src/index.ts
|
|
88
97
|
import { Command, Help } from "commander";
|
|
89
98
|
|
|
99
|
+
// src/lib/ens.ts
|
|
100
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
101
|
+
var UNIVERSAL_RESOLVER = "0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe";
|
|
102
|
+
var RESOLVE_SELECTOR = "9061b923";
|
|
103
|
+
var ADDR_SELECTOR = "3b3b57de";
|
|
104
|
+
function bytesToHex(bytes) {
|
|
105
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
106
|
+
}
|
|
107
|
+
function pad32(hex) {
|
|
108
|
+
return hex.padStart(64, "0");
|
|
109
|
+
}
|
|
110
|
+
function namehash(name) {
|
|
111
|
+
let node = new Uint8Array(32);
|
|
112
|
+
if (!name) return node;
|
|
113
|
+
const labels = name.split(".");
|
|
114
|
+
for (let i = labels.length - 1; i >= 0; i--) {
|
|
115
|
+
const labelHash = keccak_256(new TextEncoder().encode(labels[i]));
|
|
116
|
+
const combined = new Uint8Array(64);
|
|
117
|
+
combined.set(node, 0);
|
|
118
|
+
combined.set(labelHash, 32);
|
|
119
|
+
node = keccak_256(combined);
|
|
120
|
+
}
|
|
121
|
+
return node;
|
|
122
|
+
}
|
|
123
|
+
function dnsEncode(name) {
|
|
124
|
+
const labels = name.split(".");
|
|
125
|
+
const parts = [];
|
|
126
|
+
for (const label of labels) {
|
|
127
|
+
const encoded = new TextEncoder().encode(label);
|
|
128
|
+
parts.push(encoded.length);
|
|
129
|
+
parts.push(...encoded);
|
|
130
|
+
}
|
|
131
|
+
parts.push(0);
|
|
132
|
+
return new Uint8Array(parts);
|
|
133
|
+
}
|
|
134
|
+
function buildResolveCalldata(name) {
|
|
135
|
+
const dnsName = dnsEncode(name);
|
|
136
|
+
const node = namehash(name);
|
|
137
|
+
const innerHex = ADDR_SELECTOR + bytesToHex(node);
|
|
138
|
+
const innerLen = 36;
|
|
139
|
+
const dnsHex = bytesToHex(dnsName);
|
|
140
|
+
const nameLen = dnsName.length;
|
|
141
|
+
const namePad = Math.ceil(nameLen / 32) * 32;
|
|
142
|
+
const innerPad = Math.ceil(innerLen / 32) * 32;
|
|
143
|
+
const nameOffset = 64;
|
|
144
|
+
const dataOffset = nameOffset + 32 + namePad;
|
|
145
|
+
let hex = RESOLVE_SELECTOR;
|
|
146
|
+
hex += pad32(nameOffset.toString(16));
|
|
147
|
+
hex += pad32(dataOffset.toString(16));
|
|
148
|
+
hex += pad32(nameLen.toString(16));
|
|
149
|
+
hex += dnsHex.padEnd(namePad * 2, "0");
|
|
150
|
+
hex += pad32(innerLen.toString(16));
|
|
151
|
+
hex += innerHex.padEnd(innerPad * 2, "0");
|
|
152
|
+
return "0x" + hex;
|
|
153
|
+
}
|
|
154
|
+
function isENSName(value) {
|
|
155
|
+
return value.endsWith(".eth") && value.length > 4 && !value.startsWith("0x");
|
|
156
|
+
}
|
|
157
|
+
async function resolveENS(name, client) {
|
|
158
|
+
if (!client.network.startsWith("eth-")) {
|
|
159
|
+
throw errInvalidArgs(
|
|
160
|
+
`ENS resolution is only supported on Ethereum networks. Current network: ${client.network}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const calldata = buildResolveCalldata(name.toLowerCase());
|
|
164
|
+
const result = await client.call("eth_call", [
|
|
165
|
+
{ to: UNIVERSAL_RESOLVER, data: calldata },
|
|
166
|
+
"latest"
|
|
167
|
+
]);
|
|
168
|
+
if (!result || result === "0x" || result.length < 130) {
|
|
169
|
+
throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
|
|
170
|
+
}
|
|
171
|
+
const raw = result.slice(2);
|
|
172
|
+
const dataOffset = parseInt(raw.slice(0, 64), 16) * 2;
|
|
173
|
+
const dataLen = parseInt(raw.slice(dataOffset, dataOffset + 64), 16);
|
|
174
|
+
const dataHex = raw.slice(dataOffset + 64, dataOffset + 64 + dataLen * 2);
|
|
175
|
+
if (dataHex.length < 64) {
|
|
176
|
+
throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
|
|
177
|
+
}
|
|
178
|
+
const address = "0x" + dataHex.slice(24, 64);
|
|
179
|
+
if (address === "0x0000000000000000000000000000000000000000") {
|
|
180
|
+
throw errInvalidArgs(`ENS name "${name}" is not registered or has no address set.`);
|
|
181
|
+
}
|
|
182
|
+
return address;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/lib/validators.ts
|
|
186
|
+
function splitCommaList(input) {
|
|
187
|
+
return input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
188
|
+
}
|
|
189
|
+
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
190
|
+
var TX_HASH_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
191
|
+
async function readStdinArg(name) {
|
|
192
|
+
if (process.stdin.isTTY) {
|
|
193
|
+
throw errInvalidArgs(`Missing <${name}>. Provide it as an argument or pipe via stdin.`);
|
|
194
|
+
}
|
|
195
|
+
process.stdin.setEncoding("utf-8");
|
|
196
|
+
let input = "";
|
|
197
|
+
for await (const chunk of process.stdin) {
|
|
198
|
+
input += chunk;
|
|
199
|
+
}
|
|
200
|
+
const data = input.trim().split("\n")[0]?.trim() ?? "";
|
|
201
|
+
if (!data) {
|
|
202
|
+
throw errInvalidArgs(`No <${name}> received on stdin.`);
|
|
203
|
+
}
|
|
204
|
+
return data;
|
|
205
|
+
}
|
|
206
|
+
async function readStdinLines(name) {
|
|
207
|
+
if (process.stdin.isTTY) {
|
|
208
|
+
throw errInvalidArgs(`Missing <${name}>. Provide it as an argument or pipe via stdin.`);
|
|
209
|
+
}
|
|
210
|
+
process.stdin.setEncoding("utf-8");
|
|
211
|
+
let input = "";
|
|
212
|
+
for await (const chunk of process.stdin) {
|
|
213
|
+
input += chunk;
|
|
214
|
+
}
|
|
215
|
+
const lines = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
216
|
+
if (lines.length === 0) {
|
|
217
|
+
throw errInvalidArgs(`No <${name}> received on stdin.`);
|
|
218
|
+
}
|
|
219
|
+
return lines;
|
|
220
|
+
}
|
|
221
|
+
function validateAddress(address) {
|
|
222
|
+
if (!ADDRESS_RE.test(address)) {
|
|
223
|
+
throw errInvalidArgs(
|
|
224
|
+
`Invalid address "${address}". Expected 0x-prefixed 40-hex-character address.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function resolveAddress(input, client) {
|
|
229
|
+
if (isENSName(input)) {
|
|
230
|
+
return resolveENS(input, client);
|
|
231
|
+
}
|
|
232
|
+
validateAddress(input);
|
|
233
|
+
return input;
|
|
234
|
+
}
|
|
235
|
+
function validateTxHash(hash) {
|
|
236
|
+
if (!TX_HASH_RE.test(hash)) {
|
|
237
|
+
throw errInvalidArgs(
|
|
238
|
+
`Invalid transaction hash "${hash}". Expected 0x-prefixed 64-hex-character hash.`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/commands/config.ts
|
|
244
|
+
var RESET_KEY_MAP = { ...KEY_MAP, app: "app" };
|
|
245
|
+
var APP_SEARCH_THRESHOLD = 15;
|
|
246
|
+
async function saveAppWithPrompt(app) {
|
|
247
|
+
const cfg = load();
|
|
248
|
+
const updated = {
|
|
249
|
+
...cfg,
|
|
250
|
+
api_key: app.apiKey,
|
|
251
|
+
app: { id: app.id, name: app.name, apiKey: app.apiKey, webhookApiKey: app.webhookApiKey }
|
|
252
|
+
};
|
|
253
|
+
if (cfg.api_key) {
|
|
254
|
+
const replace = await promptConfirm({
|
|
255
|
+
message: "You already have an API key configured. Use the app's API key instead?",
|
|
256
|
+
initialValue: true,
|
|
257
|
+
cancelMessage: "Cancelled default app update."
|
|
258
|
+
});
|
|
259
|
+
if (replace === null) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (!replace) {
|
|
263
|
+
updated.api_key = cfg.api_key;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
save(updated);
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
async function selectOrCreateApp(admin) {
|
|
270
|
+
let apps;
|
|
271
|
+
try {
|
|
272
|
+
const result = await withSpinner(
|
|
273
|
+
"Fetching apps\u2026",
|
|
274
|
+
"Apps fetched",
|
|
275
|
+
() => admin.listAllApps()
|
|
276
|
+
);
|
|
277
|
+
apps = result.apps;
|
|
278
|
+
} catch {
|
|
279
|
+
console.log(
|
|
280
|
+
` ${dim("Could not fetch apps. Skipping app selection.")}`
|
|
281
|
+
);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (apps.length > 0) {
|
|
285
|
+
const CREATE_NEW = "__create_new__";
|
|
286
|
+
const options = [
|
|
287
|
+
...apps.map((a) => ({
|
|
288
|
+
label: `${a.name} (${a.id})`,
|
|
289
|
+
value: a.id
|
|
290
|
+
})),
|
|
291
|
+
{ label: "Create a new app", value: CREATE_NEW }
|
|
292
|
+
];
|
|
293
|
+
const selected = apps.length > APP_SEARCH_THRESHOLD ? await promptAutocomplete({
|
|
294
|
+
message: "Select default app",
|
|
295
|
+
placeholder: "Type app name or id",
|
|
296
|
+
options,
|
|
297
|
+
cancelMessage: "Cancelled app selection.",
|
|
298
|
+
commitLabel: null
|
|
299
|
+
}) : await promptSelect({
|
|
300
|
+
message: "Select default app",
|
|
301
|
+
options,
|
|
302
|
+
cancelMessage: "Cancelled app selection.",
|
|
303
|
+
commitLabel: null
|
|
304
|
+
});
|
|
305
|
+
if (selected === null) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (selected !== CREATE_NEW) {
|
|
309
|
+
const app = apps.find((a) => a.id === selected);
|
|
310
|
+
const saved = await saveAppWithPrompt(app);
|
|
311
|
+
if (saved) {
|
|
312
|
+
console.log(`
|
|
313
|
+
${green("\u2713")} Default app set to ${app.name} (${app.id})`);
|
|
314
|
+
} else {
|
|
315
|
+
console.log(` ${dim("Skipped setting default app.")}`);
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
console.log(` ${dim("No apps found. Let's create one.")}`);
|
|
321
|
+
}
|
|
322
|
+
const name = await promptText({
|
|
323
|
+
message: "App name",
|
|
324
|
+
cancelMessage: "Cancelled app creation."
|
|
325
|
+
});
|
|
326
|
+
if (name === null) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!name.trim()) {
|
|
330
|
+
console.log(` ${dim("Skipped app creation.")}`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
let chainChoices = [];
|
|
334
|
+
try {
|
|
335
|
+
const chains = await withSpinner(
|
|
336
|
+
"Fetching chains\u2026",
|
|
337
|
+
"Chains fetched",
|
|
338
|
+
() => admin.listChains()
|
|
339
|
+
);
|
|
340
|
+
chainChoices = chains.filter((c) => c.availability === "public" && !c.isTestnet).map((c) => ({ label: `${c.name} (${c.id})`, value: c.id }));
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
let networks;
|
|
344
|
+
if (chainChoices.length > 0) {
|
|
345
|
+
const selectedNetworks = await promptMultiselect({
|
|
346
|
+
message: "Select networks",
|
|
347
|
+
options: chainChoices,
|
|
348
|
+
required: true,
|
|
349
|
+
cancelMessage: "Cancelled network selection."
|
|
350
|
+
});
|
|
351
|
+
if (selectedNetworks === null) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
networks = selectedNetworks;
|
|
355
|
+
} else {
|
|
356
|
+
const raw = await promptText({
|
|
357
|
+
message: "Network IDs (comma-separated)",
|
|
358
|
+
cancelMessage: "Cancelled network selection."
|
|
359
|
+
});
|
|
360
|
+
if (raw === null) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
networks = splitCommaList(raw);
|
|
364
|
+
}
|
|
365
|
+
if (networks.length === 0) {
|
|
366
|
+
console.log(` ${dim("No networks selected. Skipped app creation.")}`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
const app = await withSpinner(
|
|
371
|
+
"Creating app\u2026",
|
|
372
|
+
"App created",
|
|
373
|
+
() => admin.createApp({ name: name.trim(), networks })
|
|
374
|
+
);
|
|
375
|
+
console.log(` ${green("\u2713")} Created app ${app.name} (${app.id})`);
|
|
376
|
+
const setDefault = await promptConfirm({
|
|
377
|
+
message: "Set as default app?",
|
|
378
|
+
initialValue: true,
|
|
379
|
+
cancelMessage: "Cancelled default app selection."
|
|
380
|
+
});
|
|
381
|
+
if (setDefault === null) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (setDefault) {
|
|
385
|
+
const saved = await saveAppWithPrompt(app);
|
|
386
|
+
if (saved) {
|
|
387
|
+
console.log(`
|
|
388
|
+
${green("\u2713")} Default app set to ${app.name} (${app.id})`);
|
|
389
|
+
} else {
|
|
390
|
+
console.log(` ${dim("Skipped setting default app.")}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
exitWithError(err);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function registerConfig(program2) {
|
|
398
|
+
const cmd = program2.command("config").description("Manage CLI configuration");
|
|
399
|
+
const setCmd = cmd.command("set").description("Set a config value");
|
|
400
|
+
setCmd.command("api-key <key>").description("Set the Alchemy API key for RPC requests").action((key) => {
|
|
401
|
+
try {
|
|
402
|
+
const cfg = load();
|
|
403
|
+
save({ ...cfg, api_key: key });
|
|
404
|
+
printHuman(`${green("\u2713")} Set api-key
|
|
405
|
+
`, { key: "api-key", status: "set" });
|
|
406
|
+
if (!isJSONMode() && cfg.app?.apiKey && cfg.app.apiKey !== key) {
|
|
407
|
+
console.log(
|
|
408
|
+
` ${yellow("\u25C6")} ${dim("Warning: api-key differs from the selected app key. RPC commands use api-key; run 'alchemy config set app <app-id>' to resync.")}`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
} catch (err) {
|
|
412
|
+
exitWithError(err);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
setCmd.command("access-key <key>").description("Set the Alchemy access key for Admin API operations").action(async (key) => {
|
|
416
|
+
try {
|
|
417
|
+
const cfg = load();
|
|
418
|
+
save({ ...cfg, access_key: key });
|
|
419
|
+
printHuman(`${green("\u2713")} Set access-key
|
|
420
|
+
`, { key: "access-key", status: "set" });
|
|
421
|
+
if (isInteractiveAllowed(program2)) {
|
|
422
|
+
await selectOrCreateApp(new AdminClient(key));
|
|
423
|
+
}
|
|
424
|
+
} catch (err) {
|
|
425
|
+
exitWithError(err);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
setCmd.command("webhook-api-key <key>").description("Set the Alchemy webhook API key for Notify operations").action((key) => {
|
|
429
|
+
try {
|
|
430
|
+
const cfg = load();
|
|
431
|
+
save({ ...cfg, webhook_api_key: key });
|
|
432
|
+
printHuman(`${green("\u2713")} Set webhook-api-key
|
|
433
|
+
`, { key: "webhook-api-key", status: "set" });
|
|
434
|
+
} catch (err) {
|
|
435
|
+
exitWithError(err);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
setCmd.command("app [app-id]").description("Select the default app (interactive) or set by ID").action(async (appId) => {
|
|
439
|
+
try {
|
|
440
|
+
const cfg = load();
|
|
441
|
+
const accessKey = program2.opts().accessKey || process.env.ALCHEMY_ACCESS_KEY || cfg.access_key;
|
|
442
|
+
if (!accessKey) throw errAccessKeyRequired();
|
|
443
|
+
if (appId) {
|
|
444
|
+
const admin = new AdminClient(accessKey);
|
|
445
|
+
const app = await withSpinner(
|
|
446
|
+
"Fetching app\u2026",
|
|
447
|
+
"App fetched",
|
|
448
|
+
() => admin.getApp(appId)
|
|
449
|
+
);
|
|
450
|
+
const updated = {
|
|
451
|
+
...cfg,
|
|
452
|
+
api_key: app.apiKey,
|
|
453
|
+
app: { id: app.id, name: app.name, apiKey: app.apiKey, webhookApiKey: app.webhookApiKey }
|
|
454
|
+
};
|
|
455
|
+
save(updated);
|
|
456
|
+
printHuman(
|
|
457
|
+
`${green("\u2713")} Default app set to ${app.name} (${app.id})
|
|
458
|
+
`,
|
|
459
|
+
{ app: { id: app.id, name: app.name }, status: "set" }
|
|
460
|
+
);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (!isInteractiveAllowed(program2)) {
|
|
464
|
+
exitWithError(
|
|
465
|
+
new Error("Interactive app selection requires an interactive terminal. Use 'config set app <app-id>' or 'alchemy apps list' to find app IDs.")
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
await selectOrCreateApp(new AdminClient(accessKey));
|
|
469
|
+
} catch (err) {
|
|
470
|
+
exitWithError(err);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
setCmd.command("network <network>").description("Set the default network (e.g. eth-mainnet, polygon-mainnet)").action((network) => {
|
|
474
|
+
try {
|
|
475
|
+
const cfg = load();
|
|
476
|
+
save({ ...cfg, network });
|
|
477
|
+
printHuman(`${green("\u2713")} Set network to ${network}
|
|
478
|
+
`, { key: "network", value: network, status: "set" });
|
|
479
|
+
} catch (err) {
|
|
480
|
+
exitWithError(err);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
setCmd.command("verbose <enabled>").description("Set default verbose output (true|false)").action((enabled) => {
|
|
484
|
+
try {
|
|
485
|
+
const normalized = enabled.trim().toLowerCase();
|
|
486
|
+
if (normalized !== "true" && normalized !== "false") {
|
|
487
|
+
throw errInvalidArgs("verbose must be 'true' or 'false'");
|
|
488
|
+
}
|
|
489
|
+
const verbose2 = normalized === "true";
|
|
490
|
+
const cfg = load();
|
|
491
|
+
save({ ...cfg, verbose: verbose2 });
|
|
492
|
+
printHuman(
|
|
493
|
+
`${green("\u2713")} Set verbose default to ${verbose2}
|
|
494
|
+
`,
|
|
495
|
+
{ key: "verbose", value: String(verbose2), status: "set" }
|
|
496
|
+
);
|
|
497
|
+
} catch (err) {
|
|
498
|
+
exitWithError(err);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
setCmd.command("wallet-key-file <path>").description("Set the path to a wallet private key file for x402").action((path) => {
|
|
502
|
+
try {
|
|
503
|
+
const cfg = load();
|
|
504
|
+
save({ ...cfg, wallet_key_file: path });
|
|
505
|
+
printHuman(`${green("\u2713")} Set wallet-key-file
|
|
506
|
+
`, { key: "wallet-key-file", status: "set" });
|
|
507
|
+
} catch (err) {
|
|
508
|
+
exitWithError(err);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
setCmd.command("x402 <enabled>").description("Enable or disable x402 wallet-based auth by default (true|false)").action((enabled) => {
|
|
512
|
+
try {
|
|
513
|
+
const normalized = enabled.trim().toLowerCase();
|
|
514
|
+
if (normalized !== "true" && normalized !== "false") {
|
|
515
|
+
throw errInvalidArgs("x402 must be 'true' or 'false'");
|
|
516
|
+
}
|
|
517
|
+
const x402 = normalized === "true";
|
|
518
|
+
const cfg = load();
|
|
519
|
+
save({ ...cfg, x402 });
|
|
520
|
+
printHuman(
|
|
521
|
+
`${green("\u2713")} Set x402 default to ${x402}
|
|
522
|
+
`,
|
|
523
|
+
{ key: "x402", value: String(x402), status: "set" }
|
|
524
|
+
);
|
|
525
|
+
} catch (err) {
|
|
526
|
+
exitWithError(err);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
cmd.command("get <key>").description("Get a config value (api-key, access-key, app, network, verbose, wallet-key-file, x402)").action((key) => {
|
|
530
|
+
const cfg = load();
|
|
531
|
+
let value = get(cfg, key);
|
|
532
|
+
let isDefault = false;
|
|
533
|
+
if (value === void 0) {
|
|
534
|
+
const defaults = {
|
|
535
|
+
network: "eth-mainnet",
|
|
536
|
+
verbose: "false",
|
|
537
|
+
x402: "false"
|
|
538
|
+
};
|
|
539
|
+
const normalizedKey = KEY_MAP[key] ?? key;
|
|
540
|
+
const defaultValue = defaults[normalizedKey] ?? defaults[key];
|
|
541
|
+
if (defaultValue !== void 0) {
|
|
542
|
+
value = defaultValue;
|
|
543
|
+
isDefault = true;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (value === void 0) {
|
|
547
|
+
exitWithError(errNotFound(`config key '${key}'`));
|
|
548
|
+
}
|
|
549
|
+
const isSecret = key === "api-key" || key === "api_key" || key === "access-key" || key === "access_key";
|
|
550
|
+
const display = isSecret ? maskIf(value) : value;
|
|
551
|
+
const humanDisplay = isDefault ? `${display} ${dim("(default)")}` : display;
|
|
552
|
+
printHuman(humanDisplay + "\n", { key, value: display, ...isDefault && { default: true } });
|
|
553
|
+
});
|
|
554
|
+
cmd.command("list").description("List all config values").action(async () => {
|
|
555
|
+
const cfg = load();
|
|
556
|
+
const hasApiKeyMismatch = Boolean(
|
|
557
|
+
cfg.api_key && cfg.app?.apiKey && cfg.api_key !== cfg.app.apiKey
|
|
558
|
+
);
|
|
559
|
+
if (isJSONMode()) {
|
|
560
|
+
printJSON(toMap(cfg));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const { resolveAuthToken } = await import("./resolve-HXKHDOJZ.js");
|
|
564
|
+
const { getCredentials, getStorageBackend } = await import("./credential-storage-T6FFW7DG.js");
|
|
565
|
+
const validToken = await resolveAuthToken(cfg);
|
|
566
|
+
const creds = await getCredentials();
|
|
567
|
+
const hasToken = creds?.auth_token || cfg.auth_token;
|
|
568
|
+
const expiresAt = creds?.auth_token_expires_at || cfg.auth_token_expires_at;
|
|
569
|
+
const backend = await getStorageBackend();
|
|
570
|
+
const storageName = creds?.auth_token ? backend : cfg.auth_token ? "config (legacy)" : "";
|
|
571
|
+
const authStatus = hasToken ? validToken ? `${green("\u2713")} authenticated${expiresAt ? ` ${dim(`(expires ${expiresAt})`)}` : ""}${storageName ? ` ${dim(`[${storageName}]`)}` : ""}` : `${yellow("\u25C6")} expired${expiresAt ? ` ${dim(`(${expiresAt})`)}` : ""}` : dim("(not set) \u2014 run 'alchemy auth' to log in");
|
|
572
|
+
const pairs = [
|
|
573
|
+
["auth", authStatus],
|
|
574
|
+
[
|
|
575
|
+
"api-key",
|
|
576
|
+
cfg.api_key ? `${hasApiKeyMismatch ? `${yellow("\u25C6")} ` : ""}${maskIf(cfg.api_key)}` : dim("(not set)")
|
|
577
|
+
],
|
|
578
|
+
["access-key", cfg.access_key ? maskIf(cfg.access_key) : dim("(not set)")],
|
|
579
|
+
["webhook-api-key", cfg.webhook_api_key ? maskIf(cfg.webhook_api_key) : dim("(not set)")],
|
|
580
|
+
[
|
|
581
|
+
"app",
|
|
582
|
+
cfg.app ? `${cfg.app.name} ${dim(`(${cfg.app.id})`)}` : dim("(not set) \u2014 set automatically via 'alchemy auth' or 'config set app'")
|
|
583
|
+
],
|
|
584
|
+
["network", cfg.network || dim("(not set, defaults to eth-mainnet)")],
|
|
585
|
+
[
|
|
586
|
+
"verbose",
|
|
587
|
+
cfg.verbose !== void 0 ? String(cfg.verbose) : dim("(not set, defaults to false)")
|
|
588
|
+
],
|
|
589
|
+
["wallet-key-file", cfg.wallet_key_file || dim("(not set)")],
|
|
590
|
+
["wallet-address", cfg.wallet_address || dim("(not set)")],
|
|
591
|
+
[
|
|
592
|
+
"x402",
|
|
593
|
+
cfg.x402 !== void 0 ? String(cfg.x402) : dim("(not set, defaults to false)")
|
|
594
|
+
]
|
|
595
|
+
];
|
|
596
|
+
printKeyValueBox(pairs);
|
|
597
|
+
if (hasApiKeyMismatch) {
|
|
598
|
+
console.log("");
|
|
599
|
+
console.log(
|
|
600
|
+
` ${yellow("\u25C6")} ${dim("Warning: api-key differs from the selected app key. RPC commands use api-key; run 'alchemy config set app <app-id>' to resync.")}`
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
cmd.command("reset [key]").description("Reset config values (all or a specific key)").option("-y, --yes", "Skip confirmation prompt for full reset").action(async (key, options) => {
|
|
605
|
+
try {
|
|
606
|
+
if (key) {
|
|
607
|
+
const mapped = RESET_KEY_MAP[key];
|
|
608
|
+
if (!mapped) {
|
|
609
|
+
throw errInvalidArgs(
|
|
610
|
+
`invalid reset key '${key}' (valid: api-key, access-key, app, network, verbose, wallet-key-file, x402)`
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
const cfg = load();
|
|
614
|
+
const updated = { ...cfg };
|
|
615
|
+
delete updated[mapped];
|
|
616
|
+
save(updated);
|
|
617
|
+
printHuman(`${green("\u2713")} Reset ${key}
|
|
618
|
+
`, {
|
|
619
|
+
status: "reset",
|
|
620
|
+
key
|
|
621
|
+
});
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (!options.yes && isInteractiveAllowed(program2)) {
|
|
625
|
+
const proceed = await promptConfirm({
|
|
626
|
+
message: "Reset all saved config values?",
|
|
627
|
+
initialValue: false,
|
|
628
|
+
cancelMessage: "Cancelled config reset."
|
|
629
|
+
});
|
|
630
|
+
if (proceed === null) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (!proceed) {
|
|
634
|
+
console.log(` ${dim("Skipped config reset.")}`);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
save({});
|
|
639
|
+
printHuman(`${green("\u2713")} Reset all config values
|
|
640
|
+
`, {
|
|
641
|
+
status: "reset",
|
|
642
|
+
scope: "all"
|
|
643
|
+
});
|
|
644
|
+
} catch (err) {
|
|
645
|
+
exitWithError(err);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
90
650
|
// src/commands/rpc.ts
|
|
91
651
|
function registerRPC(program2) {
|
|
92
652
|
program2.command("rpc").argument("<method>", "JSON-RPC method name (e.g. eth_blockNumber)").argument("[params...]", "Method parameters as JSON values").description("Make a raw JSON-RPC call").addHelpText(
|
|
@@ -962,7 +1522,7 @@ function registerApps(program2) {
|
|
|
962
1522
|
const cmd = program2.command("apps").description("Manage Alchemy apps");
|
|
963
1523
|
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) => {
|
|
964
1524
|
try {
|
|
965
|
-
const admin = adminClientFromFlags(program2);
|
|
1525
|
+
const admin = await adminClientFromFlags(program2);
|
|
966
1526
|
const fetchAll = Boolean(opts.all);
|
|
967
1527
|
const hasSearch = typeof opts.search === "string";
|
|
968
1528
|
const hasId = typeof opts.id === "string";
|
|
@@ -1089,7 +1649,7 @@ function registerApps(program2) {
|
|
|
1089
1649
|
});
|
|
1090
1650
|
cmd.command("get <id>").description("Get app details").action(async (id) => {
|
|
1091
1651
|
try {
|
|
1092
|
-
const admin = adminClientFromFlags(program2);
|
|
1652
|
+
const admin = await adminClientFromFlags(program2);
|
|
1093
1653
|
const app = await withSpinner(
|
|
1094
1654
|
"Fetching app\u2026",
|
|
1095
1655
|
"App fetched",
|
|
@@ -1123,7 +1683,7 @@ function registerApps(program2) {
|
|
|
1123
1683
|
...products && { products }
|
|
1124
1684
|
};
|
|
1125
1685
|
if (handleDryRun(opts, "create", payload, `Would create app "${opts.name}" on networks: ${networks.join(", ")}`)) return;
|
|
1126
|
-
const admin = adminClientFromFlags(program2);
|
|
1686
|
+
const admin = await adminClientFromFlags(program2);
|
|
1127
1687
|
const app = await withSpinner(
|
|
1128
1688
|
"Creating app\u2026",
|
|
1129
1689
|
"App created",
|
|
@@ -1162,7 +1722,7 @@ function registerApps(program2) {
|
|
|
1162
1722
|
return;
|
|
1163
1723
|
}
|
|
1164
1724
|
}
|
|
1165
|
-
const admin = adminClientFromFlags(program2);
|
|
1725
|
+
const admin = await adminClientFromFlags(program2);
|
|
1166
1726
|
await withSpinner(
|
|
1167
1727
|
"Deleting app\u2026",
|
|
1168
1728
|
"App deleted",
|
|
@@ -1188,7 +1748,7 @@ function registerApps(program2) {
|
|
|
1188
1748
|
...opts.description && { description: opts.description }
|
|
1189
1749
|
};
|
|
1190
1750
|
if (handleDryRun(opts, "update", payload, `Would update app ${id}`)) return;
|
|
1191
|
-
const admin = adminClientFromFlags(program2);
|
|
1751
|
+
const admin = await adminClientFromFlags(program2);
|
|
1192
1752
|
const app = await withSpinner(
|
|
1193
1753
|
"Updating app\u2026",
|
|
1194
1754
|
"App updated",
|
|
@@ -1210,7 +1770,7 @@ function registerApps(program2) {
|
|
|
1210
1770
|
try {
|
|
1211
1771
|
const networks = splitCommaList(opts.networks);
|
|
1212
1772
|
if (handleDryRun(opts, "networks", { id, networks }, `Would update networks for app ${id}: ${networks.join(", ")}`)) return;
|
|
1213
|
-
const admin = adminClientFromFlags(program2);
|
|
1773
|
+
const admin = await adminClientFromFlags(program2);
|
|
1214
1774
|
const app = await withSpinner(
|
|
1215
1775
|
"Updating networks\u2026",
|
|
1216
1776
|
"Networks updated",
|
|
@@ -1231,7 +1791,7 @@ function registerApps(program2) {
|
|
|
1231
1791
|
try {
|
|
1232
1792
|
const entries = splitCommaList(opts.addresses).map((s) => ({ value: s }));
|
|
1233
1793
|
if (handleDryRun(opts, "address-allowlist", { id, addresses: entries }, `Would update address allowlist for app ${id}`)) return;
|
|
1234
|
-
const admin = adminClientFromFlags(program2);
|
|
1794
|
+
const admin = await adminClientFromFlags(program2);
|
|
1235
1795
|
const app = await withSpinner(
|
|
1236
1796
|
"Updating address allowlist\u2026",
|
|
1237
1797
|
"Address allowlist updated",
|
|
@@ -1252,7 +1812,7 @@ function registerApps(program2) {
|
|
|
1252
1812
|
try {
|
|
1253
1813
|
const entries = splitCommaList(opts.origins).map((s) => ({ value: s }));
|
|
1254
1814
|
if (handleDryRun(opts, "origin-allowlist", { id, origins: entries }, `Would update origin allowlist for app ${id}`)) return;
|
|
1255
|
-
const admin = adminClientFromFlags(program2);
|
|
1815
|
+
const admin = await adminClientFromFlags(program2);
|
|
1256
1816
|
const app = await withSpinner(
|
|
1257
1817
|
"Updating origin allowlist\u2026",
|
|
1258
1818
|
"Origin allowlist updated",
|
|
@@ -1273,7 +1833,7 @@ function registerApps(program2) {
|
|
|
1273
1833
|
try {
|
|
1274
1834
|
const entries = splitCommaList(opts.ips).map((s) => ({ value: s }));
|
|
1275
1835
|
if (handleDryRun(opts, "ip-allowlist", { id, ips: entries }, `Would update IP allowlist for app ${id}`)) return;
|
|
1276
|
-
const admin = adminClientFromFlags(program2);
|
|
1836
|
+
const admin = await adminClientFromFlags(program2);
|
|
1277
1837
|
const app = await withSpinner(
|
|
1278
1838
|
"Updating IP allowlist\u2026",
|
|
1279
1839
|
"IP allowlist updated",
|
|
@@ -1292,7 +1852,7 @@ function registerApps(program2) {
|
|
|
1292
1852
|
});
|
|
1293
1853
|
cmd.command("configured-networks").description("List RPC network slugs configured for an app").option("--app-id <id>", "App ID (overrides saved app)").action(async (opts) => {
|
|
1294
1854
|
try {
|
|
1295
|
-
const admin = adminClientFromFlags(program2);
|
|
1855
|
+
const admin = await adminClientFromFlags(program2);
|
|
1296
1856
|
const appId = opts.appId || resolveAppId(program2);
|
|
1297
1857
|
if (!appId) throw errAppRequired();
|
|
1298
1858
|
const app = await withSpinner(
|
|
@@ -1323,7 +1883,7 @@ function registerApps(program2) {
|
|
|
1323
1883
|
});
|
|
1324
1884
|
cmd.command("select [id]").description("Select an app to use as the default").action(async (id) => {
|
|
1325
1885
|
try {
|
|
1326
|
-
const admin = adminClientFromFlags(program2);
|
|
1886
|
+
const admin = await adminClientFromFlags(program2);
|
|
1327
1887
|
let selected;
|
|
1328
1888
|
if (id) {
|
|
1329
1889
|
selected = await withSpinner(
|
|
@@ -1381,7 +1941,7 @@ function registerApps(program2) {
|
|
|
1381
1941
|
});
|
|
1382
1942
|
cmd.command("chains").description("List Admin API chain identifiers for app configuration (e.g. ETH_MAINNET)").action(async () => {
|
|
1383
1943
|
try {
|
|
1384
|
-
const admin = adminClientFromFlags(program2);
|
|
1944
|
+
const admin = await adminClientFromFlags(program2);
|
|
1385
1945
|
const chains = await withSpinner(
|
|
1386
1946
|
"Fetching chains\u2026",
|
|
1387
1947
|
"Chains fetched",
|
|
@@ -1420,6 +1980,99 @@ function registerApps(program2) {
|
|
|
1420
1980
|
});
|
|
1421
1981
|
}
|
|
1422
1982
|
|
|
1983
|
+
// src/commands/wallet.ts
|
|
1984
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
1985
|
+
import { join, dirname } from "path";
|
|
1986
|
+
import { randomUUID } from "crypto";
|
|
1987
|
+
import { generateWallet, getWalletAddress } from "@alchemy/x402";
|
|
1988
|
+
var WALLET_KEYS_DIR = "wallet-keys";
|
|
1989
|
+
var UUID_SLICE_LEN = 8;
|
|
1990
|
+
var ADDRESS_SLICE_LEN = 12;
|
|
1991
|
+
function walletKeysDirPath() {
|
|
1992
|
+
return join(configDir(), WALLET_KEYS_DIR);
|
|
1993
|
+
}
|
|
1994
|
+
function walletKeyPath(address) {
|
|
1995
|
+
const addr = address.trim().toLowerCase().replace(/^0x/, "").replace(/[^a-z0-9]/g, "").slice(0, ADDRESS_SLICE_LEN);
|
|
1996
|
+
const addressTag = addr || "unknown";
|
|
1997
|
+
const fileName = `wallet-key-${addressTag}-${Date.now()}-${randomUUID().slice(0, UUID_SLICE_LEN)}.txt`;
|
|
1998
|
+
return join(walletKeysDirPath(), fileName);
|
|
1999
|
+
}
|
|
2000
|
+
function persistWalletKey(privateKey, address) {
|
|
2001
|
+
const keyPath = walletKeyPath(address);
|
|
2002
|
+
mkdirSync(dirname(keyPath), { recursive: true, mode: 493 });
|
|
2003
|
+
writeFileSync(keyPath, privateKey + "\n", { mode: 384, flag: "wx" });
|
|
2004
|
+
return keyPath;
|
|
2005
|
+
}
|
|
2006
|
+
function generateAndPersistWallet() {
|
|
2007
|
+
const wallet = generateWallet();
|
|
2008
|
+
const keyPath = persistWalletKey(wallet.privateKey, wallet.address);
|
|
2009
|
+
const cfg = load();
|
|
2010
|
+
save({ ...cfg, wallet_key_file: keyPath, wallet_address: wallet.address });
|
|
2011
|
+
return { address: wallet.address, keyFile: keyPath };
|
|
2012
|
+
}
|
|
2013
|
+
function importAndPersistWallet(path) {
|
|
2014
|
+
let key;
|
|
2015
|
+
try {
|
|
2016
|
+
key = readFileSync(path, "utf-8").trim();
|
|
2017
|
+
} catch {
|
|
2018
|
+
throw errInvalidArgs(`Could not read key file: ${path}`);
|
|
2019
|
+
}
|
|
2020
|
+
const address = getWalletAddress(key);
|
|
2021
|
+
const keyPath = persistWalletKey(key, address);
|
|
2022
|
+
const cfg = load();
|
|
2023
|
+
save({ ...cfg, wallet_key_file: keyPath, wallet_address: address });
|
|
2024
|
+
return { address, keyFile: keyPath };
|
|
2025
|
+
}
|
|
2026
|
+
function registerWallet(program2) {
|
|
2027
|
+
const cmd = program2.command("wallet").description("Manage x402 wallet");
|
|
2028
|
+
cmd.command("generate").description("Generate a new wallet for x402 authentication").action(() => {
|
|
2029
|
+
try {
|
|
2030
|
+
const wallet = generateAndPersistWallet();
|
|
2031
|
+
if (isJSONMode()) {
|
|
2032
|
+
printJSON(wallet);
|
|
2033
|
+
} else {
|
|
2034
|
+
printKeyValueBox([
|
|
2035
|
+
["Address", green(wallet.address)],
|
|
2036
|
+
["Key file", wallet.keyFile]
|
|
2037
|
+
]);
|
|
2038
|
+
console.log(` ${green("\u2713")} Wallet generated and saved to config`);
|
|
2039
|
+
}
|
|
2040
|
+
} catch (err) {
|
|
2041
|
+
exitWithError(err);
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
cmd.command("import").argument("<path>", "Path to private key file").description("Import a wallet from a private key file").action((path) => {
|
|
2045
|
+
try {
|
|
2046
|
+
const wallet = importAndPersistWallet(path);
|
|
2047
|
+
if (isJSONMode()) {
|
|
2048
|
+
printJSON(wallet);
|
|
2049
|
+
} else {
|
|
2050
|
+
printKeyValueBox([
|
|
2051
|
+
["Address", green(wallet.address)],
|
|
2052
|
+
["Key file", wallet.keyFile]
|
|
2053
|
+
]);
|
|
2054
|
+
console.log(` ${green("\u2713")} Wallet imported and saved to config`);
|
|
2055
|
+
}
|
|
2056
|
+
} catch (err) {
|
|
2057
|
+
exitWithError(err);
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
cmd.command("address").description("Display the address of the configured wallet").action(() => {
|
|
2061
|
+
try {
|
|
2062
|
+
const key = resolveWalletKey(program2);
|
|
2063
|
+
if (!key) throw errWalletKeyRequired();
|
|
2064
|
+
const address = getWalletAddress(key);
|
|
2065
|
+
printHuman(
|
|
2066
|
+
`${address}
|
|
2067
|
+
`,
|
|
2068
|
+
{ address }
|
|
2069
|
+
);
|
|
2070
|
+
} catch (err) {
|
|
2071
|
+
exitWithError(err);
|
|
2072
|
+
}
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
|
|
1423
2076
|
// src/commands/setup.ts
|
|
1424
2077
|
function registerSetup(program2) {
|
|
1425
2078
|
const cmd = program2.command("setup").description("Setup and onboarding utilities");
|
|
@@ -1429,9 +2082,15 @@ function registerSetup(program2) {
|
|
|
1429
2082
|
printJSON(status);
|
|
1430
2083
|
return;
|
|
1431
2084
|
}
|
|
2085
|
+
const methodLabels = {
|
|
2086
|
+
api_key: "API key",
|
|
2087
|
+
access_key_app: "Access key + app",
|
|
2088
|
+
x402_wallet: "SIWx wallet",
|
|
2089
|
+
auth_token: "Browser login + app"
|
|
2090
|
+
};
|
|
1432
2091
|
printKeyValueBox([
|
|
1433
2092
|
["Complete", status.complete ? "yes" : "no"],
|
|
1434
|
-
["Satisfied by", status.satisfiedBy ?? dim("(none)")]
|
|
2093
|
+
["Satisfied by", status.satisfiedBy ? methodLabels[status.satisfiedBy] ?? status.satisfiedBy : dim("(none)")]
|
|
1435
2094
|
]);
|
|
1436
2095
|
if (status.missing.length > 0) {
|
|
1437
2096
|
console.log("");
|
|
@@ -2537,7 +3196,7 @@ function buildAgentPrompt(program2) {
|
|
|
2537
3196
|
envVar: "ALCHEMY_ACCESS_KEY",
|
|
2538
3197
|
flag: "--access-key <key>",
|
|
2539
3198
|
configKey: "access-key",
|
|
2540
|
-
commandFamilies: ["apps", "
|
|
3199
|
+
commandFamilies: ["apps", "apps configured-networks"]
|
|
2541
3200
|
},
|
|
2542
3201
|
{
|
|
2543
3202
|
method: "Webhook API key",
|
|
@@ -2738,7 +3397,7 @@ function resetUpdateNoticeState() {
|
|
|
2738
3397
|
}
|
|
2739
3398
|
program.name("alchemy").description(
|
|
2740
3399
|
"The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
|
|
2741
|
-
).version("0.
|
|
3400
|
+
).version("0.6.0", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option("--access-key <key>", "Alchemy access key (env: ALCHEMY_ACCESS_KEY)").option(
|
|
2742
3401
|
"-n, --network <network>",
|
|
2743
3402
|
"Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
|
|
2744
3403
|
).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 (auto-enabled when piped)").option("-q, --quiet", "Suppress non-essential output").option("--verbose", "Enable verbose output").option("--no-color", "Disable color output").option("--reveal", "Show secrets in plain text").option("--timeout <ms>", "Request timeout in milliseconds (default: none)", parseInt).option("--debug", "Enable debug diagnostics").option("--no-interactive", "Disable REPL and prompt-driven interactions").addHelpCommand(false).allowExcessArguments(true).exitOverride((err) => {
|
|
@@ -2881,7 +3540,7 @@ ${styledLine}`;
|
|
|
2881
3540
|
` ${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")}`,
|
|
2882
3541
|
` ${hDim("Docs:")} ${hBrand("https://www.alchemy.com/docs")}`
|
|
2883
3542
|
].join("\n");
|
|
2884
|
-
}).hook("preAction", () => {
|
|
3543
|
+
}).hook("preAction", async (thisCommand, actionCommand) => {
|
|
2885
3544
|
const opts = program.opts();
|
|
2886
3545
|
if (opts.color === false) setNoColor(true);
|
|
2887
3546
|
const cfg = load();
|
|
@@ -2893,6 +3552,29 @@ ${styledLine}`;
|
|
|
2893
3552
|
reveal: Boolean(opts.reveal),
|
|
2894
3553
|
timeout: opts.timeout
|
|
2895
3554
|
});
|
|
3555
|
+
const cmdName = actionCommand.name();
|
|
3556
|
+
const skipAppPrompt = [
|
|
3557
|
+
"auth",
|
|
3558
|
+
"config",
|
|
3559
|
+
"setup",
|
|
3560
|
+
"help",
|
|
3561
|
+
"version",
|
|
3562
|
+
"completions",
|
|
3563
|
+
"agent-prompt",
|
|
3564
|
+
"update-check",
|
|
3565
|
+
"wallet"
|
|
3566
|
+
];
|
|
3567
|
+
if (!skipAppPrompt.includes(cmdName) && isInteractiveAllowed(program) && !opts.apiKey && !process.env.ALCHEMY_API_KEY) {
|
|
3568
|
+
const { resolveAuthToken } = await import("./resolve-HXKHDOJZ.js");
|
|
3569
|
+
const authToken = await resolveAuthToken(cfg);
|
|
3570
|
+
const hasApiKey = Boolean(cfg.api_key?.trim() || cfg.app?.apiKey);
|
|
3571
|
+
if (authToken && !hasApiKey) {
|
|
3572
|
+
const { selectAppAfterAuth } = await import("./auth-QB3BA7AN.js");
|
|
3573
|
+
console.log("");
|
|
3574
|
+
console.log(` No app selected. Please select an app to continue.`);
|
|
3575
|
+
await selectAppAfterAuth(authToken);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
2896
3578
|
}).hook("postAction", () => {
|
|
2897
3579
|
if (!isJSONMode() && !quiet) {
|
|
2898
3580
|
console.log("");
|
|
@@ -2918,7 +3600,7 @@ ${styledLine}`;
|
|
|
2918
3600
|
if (isInteractiveAllowed(program)) {
|
|
2919
3601
|
let latestForInteractiveStartup = null;
|
|
2920
3602
|
if (shouldRunOnboarding(program, cfg)) {
|
|
2921
|
-
const { runOnboarding } = await import("./onboarding-
|
|
3603
|
+
const { runOnboarding } = await import("./onboarding-S3GAP4OV.js");
|
|
2922
3604
|
const latest = getAvailableUpdateOnce();
|
|
2923
3605
|
const completed = await runOnboarding(program, latest);
|
|
2924
3606
|
updateShownDuringInteractiveStartup = Boolean(latest);
|
|
@@ -2930,7 +3612,7 @@ ${styledLine}`;
|
|
|
2930
3612
|
latestForInteractiveStartup = getAvailableUpdateOnce();
|
|
2931
3613
|
updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
|
|
2932
3614
|
}
|
|
2933
|
-
const { startREPL } = await import("./interactive-
|
|
3615
|
+
const { startREPL } = await import("./interactive-OM476LBG.js");
|
|
2934
3616
|
program.exitOverride();
|
|
2935
3617
|
program.configureOutput({
|
|
2936
3618
|
writeErr: () => {
|