@alchemy/cli 0.5.2 → 0.6.1
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 +3 -3
- package/dist/{auth-PYH5WEC3.js → auth-QB3BA7AN.js} +4 -3
- package/dist/{auth-76PDHQ3U.js → auth-S4DTOWW3.js} +7 -5
- package/dist/{chunk-NSG4ZKZI.js → chunk-7GD5HACA.js} +1 -1
- package/dist/{chunk-NM25MEJZ.js → chunk-ATX65U7J.js} +17 -4
- package/dist/{chunk-5ZAK2VSS.js → chunk-FFMNT74F.js} +52 -34
- package/dist/chunk-JQRGILIS.js +53 -0
- package/dist/{chunk-FM7GQX6U.js → chunk-T5Z2GJUX.js} +4 -4
- package/dist/{chunk-DBTRDS35.js → chunk-UMKDYHMO.js} +93 -35
- package/dist/credential-storage-T6FFW7DG.js +14 -0
- package/dist/index.js +51 -38
- package/dist/{interactive-CGEVIPC2.js → interactive-QJ4REXWB.js} +5 -4
- package/dist/{onboarding-DNEXVUUH.js → onboarding-3WIM6PVV.js} +9 -12
- package/dist/{resolve-CLDYJ27A.js → resolve-HXKHDOJZ.js} +2 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
|
|
|
109
109
|
|
|
110
110
|
| Command | What it does | Example |
|
|
111
111
|
|---|---|---|
|
|
112
|
-
| `tokens [address]` | Lists ERC-20 balances for an address | `alchemy tokens 0x...` |
|
|
112
|
+
| `tokens balances [address]` | Lists ERC-20 balances for an address | `alchemy tokens balances 0x...` |
|
|
113
113
|
| `tokens metadata <contract>` | Gets ERC-20 metadata | `alchemy tokens metadata 0x...` |
|
|
114
114
|
| `tokens allowance --owner --spender --contract` | Gets ERC-20 allowance | `alchemy tokens allowance --owner 0x... --spender 0x... --contract 0x...` |
|
|
115
115
|
| `nfts [address]` | Lists NFTs owned by an address | `alchemy nfts 0x...` |
|
|
@@ -150,7 +150,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
|
|
|
150
150
|
|
|
151
151
|
| Command | What it does | Example |
|
|
152
152
|
|---|---|---|
|
|
153
|
-
| `network list` | Lists RPC network IDs for use with `--network` (e.g. `eth-mainnet`) | `alchemy network list --
|
|
153
|
+
| `network list` | Lists RPC network IDs for use with `--network` (e.g. `eth-mainnet`) | `alchemy network list --search ethereum` |
|
|
154
154
|
| `solana rpc <method> [params...]` | Calls Solana JSON-RPC methods | `alchemy solana rpc getBalance '"<pubkey>"'` |
|
|
155
155
|
| `solana das <method> [params...]` | Calls Solana DAS methods | `alchemy solana das getAssetsByOwner '{"ownerAddress":"<pubkey>"}'` |
|
|
156
156
|
|
|
@@ -251,7 +251,7 @@ Additional env vars:
|
|
|
251
251
|
| `apps address-allowlist` | `--addresses <addrs>` (required), `--dry-run` |
|
|
252
252
|
| `apps origin-allowlist` | `--origins <origins>` (required), `--dry-run` |
|
|
253
253
|
| `apps ip-allowlist` | `--ips <ips>` (required), `--dry-run` |
|
|
254
|
-
| `network list` | `--
|
|
254
|
+
| `network list` | `--mainnet-only`, `--testnet-only`, `--search <term>` |
|
|
255
255
|
| `config reset` | `-y, --yes` |
|
|
256
256
|
|
|
257
257
|
## Authentication Reference
|
|
@@ -3,10 +3,11 @@ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
|
3
3
|
import {
|
|
4
4
|
registerAuth,
|
|
5
5
|
selectAppAfterAuth
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-UMKDYHMO.js";
|
|
7
|
+
import "./chunk-FFMNT74F.js";
|
|
8
8
|
import "./chunk-KDMIWPZH.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-ATX65U7J.js";
|
|
10
|
+
import "./chunk-JQRGILIS.js";
|
|
10
11
|
import "./chunk-NBDWF4ZQ.js";
|
|
11
12
|
import "./chunk-BAAQ7ELR.js";
|
|
12
13
|
import "./chunk-56ZVYB4G.js";
|
|
@@ -2,22 +2,24 @@
|
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
3
|
import {
|
|
4
4
|
AUTH_PORT,
|
|
5
|
-
|
|
5
|
+
OAUTH_CLIENT_ID,
|
|
6
6
|
exchangeCodeForToken,
|
|
7
|
-
|
|
7
|
+
getAuthorizeUrl,
|
|
8
8
|
openBrowser,
|
|
9
9
|
performBrowserLogin,
|
|
10
|
+
prepareBrowserLogin,
|
|
10
11
|
revokeToken,
|
|
11
12
|
waitForCallback
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-FFMNT74F.js";
|
|
13
14
|
import "./chunk-56ZVYB4G.js";
|
|
14
15
|
export {
|
|
15
16
|
AUTH_PORT,
|
|
16
|
-
|
|
17
|
+
OAUTH_CLIENT_ID,
|
|
17
18
|
exchangeCodeForToken,
|
|
18
|
-
|
|
19
|
+
getAuthorizeUrl,
|
|
19
20
|
openBrowser,
|
|
20
21
|
performBrowserLogin,
|
|
22
|
+
prepareBrowserLogin,
|
|
21
23
|
revokeToken,
|
|
22
24
|
waitForCallback
|
|
23
25
|
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
|
+
import {
|
|
4
|
+
getCredentials
|
|
5
|
+
} from "./chunk-JQRGILIS.js";
|
|
3
6
|
import {
|
|
4
7
|
load,
|
|
5
8
|
save
|
|
@@ -622,7 +625,17 @@ function resolveAppId(program, cfg) {
|
|
|
622
625
|
if (config.app?.id) return config.app.id;
|
|
623
626
|
return void 0;
|
|
624
627
|
}
|
|
625
|
-
function resolveAuthToken(cfg) {
|
|
628
|
+
async function resolveAuthToken(cfg) {
|
|
629
|
+
const creds = await getCredentials();
|
|
630
|
+
if (creds?.auth_token?.trim()) {
|
|
631
|
+
if (creds.auth_token_expires_at) {
|
|
632
|
+
const expiry = new Date(creds.auth_token_expires_at);
|
|
633
|
+
if (!Number.isNaN(expiry.getTime()) && expiry <= /* @__PURE__ */ new Date()) {
|
|
634
|
+
return void 0;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return creds.auth_token;
|
|
638
|
+
}
|
|
626
639
|
const config = cfg ?? load();
|
|
627
640
|
if (!config.auth_token?.trim()) return void 0;
|
|
628
641
|
if (config.auth_token_expires_at) {
|
|
@@ -633,11 +646,11 @@ function resolveAuthToken(cfg) {
|
|
|
633
646
|
}
|
|
634
647
|
return config.auth_token;
|
|
635
648
|
}
|
|
636
|
-
function adminClientFromFlags(program) {
|
|
649
|
+
async function adminClientFromFlags(program) {
|
|
637
650
|
const cfg = load();
|
|
638
651
|
const accessKey = resolveAccessKey(program, cfg);
|
|
639
652
|
if (accessKey) return new AdminClient(accessKey);
|
|
640
|
-
const authToken = resolveAuthToken(cfg);
|
|
653
|
+
const authToken = await resolveAuthToken(cfg);
|
|
641
654
|
if (authToken) return new AdminClient({ type: "auth_token", token: authToken });
|
|
642
655
|
throw errAccessKeyRequired();
|
|
643
656
|
}
|
|
@@ -702,7 +715,7 @@ function appNetworkToSlug(rpcUrl) {
|
|
|
702
715
|
async function resolveConfiguredNetworkSlugs(program, appIdOverride) {
|
|
703
716
|
const appId = appIdOverride || resolveAppId(program);
|
|
704
717
|
if (!appId) throw errAppRequired();
|
|
705
|
-
const admin = adminClientFromFlags(program);
|
|
718
|
+
const admin = await adminClientFromFlags(program);
|
|
706
719
|
const app = await admin.getApp(appId);
|
|
707
720
|
const slugs = app.chainNetworks.map((network) => appNetworkToSlug(network.rpcUrl)).filter((slug) => Boolean(slug));
|
|
708
721
|
return Array.from(new Set(slugs)).sort((a, b) => a.localeCompare(b));
|
|
@@ -130,7 +130,7 @@ ${SHARED_STYLE}
|
|
|
130
130
|
// src/lib/auth.ts
|
|
131
131
|
var AUTH_PORT = 16424;
|
|
132
132
|
var AUTH_CALLBACK_PATH = "/callback";
|
|
133
|
-
var
|
|
133
|
+
var OAUTH_CLIENT_ID = "alchemy-cli";
|
|
134
134
|
function getAuthBaseUrl() {
|
|
135
135
|
return process.env.ALCHEMY_AUTH_URL || `https://auth.${getBaseDomain()}`;
|
|
136
136
|
}
|
|
@@ -140,14 +140,29 @@ function generateCodeVerifier() {
|
|
|
140
140
|
function deriveCodeChallenge(verifier) {
|
|
141
141
|
return createHash("sha256").update(verifier).digest("base64url");
|
|
142
142
|
}
|
|
143
|
-
function
|
|
143
|
+
function generateState() {
|
|
144
|
+
return randomBytes(32).toString("base64url");
|
|
145
|
+
}
|
|
146
|
+
function getAuthorizeUrl(port, codeChallenge, state) {
|
|
144
147
|
const base = getAuthBaseUrl();
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
const url = new URL(`${base}/oauth/authorize`);
|
|
149
|
+
url.searchParams.set("response_type", "code");
|
|
150
|
+
url.searchParams.set("client_id", OAUTH_CLIENT_ID);
|
|
151
|
+
url.searchParams.set("redirect_uri", `http://localhost:${port}${AUTH_CALLBACK_PATH}`);
|
|
152
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
153
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
154
|
+
url.searchParams.set("state", state);
|
|
155
|
+
return url.toString();
|
|
156
|
+
}
|
|
157
|
+
function prepareBrowserLogin(port = AUTH_PORT) {
|
|
158
|
+
const codeVerifier = generateCodeVerifier();
|
|
159
|
+
const codeChallenge = deriveCodeChallenge(codeVerifier);
|
|
160
|
+
const state = generateState();
|
|
161
|
+
return {
|
|
162
|
+
authorizeUrl: getAuthorizeUrl(port, codeChallenge, state),
|
|
163
|
+
codeVerifier,
|
|
164
|
+
state
|
|
165
|
+
};
|
|
151
166
|
}
|
|
152
167
|
function openBrowser(url) {
|
|
153
168
|
const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
|
|
@@ -188,6 +203,7 @@ function waitForCallback(port, timeoutMs = 12e4) {
|
|
|
188
203
|
clearTimeout(timer);
|
|
189
204
|
resolve({
|
|
190
205
|
code,
|
|
206
|
+
state: url.searchParams.get("state"),
|
|
191
207
|
sendSuccess: () => {
|
|
192
208
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
193
209
|
res.end(AUTH_SUCCESS_HTML);
|
|
@@ -220,43 +236,44 @@ function waitForCallback(port, timeoutMs = 12e4) {
|
|
|
220
236
|
async function exchangeCodeForToken(code, port, options) {
|
|
221
237
|
const baseUrl = getAuthBaseUrl();
|
|
222
238
|
const redirectUri = `http://localhost:${port}${AUTH_CALLBACK_PATH}`;
|
|
223
|
-
const body = {
|
|
239
|
+
const body = new URLSearchParams({
|
|
240
|
+
grant_type: "authorization_code",
|
|
224
241
|
code,
|
|
225
|
-
redirect_uri: redirectUri
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
if (options?.codeVerifier) {
|
|
231
|
-
body.code_verifier = options.codeVerifier;
|
|
232
|
-
}
|
|
233
|
-
const response = await fetch(`${baseUrl}/api/cli/token`, {
|
|
242
|
+
redirect_uri: redirectUri,
|
|
243
|
+
client_id: OAUTH_CLIENT_ID,
|
|
244
|
+
code_verifier: options.codeVerifier
|
|
245
|
+
});
|
|
246
|
+
const response = await fetch(`${baseUrl}/oauth/token`, {
|
|
234
247
|
method: "POST",
|
|
235
|
-
headers: { "Content-Type": "application/
|
|
236
|
-
body:
|
|
248
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
249
|
+
body: body.toString()
|
|
237
250
|
});
|
|
238
251
|
if (!response.ok) {
|
|
239
252
|
const errBody = await response.json().catch(() => ({}));
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
);
|
|
253
|
+
const errMsg = errBody.error_description || errBody.error || `Token exchange failed (HTTP ${response.status})`;
|
|
254
|
+
throw new Error(errMsg);
|
|
243
255
|
}
|
|
244
256
|
const data = await response.json();
|
|
245
|
-
if (!data.
|
|
246
|
-
throw new Error("Token exchange response missing
|
|
257
|
+
if (!data.access_token) {
|
|
258
|
+
throw new Error("Token exchange response missing access_token");
|
|
247
259
|
}
|
|
248
|
-
|
|
260
|
+
const expiresAt = new Date(Date.now() + data.expires_in * 1e3).toISOString();
|
|
261
|
+
return { token: data.access_token, expiresAt };
|
|
249
262
|
}
|
|
250
|
-
async function performBrowserLogin(
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const loginUrl = getLoginUrl(port, codeChallenge);
|
|
263
|
+
async function performBrowserLogin(prepared, options) {
|
|
264
|
+
const port = options?.port ?? AUTH_PORT;
|
|
265
|
+
const { authorizeUrl, codeVerifier, state } = prepared ?? prepareBrowserLogin(port);
|
|
254
266
|
const callbackPromise = waitForCallback(port);
|
|
255
|
-
|
|
267
|
+
if (!options?.skipBrowserOpen) {
|
|
268
|
+
openBrowser(authorizeUrl);
|
|
269
|
+
}
|
|
256
270
|
const callback = await callbackPromise;
|
|
271
|
+
if (callback.state !== state) {
|
|
272
|
+
callback.sendError("State mismatch \u2014 possible CSRF attack.");
|
|
273
|
+
throw new Error("OAuth state mismatch. Authentication aborted.");
|
|
274
|
+
}
|
|
257
275
|
try {
|
|
258
276
|
const result = await exchangeCodeForToken(callback.code, port, {
|
|
259
|
-
expiresInSeconds: options?.expiresInSeconds ?? DEFAULT_EXPIRES_IN_SECONDS,
|
|
260
277
|
codeVerifier
|
|
261
278
|
});
|
|
262
279
|
callback.sendSuccess();
|
|
@@ -290,8 +307,9 @@ async function revokeToken(token) {
|
|
|
290
307
|
|
|
291
308
|
export {
|
|
292
309
|
AUTH_PORT,
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
OAUTH_CLIENT_ID,
|
|
311
|
+
getAuthorizeUrl,
|
|
312
|
+
prepareBrowserLogin,
|
|
295
313
|
openBrowser,
|
|
296
314
|
waitForCallback,
|
|
297
315
|
exchangeCodeForToken,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
|
+
|
|
4
|
+
// src/lib/credential-storage.ts
|
|
5
|
+
import {
|
|
6
|
+
getPassword,
|
|
7
|
+
setPassword,
|
|
8
|
+
deletePassword,
|
|
9
|
+
getKeyring,
|
|
10
|
+
PasswordDeleteError
|
|
11
|
+
} from "cross-keychain";
|
|
12
|
+
var SERVICE = "alchemy-cli";
|
|
13
|
+
var ACCOUNT = "oauth-credentials";
|
|
14
|
+
async function getCredentials() {
|
|
15
|
+
try {
|
|
16
|
+
const raw = await getPassword(SERVICE, ACCOUNT);
|
|
17
|
+
if (!raw) return null;
|
|
18
|
+
const parsed = JSON.parse(raw);
|
|
19
|
+
if (parsed && typeof parsed.auth_token === "string" && typeof parsed.auth_token_expires_at === "string") {
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function saveCredentials(creds) {
|
|
28
|
+
await setPassword(SERVICE, ACCOUNT, JSON.stringify(creds));
|
|
29
|
+
}
|
|
30
|
+
async function deleteCredentials() {
|
|
31
|
+
try {
|
|
32
|
+
await deletePassword(SERVICE, ACCOUNT);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (!(err instanceof PasswordDeleteError)) {
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function getStorageBackend() {
|
|
40
|
+
try {
|
|
41
|
+
const keyring = await getKeyring();
|
|
42
|
+
return keyring.name;
|
|
43
|
+
} catch {
|
|
44
|
+
return "unknown";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
getCredentials,
|
|
50
|
+
saveCredentials,
|
|
51
|
+
deleteCredentials,
|
|
52
|
+
getStorageBackend
|
|
53
|
+
};
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-KDMIWPZH.js";
|
|
6
6
|
import {
|
|
7
7
|
resolveAuthToken
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ATX65U7J.js";
|
|
9
9
|
import {
|
|
10
10
|
getBaseDomain
|
|
11
11
|
} from "./chunk-56ZVYB4G.js";
|
|
@@ -281,14 +281,14 @@ function hasAccessKeyAndApp(cfg) {
|
|
|
281
281
|
function hasX402Wallet(cfg) {
|
|
282
282
|
return cfg.x402 === true && Boolean(cfg.wallet_key_file?.trim());
|
|
283
283
|
}
|
|
284
|
-
function
|
|
285
|
-
return resolveAuthToken(cfg) !== void 0;
|
|
284
|
+
function hasAuthTokenAndApp(cfg) {
|
|
285
|
+
return resolveAuthToken(cfg) !== void 0 && Boolean(cfg.app?.apiKey);
|
|
286
286
|
}
|
|
287
287
|
function getSetupMethod(cfg) {
|
|
288
288
|
if (hasAPIKey(cfg)) return "api_key";
|
|
289
289
|
if (hasAccessKeyAndApp(cfg)) return "access_key_app";
|
|
290
290
|
if (hasX402Wallet(cfg)) return "x402_wallet";
|
|
291
|
-
if (
|
|
291
|
+
if (hasAuthTokenAndApp(cfg)) return "auth_token";
|
|
292
292
|
return null;
|
|
293
293
|
}
|
|
294
294
|
function isSetupComplete(cfg) {
|
|
@@ -2,17 +2,25 @@
|
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
3
|
import {
|
|
4
4
|
AUTH_PORT,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
exchangeCodeForToken,
|
|
6
|
+
openBrowser,
|
|
7
|
+
prepareBrowserLogin,
|
|
8
|
+
revokeToken,
|
|
9
|
+
waitForCallback
|
|
10
|
+
} from "./chunk-FFMNT74F.js";
|
|
9
11
|
import {
|
|
10
12
|
isInteractiveAllowed
|
|
11
13
|
} from "./chunk-KDMIWPZH.js";
|
|
12
14
|
import {
|
|
13
15
|
AdminClient,
|
|
14
16
|
resolveAuthToken
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-ATX65U7J.js";
|
|
18
|
+
import {
|
|
19
|
+
deleteCredentials,
|
|
20
|
+
getCredentials,
|
|
21
|
+
getStorageBackend,
|
|
22
|
+
saveCredentials
|
|
23
|
+
} from "./chunk-JQRGILIS.js";
|
|
16
24
|
import {
|
|
17
25
|
bold,
|
|
18
26
|
brand,
|
|
@@ -23,7 +31,6 @@ import {
|
|
|
23
31
|
withSpinner
|
|
24
32
|
} from "./chunk-NBDWF4ZQ.js";
|
|
25
33
|
import {
|
|
26
|
-
configPath,
|
|
27
34
|
load,
|
|
28
35
|
maskIf,
|
|
29
36
|
save
|
|
@@ -44,7 +51,7 @@ function registerAuth(program) {
|
|
|
44
51
|
const yes = opts.yes || cmd.opts().yes;
|
|
45
52
|
try {
|
|
46
53
|
if (!opts.force) {
|
|
47
|
-
const existing = resolveAuthToken();
|
|
54
|
+
const existing = await resolveAuthToken();
|
|
48
55
|
if (existing) {
|
|
49
56
|
printHuman(
|
|
50
57
|
` ${green("\u2713")} Already authenticated
|
|
@@ -57,48 +64,89 @@ function registerAuth(program) {
|
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
66
|
if (opts.force) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
const existingCreds = await getCredentials();
|
|
68
|
+
const tokenToRevoke = existingCreds?.auth_token;
|
|
69
|
+
if (!tokenToRevoke) {
|
|
70
|
+
const cfg2 = load();
|
|
71
|
+
if (cfg2.auth_token) {
|
|
72
|
+
await revokeToken(cfg2.auth_token);
|
|
73
|
+
save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
await revokeToken(tokenToRevoke);
|
|
64
77
|
}
|
|
78
|
+
await deleteCredentials();
|
|
65
79
|
}
|
|
80
|
+
const prepared = prepareBrowserLogin();
|
|
81
|
+
const callbackPromise = waitForCallback(AUTH_PORT);
|
|
66
82
|
if (!isJSONMode()) {
|
|
67
83
|
console.log("");
|
|
68
84
|
console.log(` ${brand("\u25C6")} ${bold("Alchemy Authentication")}`);
|
|
69
85
|
console.log(` ${dim("\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")}`);
|
|
70
86
|
console.log("");
|
|
71
|
-
console.log(` ${dim(
|
|
87
|
+
console.log(` ${dim(prepared.authorizeUrl)}`);
|
|
72
88
|
console.log("");
|
|
73
89
|
}
|
|
90
|
+
let browserOpened = false;
|
|
74
91
|
if (!yes && !isJSONMode() && isInteractiveAllowed(program)) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
const promptResult = await Promise.race([
|
|
93
|
+
promptText({
|
|
94
|
+
message: "Press Enter to open browser, or paste the URL above to log in manually",
|
|
95
|
+
cancelMessage: "Login cancelled."
|
|
96
|
+
}),
|
|
97
|
+
callbackPromise.then(() => "callback_received")
|
|
98
|
+
]);
|
|
99
|
+
if (promptResult === null) return;
|
|
100
|
+
if (promptResult !== "callback_received") {
|
|
101
|
+
if (!isJSONMode()) {
|
|
102
|
+
console.log(` Opening browser to log in...`);
|
|
103
|
+
console.log(` ${dim("Waiting for authentication...")}`);
|
|
104
|
+
}
|
|
105
|
+
openBrowser(prepared.authorizeUrl);
|
|
106
|
+
browserOpened = true;
|
|
107
|
+
}
|
|
80
108
|
}
|
|
81
|
-
if (!
|
|
82
|
-
|
|
83
|
-
|
|
109
|
+
if (!browserOpened && yes) {
|
|
110
|
+
if (!isJSONMode()) {
|
|
111
|
+
console.log(` Opening browser to log in...`);
|
|
112
|
+
console.log(` ${dim("Waiting for authentication...")}`);
|
|
113
|
+
}
|
|
114
|
+
openBrowser(prepared.authorizeUrl);
|
|
84
115
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
const callback = await callbackPromise;
|
|
117
|
+
if (callback.state !== prepared.state) {
|
|
118
|
+
callback.sendError("State mismatch \u2014 possible CSRF attack.");
|
|
119
|
+
throw new Error("OAuth state mismatch. Authentication aborted.");
|
|
120
|
+
}
|
|
121
|
+
let result;
|
|
122
|
+
try {
|
|
123
|
+
result = await exchangeCodeForToken(callback.code, AUTH_PORT, {
|
|
124
|
+
codeVerifier: prepared.codeVerifier
|
|
125
|
+
});
|
|
126
|
+
callback.sendSuccess();
|
|
127
|
+
} catch (err) {
|
|
128
|
+
callback.sendError("Failed to complete authentication. Please try again.");
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
await saveCredentials({
|
|
89
132
|
auth_token: result.token,
|
|
90
133
|
auth_token_expires_at: result.expiresAt
|
|
91
134
|
});
|
|
135
|
+
const cfg = load();
|
|
136
|
+
if (cfg.auth_token) {
|
|
137
|
+
save({ ...cfg, auth_token: void 0, auth_token_expires_at: void 0 });
|
|
138
|
+
}
|
|
92
139
|
const expiresAt = result.expiresAt;
|
|
140
|
+
const backend = await getStorageBackend();
|
|
93
141
|
printHuman(
|
|
94
142
|
` ${green("\u2713")} Logged in successfully
|
|
95
|
-
${dim("
|
|
143
|
+
${dim("Credentials stored in")} ${backend}
|
|
96
144
|
${dim("Expires:")} ${expiresAt}
|
|
97
145
|
`,
|
|
98
146
|
{
|
|
99
147
|
status: "authenticated",
|
|
100
148
|
expiresAt,
|
|
101
|
-
|
|
149
|
+
storageBackend: backend
|
|
102
150
|
}
|
|
103
151
|
);
|
|
104
152
|
if (isInteractiveAllowed(program)) {
|
|
@@ -111,11 +159,13 @@ function registerAuth(program) {
|
|
|
111
159
|
);
|
|
112
160
|
}
|
|
113
161
|
});
|
|
114
|
-
cmd.command("status").description("Show current authentication status").action(() => {
|
|
162
|
+
cmd.command("status").description("Show current authentication status").action(async () => {
|
|
115
163
|
try {
|
|
164
|
+
const creds = await getCredentials();
|
|
116
165
|
const cfg = load();
|
|
117
|
-
const validToken = resolveAuthToken(cfg);
|
|
118
|
-
|
|
166
|
+
const validToken = await resolveAuthToken(cfg);
|
|
167
|
+
const hasToken = creds?.auth_token || cfg.auth_token;
|
|
168
|
+
if (!hasToken) {
|
|
119
169
|
printHuman(
|
|
120
170
|
` ${dim("Not authenticated. Run")} alchemy auth ${dim("to log in.")}
|
|
121
171
|
`,
|
|
@@ -131,15 +181,20 @@ function registerAuth(program) {
|
|
|
131
181
|
);
|
|
132
182
|
return;
|
|
133
183
|
}
|
|
184
|
+
const expiresAt = creds?.auth_token_expires_at || cfg.auth_token_expires_at || "unknown";
|
|
185
|
+
const backend = await getStorageBackend();
|
|
186
|
+
const storedIn = creds?.auth_token ? backend : "config file (legacy)";
|
|
134
187
|
printHuman(
|
|
135
188
|
` ${green("\u2713")} Authenticated
|
|
136
189
|
${dim("Token:")} ${maskIf(validToken)}
|
|
137
|
-
${dim("
|
|
190
|
+
${dim("Storage:")} ${storedIn}
|
|
191
|
+
${dim("Expires:")} ${expiresAt}
|
|
138
192
|
`,
|
|
139
193
|
{
|
|
140
194
|
authenticated: true,
|
|
141
195
|
expired: false,
|
|
142
|
-
expiresAt
|
|
196
|
+
expiresAt,
|
|
197
|
+
storageBackend: storedIn
|
|
143
198
|
}
|
|
144
199
|
);
|
|
145
200
|
} catch (err) {
|
|
@@ -148,14 +203,17 @@ function registerAuth(program) {
|
|
|
148
203
|
});
|
|
149
204
|
cmd.command("logout").description("Clear saved authentication token").action(async () => {
|
|
150
205
|
try {
|
|
206
|
+
const creds = await getCredentials();
|
|
151
207
|
const cfg = load();
|
|
208
|
+
const activeToken = creds?.auth_token || cfg.auth_token;
|
|
152
209
|
let revokeResult;
|
|
153
|
-
if (
|
|
154
|
-
revokeResult = await revokeToken(
|
|
210
|
+
if (activeToken) {
|
|
211
|
+
revokeResult = await revokeToken(activeToken);
|
|
155
212
|
}
|
|
156
|
-
|
|
213
|
+
await deleteCredentials();
|
|
214
|
+
const { auth_token: _, auth_token_expires_at: __, app: ___, ...rest } = cfg;
|
|
157
215
|
save(rest);
|
|
158
|
-
if (!
|
|
216
|
+
if (!activeToken) {
|
|
159
217
|
printHuman(
|
|
160
218
|
` ${dim("No active session.")}
|
|
161
219
|
`,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
|
+
import {
|
|
4
|
+
deleteCredentials,
|
|
5
|
+
getCredentials,
|
|
6
|
+
getStorageBackend,
|
|
7
|
+
saveCredentials
|
|
8
|
+
} from "./chunk-JQRGILIS.js";
|
|
9
|
+
export {
|
|
10
|
+
deleteCredentials,
|
|
11
|
+
getCredentials,
|
|
12
|
+
getStorageBackend,
|
|
13
|
+
saveCredentials
|
|
14
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
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-
|
|
5
|
+
} from "./chunk-UMKDYHMO.js";
|
|
6
|
+
import "./chunk-FFMNT74F.js";
|
|
7
7
|
import {
|
|
8
8
|
getRPCNetworks,
|
|
9
9
|
getSetupStatus,
|
|
10
10
|
isSetupComplete,
|
|
11
11
|
nativeTokenSymbol,
|
|
12
12
|
shouldRunOnboarding
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-T5Z2GJUX.js";
|
|
14
14
|
import {
|
|
15
15
|
isInteractiveAllowed
|
|
16
16
|
} from "./chunk-KDMIWPZH.js";
|
|
@@ -23,12 +23,13 @@ import {
|
|
|
23
23
|
resolveNetwork,
|
|
24
24
|
resolveWalletKey,
|
|
25
25
|
resolveX402Client
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ATX65U7J.js";
|
|
27
|
+
import "./chunk-JQRGILIS.js";
|
|
27
28
|
import {
|
|
28
29
|
getAvailableUpdate,
|
|
29
30
|
getUpdateStatus,
|
|
30
31
|
printUpdateNotice
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-7GD5HACA.js";
|
|
32
33
|
import {
|
|
33
34
|
bold,
|
|
34
35
|
brand,
|
|
@@ -559,9 +560,15 @@ function registerConfig(program2) {
|
|
|
559
560
|
printJSON(toMap(cfg));
|
|
560
561
|
return;
|
|
561
562
|
}
|
|
562
|
-
const { resolveAuthToken } = await import("./resolve-
|
|
563
|
-
const
|
|
564
|
-
const
|
|
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");
|
|
565
572
|
const pairs = [
|
|
566
573
|
["auth", authStatus],
|
|
567
574
|
[
|
|
@@ -642,7 +649,7 @@ function registerConfig(program2) {
|
|
|
642
649
|
|
|
643
650
|
// src/commands/rpc.ts
|
|
644
651
|
function registerRPC(program2) {
|
|
645
|
-
program2.command("rpc").argument("<method>", "JSON-RPC method name (e.g. eth_blockNumber)").argument("[params...]", "Method parameters as JSON values").description("
|
|
652
|
+
program2.command("rpc").argument("<method>", "JSON-RPC method name (e.g. eth_blockNumber)").argument("[params...]", "Method parameters as JSON values").description("Sends a raw JSON-RPC request to an Alchemy node (e.g. eth_call, eth_getCode, eth_blockNumber). Use for low-level RPC calls only. For higher-level operations like balances, transfers, simulation, or token data, use the dedicated subcommands instead.").addHelpText(
|
|
646
653
|
"after",
|
|
647
654
|
`
|
|
648
655
|
Examples:
|
|
@@ -722,7 +729,7 @@ function resolveBlockParam(block) {
|
|
|
722
729
|
return blockParam;
|
|
723
730
|
}
|
|
724
731
|
function registerBalance(program2) {
|
|
725
|
-
program2.command("balance").argument("[address]", "Wallet address (0x...) or ENS name, or pipe via stdin").alias("bal").description("
|
|
732
|
+
program2.command("balance").argument("[address]", "Wallet address (0x...) or ENS name, or pipe via stdin").alias("bal").description("Returns the native token balance (ETH, MATIC, etc.) for any wallet address or ENS name. Use when the user wants to know how much native currency a wallet holds. Does NOT return ERC-20 tokens \u2014 use `alchemy tokens balances` for that.").addHelpText(
|
|
726
733
|
"after",
|
|
727
734
|
`
|
|
728
735
|
Examples:
|
|
@@ -1052,7 +1059,7 @@ function formatNFTRows(nfts) {
|
|
|
1052
1059
|
]);
|
|
1053
1060
|
}
|
|
1054
1061
|
function registerNFTs(program2) {
|
|
1055
|
-
const cmd = program2.command("nfts").description("NFT API
|
|
1062
|
+
const cmd = program2.command("nfts").description("Lists all NFTs owned by a wallet address on the current network via the Alchemy NFT API. Use for single-network NFT ownership queries. For all NFTs across multiple networks in a portfolio view, use `alchemy portfolio nfts`.").argument("[address]", "Wallet address or ENS name (default action: list owned NFTs)").option("--limit <n>", "Maximum number of NFTs to return per page", parseInt).option("--page-key <key>", "Pagination key from a previous response").addHelpText(
|
|
1056
1063
|
"after",
|
|
1057
1064
|
`
|
|
1058
1065
|
Examples:
|
|
@@ -1515,7 +1522,7 @@ function registerApps(program2) {
|
|
|
1515
1522
|
const cmd = program2.command("apps").description("Manage Alchemy apps");
|
|
1516
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) => {
|
|
1517
1524
|
try {
|
|
1518
|
-
const admin = adminClientFromFlags(program2);
|
|
1525
|
+
const admin = await adminClientFromFlags(program2);
|
|
1519
1526
|
const fetchAll = Boolean(opts.all);
|
|
1520
1527
|
const hasSearch = typeof opts.search === "string";
|
|
1521
1528
|
const hasId = typeof opts.id === "string";
|
|
@@ -1642,7 +1649,7 @@ function registerApps(program2) {
|
|
|
1642
1649
|
});
|
|
1643
1650
|
cmd.command("get <id>").description("Get app details").action(async (id) => {
|
|
1644
1651
|
try {
|
|
1645
|
-
const admin = adminClientFromFlags(program2);
|
|
1652
|
+
const admin = await adminClientFromFlags(program2);
|
|
1646
1653
|
const app = await withSpinner(
|
|
1647
1654
|
"Fetching app\u2026",
|
|
1648
1655
|
"App fetched",
|
|
@@ -1676,7 +1683,7 @@ function registerApps(program2) {
|
|
|
1676
1683
|
...products && { products }
|
|
1677
1684
|
};
|
|
1678
1685
|
if (handleDryRun(opts, "create", payload, `Would create app "${opts.name}" on networks: ${networks.join(", ")}`)) return;
|
|
1679
|
-
const admin = adminClientFromFlags(program2);
|
|
1686
|
+
const admin = await adminClientFromFlags(program2);
|
|
1680
1687
|
const app = await withSpinner(
|
|
1681
1688
|
"Creating app\u2026",
|
|
1682
1689
|
"App created",
|
|
@@ -1715,7 +1722,7 @@ function registerApps(program2) {
|
|
|
1715
1722
|
return;
|
|
1716
1723
|
}
|
|
1717
1724
|
}
|
|
1718
|
-
const admin = adminClientFromFlags(program2);
|
|
1725
|
+
const admin = await adminClientFromFlags(program2);
|
|
1719
1726
|
await withSpinner(
|
|
1720
1727
|
"Deleting app\u2026",
|
|
1721
1728
|
"App deleted",
|
|
@@ -1741,7 +1748,7 @@ function registerApps(program2) {
|
|
|
1741
1748
|
...opts.description && { description: opts.description }
|
|
1742
1749
|
};
|
|
1743
1750
|
if (handleDryRun(opts, "update", payload, `Would update app ${id}`)) return;
|
|
1744
|
-
const admin = adminClientFromFlags(program2);
|
|
1751
|
+
const admin = await adminClientFromFlags(program2);
|
|
1745
1752
|
const app = await withSpinner(
|
|
1746
1753
|
"Updating app\u2026",
|
|
1747
1754
|
"App updated",
|
|
@@ -1763,7 +1770,7 @@ function registerApps(program2) {
|
|
|
1763
1770
|
try {
|
|
1764
1771
|
const networks = splitCommaList(opts.networks);
|
|
1765
1772
|
if (handleDryRun(opts, "networks", { id, networks }, `Would update networks for app ${id}: ${networks.join(", ")}`)) return;
|
|
1766
|
-
const admin = adminClientFromFlags(program2);
|
|
1773
|
+
const admin = await adminClientFromFlags(program2);
|
|
1767
1774
|
const app = await withSpinner(
|
|
1768
1775
|
"Updating networks\u2026",
|
|
1769
1776
|
"Networks updated",
|
|
@@ -1784,7 +1791,7 @@ function registerApps(program2) {
|
|
|
1784
1791
|
try {
|
|
1785
1792
|
const entries = splitCommaList(opts.addresses).map((s) => ({ value: s }));
|
|
1786
1793
|
if (handleDryRun(opts, "address-allowlist", { id, addresses: entries }, `Would update address allowlist for app ${id}`)) return;
|
|
1787
|
-
const admin = adminClientFromFlags(program2);
|
|
1794
|
+
const admin = await adminClientFromFlags(program2);
|
|
1788
1795
|
const app = await withSpinner(
|
|
1789
1796
|
"Updating address allowlist\u2026",
|
|
1790
1797
|
"Address allowlist updated",
|
|
@@ -1805,7 +1812,7 @@ function registerApps(program2) {
|
|
|
1805
1812
|
try {
|
|
1806
1813
|
const entries = splitCommaList(opts.origins).map((s) => ({ value: s }));
|
|
1807
1814
|
if (handleDryRun(opts, "origin-allowlist", { id, origins: entries }, `Would update origin allowlist for app ${id}`)) return;
|
|
1808
|
-
const admin = adminClientFromFlags(program2);
|
|
1815
|
+
const admin = await adminClientFromFlags(program2);
|
|
1809
1816
|
const app = await withSpinner(
|
|
1810
1817
|
"Updating origin allowlist\u2026",
|
|
1811
1818
|
"Origin allowlist updated",
|
|
@@ -1826,7 +1833,7 @@ function registerApps(program2) {
|
|
|
1826
1833
|
try {
|
|
1827
1834
|
const entries = splitCommaList(opts.ips).map((s) => ({ value: s }));
|
|
1828
1835
|
if (handleDryRun(opts, "ip-allowlist", { id, ips: entries }, `Would update IP allowlist for app ${id}`)) return;
|
|
1829
|
-
const admin = adminClientFromFlags(program2);
|
|
1836
|
+
const admin = await adminClientFromFlags(program2);
|
|
1830
1837
|
const app = await withSpinner(
|
|
1831
1838
|
"Updating IP allowlist\u2026",
|
|
1832
1839
|
"IP allowlist updated",
|
|
@@ -1845,7 +1852,7 @@ function registerApps(program2) {
|
|
|
1845
1852
|
});
|
|
1846
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) => {
|
|
1847
1854
|
try {
|
|
1848
|
-
const admin = adminClientFromFlags(program2);
|
|
1855
|
+
const admin = await adminClientFromFlags(program2);
|
|
1849
1856
|
const appId = opts.appId || resolveAppId(program2);
|
|
1850
1857
|
if (!appId) throw errAppRequired();
|
|
1851
1858
|
const app = await withSpinner(
|
|
@@ -1876,7 +1883,7 @@ function registerApps(program2) {
|
|
|
1876
1883
|
});
|
|
1877
1884
|
cmd.command("select [id]").description("Select an app to use as the default").action(async (id) => {
|
|
1878
1885
|
try {
|
|
1879
|
-
const admin = adminClientFromFlags(program2);
|
|
1886
|
+
const admin = await adminClientFromFlags(program2);
|
|
1880
1887
|
let selected;
|
|
1881
1888
|
if (id) {
|
|
1882
1889
|
selected = await withSpinner(
|
|
@@ -1934,7 +1941,7 @@ function registerApps(program2) {
|
|
|
1934
1941
|
});
|
|
1935
1942
|
cmd.command("chains").description("List Admin API chain identifiers for app configuration (e.g. ETH_MAINNET)").action(async () => {
|
|
1936
1943
|
try {
|
|
1937
|
-
const admin = adminClientFromFlags(program2);
|
|
1944
|
+
const admin = await adminClientFromFlags(program2);
|
|
1938
1945
|
const chains = await withSpinner(
|
|
1939
1946
|
"Fetching chains\u2026",
|
|
1940
1947
|
"Chains fetched",
|
|
@@ -2050,7 +2057,7 @@ function registerWallet(program2) {
|
|
|
2050
2057
|
exitWithError(err);
|
|
2051
2058
|
}
|
|
2052
2059
|
});
|
|
2053
|
-
cmd.command("address").description("Display the address of the configured wallet").action(() => {
|
|
2060
|
+
cmd.command("address").description("Display the address of the locally configured x402 wallet. This shows the CLI's own signing wallet only \u2014 it does NOT look up arbitrary addresses. To check a wallet's ETH balance, use `alchemy balance`.").action(() => {
|
|
2054
2061
|
try {
|
|
2055
2062
|
const key = resolveWalletKey(program2);
|
|
2056
2063
|
if (!key) throw errWalletKeyRequired();
|
|
@@ -2075,9 +2082,15 @@ function registerSetup(program2) {
|
|
|
2075
2082
|
printJSON(status);
|
|
2076
2083
|
return;
|
|
2077
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
|
+
};
|
|
2078
2091
|
printKeyValueBox([
|
|
2079
2092
|
["Complete", status.complete ? "yes" : "no"],
|
|
2080
|
-
["Satisfied by", status.satisfiedBy ?? dim("(none)")]
|
|
2093
|
+
["Satisfied by", status.satisfiedBy ? methodLabels[status.satisfiedBy] ?? status.satisfiedBy : dim("(none)")]
|
|
2081
2094
|
]);
|
|
2082
2095
|
if (status.missing.length > 0) {
|
|
2083
2096
|
console.log("");
|
|
@@ -2189,7 +2202,7 @@ function formatTransferRows(transfers) {
|
|
|
2189
2202
|
});
|
|
2190
2203
|
}
|
|
2191
2204
|
function registerTransfers(program2) {
|
|
2192
|
-
program2.command("transfers").argument("[address]", "Wallet address or ENS name \u2014 queries outgoing transfers (use --to-address for incoming)").description("
|
|
2205
|
+
program2.command("transfers").argument("[address]", "Wallet address or ENS name \u2014 queries outgoing transfers (use --to-address for incoming)").description("Lists all asset transfer transactions for a wallet address \u2014 ERC-20, ERC-721, ERC-1155, and native (ETH) transfers. Use for transaction history or transfer activity. Does NOT return current balances \u2014 use `alchemy balance` or `alchemy tokens balances` for that.").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 per page").option("--page-key <key>", "Pagination key").addHelpText(
|
|
2193
2206
|
"after",
|
|
2194
2207
|
`
|
|
2195
2208
|
Examples:
|
|
@@ -2411,7 +2424,7 @@ async function runDataCall(program2, title, path, body) {
|
|
|
2411
2424
|
}
|
|
2412
2425
|
function registerPortfolio(program2) {
|
|
2413
2426
|
const cmd = program2.command("portfolio").description("Portfolio API wrappers");
|
|
2414
|
-
cmd.command("tokens").description("
|
|
2427
|
+
cmd.command("tokens").description("Returns all ERC-20 token holdings and their USD values across one or more networks for a wallet address. Use for a portfolio view of fungible token assets. For a single-network NFT list, use `alchemy nfts`; for native token balance, use `alchemy balance`.").requiredOption("--body <json>", "JSON body for /assets/tokens/by-address").action(async (opts) => {
|
|
2415
2428
|
try {
|
|
2416
2429
|
const result = await runDataCall(
|
|
2417
2430
|
program2,
|
|
@@ -2439,7 +2452,7 @@ function registerPortfolio(program2) {
|
|
|
2439
2452
|
exitWithError(err);
|
|
2440
2453
|
}
|
|
2441
2454
|
});
|
|
2442
|
-
cmd.command("nfts").description("
|
|
2455
|
+
cmd.command("nfts").description("Returns all NFTs owned by a wallet address across one or more networks. Use to list all NFTs a wallet holds in a portfolio view. For metadata on a specific NFT by contract + token ID, use `alchemy nfts metadata`.").requiredOption("--body <json>", "JSON body for /assets/nfts/by-address").action(async (opts) => {
|
|
2443
2456
|
try {
|
|
2444
2457
|
const result = await runDataCall(
|
|
2445
2458
|
program2,
|
|
@@ -2481,7 +2494,7 @@ async function runSimulateCall(program2, options) {
|
|
|
2481
2494
|
}
|
|
2482
2495
|
function registerSimulate(program2) {
|
|
2483
2496
|
const cmd = program2.command("simulate").description("Simulation API wrappers");
|
|
2484
|
-
cmd.command("asset-changes").description("
|
|
2497
|
+
cmd.command("asset-changes").description("Simulates a transaction and returns a human-readable breakdown of asset changes (token transfers, ETH movements, NFT transfers) before it is broadcast. Use to preview what a transaction will do. For raw JSON-RPC calls, use `alchemy rpc` instead.").requiredOption("--tx <json>", "Transaction object JSON").option("--block-tag <tag>", "Block tag (default latest)", "latest").action(async (opts) => {
|
|
2485
2498
|
try {
|
|
2486
2499
|
await runSimulateCall(program2, {
|
|
2487
2500
|
method: "alchemy_simulateAssetChanges",
|
|
@@ -2531,7 +2544,7 @@ function resolveWebhookApiKey(opts) {
|
|
|
2531
2544
|
function registerWebhooks(program2) {
|
|
2532
2545
|
const cmd = program2.command("webhooks").description("Notify API wrappers");
|
|
2533
2546
|
cmd.option("--webhook-api-key <key>", "Webhook API key").option("--notify-token <token>", "Deprecated alias for webhook API key");
|
|
2534
|
-
cmd.command("list").description("
|
|
2547
|
+
cmd.command("list").description("Lists all existing Alchemy webhooks configured for this team. Use to view or audit registered webhooks. Does NOT create webhooks \u2014 use `alchemy webhooks create` for that.").action(async () => {
|
|
2535
2548
|
try {
|
|
2536
2549
|
const token = resolveWebhookApiKey(cmd.opts());
|
|
2537
2550
|
const result = await withSpinner(
|
|
@@ -2545,7 +2558,7 @@ function registerWebhooks(program2) {
|
|
|
2545
2558
|
exitWithError(err);
|
|
2546
2559
|
}
|
|
2547
2560
|
});
|
|
2548
|
-
cmd.command("create").description("
|
|
2561
|
+
cmd.command("create").description("Creates a new Alchemy webhook subscription for a given event type (address activity, mined transactions, dropped transactions, etc.). Use this to register a new webhook endpoint. To view existing webhooks, use `alchemy webhooks list`.").requiredOption("--body <json>", "Create webhook JSON payload").option("--dry-run", "Preview without executing").action(async (opts) => {
|
|
2549
2562
|
try {
|
|
2550
2563
|
const payload = parseRequiredJSON(opts.body, "--body");
|
|
2551
2564
|
if (opts.dryRun) {
|
|
@@ -3183,7 +3196,7 @@ function buildAgentPrompt(program2) {
|
|
|
3183
3196
|
envVar: "ALCHEMY_ACCESS_KEY",
|
|
3184
3197
|
flag: "--access-key <key>",
|
|
3185
3198
|
configKey: "access-key",
|
|
3186
|
-
commandFamilies: ["apps", "
|
|
3199
|
+
commandFamilies: ["apps", "apps configured-networks"]
|
|
3187
3200
|
},
|
|
3188
3201
|
{
|
|
3189
3202
|
method: "Webhook API key",
|
|
@@ -3384,7 +3397,7 @@ function resetUpdateNoticeState() {
|
|
|
3384
3397
|
}
|
|
3385
3398
|
program.name("alchemy").description(
|
|
3386
3399
|
"The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
|
|
3387
|
-
).version("0.
|
|
3400
|
+
).version("0.6.1", "-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(
|
|
3388
3401
|
"-n, --network <network>",
|
|
3389
3402
|
"Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
|
|
3390
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) => {
|
|
@@ -3552,11 +3565,11 @@ ${styledLine}`;
|
|
|
3552
3565
|
"wallet"
|
|
3553
3566
|
];
|
|
3554
3567
|
if (!skipAppPrompt.includes(cmdName) && isInteractiveAllowed(program) && !opts.apiKey && !process.env.ALCHEMY_API_KEY) {
|
|
3555
|
-
const { resolveAuthToken } = await import("./resolve-
|
|
3556
|
-
const authToken = resolveAuthToken(cfg);
|
|
3568
|
+
const { resolveAuthToken } = await import("./resolve-HXKHDOJZ.js");
|
|
3569
|
+
const authToken = await resolveAuthToken(cfg);
|
|
3557
3570
|
const hasApiKey = Boolean(cfg.api_key?.trim() || cfg.app?.apiKey);
|
|
3558
3571
|
if (authToken && !hasApiKey) {
|
|
3559
|
-
const { selectAppAfterAuth } = await import("./auth-
|
|
3572
|
+
const { selectAppAfterAuth } = await import("./auth-QB3BA7AN.js");
|
|
3560
3573
|
console.log("");
|
|
3561
3574
|
console.log(` No app selected. Please select an app to continue.`);
|
|
3562
3575
|
await selectAppAfterAuth(authToken);
|
|
@@ -3587,7 +3600,7 @@ ${styledLine}`;
|
|
|
3587
3600
|
if (isInteractiveAllowed(program)) {
|
|
3588
3601
|
let latestForInteractiveStartup = null;
|
|
3589
3602
|
if (shouldRunOnboarding(program, cfg)) {
|
|
3590
|
-
const { runOnboarding } = await import("./onboarding-
|
|
3603
|
+
const { runOnboarding } = await import("./onboarding-3WIM6PVV.js");
|
|
3591
3604
|
const latest = getAvailableUpdateOnce();
|
|
3592
3605
|
const completed = await runOnboarding(program, latest);
|
|
3593
3606
|
updateShownDuringInteractiveStartup = Boolean(latest);
|
|
@@ -3599,7 +3612,7 @@ ${styledLine}`;
|
|
|
3599
3612
|
latestForInteractiveStartup = getAvailableUpdateOnce();
|
|
3600
3613
|
updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
|
|
3601
3614
|
}
|
|
3602
|
-
const { startREPL } = await import("./interactive-
|
|
3615
|
+
const { startREPL } = await import("./interactive-QJ4REXWB.js");
|
|
3603
3616
|
program.exitOverride();
|
|
3604
3617
|
program.configureOutput({
|
|
3605
3618
|
writeErr: () => {
|
|
@@ -3,12 +3,13 @@ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
|
3
3
|
import {
|
|
4
4
|
getRPCNetworkIds,
|
|
5
5
|
getSetupMethod
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-T5Z2GJUX.js";
|
|
7
7
|
import "./chunk-KDMIWPZH.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-ATX65U7J.js";
|
|
9
|
+
import "./chunk-JQRGILIS.js";
|
|
9
10
|
import {
|
|
10
11
|
getUpdateNoticeLines
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-7GD5HACA.js";
|
|
12
13
|
import {
|
|
13
14
|
bold,
|
|
14
15
|
brand,
|
|
@@ -127,7 +128,7 @@ function formatSetupMethodLabel() {
|
|
|
127
128
|
if (method === "api_key") return "API key";
|
|
128
129
|
if (method === "access_key_app") return "Access key + app";
|
|
129
130
|
if (method === "x402_wallet") return "SIWx wallet";
|
|
130
|
-
if (method === "auth_token") return "
|
|
131
|
+
if (method === "auth_token") return "Browser login + app";
|
|
131
132
|
return "Not configured";
|
|
132
133
|
}
|
|
133
134
|
function replHistoryPath() {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
3
|
import {
|
|
4
4
|
getUpdateNoticeLines
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-7GD5HACA.js";
|
|
6
6
|
import {
|
|
7
7
|
bold,
|
|
8
8
|
brand,
|
|
@@ -11,10 +11,7 @@ import {
|
|
|
11
11
|
green,
|
|
12
12
|
promptText
|
|
13
13
|
} from "./chunk-NBDWF4ZQ.js";
|
|
14
|
-
import
|
|
15
|
-
load,
|
|
16
|
-
save
|
|
17
|
-
} from "./chunk-BAAQ7ELR.js";
|
|
14
|
+
import "./chunk-BAAQ7ELR.js";
|
|
18
15
|
import "./chunk-56ZVYB4G.js";
|
|
19
16
|
|
|
20
17
|
// src/commands/onboarding.ts
|
|
@@ -38,20 +35,20 @@ async function runOnboarding(_program, latestUpdate = null) {
|
|
|
38
35
|
if (answer === null) {
|
|
39
36
|
return false;
|
|
40
37
|
}
|
|
41
|
-
const { performBrowserLogin,
|
|
38
|
+
const { performBrowserLogin, prepareBrowserLogin } = await import("./auth-S4DTOWW3.js");
|
|
39
|
+
const prepared = prepareBrowserLogin();
|
|
42
40
|
console.log(` Opening browser to log in...`);
|
|
43
|
-
console.log(` ${dim(
|
|
41
|
+
console.log(` ${dim(prepared.authorizeUrl)}`);
|
|
44
42
|
console.log(` ${dim("Waiting for authentication...")}`);
|
|
45
43
|
try {
|
|
46
|
-
const result = await performBrowserLogin();
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
...cfg,
|
|
44
|
+
const result = await performBrowserLogin(prepared);
|
|
45
|
+
const { saveCredentials } = await import("./credential-storage-T6FFW7DG.js");
|
|
46
|
+
await saveCredentials({
|
|
50
47
|
auth_token: result.token,
|
|
51
48
|
auth_token_expires_at: result.expiresAt
|
|
52
49
|
});
|
|
53
50
|
console.log(` ${green("\u2713")} Logged in successfully`);
|
|
54
|
-
const { selectAppAfterAuth } = await import("./auth-
|
|
51
|
+
const { selectAppAfterAuth } = await import("./auth-QB3BA7AN.js");
|
|
55
52
|
await selectAppAfterAuth(result.token);
|
|
56
53
|
return true;
|
|
57
54
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alchemy/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Alchemy CLI — interact with blockchain data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@noble/hashes": "^2.0.1",
|
|
34
34
|
"cli-table3": "^0.6.5",
|
|
35
35
|
"commander": "^14.0.3",
|
|
36
|
+
"cross-keychain": "^1.1.0",
|
|
36
37
|
"zod": "^4.3.6"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|