@alchemy/cli 0.7.2 → 0.7.4-alpha.37

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.
@@ -4,7 +4,7 @@ import {
4
4
  configDir,
5
5
  load,
6
6
  save
7
- } from "./chunk-BAZ4NGOD.js";
7
+ } from "./chunk-PX2YJ7XC.js";
8
8
  import {
9
9
  CLIError,
10
10
  ErrorCode,
@@ -29,11 +29,275 @@ import {
29
29
  parseBaseURLOverride,
30
30
  redactSensitiveText,
31
31
  verbose
32
- } from "./chunk-KLPWJFWP.js";
32
+ } from "./chunk-MYHXAACL.js";
33
33
 
34
34
  // src/lib/resolve.ts
35
35
  import { readFileSync as readFileSync2 } from "fs";
36
36
 
37
+ // src/lib/networks.ts
38
+ 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;
39
+ var FAMILY_ALIASES = {
40
+ arb: "Arbitrum",
41
+ arbnova: "Arbitrum Nova",
42
+ avax: "Avalanche",
43
+ bnb: "BNB Smart Chain",
44
+ eth: "Ethereum",
45
+ opt: "OP Mainnet",
46
+ polygonzkevm: "Polygon zkEVM"
47
+ };
48
+ var NAME_ALIASES = {
49
+ arb: "Arbitrum",
50
+ avax: "Avalanche",
51
+ bnb: "BNB",
52
+ eth: "Ethereum",
53
+ opbnb: "opBNB",
54
+ opt: "OP Mainnet",
55
+ sui: "SUI",
56
+ xmtp: "XMTP",
57
+ zksync: "ZKsync"
58
+ };
59
+ var RPC_NETWORK_IDS = [
60
+ "abstract-mainnet",
61
+ "abstract-testnet",
62
+ "adi-mainnet",
63
+ "adi-testnet",
64
+ "alchemy-internal",
65
+ "alchemy-sepolia",
66
+ "alchemyarb-fam",
67
+ "alchemyarb-sepolia",
68
+ "alterscope-mainnet",
69
+ "anime-mainnet",
70
+ "anime-sepolia",
71
+ "apechain-curtis",
72
+ "apechain-mainnet",
73
+ "aptos-mainnet",
74
+ "aptos-testnet",
75
+ "arb-mainnet",
76
+ "arb-sepolia",
77
+ "arbnova-mainnet",
78
+ "arc-testnet",
79
+ "astar-mainnet",
80
+ "avax-fuji",
81
+ "avax-mainnet",
82
+ "base-mainnet",
83
+ "base-sepolia",
84
+ "berachain-bepolia",
85
+ "berachain-mainnet",
86
+ "bitcoin-mainnet",
87
+ "bitcoin-signet",
88
+ "bitcoin-testnet",
89
+ "blast-mainnet",
90
+ "blast-sepolia",
91
+ "bnb-mainnet",
92
+ "bnb-testnet",
93
+ "bob-mainnet",
94
+ "bob-sepolia",
95
+ "boba-mainnet",
96
+ "boba-sepolia",
97
+ "botanix-mainnet",
98
+ "botanix-testnet",
99
+ "celestiabridge-mainnet",
100
+ "celestiabridge-mocha",
101
+ "celo-mainnet",
102
+ "celo-sepolia",
103
+ "citrea-mainnet",
104
+ "citrea-testnet",
105
+ "clankermon-mainnet",
106
+ "commons-mainnet",
107
+ "crossfi-mainnet",
108
+ "crossfi-testnet",
109
+ "degen-mainnet",
110
+ "degen-sepolia",
111
+ "earnm-mainnet",
112
+ "earnm-sepolia",
113
+ "edge-mainnet",
114
+ "edge-testnet",
115
+ "eth-holesky",
116
+ "eth-holeskybeacon",
117
+ "eth-hoodi",
118
+ "eth-hoodibeacon",
119
+ "eth-mainnet",
120
+ "eth-mainnetbeacon",
121
+ "eth-sepolia",
122
+ "eth-sepoliabeacon",
123
+ "flow-mainnet",
124
+ "flow-testnet",
125
+ "frax-hoodi",
126
+ "frax-mainnet",
127
+ "galactica-cassiopeia",
128
+ "galactica-mainnet",
129
+ "gensyn-mainnet",
130
+ "gensyn-testnet",
131
+ "gnosis-chiado",
132
+ "gnosis-mainnet",
133
+ "humanity-mainnet",
134
+ "humanity-testnet",
135
+ "hyperliquid-mainnet",
136
+ "hyperliquid-testnet",
137
+ "ink-mainnet",
138
+ "ink-sepolia",
139
+ "lens-mainnet",
140
+ "lens-sepolia",
141
+ "linea-mainnet",
142
+ "linea-sepolia",
143
+ "mantle-mainnet",
144
+ "mantle-sepolia",
145
+ "megaeth-mainnet",
146
+ "megaeth-testnet",
147
+ "metis-mainnet",
148
+ "mode-mainnet",
149
+ "mode-sepolia",
150
+ "monad-mainnet",
151
+ "monad-testnet",
152
+ "moonbeam-mainnet",
153
+ "mythos-mainnet",
154
+ "opbnb-mainnet",
155
+ "opbnb-testnet",
156
+ "openloot-sepolia",
157
+ "opt-mainnet",
158
+ "opt-sepolia",
159
+ "plasma-mainnet",
160
+ "plasma-testnet",
161
+ "polygon-amoy",
162
+ "polygon-mainnet",
163
+ "polygonzkevm-cardona",
164
+ "polygonzkevm-mainnet",
165
+ "polynomial-mainnet",
166
+ "polynomial-sepolia",
167
+ "race-mainnet",
168
+ "race-sepolia",
169
+ "risa-testnet",
170
+ "rise-testnet",
171
+ "ronin-mainnet",
172
+ "ronin-saigon",
173
+ "rootstock-mainnet",
174
+ "rootstock-testnet",
175
+ "scroll-mainnet",
176
+ "scroll-sepolia",
177
+ "sei-mainnet",
178
+ "sei-testnet",
179
+ "settlus-mainnet",
180
+ "settlus-septestnet",
181
+ "shape-mainnet",
182
+ "shape-sepolia",
183
+ "solana-devnet",
184
+ "solana-mainnet",
185
+ "soneium-mainnet",
186
+ "soneium-minato",
187
+ "sonic-blaze",
188
+ "sonic-mainnet",
189
+ "sonic-testnet",
190
+ "stable-mainnet",
191
+ "stable-testnet",
192
+ "standard-mainnet",
193
+ "starknet-mainnet",
194
+ "starknet-sepolia",
195
+ "story-aeneid",
196
+ "story-mainnet",
197
+ "sui-mainnet",
198
+ "sui-testnet",
199
+ "superseed-mainnet",
200
+ "superseed-sepolia",
201
+ "synd-mainnet",
202
+ "syndicate-manchego",
203
+ "tea-sepolia",
204
+ "tempo-testnet",
205
+ "tron-mainnet",
206
+ "tron-testnet",
207
+ "unichain-mainnet",
208
+ "unichain-sepolia",
209
+ "unite-mainnet",
210
+ "unite-testnet",
211
+ "worldchain-mainnet",
212
+ "worldchain-sepolia",
213
+ "worldl3-devnet",
214
+ "worldmobile-devnet",
215
+ "worldmobile-testnet",
216
+ "worldmobilechain-mainnet",
217
+ "xmtp-mainnet",
218
+ "xmtp-ropsten",
219
+ "xprotocol-mainnet",
220
+ "zetachain-mainnet",
221
+ "zetachain-testnet",
222
+ "zksync-mainnet",
223
+ "zksync-sepolia",
224
+ "zora-mainnet",
225
+ "zora-sepolia"
226
+ ];
227
+ function isTestnetNetwork(id) {
228
+ return TESTNET_TOKEN_RE.test(id);
229
+ }
230
+ function tokenToName(token) {
231
+ const alias = NAME_ALIASES[token];
232
+ if (alias) return alias;
233
+ return token.charAt(0).toUpperCase() + token.slice(1);
234
+ }
235
+ function toFamily(id) {
236
+ const [head] = id.split("-");
237
+ return FAMILY_ALIASES[head] ?? tokenToName(head);
238
+ }
239
+ function toDisplayName(id) {
240
+ return id.split("-").map((part) => tokenToName(part)).join(" ");
241
+ }
242
+ function toHttpsUrlTemplate(id) {
243
+ const domain = getBaseDomain();
244
+ if (id === "starknet-mainnet" || id === "starknet-sepolia") {
245
+ return `https://${id}.g.${domain}/starknet/version/rpc/v0_10/{apiKey}`;
246
+ }
247
+ return `https://${id}.g.${domain}/v2/{apiKey}`;
248
+ }
249
+ function getRPCNetworks() {
250
+ return RPC_NETWORK_IDS.map((id) => ({
251
+ id,
252
+ name: toDisplayName(id),
253
+ family: toFamily(id),
254
+ isTestnet: isTestnetNetwork(id),
255
+ httpsUrlTemplate: toHttpsUrlTemplate(id)
256
+ }));
257
+ }
258
+ function getRPCNetworkIds() {
259
+ return [...RPC_NETWORK_IDS];
260
+ }
261
+ var NATIVE_TOKEN_SYMBOLS = {
262
+ eth: "ETH",
263
+ arb: "ETH",
264
+ arbnova: "ETH",
265
+ opt: "ETH",
266
+ base: "ETH",
267
+ zksync: "ETH",
268
+ scroll: "ETH",
269
+ blast: "ETH",
270
+ linea: "ETH",
271
+ zora: "ETH",
272
+ shape: "ETH",
273
+ polygon: "POL",
274
+ polygonzkevm: "ETH",
275
+ bnb: "BNB",
276
+ opbnb: "BNB",
277
+ avax: "AVAX",
278
+ solana: "SOL",
279
+ starknet: "ETH",
280
+ fantom: "FTM",
281
+ metis: "METIS",
282
+ mantle: "MNT",
283
+ celo: "CELO",
284
+ gnosis: "xDAI",
285
+ frax: "frxETH",
286
+ worldchain: "ETH",
287
+ berachain: "BERA",
288
+ flow: "FLOW",
289
+ rootstock: "RBTC",
290
+ zetachain: "ZETA",
291
+ sui: "SUI"
292
+ };
293
+ function isSolanaNetwork(networkId) {
294
+ return networkId.startsWith("solana-");
295
+ }
296
+ function nativeTokenSymbol(networkId) {
297
+ const prefix = networkId.replace(/-(mainnet|testnet|sepolia|holesky|hoodi|devnet|amoy|fuji|cardona|saigon|chiado|signet|mocha|blaze|curtis|bepolia).*$/, "");
298
+ return NATIVE_TOKEN_SYMBOLS[prefix] ?? "ETH";
299
+ }
300
+
37
301
  // src/lib/client.ts
38
302
  var Client = class _Client {
39
303
  apiKey;
@@ -49,6 +313,11 @@ var Client = class _Client {
49
313
  if (this.rpcBaseURLOverride()) {
50
314
  return;
51
315
  }
316
+ if (!getRPCNetworkIds().includes(network)) {
317
+ throw errInvalidArgs(
318
+ `Unknown network '${network}'. Run 'alchemy evm network list' to see available networks.`
319
+ );
320
+ }
52
321
  const domain = getBaseDomain();
53
322
  const hostname = `${network}.g.${domain}`;
54
323
  let parsed;
@@ -376,486 +645,222 @@ var X402Client = class _X402Client {
376
645
  } catch {
377
646
  return new CLIError(
378
647
  ErrorCode.PAYMENT_REQUIRED,
379
- `x402 payment failed: ${text}`
380
- );
381
- }
382
- }
383
- async handleAuthAndPayment(resp, retries) {
384
- if (resp.status === 401) {
385
- const detail = await resp.text().catch(() => "");
386
- if (detail.includes("MESSAGE_EXPIRED")) {
387
- return retries.authRetry();
388
- }
389
- throw new CLIError(
390
- ErrorCode.AUTH_REQUIRED,
391
- `x402 authentication failed: ${detail || "unauthorized"}`,
392
- "Check your wallet key and try again."
393
- );
394
- }
395
- if (resp.status === 402) {
396
- const paymentRequiredHeader = resp.headers.get("payment-required");
397
- if (!paymentRequiredHeader) {
398
- throw new CLIError(
399
- ErrorCode.PAYMENT_REQUIRED,
400
- "x402 payment required but no Payment-Required header received."
401
- );
402
- }
403
- const paymentSignature = await createPayment({
404
- privateKey: this.privateKey,
405
- paymentRequiredHeader
406
- });
407
- return retries.paymentRetry(paymentSignature);
408
- }
409
- return resp;
410
- }
411
- };
412
-
413
- // src/lib/admin-client.ts
414
- var STAGING_ADMIN_API_HOST = "admin-api.alchemypreview.com";
415
- var AdminClient = class _AdminClient {
416
- static get ADMIN_API_HOST() {
417
- return `admin-api.${getBaseDomain()}`;
418
- }
419
- // Test/debug only: used by mock E2E to route admin requests locally.
420
- static ADMIN_API_BASE_URL_ENV = "ALCHEMY_ADMIN_API_BASE_URL";
421
- credential;
422
- constructor(credential) {
423
- if (typeof credential === "string") {
424
- this.validateAccessKey(credential);
425
- this.credential = { type: "access_key", key: credential };
426
- } else {
427
- if (credential.type === "access_key") {
428
- this.validateAccessKey(credential.key);
429
- } else if (!credential.token.trim()) {
430
- throw errAuthRequired();
431
- }
432
- this.credential = credential;
433
- }
434
- }
435
- baseURL() {
436
- const override = this.baseURLOverride();
437
- if (override) return override.toString().replace(/\/$/, "");
438
- return `https://admin-api.${getBaseDomain()}`;
439
- }
440
- allowedHosts() {
441
- const hosts = /* @__PURE__ */ new Set([_AdminClient.ADMIN_API_HOST]);
442
- const override = this.baseURLOverride();
443
- if (override) hosts.add(override.hostname);
444
- return hosts;
445
- }
446
- allowInsecureTransport(hostname) {
447
- return isLocalhost(hostname);
448
- }
449
- baseURLOverride() {
450
- return parseBaseURLOverride(_AdminClient.ADMIN_API_BASE_URL_ENV, {
451
- allowedHostnames: [STAGING_ADMIN_API_HOST]
452
- });
453
- }
454
- validateAccessKey(accessKey) {
455
- if (!accessKey.trim() || /\s/.test(accessKey)) {
456
- throw errInvalidAccessKey();
457
- }
458
- }
459
- assertSafeRequestTarget(url) {
460
- let parsed;
461
- try {
462
- parsed = new URL(url);
463
- } catch {
464
- throw errInvalidArgs("Invalid admin API URL.");
465
- }
466
- if (!this.allowedHosts().has(parsed.hostname)) {
467
- throw errInvalidArgs(`Refusing to send credentials to unexpected host: ${parsed.hostname}`);
468
- }
469
- if (parsed.protocol !== "https:" && !this.allowInsecureTransport(parsed.hostname)) {
470
- throw errInvalidArgs("Refusing to send credentials over non-HTTPS connection.");
471
- }
472
- }
473
- async request(method, path, body) {
474
- const url = `${this.baseURL()}${path}`;
475
- debug(`${method} ${url}`);
476
- this.assertSafeRequestTarget(url);
477
- const resp = await fetchWithTimeout(url, {
478
- method,
479
- redirect: "error",
480
- headers: {
481
- Authorization: `Bearer ${this.credential.type === "access_key" ? this.credential.key : this.credential.token}`,
482
- "Content-Type": "application/json",
483
- Accept: "application/json"
484
- },
485
- ...body !== void 0 && { body: JSON.stringify(body) }
486
- });
487
- if (resp.status === 401) {
488
- debug(`401 Unauthorized from ${url}`);
489
- throw errInvalidAccessKey();
490
- }
491
- if (resp.status === 403) {
492
- const detail = await resp.text().catch(() => "");
493
- let reason;
494
- try {
495
- const parsed = JSON.parse(detail);
496
- reason = parsed?.message || parsed?.error?.message || parsed?.error || void 0;
497
- } catch {
498
- reason = detail || void 0;
499
- }
500
- throw errAccessDenied(typeof reason === "string" ? reason : void 0);
501
- }
502
- if (resp.status === 404) {
503
- const text = await resp.text().catch(() => "");
504
- throw errNotFound(text || path);
505
- }
506
- if (resp.status === 429) throw errRateLimited();
507
- if (!resp.ok) {
508
- const text = await resp.text().catch(() => "");
509
- throw errAdminAPI(resp.status, text);
510
- }
511
- return resp.json();
512
- }
513
- async listChains() {
514
- const result = await this.request("GET", "/v1/chains");
515
- const chains = (Array.isArray(result.data) ? result.data : void 0) ?? (!Array.isArray(result.data) ? result.data?.networks : void 0) ?? (!Array.isArray(result.data) ? result.data?.chains : void 0) ?? result.networks ?? result.chains;
516
- if (!Array.isArray(chains)) {
517
- throw errAdminAPI(200, "Unexpected response shape for /v1/chains.");
518
- }
519
- return chains;
520
- }
521
- async listApps(opts) {
522
- const params = new URLSearchParams();
523
- if (opts?.cursor) params.set("cursor", opts.cursor);
524
- if (opts?.limit) params.set("limit", String(opts.limit));
525
- const qs = params.toString();
526
- const resp = await this.request(
527
- "GET",
528
- `/v1/apps${qs ? `?${qs}` : ""}`
529
- );
530
- return resp.data;
531
- }
532
- async listAllApps(opts) {
533
- const apps = [];
534
- const seenCursors = /* @__PURE__ */ new Set();
535
- let cursor;
536
- let pages = 0;
537
- do {
538
- const page = await this.listApps({
539
- ...cursor && { cursor },
540
- ...opts?.limit !== void 0 && { limit: opts.limit }
541
- });
542
- pages += 1;
543
- apps.push(...page.apps);
544
- cursor = page.cursor;
545
- if (cursor && seenCursors.has(cursor)) break;
546
- if (cursor) seenCursors.add(cursor);
547
- } while (cursor);
548
- return { apps, pages };
549
- }
550
- async getApp(id) {
551
- const resp = await this.request("GET", `/v1/apps/${id}`);
552
- return resp.data;
553
- }
554
- async createApp(opts) {
555
- const resp = await this.request("POST", "/v1/apps", {
556
- name: opts.name,
557
- chainNetworks: opts.networks,
558
- ...opts.description && { description: opts.description },
559
- ...opts.products && { products: opts.products }
560
- });
561
- return resp.data;
562
- }
563
- async deleteApp(id) {
564
- await this.request("DELETE", `/v1/apps/${id}`);
565
- }
566
- async updateApp(id, opts) {
567
- const resp = await this.request("PATCH", `/v1/apps/${id}`, opts);
568
- return resp.data;
569
- }
570
- async updateNetworkAllowlist(id, networks) {
571
- const resp = await this.request("PUT", `/v1/apps/${id}/networks`, {
572
- chainNetworks: networks
573
- });
574
- return resp.data;
575
- }
576
- async updateAddressAllowlist(id, addresses) {
577
- const resp = await this.request("PUT", `/v1/apps/${id}/address-allowlist`, {
578
- addressAllowlist: addresses
579
- });
580
- return resp.data;
581
- }
582
- async updateOriginAllowlist(id, origins) {
583
- const resp = await this.request("PUT", `/v1/apps/${id}/origin-allowlist`, {
584
- originAllowlist: origins
585
- });
586
- return resp.data;
648
+ `x402 payment failed: ${text}`
649
+ );
650
+ }
587
651
  }
588
- async updateIpAllowlist(id, ips) {
589
- const resp = await this.request("PUT", `/v1/apps/${id}/ip-allowlist`, {
590
- ipAllowlist: ips
591
- });
592
- return resp.data;
652
+ async handleAuthAndPayment(resp, retries) {
653
+ if (resp.status === 401) {
654
+ const detail = await resp.text().catch(() => "");
655
+ if (detail.includes("MESSAGE_EXPIRED")) {
656
+ return retries.authRetry();
657
+ }
658
+ throw new CLIError(
659
+ ErrorCode.AUTH_REQUIRED,
660
+ `x402 authentication failed: ${detail || "unauthorized"}`,
661
+ "Check your wallet key and try again."
662
+ );
663
+ }
664
+ if (resp.status === 402) {
665
+ const paymentRequiredHeader = resp.headers.get("payment-required");
666
+ if (!paymentRequiredHeader) {
667
+ throw new CLIError(
668
+ ErrorCode.PAYMENT_REQUIRED,
669
+ "x402 payment required but no Payment-Required header received."
670
+ );
671
+ }
672
+ const paymentSignature = await createPayment({
673
+ privateKey: this.privateKey,
674
+ paymentRequiredHeader
675
+ });
676
+ return retries.paymentRetry(paymentSignature);
677
+ }
678
+ return resp;
593
679
  }
594
680
  };
595
681
 
596
- // src/lib/networks.ts
597
- 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;
598
- var FAMILY_ALIASES = {
599
- arb: "Arbitrum",
600
- arbnova: "Arbitrum Nova",
601
- avax: "Avalanche",
602
- bnb: "BNB Smart Chain",
603
- eth: "Ethereum",
604
- opt: "OP Mainnet",
605
- polygonzkevm: "Polygon zkEVM"
606
- };
607
- var NAME_ALIASES = {
608
- arb: "Arbitrum",
609
- avax: "Avalanche",
610
- bnb: "BNB",
611
- eth: "Ethereum",
612
- opbnb: "opBNB",
613
- opt: "OP Mainnet",
614
- sui: "SUI",
615
- xmtp: "XMTP",
616
- zksync: "ZKsync"
617
- };
618
- var RPC_NETWORK_IDS = [
619
- "abstract-mainnet",
620
- "abstract-testnet",
621
- "adi-mainnet",
622
- "adi-testnet",
623
- "alchemy-internal",
624
- "alchemy-sepolia",
625
- "alchemyarb-fam",
626
- "alchemyarb-sepolia",
627
- "alterscope-mainnet",
628
- "anime-mainnet",
629
- "anime-sepolia",
630
- "apechain-curtis",
631
- "apechain-mainnet",
632
- "aptos-mainnet",
633
- "aptos-testnet",
634
- "arb-mainnet",
635
- "arb-sepolia",
636
- "arbnova-mainnet",
637
- "arc-testnet",
638
- "astar-mainnet",
639
- "avax-fuji",
640
- "avax-mainnet",
641
- "base-mainnet",
642
- "base-sepolia",
643
- "berachain-bepolia",
644
- "berachain-mainnet",
645
- "bitcoin-mainnet",
646
- "bitcoin-signet",
647
- "bitcoin-testnet",
648
- "blast-mainnet",
649
- "blast-sepolia",
650
- "bnb-mainnet",
651
- "bnb-testnet",
652
- "bob-mainnet",
653
- "bob-sepolia",
654
- "boba-mainnet",
655
- "boba-sepolia",
656
- "botanix-mainnet",
657
- "botanix-testnet",
658
- "celestiabridge-mainnet",
659
- "celestiabridge-mocha",
660
- "celo-mainnet",
661
- "celo-sepolia",
662
- "citrea-mainnet",
663
- "citrea-testnet",
664
- "clankermon-mainnet",
665
- "commons-mainnet",
666
- "crossfi-mainnet",
667
- "crossfi-testnet",
668
- "degen-mainnet",
669
- "degen-sepolia",
670
- "earnm-mainnet",
671
- "earnm-sepolia",
672
- "edge-mainnet",
673
- "edge-testnet",
674
- "eth-holesky",
675
- "eth-holeskybeacon",
676
- "eth-hoodi",
677
- "eth-hoodibeacon",
678
- "eth-mainnet",
679
- "eth-mainnetbeacon",
680
- "eth-sepolia",
681
- "eth-sepoliabeacon",
682
- "flow-mainnet",
683
- "flow-testnet",
684
- "frax-hoodi",
685
- "frax-mainnet",
686
- "galactica-cassiopeia",
687
- "galactica-mainnet",
688
- "gensyn-mainnet",
689
- "gensyn-testnet",
690
- "gnosis-chiado",
691
- "gnosis-mainnet",
692
- "humanity-mainnet",
693
- "humanity-testnet",
694
- "hyperliquid-mainnet",
695
- "hyperliquid-testnet",
696
- "ink-mainnet",
697
- "ink-sepolia",
698
- "lens-mainnet",
699
- "lens-sepolia",
700
- "linea-mainnet",
701
- "linea-sepolia",
702
- "mantle-mainnet",
703
- "mantle-sepolia",
704
- "megaeth-mainnet",
705
- "megaeth-testnet",
706
- "metis-mainnet",
707
- "mode-mainnet",
708
- "mode-sepolia",
709
- "monad-mainnet",
710
- "monad-testnet",
711
- "moonbeam-mainnet",
712
- "mythos-mainnet",
713
- "opbnb-mainnet",
714
- "opbnb-testnet",
715
- "openloot-sepolia",
716
- "opt-mainnet",
717
- "opt-sepolia",
718
- "plasma-mainnet",
719
- "plasma-testnet",
720
- "polygon-amoy",
721
- "polygon-mainnet",
722
- "polygonzkevm-cardona",
723
- "polygonzkevm-mainnet",
724
- "polynomial-mainnet",
725
- "polynomial-sepolia",
726
- "race-mainnet",
727
- "race-sepolia",
728
- "risa-testnet",
729
- "rise-testnet",
730
- "ronin-mainnet",
731
- "ronin-saigon",
732
- "rootstock-mainnet",
733
- "rootstock-testnet",
734
- "scroll-mainnet",
735
- "scroll-sepolia",
736
- "sei-mainnet",
737
- "sei-testnet",
738
- "settlus-mainnet",
739
- "settlus-septestnet",
740
- "shape-mainnet",
741
- "shape-sepolia",
742
- "solana-devnet",
743
- "solana-mainnet",
744
- "soneium-mainnet",
745
- "soneium-minato",
746
- "sonic-blaze",
747
- "sonic-mainnet",
748
- "sonic-testnet",
749
- "stable-mainnet",
750
- "stable-testnet",
751
- "standard-mainnet",
752
- "starknet-mainnet",
753
- "starknet-sepolia",
754
- "story-aeneid",
755
- "story-mainnet",
756
- "sui-mainnet",
757
- "sui-testnet",
758
- "superseed-mainnet",
759
- "superseed-sepolia",
760
- "synd-mainnet",
761
- "syndicate-manchego",
762
- "tea-sepolia",
763
- "tempo-testnet",
764
- "tron-mainnet",
765
- "tron-testnet",
766
- "unichain-mainnet",
767
- "unichain-sepolia",
768
- "unite-mainnet",
769
- "unite-testnet",
770
- "worldchain-mainnet",
771
- "worldchain-sepolia",
772
- "worldl3-devnet",
773
- "worldmobile-devnet",
774
- "worldmobile-testnet",
775
- "worldmobilechain-mainnet",
776
- "xmtp-mainnet",
777
- "xmtp-ropsten",
778
- "xprotocol-mainnet",
779
- "zetachain-mainnet",
780
- "zetachain-testnet",
781
- "zksync-mainnet",
782
- "zksync-sepolia",
783
- "zora-mainnet",
784
- "zora-sepolia"
785
- ];
786
- function isTestnetNetwork(id) {
787
- return TESTNET_TOKEN_RE.test(id);
788
- }
789
- function tokenToName(token) {
790
- const alias = NAME_ALIASES[token];
791
- if (alias) return alias;
792
- return token.charAt(0).toUpperCase() + token.slice(1);
793
- }
794
- function toFamily(id) {
795
- const [head] = id.split("-");
796
- return FAMILY_ALIASES[head] ?? tokenToName(head);
797
- }
798
- function toDisplayName(id) {
799
- return id.split("-").map((part) => tokenToName(part)).join(" ");
800
- }
801
- function toHttpsUrlTemplate(id) {
802
- const domain = getBaseDomain();
803
- if (id === "starknet-mainnet" || id === "starknet-sepolia") {
804
- return `https://${id}.g.${domain}/starknet/version/rpc/v0_10/{apiKey}`;
682
+ // src/lib/admin-client.ts
683
+ var STAGING_ADMIN_API_HOST = "admin-api.alchemypreview.com";
684
+ var AdminClient = class _AdminClient {
685
+ static get ADMIN_API_HOST() {
686
+ return `admin-api.${getBaseDomain()}`;
687
+ }
688
+ // Test/debug only: used by mock E2E to route admin requests locally.
689
+ static ADMIN_API_BASE_URL_ENV = "ALCHEMY_ADMIN_API_BASE_URL";
690
+ credential;
691
+ constructor(credential) {
692
+ if (typeof credential === "string") {
693
+ this.validateAccessKey(credential);
694
+ this.credential = { type: "access_key", key: credential };
695
+ } else {
696
+ if (credential.type === "access_key") {
697
+ this.validateAccessKey(credential.key);
698
+ } else if (!credential.token.trim()) {
699
+ throw errAuthRequired();
700
+ }
701
+ this.credential = credential;
702
+ }
703
+ }
704
+ baseURL() {
705
+ const override = this.baseURLOverride();
706
+ if (override) return override.toString().replace(/\/$/, "");
707
+ return `https://admin-api.${getBaseDomain()}`;
708
+ }
709
+ allowedHosts() {
710
+ const hosts = /* @__PURE__ */ new Set([_AdminClient.ADMIN_API_HOST]);
711
+ const override = this.baseURLOverride();
712
+ if (override) hosts.add(override.hostname);
713
+ return hosts;
714
+ }
715
+ allowInsecureTransport(hostname) {
716
+ return isLocalhost(hostname);
717
+ }
718
+ baseURLOverride() {
719
+ return parseBaseURLOverride(_AdminClient.ADMIN_API_BASE_URL_ENV, {
720
+ allowedHostnames: [STAGING_ADMIN_API_HOST]
721
+ });
722
+ }
723
+ validateAccessKey(accessKey) {
724
+ if (!accessKey.trim() || /\s/.test(accessKey)) {
725
+ throw errInvalidAccessKey();
726
+ }
727
+ }
728
+ assertSafeRequestTarget(url) {
729
+ let parsed;
730
+ try {
731
+ parsed = new URL(url);
732
+ } catch {
733
+ throw errInvalidArgs("Invalid admin API URL.");
734
+ }
735
+ if (!this.allowedHosts().has(parsed.hostname)) {
736
+ throw errInvalidArgs(`Refusing to send credentials to unexpected host: ${parsed.hostname}`);
737
+ }
738
+ if (parsed.protocol !== "https:" && !this.allowInsecureTransport(parsed.hostname)) {
739
+ throw errInvalidArgs("Refusing to send credentials over non-HTTPS connection.");
740
+ }
741
+ }
742
+ async request(method, path, body) {
743
+ const url = `${this.baseURL()}${path}`;
744
+ debug(`${method} ${url}`);
745
+ this.assertSafeRequestTarget(url);
746
+ const resp = await fetchWithTimeout(url, {
747
+ method,
748
+ redirect: "error",
749
+ headers: {
750
+ Authorization: `Bearer ${this.credential.type === "access_key" ? this.credential.key : this.credential.token}`,
751
+ "Content-Type": "application/json",
752
+ Accept: "application/json"
753
+ },
754
+ ...body !== void 0 && { body: JSON.stringify(body) }
755
+ });
756
+ if (resp.status === 401) {
757
+ debug(`401 Unauthorized from ${url}`);
758
+ throw errInvalidAccessKey();
759
+ }
760
+ if (resp.status === 403) {
761
+ const detail = await resp.text().catch(() => "");
762
+ let reason;
763
+ try {
764
+ const parsed = JSON.parse(detail);
765
+ reason = parsed?.message || parsed?.error?.message || parsed?.error || void 0;
766
+ } catch {
767
+ reason = detail || void 0;
768
+ }
769
+ throw errAccessDenied(typeof reason === "string" ? reason : void 0);
770
+ }
771
+ if (resp.status === 404) {
772
+ const text = await resp.text().catch(() => "");
773
+ throw errNotFound(text || path);
774
+ }
775
+ if (resp.status === 429) throw errRateLimited();
776
+ if (!resp.ok) {
777
+ const text = await resp.text().catch(() => "");
778
+ throw errAdminAPI(resp.status, text);
779
+ }
780
+ return resp.json();
781
+ }
782
+ async listChains() {
783
+ const result = await this.request("GET", "/v1/chains");
784
+ const chains = (Array.isArray(result.data) ? result.data : void 0) ?? (!Array.isArray(result.data) ? result.data?.networks : void 0) ?? (!Array.isArray(result.data) ? result.data?.chains : void 0) ?? result.networks ?? result.chains;
785
+ if (!Array.isArray(chains)) {
786
+ throw errAdminAPI(200, "Unexpected response shape for /v1/chains.");
787
+ }
788
+ return chains;
789
+ }
790
+ async listApps(opts) {
791
+ const params = new URLSearchParams();
792
+ if (opts?.cursor) params.set("cursor", opts.cursor);
793
+ if (opts?.limit) params.set("limit", String(opts.limit));
794
+ const qs = params.toString();
795
+ const resp = await this.request(
796
+ "GET",
797
+ `/v1/apps${qs ? `?${qs}` : ""}`
798
+ );
799
+ return resp.data;
800
+ }
801
+ async listAllApps(opts) {
802
+ const apps = [];
803
+ const seenCursors = /* @__PURE__ */ new Set();
804
+ let cursor;
805
+ let pages = 0;
806
+ do {
807
+ const page = await this.listApps({
808
+ ...cursor && { cursor },
809
+ ...opts?.limit !== void 0 && { limit: opts.limit }
810
+ });
811
+ pages += 1;
812
+ apps.push(...page.apps);
813
+ cursor = page.cursor;
814
+ if (cursor && seenCursors.has(cursor)) break;
815
+ if (cursor) seenCursors.add(cursor);
816
+ } while (cursor);
817
+ return { apps, pages };
818
+ }
819
+ async getApp(id) {
820
+ const resp = await this.request("GET", `/v1/apps/${id}`);
821
+ return resp.data;
822
+ }
823
+ async createApp(opts) {
824
+ const resp = await this.request("POST", "/v1/apps", {
825
+ name: opts.name,
826
+ chainNetworks: opts.networks,
827
+ ...opts.description && { description: opts.description },
828
+ ...opts.products && { products: opts.products }
829
+ });
830
+ return resp.data;
831
+ }
832
+ async deleteApp(id) {
833
+ await this.request("DELETE", `/v1/apps/${id}`);
834
+ }
835
+ async updateApp(id, opts) {
836
+ const resp = await this.request("PATCH", `/v1/apps/${id}`, opts);
837
+ return resp.data;
838
+ }
839
+ async updateNetworkAllowlist(id, networks) {
840
+ const resp = await this.request("PUT", `/v1/apps/${id}/networks`, {
841
+ chainNetworks: networks
842
+ });
843
+ return resp.data;
844
+ }
845
+ async updateAddressAllowlist(id, addresses) {
846
+ const resp = await this.request("PUT", `/v1/apps/${id}/address-allowlist`, {
847
+ addressAllowlist: addresses
848
+ });
849
+ return resp.data;
850
+ }
851
+ async updateOriginAllowlist(id, origins) {
852
+ const resp = await this.request("PUT", `/v1/apps/${id}/origin-allowlist`, {
853
+ originAllowlist: origins
854
+ });
855
+ return resp.data;
856
+ }
857
+ async updateIpAllowlist(id, ips) {
858
+ const resp = await this.request("PUT", `/v1/apps/${id}/ip-allowlist`, {
859
+ ipAllowlist: ips
860
+ });
861
+ return resp.data;
805
862
  }
806
- return `https://${id}.g.${domain}/v2/{apiKey}`;
807
- }
808
- function getRPCNetworks() {
809
- return RPC_NETWORK_IDS.map((id) => ({
810
- id,
811
- name: toDisplayName(id),
812
- family: toFamily(id),
813
- isTestnet: isTestnetNetwork(id),
814
- httpsUrlTemplate: toHttpsUrlTemplate(id)
815
- }));
816
- }
817
- function getRPCNetworkIds() {
818
- return [...RPC_NETWORK_IDS];
819
- }
820
- var NATIVE_TOKEN_SYMBOLS = {
821
- eth: "ETH",
822
- arb: "ETH",
823
- arbnova: "ETH",
824
- opt: "ETH",
825
- base: "ETH",
826
- zksync: "ETH",
827
- scroll: "ETH",
828
- blast: "ETH",
829
- linea: "ETH",
830
- zora: "ETH",
831
- shape: "ETH",
832
- polygon: "POL",
833
- polygonzkevm: "ETH",
834
- bnb: "BNB",
835
- opbnb: "BNB",
836
- avax: "AVAX",
837
- solana: "SOL",
838
- starknet: "ETH",
839
- fantom: "FTM",
840
- metis: "METIS",
841
- mantle: "MNT",
842
- celo: "CELO",
843
- gnosis: "xDAI",
844
- frax: "frxETH",
845
- worldchain: "ETH",
846
- berachain: "BERA",
847
- flow: "FLOW",
848
- rootstock: "RBTC",
849
- zetachain: "ZETA",
850
- sui: "SUI"
851
863
  };
852
- function isSolanaNetwork(networkId) {
853
- return networkId.startsWith("solana-");
854
- }
855
- function nativeTokenSymbol(networkId) {
856
- const prefix = networkId.replace(/-(mainnet|testnet|sepolia|holesky|hoodi|devnet|amoy|fuji|cardona|saigon|chiado|signet|mocha|blaze|curtis|bepolia).*$/, "");
857
- return NATIVE_TOKEN_SYMBOLS[prefix] ?? "ETH";
858
- }
859
864
 
860
865
  // src/lib/wallet-session.ts
861
866
  import { generateKeyPairSync, randomUUID } from "crypto";
@@ -879,7 +884,20 @@ var walletSessionEnvironmentSchema = z.object({
879
884
  clientInstanceId: z.string().uuid().optional(),
880
885
  clientInstanceName: z.string().min(1).max(64).optional()
881
886
  }).strict();
887
+ var chainWalletSessionSchema = z.object({
888
+ sessionId: z.string().uuid(),
889
+ status: z.enum(["pending", "approved", "revoked", "expired"]),
890
+ expiresAt: z.string().datetime(),
891
+ chainType: z.enum(["evm", "solana"]),
892
+ walletId: z.string().min(1).optional(),
893
+ walletAddress: z.string().min(1).optional(),
894
+ providerKeyQuorumId: z.string().min(1).optional(),
895
+ providerSignerId: z.string().min(1).optional(),
896
+ capabilities: walletSessionCapabilitiesSchema.optional()
897
+ }).strict();
882
898
  var walletSessionSchema = z.object({
899
+ version: z.number().int().positive().optional(),
900
+ connectionRequestId: z.string().min(1).optional(),
883
901
  sessionId: z.string().uuid(),
884
902
  status: z.enum(["pending", "approved", "revoked", "expired"]),
885
903
  createdAt: z.string().datetime(),
@@ -898,7 +916,11 @@ var walletSessionSchema = z.object({
898
916
  chainType: z.string().min(1).optional(),
899
917
  backendBaseUrl: z.string().min(1).optional(),
900
918
  environment: walletSessionEnvironmentSchema.optional(),
901
- capabilities: walletSessionCapabilitiesSchema.optional()
919
+ capabilities: walletSessionCapabilitiesSchema.optional(),
920
+ sessionsByChain: z.object({
921
+ evm: chainWalletSessionSchema.optional(),
922
+ solana: chainWalletSessionSchema.optional()
923
+ }).strict().optional()
902
924
  }).strict();
903
925
  function sessionPath() {
904
926
  return join(configDir(), SESSION_FILE);
@@ -906,7 +928,59 @@ function sessionPath() {
906
928
  function parseStoredSession(value) {
907
929
  const parsed = walletSessionSchema.safeParse(value);
908
930
  if (!parsed.success) return null;
909
- return parsed.data;
931
+ return withLegacyChainSessions(parsed.data);
932
+ }
933
+ function withLegacyChainSessions(session) {
934
+ if (session.sessionsByChain) {
935
+ return withCompatibilityAliases(session);
936
+ }
937
+ const chainType = session.chainType === "solana" ? "solana" : "evm";
938
+ const walletId = chainType === "solana" ? session.solanaWalletId ?? session.walletId : session.evmWalletId ?? session.walletId;
939
+ const walletAddress = chainType === "solana" ? session.solanaAddress : session.evmAddress;
940
+ return withCompatibilityAliases({
941
+ ...session,
942
+ version: session.version ?? 1,
943
+ sessionsByChain: {
944
+ [chainType]: {
945
+ sessionId: session.sessionId,
946
+ status: session.status,
947
+ expiresAt: session.expiresAt,
948
+ chainType,
949
+ ...walletId ? { walletId } : {},
950
+ ...walletAddress ? { walletAddress } : {},
951
+ ...session.privyKeyQuorumId ? { providerKeyQuorumId: session.privyKeyQuorumId } : {},
952
+ ...session.privySignerId ? { providerSignerId: session.privySignerId } : {},
953
+ ...session.capabilities ? { capabilities: session.capabilities } : {}
954
+ }
955
+ }
956
+ });
957
+ }
958
+ function withCompatibilityAliases(session) {
959
+ const evm = session.sessionsByChain?.evm;
960
+ const solana = session.sessionsByChain?.solana;
961
+ const preferred = evm ?? solana;
962
+ if (!preferred) {
963
+ return session;
964
+ }
965
+ return {
966
+ ...session,
967
+ sessionId: preferred.sessionId,
968
+ status: preferred.status,
969
+ expiresAt: preferred.expiresAt,
970
+ walletId: evm?.walletId ?? solana?.walletId ?? session.walletId,
971
+ evmWalletId: evm?.walletId ?? session.evmWalletId,
972
+ evmAddress: evm?.walletAddress ?? session.evmAddress,
973
+ solanaWalletId: solana?.walletId ?? session.solanaWalletId,
974
+ solanaAddress: solana?.walletAddress ?? session.solanaAddress,
975
+ privyKeyQuorumId: evm?.providerKeyQuorumId ?? solana?.providerKeyQuorumId ?? session.privyKeyQuorumId,
976
+ privySignerId: evm?.providerSignerId ?? solana?.providerSignerId ?? session.privySignerId,
977
+ chainType: evm && solana ? "both" : preferred.chainType,
978
+ capabilities: {
979
+ ...evm?.capabilities ?? {},
980
+ ...solana?.capabilities ?? {},
981
+ ...session.capabilities ?? {}
982
+ }
983
+ };
910
984
  }
911
985
  function loadStoredSessionFromPath(path) {
912
986
  if (!existsSync(path)) return null;
@@ -920,13 +994,23 @@ function isExpired(session) {
920
994
  const expiresAt = new Date(session.expiresAt);
921
995
  return !Number.isNaN(expiresAt.getTime()) && expiresAt <= /* @__PURE__ */ new Date();
922
996
  }
997
+ function isSessionLoadable(session) {
998
+ if (session.sessionsByChain) {
999
+ return Object.values(session.sessionsByChain).some((chainSession) => {
1000
+ return Boolean(
1001
+ chainSession && chainSession.status !== "revoked" && !isExpired(chainSession)
1002
+ );
1003
+ });
1004
+ }
1005
+ if (session.status === "revoked") return false;
1006
+ return !isExpired(session);
1007
+ }
923
1008
  function createFileWalletSessionStore(path = sessionPath()) {
924
1009
  return {
925
1010
  load() {
926
1011
  const session = loadStoredSessionFromPath(path);
927
1012
  if (!session) return null;
928
- if (session.status === "revoked") return null;
929
- if (isExpired(session)) return null;
1013
+ if (!isSessionLoadable(session)) return null;
930
1014
  return session;
931
1015
  },
932
1016
  loadRaw() {
@@ -970,6 +1054,7 @@ function createPendingSession() {
970
1054
  const now = /* @__PURE__ */ new Date();
971
1055
  const expiresAt = new Date(now.getTime() + SESSION_TTL_DAYS * 24 * 60 * 60 * 1e3);
972
1056
  const session = {
1057
+ version: 2,
973
1058
  sessionId: randomUUID(),
974
1059
  status: "pending",
975
1060
  createdAt: now.toISOString(),
@@ -992,12 +1077,12 @@ function loadStoredSession() {
992
1077
  return store.load();
993
1078
  }
994
1079
  function saveSession(session) {
995
- getWalletSessionStore().save(session);
1080
+ getWalletSessionStore().save(withCompatibilityAliases(session));
996
1081
  }
997
1082
  function updateSession(updates) {
998
1083
  const session = loadSession();
999
1084
  if (!session) return null;
1000
- const updated = { ...session, ...updates };
1085
+ const updated = withCompatibilityAliases({ ...session, ...updates });
1001
1086
  saveSession(updated);
1002
1087
  return updated;
1003
1088
  }
@@ -1005,11 +1090,52 @@ function clearSession() {
1005
1090
  return getWalletSessionStore().clear();
1006
1091
  }
1007
1092
  function isSessionValid(session) {
1093
+ if (session.sessionsByChain) {
1094
+ return Object.values(session.sessionsByChain).some((chainSession) => {
1095
+ return Boolean(chainSession && isChainSessionValid(chainSession));
1096
+ });
1097
+ }
1098
+ if (session.status !== "approved") return false;
1099
+ const expiresAt = new Date(session.expiresAt);
1100
+ if (Number.isNaN(expiresAt.getTime())) return false;
1101
+ return expiresAt > /* @__PURE__ */ new Date();
1102
+ }
1103
+ function isChainSessionValid(session) {
1008
1104
  if (session.status !== "approved") return false;
1009
1105
  const expiresAt = new Date(session.expiresAt);
1010
1106
  if (Number.isNaN(expiresAt.getTime())) return false;
1011
1107
  return expiresAt > /* @__PURE__ */ new Date();
1012
1108
  }
1109
+ function getWalletSessionByChain(session, chainType) {
1110
+ const chainSession = session.sessionsByChain?.[chainType];
1111
+ if (!chainSession) {
1112
+ if (!isSessionValid(session)) return null;
1113
+ if (chainType === "evm" && session.evmAddress) return session;
1114
+ if (chainType === "solana" && session.solanaAddress) return session;
1115
+ return null;
1116
+ }
1117
+ if (!isChainSessionValid(chainSession)) {
1118
+ return null;
1119
+ }
1120
+ return withCompatibilityAliases({
1121
+ ...session,
1122
+ sessionId: chainSession.sessionId,
1123
+ status: chainSession.status,
1124
+ expiresAt: chainSession.expiresAt,
1125
+ walletId: chainSession.walletId,
1126
+ evmWalletId: chainType === "evm" ? chainSession.walletId : session.evmWalletId,
1127
+ evmAddress: chainType === "evm" ? chainSession.walletAddress : session.evmAddress,
1128
+ solanaWalletId: chainType === "solana" ? chainSession.walletId : session.solanaWalletId,
1129
+ solanaAddress: chainType === "solana" ? chainSession.walletAddress : session.solanaAddress,
1130
+ privyKeyQuorumId: chainSession.providerKeyQuorumId,
1131
+ privySignerId: chainSession.providerSignerId,
1132
+ chainType,
1133
+ capabilities: chainSession.capabilities ?? session.capabilities,
1134
+ sessionsByChain: {
1135
+ [chainType]: chainSession
1136
+ }
1137
+ });
1138
+ }
1013
1139
 
1014
1140
  // src/lib/resolve.ts
1015
1141
  function getCommandOptions(program) {
@@ -1232,11 +1358,11 @@ function resolveWalletSession() {
1232
1358
  var resolveDelegatedSession = resolveWalletSession;
1233
1359
 
1234
1360
  export {
1235
- AdminClient,
1236
1361
  getRPCNetworks,
1237
1362
  getRPCNetworkIds,
1238
1363
  isSolanaNetwork,
1239
1364
  nativeTokenSymbol,
1365
+ AdminClient,
1240
1366
  createPendingSession,
1241
1367
  loadSession,
1242
1368
  loadStoredSession,
@@ -1244,6 +1370,7 @@ export {
1244
1370
  updateSession,
1245
1371
  clearSession,
1246
1372
  isSessionValid,
1373
+ getWalletSessionByChain,
1247
1374
  resolveAPIKey,
1248
1375
  resolveAccessKey,
1249
1376
  resolveNetwork,