@alchemy/cli 0.2.1 → 0.3.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 +29 -9
- package/dist/{chunk-SDUCDSCZ.js → chunk-MF6DXNO7.js} +24 -4
- package/dist/{chunk-XP5KF4W2.js → chunk-UPQTWEPP.js} +204 -16
- package/dist/{chunk-HBRTTBCY.js → chunk-VYQ5V2ZR.js} +38 -1
- package/dist/index.js +784 -177
- package/dist/{interactive-L7N4HI7K.js → interactive-BFAXB5SN.js} +44 -10
- package/dist/{onboarding-YA32OJOT.js → onboarding-MUJF5QIE.js} +2 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -18,6 +18,23 @@ Or run without installing globally:
|
|
|
18
18
|
npx @alchemy/cli <command>
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Shell Completions
|
|
22
|
+
|
|
23
|
+
Enable Tab completion for all commands and subcommands:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# zsh (add to ~/.zshrc)
|
|
27
|
+
echo 'eval "$(alchemy completions zsh)"' >> ~/.zshrc
|
|
28
|
+
source ~/.zshrc
|
|
29
|
+
|
|
30
|
+
# bash (add to ~/.bashrc)
|
|
31
|
+
echo 'eval "$(alchemy completions bash)"' >> ~/.bashrc
|
|
32
|
+
source ~/.bashrc
|
|
33
|
+
|
|
34
|
+
# fish
|
|
35
|
+
alchemy completions fish > ~/.config/fish/completions/alchemy.fish
|
|
36
|
+
```
|
|
37
|
+
|
|
21
38
|
## Getting Started
|
|
22
39
|
|
|
23
40
|
### Authentication Quick Start
|
|
@@ -47,7 +64,7 @@ For setup commands, env vars, and resolution order, see [Authentication Referenc
|
|
|
47
64
|
After auth is configured, use the CLI differently depending on who is driving it:
|
|
48
65
|
|
|
49
66
|
- **Humans (interactive terminal):** start with `alchemy` and use the terminal UI/setup flow; this is the recommended path for human usage
|
|
50
|
-
- **Agents/scripts (automation):**
|
|
67
|
+
- **Agents/scripts (automation):** use `--json --no-interactive` to guarantee JSON output and disable prompts (JSON is auto-enabled when piped, but `--json` is a safe default)
|
|
51
68
|
|
|
52
69
|
Quick usage examples:
|
|
53
70
|
|
|
@@ -86,7 +103,10 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
|
|
|
86
103
|
|---|---|---|
|
|
87
104
|
| `balance [address]` (`bal [address]`) | Gets ETH balance for an address | `alchemy bal 0x...` |
|
|
88
105
|
| `tx [hash]` | Gets transaction + receipt by hash | `alchemy tx 0x...` |
|
|
106
|
+
| `receipt [hash]` | Gets transaction receipt (status, gas, logs) | `alchemy receipt 0x...` |
|
|
89
107
|
| `block <number>` | Gets block details (`latest`, decimal, or hex) | `alchemy block latest` |
|
|
108
|
+
| `gas` | Gets current gas prices (base fee + priority fee) | `alchemy gas -n polygon-mainnet` |
|
|
109
|
+
| `logs` | Queries event logs (`eth_getLogs`) | `alchemy logs --address 0x... --from-block 18000000 --to-block 18000010` |
|
|
90
110
|
| `rpc <method> [params...]` | Makes raw JSON-RPC call | `alchemy rpc eth_blockNumber` |
|
|
91
111
|
| `trace <method> [params...]` | Calls Trace API methods | `alchemy trace call '{"to":"0x..."}' '["trace"]' latest` |
|
|
92
112
|
| `debug <method> [params...]` | Calls Debug API methods | `alchemy debug traceTransaction "0x..."` |
|
|
@@ -136,8 +156,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
|
|
|
136
156
|
|
|
137
157
|
| Command | What it does | Example |
|
|
138
158
|
|---|---|---|
|
|
139
|
-
| `network list` | Lists
|
|
140
|
-
| `chains list` | Lists Admin API chain enums | `alchemy chains list` |
|
|
159
|
+
| `network list` | Lists RPC network IDs for use with `--network` (e.g. `eth-mainnet`) | `alchemy network list --configured` |
|
|
141
160
|
| `solana rpc <method> [params...]` | Calls Solana JSON-RPC methods | `alchemy solana rpc getBalance '"<pubkey>"'` |
|
|
142
161
|
| `solana das <method> [params...]` | Calls Solana DAS methods | `alchemy solana das getAssetsByOwner '{"ownerAddress":"<pubkey>"}'` |
|
|
143
162
|
|
|
@@ -147,6 +166,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
|
|
|
147
166
|
|---|---|---|
|
|
148
167
|
| `(no command)` | Starts interactive REPL mode (TTY only) | `alchemy` |
|
|
149
168
|
| `apps list` | Lists apps (supports pagination/filtering) | `alchemy apps list --all` |
|
|
169
|
+
| `apps chains` | Lists Admin API chain identifiers (e.g. `ETH_MAINNET`) | `alchemy apps chains` |
|
|
150
170
|
| `apps get <id>` | Gets app details | `alchemy apps get <app-id>` |
|
|
151
171
|
| `apps create` | Creates app | `alchemy apps create --name "My App" --networks eth-mainnet` |
|
|
152
172
|
| `apps update <id>` | Updates app name/description | `alchemy apps update <app-id> --name "New Name"` |
|
|
@@ -161,6 +181,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
|
|
|
161
181
|
| `config get <key>` | Gets one config value | `alchemy config get network` |
|
|
162
182
|
| `config list` | Lists all config values | `alchemy config list` |
|
|
163
183
|
| `config reset [key]` | Resets one or all config values | `alchemy config reset --yes` |
|
|
184
|
+
| `completions <shell>` | Generates shell completion scripts (bash/zsh/fish) | `eval "$(alchemy completions zsh)"` |
|
|
164
185
|
| `agent-prompt` | Emits complete agent/automation usage instructions | `alchemy --json agent-prompt` |
|
|
165
186
|
| `version` | Prints CLI version | `alchemy version` |
|
|
166
187
|
|
|
@@ -184,7 +205,7 @@ These apply to all commands.
|
|
|
184
205
|
|
|
185
206
|
| Flag | Env var | Description |
|
|
186
207
|
|---|---|---|
|
|
187
|
-
| `--json` | — | Force JSON output |
|
|
208
|
+
| `--json` | — | Force JSON output (auto-enabled when piped) |
|
|
188
209
|
| `-q, --quiet` | — | Suppress non-essential output |
|
|
189
210
|
| `--verbose` | — | Enable verbose output |
|
|
190
211
|
| `--no-color` | `NO_COLOR` | Disable color output |
|
|
@@ -321,11 +342,10 @@ Use `--no-interactive` to disable REPL/prompts in automation.
|
|
|
321
342
|
|
|
322
343
|
## Output Modes
|
|
323
344
|
|
|
324
|
-
- TTY
|
|
325
|
-
- Non-TTY
|
|
326
|
-
-
|
|
327
|
-
- `--
|
|
328
|
-
- `--verbose` or `alchemy config set verbose true`: includes richer payload output on supported commands
|
|
345
|
+
- **TTY (terminal):** formatted human output (tables, colors, spinners)
|
|
346
|
+
- **Non-TTY (piped/redirected):** JSON output automatically — no flag needed
|
|
347
|
+
- `--json`: forces JSON output even in a terminal
|
|
348
|
+
- `--verbose` or `alchemy config set verbose true`: logs request/response details (method, URL, status, timing) to stderr
|
|
329
349
|
|
|
330
350
|
## Error Format
|
|
331
351
|
|
|
@@ -326,8 +326,16 @@ function errNetwork(detail) {
|
|
|
326
326
|
"Check your internet connection and try again."
|
|
327
327
|
);
|
|
328
328
|
}
|
|
329
|
+
var RPC_ERROR_HINTS = {
|
|
330
|
+
[-32700]: "Parse error. The request JSON is malformed.",
|
|
331
|
+
[-32600]: "Invalid request. Check the JSON-RPC request format.",
|
|
332
|
+
[-32601]: "Method not supported. Check the method name and ensure your plan supports it.",
|
|
333
|
+
[-32602]: "Invalid parameters. Check argument types and format.",
|
|
334
|
+
[-32603]: "Internal JSON-RPC error."
|
|
335
|
+
};
|
|
329
336
|
function errRPC(code, message) {
|
|
330
|
-
|
|
337
|
+
const hint = RPC_ERROR_HINTS[code];
|
|
338
|
+
return new CLIError(ErrorCode.RPC_ERROR, `RPC error ${code}: ${message}`, hint);
|
|
331
339
|
}
|
|
332
340
|
function errInvalidArgs(detail) {
|
|
333
341
|
return new CLIError(ErrorCode.INVALID_ARGS, detail);
|
|
@@ -349,6 +357,14 @@ function errInvalidAccessKey() {
|
|
|
349
357
|
"Get an access key: https://www.alchemy.com/docs/reference/admin-api/overview"
|
|
350
358
|
);
|
|
351
359
|
}
|
|
360
|
+
function errAccessDenied(detail) {
|
|
361
|
+
const message = detail ? `Access denied: ${detail}` : "Access denied. Your access key may not have permission for this operation.";
|
|
362
|
+
return new CLIError(
|
|
363
|
+
ErrorCode.INVALID_ACCESS_KEY,
|
|
364
|
+
message,
|
|
365
|
+
"Check your account tier and feature access at https://dashboard.alchemy.com/"
|
|
366
|
+
);
|
|
367
|
+
}
|
|
352
368
|
function errAppRequired() {
|
|
353
369
|
return new CLIError(
|
|
354
370
|
ErrorCode.APP_REQUIRED,
|
|
@@ -631,8 +647,9 @@ async function runListPrompt(opts) {
|
|
|
631
647
|
stdin.setRawMode(previousRawMode);
|
|
632
648
|
stdin.removeListener("keypress", onKeypress);
|
|
633
649
|
restoreKeypressListeners();
|
|
634
|
-
|
|
635
|
-
|
|
650
|
+
if (!previousRawMode) {
|
|
651
|
+
stdin.pause();
|
|
652
|
+
}
|
|
636
653
|
};
|
|
637
654
|
const commitSingleLine = (text) => {
|
|
638
655
|
if (opts.commitLabel === null) return;
|
|
@@ -1093,7 +1110,7 @@ function semverLT(a, b) {
|
|
|
1093
1110
|
return false;
|
|
1094
1111
|
}
|
|
1095
1112
|
function currentVersion() {
|
|
1096
|
-
return true ? "0.
|
|
1113
|
+
return true ? "0.3.0" : "0.0.0";
|
|
1097
1114
|
}
|
|
1098
1115
|
function toUpdateStatus(latestVersion, checkedAt) {
|
|
1099
1116
|
const current = currentVersion();
|
|
@@ -1156,6 +1173,7 @@ export {
|
|
|
1156
1173
|
esc,
|
|
1157
1174
|
rgb,
|
|
1158
1175
|
bgRgb,
|
|
1176
|
+
redactSensitiveText,
|
|
1159
1177
|
quiet,
|
|
1160
1178
|
verbose,
|
|
1161
1179
|
timeout,
|
|
@@ -1178,6 +1196,7 @@ export {
|
|
|
1178
1196
|
errNotFound,
|
|
1179
1197
|
errRateLimited,
|
|
1180
1198
|
errInvalidAccessKey,
|
|
1199
|
+
errAccessDenied,
|
|
1181
1200
|
errAppRequired,
|
|
1182
1201
|
errWalletKeyRequired,
|
|
1183
1202
|
errAdminAPI,
|
|
@@ -1197,6 +1216,7 @@ export {
|
|
|
1197
1216
|
promptAutocomplete,
|
|
1198
1217
|
promptMultiselect,
|
|
1199
1218
|
green,
|
|
1219
|
+
red,
|
|
1200
1220
|
dim,
|
|
1201
1221
|
bold,
|
|
1202
1222
|
yellow,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
configDir,
|
|
8
8
|
debug,
|
|
9
9
|
dim,
|
|
10
|
+
errAccessDenied,
|
|
10
11
|
errAccessKeyRequired,
|
|
11
12
|
errAdminAPI,
|
|
12
13
|
errAppRequired,
|
|
@@ -35,12 +36,14 @@ import {
|
|
|
35
36
|
promptMultiselect,
|
|
36
37
|
promptSelect,
|
|
37
38
|
promptText,
|
|
39
|
+
redactSensitiveText,
|
|
38
40
|
save,
|
|
39
41
|
timeout,
|
|
40
42
|
toMap,
|
|
43
|
+
verbose,
|
|
41
44
|
withSpinner,
|
|
42
45
|
yellow
|
|
43
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-MF6DXNO7.js";
|
|
44
47
|
|
|
45
48
|
// src/lib/client-utils.ts
|
|
46
49
|
function isLocalhost(hostname) {
|
|
@@ -72,17 +75,39 @@ function parseBaseURLOverride(envVarName) {
|
|
|
72
75
|
}
|
|
73
76
|
return parsed;
|
|
74
77
|
}
|
|
78
|
+
var BREADCRUMB_HEADER = "alchemy-cli";
|
|
75
79
|
async function fetchWithTimeout(url, init) {
|
|
76
80
|
try {
|
|
77
81
|
return await fetch(url, {
|
|
78
82
|
...init,
|
|
83
|
+
headers: {
|
|
84
|
+
...init.headers,
|
|
85
|
+
"x-alchemy-client-breadcrumb": BREADCRUMB_HEADER
|
|
86
|
+
},
|
|
79
87
|
...timeout && { signal: AbortSignal.timeout(timeout) }
|
|
80
88
|
});
|
|
81
89
|
} catch (err) {
|
|
82
90
|
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
83
91
|
throw errNetwork(`Request timed out after ${timeout}ms`);
|
|
84
92
|
}
|
|
85
|
-
|
|
93
|
+
const message = err.message ?? String(err);
|
|
94
|
+
const causeMessage = err.cause?.message ?? "";
|
|
95
|
+
const causeCode = err.cause?.code ?? "";
|
|
96
|
+
const fullErrorText = `${message} ${causeMessage} ${causeCode}`;
|
|
97
|
+
if (/ENOTFOUND|EAI_AGAIN|getaddrinfo/i.test(fullErrorText)) {
|
|
98
|
+
try {
|
|
99
|
+
const hostname = new URL(url).hostname;
|
|
100
|
+
const networkSlug = hostname.replace(/\.g\.alchemy\.com$/, "");
|
|
101
|
+
if (networkSlug !== hostname) {
|
|
102
|
+
throw errInvalidArgs(
|
|
103
|
+
`Unknown network '${networkSlug}'. Run 'alchemy network list' to see available networks.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
} catch (innerErr) {
|
|
107
|
+
if (innerErr instanceof CLIError) throw innerErr;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw errNetwork(message);
|
|
86
111
|
}
|
|
87
112
|
}
|
|
88
113
|
|
|
@@ -145,7 +170,18 @@ var AdminClient = class _AdminClient {
|
|
|
145
170
|
},
|
|
146
171
|
...body !== void 0 && { body: JSON.stringify(body) }
|
|
147
172
|
});
|
|
148
|
-
if (resp.status === 401
|
|
173
|
+
if (resp.status === 401) throw errInvalidAccessKey();
|
|
174
|
+
if (resp.status === 403) {
|
|
175
|
+
const detail = await resp.text().catch(() => "");
|
|
176
|
+
let reason;
|
|
177
|
+
try {
|
|
178
|
+
const parsed = JSON.parse(detail);
|
|
179
|
+
reason = parsed?.message || parsed?.error?.message || parsed?.error || void 0;
|
|
180
|
+
} catch {
|
|
181
|
+
reason = detail || void 0;
|
|
182
|
+
}
|
|
183
|
+
throw errAccessDenied(typeof reason === "string" ? reason : void 0);
|
|
184
|
+
}
|
|
149
185
|
if (resp.status === 404) {
|
|
150
186
|
const text = await resp.text().catch(() => "");
|
|
151
187
|
throw errNotFound(text || path);
|
|
@@ -240,6 +276,92 @@ var AdminClient = class _AdminClient {
|
|
|
240
276
|
}
|
|
241
277
|
};
|
|
242
278
|
|
|
279
|
+
// src/lib/ens.ts
|
|
280
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
281
|
+
var UNIVERSAL_RESOLVER = "0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe";
|
|
282
|
+
var RESOLVE_SELECTOR = "9061b923";
|
|
283
|
+
var ADDR_SELECTOR = "3b3b57de";
|
|
284
|
+
function bytesToHex(bytes) {
|
|
285
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
286
|
+
}
|
|
287
|
+
function pad32(hex) {
|
|
288
|
+
return hex.padStart(64, "0");
|
|
289
|
+
}
|
|
290
|
+
function namehash(name) {
|
|
291
|
+
let node = new Uint8Array(32);
|
|
292
|
+
if (!name) return node;
|
|
293
|
+
const labels = name.split(".");
|
|
294
|
+
for (let i = labels.length - 1; i >= 0; i--) {
|
|
295
|
+
const labelHash = keccak_256(new TextEncoder().encode(labels[i]));
|
|
296
|
+
const combined = new Uint8Array(64);
|
|
297
|
+
combined.set(node, 0);
|
|
298
|
+
combined.set(labelHash, 32);
|
|
299
|
+
node = keccak_256(combined);
|
|
300
|
+
}
|
|
301
|
+
return node;
|
|
302
|
+
}
|
|
303
|
+
function dnsEncode(name) {
|
|
304
|
+
const labels = name.split(".");
|
|
305
|
+
const parts = [];
|
|
306
|
+
for (const label of labels) {
|
|
307
|
+
const encoded = new TextEncoder().encode(label);
|
|
308
|
+
parts.push(encoded.length);
|
|
309
|
+
parts.push(...encoded);
|
|
310
|
+
}
|
|
311
|
+
parts.push(0);
|
|
312
|
+
return new Uint8Array(parts);
|
|
313
|
+
}
|
|
314
|
+
function buildResolveCalldata(name) {
|
|
315
|
+
const dnsName = dnsEncode(name);
|
|
316
|
+
const node = namehash(name);
|
|
317
|
+
const innerHex = ADDR_SELECTOR + bytesToHex(node);
|
|
318
|
+
const innerLen = 36;
|
|
319
|
+
const dnsHex = bytesToHex(dnsName);
|
|
320
|
+
const nameLen = dnsName.length;
|
|
321
|
+
const namePad = Math.ceil(nameLen / 32) * 32;
|
|
322
|
+
const innerPad = Math.ceil(innerLen / 32) * 32;
|
|
323
|
+
const nameOffset = 64;
|
|
324
|
+
const dataOffset = nameOffset + 32 + namePad;
|
|
325
|
+
let hex = RESOLVE_SELECTOR;
|
|
326
|
+
hex += pad32(nameOffset.toString(16));
|
|
327
|
+
hex += pad32(dataOffset.toString(16));
|
|
328
|
+
hex += pad32(nameLen.toString(16));
|
|
329
|
+
hex += dnsHex.padEnd(namePad * 2, "0");
|
|
330
|
+
hex += pad32(innerLen.toString(16));
|
|
331
|
+
hex += innerHex.padEnd(innerPad * 2, "0");
|
|
332
|
+
return "0x" + hex;
|
|
333
|
+
}
|
|
334
|
+
function isENSName(value) {
|
|
335
|
+
return value.endsWith(".eth") && value.length > 4 && !value.startsWith("0x");
|
|
336
|
+
}
|
|
337
|
+
async function resolveENS(name, client) {
|
|
338
|
+
if (!client.network.startsWith("eth-")) {
|
|
339
|
+
throw errInvalidArgs(
|
|
340
|
+
`ENS resolution is only supported on Ethereum networks. Current network: ${client.network}`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const calldata = buildResolveCalldata(name.toLowerCase());
|
|
344
|
+
const result = await client.call("eth_call", [
|
|
345
|
+
{ to: UNIVERSAL_RESOLVER, data: calldata },
|
|
346
|
+
"latest"
|
|
347
|
+
]);
|
|
348
|
+
if (!result || result === "0x" || result.length < 130) {
|
|
349
|
+
throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
|
|
350
|
+
}
|
|
351
|
+
const raw = result.slice(2);
|
|
352
|
+
const dataOffset = parseInt(raw.slice(0, 64), 16) * 2;
|
|
353
|
+
const dataLen = parseInt(raw.slice(dataOffset, dataOffset + 64), 16);
|
|
354
|
+
const dataHex = raw.slice(dataOffset + 64, dataOffset + 64 + dataLen * 2);
|
|
355
|
+
if (dataHex.length < 64) {
|
|
356
|
+
throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
|
|
357
|
+
}
|
|
358
|
+
const address = "0x" + dataHex.slice(24, 64);
|
|
359
|
+
if (address === "0x0000000000000000000000000000000000000000") {
|
|
360
|
+
throw errInvalidArgs(`ENS name "${name}" is not registered or has no address set.`);
|
|
361
|
+
}
|
|
362
|
+
return address;
|
|
363
|
+
}
|
|
364
|
+
|
|
243
365
|
// src/lib/validators.ts
|
|
244
366
|
function splitCommaList(input) {
|
|
245
367
|
return input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -268,6 +390,13 @@ function validateAddress(address) {
|
|
|
268
390
|
);
|
|
269
391
|
}
|
|
270
392
|
}
|
|
393
|
+
async function resolveAddress(input, client) {
|
|
394
|
+
if (isENSName(input)) {
|
|
395
|
+
return resolveENS(input, client);
|
|
396
|
+
}
|
|
397
|
+
validateAddress(input);
|
|
398
|
+
return input;
|
|
399
|
+
}
|
|
271
400
|
function validateTxHash(hash) {
|
|
272
401
|
if (!TX_HASH_RE.test(hash)) {
|
|
273
402
|
throw errInvalidArgs(
|
|
@@ -522,13 +651,13 @@ function registerConfig(program) {
|
|
|
522
651
|
if (normalized !== "true" && normalized !== "false") {
|
|
523
652
|
throw errInvalidArgs("verbose must be 'true' or 'false'");
|
|
524
653
|
}
|
|
525
|
-
const
|
|
654
|
+
const verbose2 = normalized === "true";
|
|
526
655
|
const cfg = load();
|
|
527
|
-
save({ ...cfg, verbose });
|
|
656
|
+
save({ ...cfg, verbose: verbose2 });
|
|
528
657
|
printHuman(
|
|
529
|
-
`${green("\u2713")} Set verbose default to ${
|
|
658
|
+
`${green("\u2713")} Set verbose default to ${verbose2}
|
|
530
659
|
`,
|
|
531
|
-
{ key: "verbose", value: String(
|
|
660
|
+
{ key: "verbose", value: String(verbose2), status: "set" }
|
|
532
661
|
);
|
|
533
662
|
} catch (err) {
|
|
534
663
|
exitWithError(err);
|
|
@@ -564,13 +693,28 @@ function registerConfig(program) {
|
|
|
564
693
|
});
|
|
565
694
|
cmd.command("get <key>").description("Get a config value (api-key, access-key, app, network, verbose, wallet-key-file, x402)").action((key) => {
|
|
566
695
|
const cfg = load();
|
|
567
|
-
|
|
696
|
+
let value = get(cfg, key);
|
|
697
|
+
let isDefault = false;
|
|
698
|
+
if (value === void 0) {
|
|
699
|
+
const defaults = {
|
|
700
|
+
network: "eth-mainnet",
|
|
701
|
+
verbose: "false",
|
|
702
|
+
x402: "false"
|
|
703
|
+
};
|
|
704
|
+
const normalizedKey = KEY_MAP[key] ?? key;
|
|
705
|
+
const defaultValue = defaults[normalizedKey] ?? defaults[key];
|
|
706
|
+
if (defaultValue !== void 0) {
|
|
707
|
+
value = defaultValue;
|
|
708
|
+
isDefault = true;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
568
711
|
if (value === void 0) {
|
|
569
712
|
exitWithError(errNotFound(`config key '${key}'`));
|
|
570
713
|
}
|
|
571
714
|
const isSecret = key === "api-key" || key === "api_key" || key === "access-key" || key === "access_key";
|
|
572
715
|
const display = isSecret ? maskIf(value) : value;
|
|
573
|
-
|
|
716
|
+
const humanDisplay = isDefault ? `${display} ${dim("(default)")}` : display;
|
|
717
|
+
printHuman(humanDisplay + "\n", { key, value: display, ...isDefault && { default: true } });
|
|
574
718
|
});
|
|
575
719
|
cmd.command("list").description("List all config values").action(() => {
|
|
576
720
|
const cfg = load();
|
|
@@ -686,10 +830,14 @@ var Client = class _Client {
|
|
|
686
830
|
try {
|
|
687
831
|
parsed = new URL(`https://${hostname}`);
|
|
688
832
|
} catch {
|
|
689
|
-
throw errInvalidArgs(
|
|
833
|
+
throw errInvalidArgs(
|
|
834
|
+
`Unknown network '${network}'. Run 'alchemy network list' to see available networks.`
|
|
835
|
+
);
|
|
690
836
|
}
|
|
691
837
|
if (!parsed.hostname.endsWith(".g.alchemy.com")) {
|
|
692
|
-
throw errInvalidArgs(
|
|
838
|
+
throw errInvalidArgs(
|
|
839
|
+
`Unknown network '${network}'. Run 'alchemy network list' to see available networks.`
|
|
840
|
+
);
|
|
693
841
|
}
|
|
694
842
|
}
|
|
695
843
|
rpcBaseURLOverride() {
|
|
@@ -718,6 +866,22 @@ var Client = class _Client {
|
|
|
718
866
|
if (networkNotEnabled) return networkNotEnabled;
|
|
719
867
|
return errInvalidAPIKey(detail || void 0);
|
|
720
868
|
}
|
|
869
|
+
tryParseRPCError(text) {
|
|
870
|
+
try {
|
|
871
|
+
const parsed = JSON.parse(text);
|
|
872
|
+
if (parsed?.error?.code !== void 0 && parsed?.error?.message !== void 0) {
|
|
873
|
+
return errRPC(parsed.error.code, parsed.error.message);
|
|
874
|
+
}
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
verboseLog(message) {
|
|
880
|
+
if (verbose) {
|
|
881
|
+
process.stderr.write(`[verbose] ${message}
|
|
882
|
+
`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
721
885
|
async doFetch(url, init) {
|
|
722
886
|
return fetchWithTimeout(url, init);
|
|
723
887
|
}
|
|
@@ -728,6 +892,14 @@ var Client = class _Client {
|
|
|
728
892
|
params,
|
|
729
893
|
id: 1
|
|
730
894
|
};
|
|
895
|
+
const redactedURL = redactSensitiveText(this.rpcURL());
|
|
896
|
+
this.verboseLog(`\u2192 POST ${redactedURL}`);
|
|
897
|
+
this.verboseLog(` method: ${method}`);
|
|
898
|
+
const hasParams = Array.isArray(params) ? params.length > 0 : Object.keys(params).length > 0;
|
|
899
|
+
if (hasParams) {
|
|
900
|
+
this.verboseLog(` params: ${JSON.stringify(params)}`);
|
|
901
|
+
}
|
|
902
|
+
const startTime = Date.now();
|
|
731
903
|
const resp = await this.doFetch(this.rpcURL(), {
|
|
732
904
|
method: "POST",
|
|
733
905
|
headers: {
|
|
@@ -736,6 +908,7 @@ var Client = class _Client {
|
|
|
736
908
|
},
|
|
737
909
|
body: JSON.stringify(body)
|
|
738
910
|
});
|
|
911
|
+
this.verboseLog(`\u2190 ${resp.status} ${resp.statusText} (${Date.now() - startTime}ms)`);
|
|
739
912
|
if (resp.status === 429) throw errRateLimited();
|
|
740
913
|
if (resp.status === 401 || resp.status === 403) {
|
|
741
914
|
const detail = await resp.text().catch(() => "");
|
|
@@ -743,6 +916,8 @@ var Client = class _Client {
|
|
|
743
916
|
}
|
|
744
917
|
if (!resp.ok) {
|
|
745
918
|
const text = await resp.text().catch(() => "");
|
|
919
|
+
const rpcError = this.tryParseRPCError(text);
|
|
920
|
+
if (rpcError) throw rpcError;
|
|
746
921
|
throw errNetwork(`HTTP ${resp.status}: ${text}`);
|
|
747
922
|
}
|
|
748
923
|
const rpcResp = await resp.json();
|
|
@@ -756,9 +931,13 @@ var Client = class _Client {
|
|
|
756
931
|
for (const [k, v] of Object.entries(params)) {
|
|
757
932
|
url.searchParams.set(k, v);
|
|
758
933
|
}
|
|
934
|
+
const redactedURL = redactSensitiveText(url.toString());
|
|
935
|
+
this.verboseLog(`\u2192 GET ${redactedURL}`);
|
|
936
|
+
const startTime = Date.now();
|
|
759
937
|
const resp = await this.doFetch(url.toString(), {
|
|
760
938
|
headers: { Accept: "application/json" }
|
|
761
939
|
});
|
|
940
|
+
this.verboseLog(`\u2190 ${resp.status} ${resp.statusText} (${Date.now() - startTime}ms)`);
|
|
762
941
|
if (resp.status === 429) throw errRateLimited();
|
|
763
942
|
if (resp.status === 401 || resp.status === 403) {
|
|
764
943
|
const detail = await resp.text().catch(() => "");
|
|
@@ -766,6 +945,8 @@ var Client = class _Client {
|
|
|
766
945
|
}
|
|
767
946
|
if (!resp.ok) {
|
|
768
947
|
const text = await resp.text().catch(() => "");
|
|
948
|
+
const rpcError = this.tryParseRPCError(text);
|
|
949
|
+
if (rpcError) throw rpcError;
|
|
769
950
|
throw errNetwork(`HTTP ${resp.status}: ${text}`);
|
|
770
951
|
}
|
|
771
952
|
return resp.json();
|
|
@@ -961,13 +1142,13 @@ function resolveAccessKey(program, cfg) {
|
|
|
961
1142
|
if (config.access_key) return config.access_key;
|
|
962
1143
|
return void 0;
|
|
963
1144
|
}
|
|
964
|
-
function resolveNetwork(program, cfg) {
|
|
1145
|
+
function resolveNetwork(program, cfg, defaultNetwork) {
|
|
965
1146
|
const opts = program.opts();
|
|
966
1147
|
if (opts.network) return opts.network;
|
|
967
1148
|
if (process.env.ALCHEMY_NETWORK) return process.env.ALCHEMY_NETWORK;
|
|
968
1149
|
const config = cfg ?? load();
|
|
969
1150
|
if (config.network) return config.network;
|
|
970
|
-
return "eth-mainnet";
|
|
1151
|
+
return defaultNetwork ?? "eth-mainnet";
|
|
971
1152
|
}
|
|
972
1153
|
function resolveAppId(program, cfg) {
|
|
973
1154
|
const opts = program.opts();
|
|
@@ -1001,10 +1182,16 @@ function resolveWalletKey(program, cfg) {
|
|
|
1001
1182
|
}
|
|
1002
1183
|
return void 0;
|
|
1003
1184
|
}
|
|
1004
|
-
function clientFromFlags(program) {
|
|
1185
|
+
function clientFromFlags(program, opts) {
|
|
1005
1186
|
const cfg = load();
|
|
1006
|
-
const network = resolveNetwork(program, cfg);
|
|
1187
|
+
const network = resolveNetwork(program, cfg, opts?.defaultNetwork);
|
|
1007
1188
|
debug(`using network=${network}`);
|
|
1189
|
+
const programOpts = program.opts();
|
|
1190
|
+
if (programOpts.accessKey) {
|
|
1191
|
+
throw errInvalidArgs(
|
|
1192
|
+
"--access-key is for admin commands (apps, chains, webhooks). Use --api-key for RPC commands."
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1008
1195
|
if (resolveX402(program, cfg)) {
|
|
1009
1196
|
const walletKey = resolveWalletKey(program, cfg);
|
|
1010
1197
|
if (!walletKey) throw errWalletKeyRequired();
|
|
@@ -1092,7 +1279,7 @@ function registerWallet(program) {
|
|
|
1092
1279
|
exitWithError(err);
|
|
1093
1280
|
}
|
|
1094
1281
|
});
|
|
1095
|
-
cmd.command("import
|
|
1282
|
+
cmd.command("import").argument("<path>", "Path to private key file").description("Import a wallet from a private key file").action((path) => {
|
|
1096
1283
|
try {
|
|
1097
1284
|
const wallet = importAndPersistWallet(path);
|
|
1098
1285
|
if (isJSONMode()) {
|
|
@@ -1130,6 +1317,7 @@ export {
|
|
|
1130
1317
|
splitCommaList,
|
|
1131
1318
|
readStdinArg,
|
|
1132
1319
|
validateAddress,
|
|
1320
|
+
resolveAddress,
|
|
1133
1321
|
validateTxHash,
|
|
1134
1322
|
selectOrCreateApp,
|
|
1135
1323
|
registerConfig,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
3
|
import {
|
|
4
4
|
isInteractiveAllowed
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MF6DXNO7.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/networks.ts
|
|
8
8
|
var TESTNET_TOKEN_RE = /(testnet|sepolia|holesky|hoodi|devnet|minato|amoy|fuji|saigon|cardona|aeneid|curtis|chiado|cassiopeia|blaze|ropsten|signet|mocha|fam|bepolia)$/i;
|
|
@@ -227,6 +227,42 @@ function getRPCNetworks() {
|
|
|
227
227
|
function getRPCNetworkIds() {
|
|
228
228
|
return [...RPC_NETWORK_IDS];
|
|
229
229
|
}
|
|
230
|
+
var NATIVE_TOKEN_SYMBOLS = {
|
|
231
|
+
eth: "ETH",
|
|
232
|
+
arb: "ETH",
|
|
233
|
+
arbnova: "ETH",
|
|
234
|
+
opt: "ETH",
|
|
235
|
+
base: "ETH",
|
|
236
|
+
zksync: "ETH",
|
|
237
|
+
scroll: "ETH",
|
|
238
|
+
blast: "ETH",
|
|
239
|
+
linea: "ETH",
|
|
240
|
+
zora: "ETH",
|
|
241
|
+
shape: "ETH",
|
|
242
|
+
polygon: "POL",
|
|
243
|
+
polygonzkevm: "ETH",
|
|
244
|
+
bnb: "BNB",
|
|
245
|
+
opbnb: "BNB",
|
|
246
|
+
avax: "AVAX",
|
|
247
|
+
solana: "SOL",
|
|
248
|
+
starknet: "ETH",
|
|
249
|
+
fantom: "FTM",
|
|
250
|
+
metis: "METIS",
|
|
251
|
+
mantle: "MNT",
|
|
252
|
+
celo: "CELO",
|
|
253
|
+
gnosis: "xDAI",
|
|
254
|
+
frax: "frxETH",
|
|
255
|
+
worldchain: "ETH",
|
|
256
|
+
berachain: "BERA",
|
|
257
|
+
flow: "FLOW",
|
|
258
|
+
rootstock: "RBTC",
|
|
259
|
+
zetachain: "ZETA",
|
|
260
|
+
sui: "SUI"
|
|
261
|
+
};
|
|
262
|
+
function nativeTokenSymbol(networkId) {
|
|
263
|
+
const prefix = networkId.replace(/-(mainnet|testnet|sepolia|holesky|hoodi|devnet|amoy|fuji|cardona|saigon|chiado|signet|mocha|blaze|curtis|bepolia).*$/, "");
|
|
264
|
+
return NATIVE_TOKEN_SYMBOLS[prefix] ?? "ETH";
|
|
265
|
+
}
|
|
230
266
|
|
|
231
267
|
// src/lib/onboarding.ts
|
|
232
268
|
function hasAPIKey(cfg) {
|
|
@@ -275,6 +311,7 @@ function shouldRunOnboarding(program, cfg) {
|
|
|
275
311
|
export {
|
|
276
312
|
getRPCNetworks,
|
|
277
313
|
getRPCNetworkIds,
|
|
314
|
+
nativeTokenSymbol,
|
|
278
315
|
getSetupMethod,
|
|
279
316
|
isSetupComplete,
|
|
280
317
|
getSetupStatus,
|