@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.
package/dist/index.js CHANGED
@@ -2,24 +2,27 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  registerAuth
5
- } from "./chunk-LQXLZLCY.js";
5
+ } from "./chunk-2OUAYCVA.js";
6
6
  import {
7
7
  openBrowser
8
- } from "./chunk-INVT5BV6.js";
8
+ } from "./chunk-WCZIVY4O.js";
9
9
  import {
10
+ SETUP_CAPABILITY_LABELS,
11
+ SETUP_CAPABILITY_ORDER,
10
12
  getSetupStatus,
11
13
  isSetupComplete,
12
14
  shouldRunOnboarding
13
- } from "./chunk-EZ2ZD7YO.js";
15
+ } from "./chunk-UYZH6GSY.js";
14
16
  import {
15
17
  isInteractiveAllowed
16
- } from "./chunk-RE5HSYJJ.js";
18
+ } from "./chunk-AUGBYMHT.js";
17
19
  import {
18
20
  adminClientFromFlags,
19
21
  clearSession,
20
22
  clientFromFlags,
21
23
  createPendingSession,
22
24
  getRPCNetworks,
25
+ getWalletSessionByChain,
23
26
  isSessionValid,
24
27
  isSolanaNetwork,
25
28
  loadSession,
@@ -41,12 +44,12 @@ import {
41
44
  resolveX402Client,
42
45
  saveSession,
43
46
  updateSession
44
- } from "./chunk-5LCA2B6S.js";
47
+ } from "./chunk-5HYOZ773.js";
45
48
  import {
46
49
  getAvailableUpdate,
47
50
  getUpdateStatus,
48
51
  printUpdateNotice
49
- } from "./chunk-MB6REYQL.js";
52
+ } from "./chunk-6UHKZ5EN.js";
50
53
  import {
51
54
  bold,
52
55
  brand,
@@ -70,7 +73,7 @@ import {
70
73
  weiToEth,
71
74
  withSpinner,
72
75
  yellow
73
- } from "./chunk-DGKUBK7G.js";
76
+ } from "./chunk-HR2UZ6ZU.js";
74
77
  import {
75
78
  KEY_MAP,
76
79
  configDir,
@@ -81,7 +84,7 @@ import {
81
84
  save,
82
85
  toMap,
83
86
  validKeys
84
- } from "./chunk-BAZ4NGOD.js";
87
+ } from "./chunk-PX2YJ7XC.js";
85
88
  import {
86
89
  CLIError,
87
90
  EXIT_CODES,
@@ -117,7 +120,7 @@ import {
117
120
  setFlags,
118
121
  setNoColor,
119
122
  verbose
120
- } from "./chunk-KLPWJFWP.js";
123
+ } from "./chunk-MYHXAACL.js";
121
124
 
122
125
  // src/index.ts
123
126
  import { Command, Help } from "commander";
@@ -624,7 +627,7 @@ function registerConfig(program2) {
624
627
  printJSON(toMap(cfg));
625
628
  return;
626
629
  }
627
- const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-ZCR3YCHJ.js");
630
+ const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-PAQKIAX3.js");
628
631
  const validToken = resolveAuthToken2(cfg);
629
632
  const authStatus = cfg.auth_token ? validToken ? `${green("\u2713")} authenticated${cfg.auth_token_expires_at ? ` ${dim(`(expires ${cfg.auth_token_expires_at})`)}` : ""}` : `${yellow("\u25C6")} expired${cfg.auth_token_expires_at ? ` ${dim(`(${cfg.auth_token_expires_at})`)}` : ""}` : dim("(not set) \u2014 run 'alchemy auth' to log in");
630
633
  const pairs = [
@@ -738,6 +741,15 @@ function registerConfig(program2) {
738
741
  console.log(` ${dim(`- ${command}`)}`);
739
742
  }
740
743
  }
744
+ console.log("");
745
+ console.log(` ${dim("Capabilities:")}`);
746
+ printKeyValue(
747
+ SETUP_CAPABILITY_ORDER.map((capability) => {
748
+ const capabilityStatus = status.capabilities[capability];
749
+ const value = capabilityStatus.complete ? `ready (${capabilityStatus.satisfiedBy})` : `missing ${capabilityStatus.missing.join(", ")}`;
750
+ return [SETUP_CAPABILITY_LABELS[capability], value];
751
+ })
752
+ );
741
753
  });
742
754
  }
743
755
 
@@ -1294,7 +1306,19 @@ var walletSessionCreateResponseSchema = z.object({
1294
1306
  approvalUrl: z.string().url(),
1295
1307
  expiresAt: z.string().datetime().optional()
1296
1308
  }).passthrough();
1309
+ var walletSessionRequestSessionCreateResponseSchema = z.object({
1310
+ walletSessionId: z.string().min(1),
1311
+ chainType: z.string().min(1),
1312
+ status: rawWalletSessionStateSchema.transform(normalizeWalletSessionState)
1313
+ }).passthrough();
1314
+ var walletSessionRequestCreateResponseSchema = z.object({
1315
+ sessionId: z.string().min(1),
1316
+ approvalUrl: z.string().url(),
1317
+ expiresAt: z.string().datetime().optional(),
1318
+ sessions: z.array(walletSessionRequestSessionCreateResponseSchema).min(1)
1319
+ }).passthrough();
1297
1320
  var rawWalletSessionStatusResponseSchema = z.object({
1321
+ type: z.literal("session").optional(),
1298
1322
  sessionId: z.string().min(1).optional(),
1299
1323
  status: rawWalletSessionStateSchema,
1300
1324
  expiresAt: z.string().datetime().optional(),
@@ -1310,6 +1334,15 @@ var rawWalletSessionStatusResponseSchema = z.object({
1310
1334
  chainType: z.string().min(1).optional(),
1311
1335
  capabilities: z.record(z.string(), z.boolean()).optional()
1312
1336
  }).passthrough();
1337
+ var walletSessionRequestStatusResponseSchema = z.object({
1338
+ type: z.literal("sessionRequest"),
1339
+ sessionId: z.string().min(1),
1340
+ sessions: z.array(
1341
+ rawWalletSessionStatusResponseSchema.extend({
1342
+ walletSessionId: z.string().min(1)
1343
+ })
1344
+ ).min(1)
1345
+ }).passthrough();
1313
1346
  var walletSessionDisconnectResponseSchema = z.object({
1314
1347
  sessionId: z.string().min(1).optional(),
1315
1348
  status: rawWalletSessionStateSchema.optional().transform(
@@ -1336,9 +1369,14 @@ var rawEvmSigningChallengeResponseSchema = z.object({
1336
1369
  challenge: z.string().min(1),
1337
1370
  expiresAt: z.string().datetime(),
1338
1371
  requestExpiry: z.string().min(1),
1339
- method: z.enum(["personal_sign", "eth_signTypedData_v4", "eth_sign7702Authorization"]),
1372
+ method: z.enum([
1373
+ "personal_sign",
1374
+ "eth_signTypedData_v4",
1375
+ "eth_sign7702Authorization",
1376
+ "signTransaction"
1377
+ ]),
1340
1378
  walletId: z.string().min(1),
1341
- walletAddress: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
1379
+ walletAddress: z.string().min(1),
1342
1380
  providerKeyQuorumId: z.string().min(1).optional(),
1343
1381
  providerSignerId: z.string().min(1).optional()
1344
1382
  }).strict();
@@ -1364,6 +1402,10 @@ var signAuthorizationCompleteResponseSchema = z.object({
1364
1402
  y_parity: z.number().int()
1365
1403
  }).strict()
1366
1404
  }).strict();
1405
+ var solanaSignTransactionCompleteResponseSchema = z.object({
1406
+ signedTransaction: z.string().min(1),
1407
+ encoding: z.literal("base64")
1408
+ }).strict();
1367
1409
  function baseURLOverride() {
1368
1410
  const options = { allowedHostnames: [STAGING_ADMIN_API_HOST] };
1369
1411
  return parseBaseURLOverride(WALLET_API_BASE_URL_ENV, options) ?? parseBaseURLOverride(ADMIN_API_BASE_URL_ENV, options);
@@ -1457,6 +1499,17 @@ function normalizeWalletSessionStatus(session) {
1457
1499
  privySignerId: session.providerSignerId
1458
1500
  };
1459
1501
  }
1502
+ function normalizeWalletSessionRequestStatus(request2) {
1503
+ return {
1504
+ sessionId: request2.sessionId,
1505
+ sessions: request2.sessions.map(({ walletSessionId, ...session }) => {
1506
+ return normalizeWalletSessionStatus({
1507
+ ...session,
1508
+ sessionId: walletSessionId
1509
+ });
1510
+ })
1511
+ };
1512
+ }
1460
1513
  function normalizeEvmSigningChallengeResponse(response) {
1461
1514
  return {
1462
1515
  ...response,
@@ -1472,7 +1525,15 @@ function toProviderSigningBindingInput(input) {
1472
1525
  ...privySignerId ? { providerSignerId: privySignerId } : {}
1473
1526
  };
1474
1527
  }
1475
- async function createRemoteWalletSession(token, input) {
1528
+ function toProviderSolanaSigningBindingInput(input) {
1529
+ const { privyKeyQuorumId, privySignerId, ...rest } = input;
1530
+ return {
1531
+ ...rest,
1532
+ ...privyKeyQuorumId ? { providerKeyQuorumId: privyKeyQuorumId } : {},
1533
+ ...privySignerId ? { providerSignerId: privySignerId } : {}
1534
+ };
1535
+ }
1536
+ async function createRemoteWalletSessionRequest(token, input) {
1476
1537
  let data;
1477
1538
  let requestInput = input;
1478
1539
  for (let attempt = 0; attempt < 3; attempt += 1) {
@@ -1483,16 +1544,19 @@ async function createRemoteWalletSession(token, input) {
1483
1544
  "/wallet/sessions",
1484
1545
  requestInput
1485
1546
  );
1486
- return unwrapAdminData(walletSessionCreateResponseSchema, data);
1547
+ return unwrapAdminData(walletSessionRequestCreateResponseSchema, data);
1487
1548
  } catch (err) {
1488
- const retryInput = getClientInstanceCompatibilityRetryInput(requestInput, err);
1549
+ const retryInput = getClientInstanceCompatibilityRetryInput(
1550
+ requestInput,
1551
+ err
1552
+ );
1489
1553
  if (retryInput === void 0) {
1490
1554
  throw err;
1491
1555
  }
1492
1556
  requestInput = retryInput;
1493
1557
  }
1494
1558
  }
1495
- throw new CLIError(ErrorCode.NETWORK_ERROR, "Unable to create wallet session.");
1559
+ throw new CLIError(ErrorCode.NETWORK_ERROR, "Unable to create wallet session request.");
1496
1560
  }
1497
1561
  function getClientInstanceCompatibilityRetryInput(input, err) {
1498
1562
  if (input.environment.clientInstanceName !== void 0 && isUnsupportedClientInstanceMetadataError(err, "clientInstanceName")) {
@@ -1527,6 +1591,16 @@ async function getRemoteWalletSession(token, sessionId) {
1527
1591
  unwrapAdminData(rawWalletSessionStatusResponseSchema, data)
1528
1592
  );
1529
1593
  }
1594
+ async function getRemoteWalletSessionRequest(token, sessionId) {
1595
+ const data = await request(
1596
+ token,
1597
+ "GET",
1598
+ `/wallet/sessions/${encodeURIComponent(sessionId)}`
1599
+ );
1600
+ return normalizeWalletSessionRequestStatus(
1601
+ unwrapAdminData(walletSessionRequestStatusResponseSchema, data)
1602
+ );
1603
+ }
1530
1604
  async function disconnectRemoteWalletSession(token, sessionId) {
1531
1605
  try {
1532
1606
  const data = await request(
@@ -1605,6 +1679,24 @@ var signAuthorizationChallengeInputSchema = evmSigningSessionBindingBaseSchema.e
1605
1679
  executor: z.literal("self").optional()
1606
1680
  }).strict()
1607
1681
  }).strict().superRefine(ensureSessionSignerBinding);
1682
+ var solanaSigningSessionBindingBaseSchema = z.object({
1683
+ sessionId: z.string().uuid(),
1684
+ walletId: z.string().min(1),
1685
+ walletAddress: z.string().min(1),
1686
+ privyKeyQuorumId: z.string().min(1).optional(),
1687
+ privySignerId: z.string().min(1).optional()
1688
+ });
1689
+ var solanaSignTransactionChallengeInputSchema = solanaSigningSessionBindingBaseSchema.extend({
1690
+ transaction: z.string().min(1),
1691
+ encoding: z.literal("base64")
1692
+ }).strict().superRefine((input, ctx) => {
1693
+ if (!input.privyKeyQuorumId && !input.privySignerId) {
1694
+ ctx.addIssue({
1695
+ code: z.ZodIssueCode.custom,
1696
+ message: "Provide privyKeyQuorumId or privySignerId."
1697
+ });
1698
+ }
1699
+ });
1608
1700
  async function createRemoteSignTypedDataChallenge(token, input) {
1609
1701
  const parsedInput = signTypedDataChallengeInputSchema.parse(input);
1610
1702
  return createEvmSigningChallenge(
@@ -1643,6 +1735,25 @@ async function completeRemoteSignAuthorizationChallenge(token, input) {
1643
1735
  adminApiResponseSchema(signAuthorizationCompleteResponseSchema).transform((resp) => resp.data)
1644
1736
  );
1645
1737
  }
1738
+ async function createRemoteSolanaSignTransactionChallenge(token, input) {
1739
+ const parsedInput = solanaSignTransactionChallengeInputSchema.parse(input);
1740
+ return createEvmSigningChallenge(
1741
+ token,
1742
+ "/wallet/solana/sign-transaction/challenge",
1743
+ toProviderSolanaSigningBindingInput(parsedInput),
1744
+ adminApiResponseSchema(rawEvmSigningChallengeResponseSchema).transform(
1745
+ (resp) => normalizeEvmSigningChallengeResponse(resp.data)
1746
+ )
1747
+ );
1748
+ }
1749
+ async function completeRemoteSolanaSignTransactionChallenge(token, input) {
1750
+ return completeEvmSigningChallenge(
1751
+ token,
1752
+ "/wallet/solana/sign-transaction/complete",
1753
+ input,
1754
+ adminApiResponseSchema(solanaSignTransactionCompleteResponseSchema).transform((resp) => resp.data)
1755
+ );
1756
+ }
1646
1757
 
1647
1758
  // src/commands/wallet.ts
1648
1759
  import QRCode from "qrcode";
@@ -1653,6 +1764,9 @@ var DEFAULT_WALLET_CAPABILITIES = {
1653
1764
  "evm.signTypedData": true,
1654
1765
  "evm.signAuthorization": true
1655
1766
  };
1767
+ var DEFAULT_SOLANA_SESSION_CAPABILITIES = {
1768
+ "solana.signTransaction": true
1769
+ };
1656
1770
  function createEvmWallet() {
1657
1771
  const privateKey = generatePrivateKey();
1658
1772
  const account = privateKeyToAccount(privateKey);
@@ -1776,7 +1890,13 @@ async function withApprovalInterruptHandler(args) {
1776
1890
  return await args.run(controller.signal);
1777
1891
  } catch (err) {
1778
1892
  if (isWalletConnectInterruptedError(err)) {
1779
- await disconnectRemoteWalletSession(args.authToken, args.sessionId).catch(() => void 0);
1893
+ await Promise.all(
1894
+ args.sessionIds.map(
1895
+ (sessionId) => disconnectRemoteWalletSession(args.authToken, sessionId).catch(
1896
+ () => void 0
1897
+ )
1898
+ )
1899
+ );
1780
1900
  clearSession();
1781
1901
  }
1782
1902
  throw err;
@@ -1787,23 +1907,9 @@ async function withApprovalInterruptHandler(args) {
1787
1907
  }
1788
1908
  }
1789
1909
  }
1790
- function isFutureIsoDate(value) {
1791
- const date = new Date(value);
1792
- if (Number.isNaN(date.getTime())) {
1793
- return false;
1794
- }
1795
- return date > /* @__PURE__ */ new Date();
1796
- }
1797
- function isActiveWalletSession(session, remoteStatus) {
1798
- if (!session) return false;
1799
- if (session.status !== "approved") return false;
1800
- if (!isFutureIsoDate(session.expiresAt)) return false;
1801
- if (remoteStatus && remoteStatus !== "approved") return false;
1802
- return true;
1803
- }
1804
- function deriveWalletStatus(session, remoteStatus) {
1910
+ function deriveWalletStatus(session) {
1805
1911
  if (!session) return "none";
1806
- if (isActiveWalletSession(session, remoteStatus)) return "active";
1912
+ if (isSessionValid(session)) return "active";
1807
1913
  if (session.status === "pending") return "none";
1808
1914
  return "expired";
1809
1915
  }
@@ -1913,7 +2019,7 @@ function resolveQrAddress(args) {
1913
2019
  function missingQrWalletError(type, source) {
1914
2020
  if (source === "session") {
1915
2021
  return type === "solana" ? errInvalidArgs(
1916
- "The session wallet does not yet support Solana QR selection. Use `alchemy wallet use local` or configure a local Solana wallet with `alchemy wallet connect --mode local --chain solana`."
2022
+ "No Solana session wallet is configured. Run `alchemy wallet connect --mode session --force`."
1917
2023
  ) : errNoActiveSession();
1918
2024
  }
1919
2025
  if (source === "local") {
@@ -1924,20 +2030,6 @@ function missingQrWalletError(type, source) {
1924
2030
  function indentBlock(text, prefix = " ") {
1925
2031
  return text.split("\n").map((line) => line ? `${prefix}${line}` : line).join("\n");
1926
2032
  }
1927
- function generateAndPersistWallet() {
1928
- const wallet = createEvmWallet();
1929
- const keyPath = persistWalletKey("wallet-key", wallet.privateKey, wallet.address);
1930
- const cfg = load();
1931
- save({ ...cfg, wallet_key_file: keyPath, wallet_address: wallet.address });
1932
- return { address: wallet.address, keyFile: keyPath };
1933
- }
1934
- async function generateAndPersistSolanaWallet() {
1935
- const wallet = await createSolanaWallet();
1936
- const keyPath = persistWalletKey("solana-wallet-key", wallet.secretKey, wallet.address);
1937
- const cfg = load();
1938
- save({ ...cfg, solana_wallet_key_file: keyPath, solana_wallet_address: wallet.address });
1939
- return { address: wallet.address, keyFile: keyPath };
1940
- }
1941
2033
  async function createAndPersistWallets() {
1942
2034
  const evm = createEvmWallet();
1943
2035
  const solana = await createSolanaWallet();
@@ -2002,14 +2094,12 @@ function getEffectiveLocalWalletState(program2, cfg) {
2002
2094
  }
2003
2095
  async function runLocalCreate(opts) {
2004
2096
  const cfg = load();
2005
- const wantsEvm = opts.chain === "evm" || opts.chain === "both";
2006
- const wantsSolana = opts.chain === "solana" || opts.chain === "both";
2007
- const evmConflict = wantsEvm && Boolean(cfg.wallet_key_file);
2008
- const solanaConflict = wantsSolana && Boolean(cfg.solana_wallet_key_file);
2097
+ const evmConflict = Boolean(cfg.wallet_key_file);
2098
+ const solanaConflict = Boolean(cfg.solana_wallet_key_file);
2009
2099
  if ((evmConflict || solanaConflict) && !opts.force) {
2010
2100
  if (!isInteractiveAllowed(opts.program)) {
2011
2101
  throw errInvalidArgs(
2012
- "A local key is already configured for the selected chain. Re-run with --force to replace it."
2102
+ "A local key is already configured. Re-run with --force to replace it."
2013
2103
  );
2014
2104
  }
2015
2105
  const kinds = [evmConflict ? "EVM" : null, solanaConflict ? "Solana" : null].filter(Boolean).join(" and ");
@@ -2022,16 +2112,7 @@ async function runLocalCreate(opts) {
2022
2112
  throw errInvalidArgs("Aborted. Existing local key preserved.");
2023
2113
  }
2024
2114
  }
2025
- if (opts.chain === "both") {
2026
- const { evm, solana: solana2 } = await createAndPersistWallets();
2027
- return { evm, solana: solana2 };
2028
- }
2029
- if (opts.chain === "evm") {
2030
- const evm = generateAndPersistWallet();
2031
- return { evm };
2032
- }
2033
- const solana = await generateAndPersistSolanaWallet();
2034
- return { solana };
2115
+ return await createAndPersistWallets();
2035
2116
  }
2036
2117
  function runLocalImport(opts) {
2037
2118
  const cfg = load();
@@ -2064,7 +2145,27 @@ async function runLocalImportInteractive(opts) {
2064
2145
  return runLocalImport({ ...opts, force: true });
2065
2146
  }
2066
2147
  }
2148
+ function capabilitiesForChain(chain) {
2149
+ return chain === "evm" ? { ...DEFAULT_WALLET_CAPABILITIES } : { ...DEFAULT_SOLANA_SESSION_CAPABILITIES };
2150
+ }
2151
+ function capabilitiesForChains(chains) {
2152
+ return Object.fromEntries(
2153
+ chains.map((chain) => [chain, capabilitiesForChain(chain)])
2154
+ );
2155
+ }
2156
+ function getApprovedSessionWalletMetadata(chain, session) {
2157
+ const walletId = chain === "evm" ? session.walletId ?? session.evmWalletId : session.solanaWalletId ?? session.walletId;
2158
+ const walletAddress = chain === "evm" ? session.evmAddress ?? session.address : session.solanaAddress ?? session.address;
2159
+ if (!walletId || !walletAddress) {
2160
+ throw new CLIError(
2161
+ ErrorCode.INTERNAL_ERROR,
2162
+ "Approved wallet session response missing required metadata."
2163
+ );
2164
+ }
2165
+ return { walletId, walletAddress };
2166
+ }
2067
2167
  async function runSessionConnect(opts) {
2168
+ const requestedChains = ["evm", "solana"];
2068
2169
  const authToken = resolveAuthToken();
2069
2170
  if (!authToken) throw errAuthRequired();
2070
2171
  const existing = loadSession();
@@ -2102,32 +2203,51 @@ async function runSessionConnect(opts) {
2102
2203
  ...clientInstance.name === void 0 ? {} : { clientInstanceName: clientInstance.name }
2103
2204
  };
2104
2205
  updateSession({
2105
- chainType: "evm",
2106
- capabilities: { ...DEFAULT_WALLET_CAPABILITIES },
2206
+ chainType: "both",
2207
+ capabilities: Object.assign(
2208
+ {},
2209
+ ...requestedChains.map((chain) => capabilitiesForChain(chain))
2210
+ ),
2107
2211
  backendBaseUrl: getWalletApiBaseUrl(),
2108
2212
  environment: sessionEnvironment
2109
2213
  });
2110
- const remoteSession = await createRemoteWalletSession(authToken, {
2214
+ const remoteRequest = await createRemoteWalletSessionRequest(authToken, {
2111
2215
  publicKeyJwk: session.publicKeyJwk,
2112
2216
  requestSignerVersion: session.envelopeVersion,
2113
- chainType: "evm",
2114
- capabilities: { ...DEFAULT_WALLET_CAPABILITIES },
2217
+ chains: requestedChains,
2218
+ capabilities: capabilitiesForChains(requestedChains),
2115
2219
  environment: sessionEnvironment
2116
2220
  }).catch((err) => {
2117
2221
  clearSession();
2118
2222
  throw err;
2119
2223
  });
2224
+ const pendingSessionsByChain = Object.fromEntries(
2225
+ remoteRequest.sessions.map((remoteSession) => [
2226
+ remoteSession.chainType,
2227
+ {
2228
+ sessionId: remoteSession.walletSessionId,
2229
+ status: "pending",
2230
+ expiresAt: remoteRequest.expiresAt ?? session.expiresAt,
2231
+ chainType: remoteSession.chainType,
2232
+ capabilities: capabilitiesForChain(
2233
+ remoteSession.chainType
2234
+ )
2235
+ }
2236
+ ])
2237
+ );
2120
2238
  updateSession({
2121
- sessionId: remoteSession.sessionId,
2122
- expiresAt: remoteSession.expiresAt ?? session.expiresAt
2239
+ connectionRequestId: remoteRequest.sessionId,
2240
+ sessionId: remoteRequest.sessions[0].walletSessionId,
2241
+ expiresAt: remoteRequest.expiresAt ?? session.expiresAt,
2242
+ sessionsByChain: pendingSessionsByChain
2123
2243
  });
2124
2244
  if (!isJSONMode()) {
2125
2245
  const summaryPairs = [
2126
- ["Session ID", remoteSession.sessionId],
2246
+ ["Connection Request ID", remoteRequest.sessionId],
2127
2247
  ["Status", "pending"],
2128
- ["Approval URL", remoteSession.approvalUrl],
2248
+ ["Approval URL", remoteRequest.approvalUrl],
2129
2249
  ["Created", session.createdAt],
2130
- ["Expires", remoteSession.expiresAt ?? session.expiresAt]
2250
+ ["Expires", remoteRequest.expiresAt ?? session.expiresAt]
2131
2251
  ];
2132
2252
  if (clientInstance.name !== void 0) {
2133
2253
  summaryPairs.splice(1, 0, ["Instance", clientInstance.name]);
@@ -2142,7 +2262,14 @@ async function runSessionConnect(opts) {
2142
2262
  });
2143
2263
  if (answer === null) {
2144
2264
  clearSession();
2145
- await disconnectRemoteWalletSession(authToken, remoteSession.sessionId).catch(() => void 0);
2265
+ await Promise.all(
2266
+ remoteRequest.sessions.map(
2267
+ (remoteSession) => disconnectRemoteWalletSession(
2268
+ authToken,
2269
+ remoteSession.walletSessionId
2270
+ ).catch(() => void 0)
2271
+ )
2272
+ );
2146
2273
  throw new WalletConnectInterruptedError();
2147
2274
  }
2148
2275
  }
@@ -2150,25 +2277,41 @@ async function runSessionConnect(opts) {
2150
2277
  console.log(` Opening browser for approval...`);
2151
2278
  console.log(` ${dim("Waiting for dashboard approval to complete connection.")}`);
2152
2279
  }
2153
- openBrowser(remoteSession.approvalUrl);
2154
- const approvedSession = await withApprovalInterruptHandler({
2280
+ openBrowser(remoteRequest.approvalUrl);
2281
+ const approvedSessions = await withApprovalInterruptHandler({
2155
2282
  authToken,
2156
- sessionId: remoteSession.sessionId,
2283
+ sessionIds: remoteRequest.sessions.map(
2284
+ (remoteSession) => remoteSession.walletSessionId
2285
+ ),
2157
2286
  run: async (signal) => {
2158
2287
  const startedAt = Date.now();
2159
2288
  while (Date.now() - startedAt < CONNECT_TIMEOUT_MS) {
2160
- const current = await waitForInterruptible(
2161
- getRemoteWalletSession(authToken, remoteSession.sessionId),
2289
+ const currentSessions = await waitForInterruptible(
2290
+ remoteRequest.sessionId ? getRemoteWalletSessionRequest(
2291
+ authToken,
2292
+ remoteRequest.sessionId
2293
+ ).then((request2) => request2.sessions) : Promise.all(
2294
+ remoteRequest.sessions.map(
2295
+ (remoteSession) => getRemoteWalletSession(authToken, remoteSession.walletSessionId)
2296
+ )
2297
+ ),
2162
2298
  signal
2163
2299
  );
2164
- if (current.status === "approved") {
2165
- return current;
2300
+ if (requestedChains.every(
2301
+ (chain) => currentSessions.some((current) => {
2302
+ return current.chainType === chain && current.status === "approved";
2303
+ })
2304
+ )) {
2305
+ return currentSessions;
2166
2306
  }
2167
- if (current.status === "denied" || current.status === "revoked" || current.status === "expired") {
2307
+ const terminalSession = currentSessions.find((current) => {
2308
+ return current.status === "denied" || current.status === "revoked" || current.status === "expired";
2309
+ });
2310
+ if (terminalSession) {
2168
2311
  clearSession();
2169
2312
  throw new CLIError(
2170
2313
  ErrorCode.INTERNAL_ERROR,
2171
- `Wallet session ${current.status}.`,
2314
+ `Wallet session ${terminalSession.status}.`,
2172
2315
  "Run 'alchemy wallet connect' to start a new approval flow."
2173
2316
  );
2174
2317
  }
@@ -2177,58 +2320,123 @@ async function runSessionConnect(opts) {
2177
2320
  return null;
2178
2321
  }
2179
2322
  });
2180
- if (!approvedSession) {
2323
+ if (!approvedSessions) {
2181
2324
  throw new CLIError(
2182
2325
  ErrorCode.NETWORK_ERROR,
2183
2326
  "Timed out waiting for wallet approval.",
2184
2327
  "Finish approval in the dashboard and rerun 'alchemy wallet connect --force' if needed."
2185
2328
  );
2186
2329
  }
2187
- const walletId = approvedSession.walletId ?? approvedSession.evmWalletId;
2188
- const evmAddress = approvedSession.evmAddress ?? approvedSession.address;
2189
- if (!approvedSession.privyAppId || !walletId || !evmAddress) {
2330
+ const missingProviderAppId = approvedSessions.find((approvedSession) => {
2331
+ return !approvedSession.privyAppId;
2332
+ });
2333
+ if (missingProviderAppId) {
2190
2334
  throw new CLIError(
2191
2335
  ErrorCode.INTERNAL_ERROR,
2192
2336
  "Approved wallet session response missing required metadata."
2193
2337
  );
2194
2338
  }
2339
+ const approvedSessionsByChain = Object.fromEntries(
2340
+ requestedChains.map((chain) => {
2341
+ const approvedSession = approvedSessions.find((current) => {
2342
+ return current.chainType === chain;
2343
+ });
2344
+ if (!approvedSession) {
2345
+ throw new CLIError(
2346
+ ErrorCode.INTERNAL_ERROR,
2347
+ "Approved wallet session response missing requested chain."
2348
+ );
2349
+ }
2350
+ const { walletId, walletAddress } = getApprovedSessionWalletMetadata(
2351
+ chain,
2352
+ approvedSession
2353
+ );
2354
+ return [
2355
+ chain,
2356
+ {
2357
+ sessionId: approvedSession.sessionId ?? remoteRequest.sessions.find((remoteSession) => remoteSession.chainType === chain)?.walletSessionId ?? session.sessionId,
2358
+ status: "approved",
2359
+ expiresAt: approvedSession.expiresAt ?? remoteRequest.expiresAt ?? session.expiresAt,
2360
+ chainType: chain,
2361
+ walletId,
2362
+ walletAddress,
2363
+ providerKeyQuorumId: approvedSession.privyKeyQuorumId,
2364
+ providerSignerId: approvedSession.privySignerId,
2365
+ capabilities: approvedSession.capabilities ?? capabilitiesForChain(chain)
2366
+ }
2367
+ ];
2368
+ })
2369
+ );
2370
+ const firstApprovedSession = approvedSessions.find((approvedSession) => {
2371
+ return approvedSession.chainType === requestedChains[0];
2372
+ }) ?? approvedSessions[0];
2373
+ const evmSession = approvedSessionsByChain.evm;
2374
+ const solanaSession = approvedSessionsByChain.solana;
2195
2375
  updateSession({
2196
- sessionId: approvedSession.sessionId ?? remoteSession.sessionId,
2376
+ connectionRequestId: remoteRequest.sessionId,
2377
+ sessionId: firstApprovedSession.sessionId ?? remoteRequest.sessions[0].walletSessionId,
2197
2378
  status: "approved",
2198
- expiresAt: approvedSession.expiresAt ?? remoteSession.expiresAt ?? session.expiresAt,
2199
- privyAppId: approvedSession.privyAppId,
2200
- walletId,
2201
- evmWalletId: approvedSession.evmWalletId ?? walletId,
2202
- evmAddress,
2203
- solanaWalletId: approvedSession.solanaWalletId,
2204
- solanaAddress: approvedSession.solanaAddress,
2205
- privyKeyQuorumId: approvedSession.privyKeyQuorumId,
2206
- privySignerId: approvedSession.privySignerId,
2207
- chainType: approvedSession.chainType ?? "evm",
2208
- capabilities: approvedSession.capabilities ?? { ...DEFAULT_WALLET_CAPABILITIES },
2379
+ expiresAt: firstApprovedSession.expiresAt ?? remoteRequest.expiresAt ?? session.expiresAt,
2380
+ privyAppId: firstApprovedSession.privyAppId,
2381
+ walletId: evmSession?.walletId ?? solanaSession?.walletId,
2382
+ evmWalletId: evmSession?.walletId,
2383
+ evmAddress: evmSession?.walletAddress,
2384
+ solanaWalletId: solanaSession?.walletId,
2385
+ solanaAddress: solanaSession?.walletAddress,
2386
+ privyKeyQuorumId: evmSession?.providerKeyQuorumId ?? solanaSession?.providerKeyQuorumId,
2387
+ privySignerId: evmSession?.providerSignerId ?? solanaSession?.providerSignerId,
2388
+ chainType: "both",
2389
+ capabilities: Object.assign(
2390
+ {},
2391
+ ...requestedChains.map((chain) => capabilitiesForChain(chain)),
2392
+ ...approvedSessions.map((approvedSession) => approvedSession.capabilities ?? {})
2393
+ ),
2394
+ sessionsByChain: approvedSessionsByChain,
2209
2395
  backendBaseUrl: getWalletApiBaseUrl(),
2210
2396
  environment: sessionEnvironment
2211
2397
  });
2212
2398
  if (isJSONMode()) {
2213
2399
  printJSON({
2214
- sessionId: approvedSession.sessionId ?? remoteSession.sessionId,
2400
+ connectionRequestId: remoteRequest.sessionId,
2401
+ sessionId: firstApprovedSession.sessionId ?? remoteRequest.sessions[0].walletSessionId,
2402
+ walletSessionId: firstApprovedSession.sessionId ?? remoteRequest.sessions[0].walletSessionId,
2215
2403
  status: "approved",
2216
- privyAppId: approvedSession.privyAppId,
2217
- walletId,
2218
- evmAddress,
2219
- evmWalletId: approvedSession.evmWalletId ?? walletId,
2220
- chainType: approvedSession.chainType ?? "evm",
2221
- capabilities: approvedSession.capabilities ?? { ...DEFAULT_WALLET_CAPABILITIES },
2222
- expiresAt: approvedSession.expiresAt ?? remoteSession.expiresAt ?? session.expiresAt
2404
+ privyAppId: firstApprovedSession.privyAppId,
2405
+ walletId: evmSession?.walletId ?? solanaSession?.walletId,
2406
+ ...evmSession ? {
2407
+ evmAddress: evmSession.walletAddress,
2408
+ evmWalletId: evmSession.walletId
2409
+ } : {},
2410
+ ...solanaSession ? {
2411
+ solanaAddress: solanaSession.walletAddress,
2412
+ solanaWalletId: solanaSession.walletId
2413
+ } : {},
2414
+ chainType: "both",
2415
+ capabilities: Object.assign(
2416
+ {},
2417
+ ...requestedChains.map((chain) => capabilitiesForChain(chain))
2418
+ ),
2419
+ expiresAt: firstApprovedSession.expiresAt ?? remoteRequest.expiresAt ?? session.expiresAt,
2420
+ sessionsByChain: approvedSessionsByChain
2223
2421
  });
2224
2422
  } else {
2225
- printKeyValue([
2226
- ["Session ID", approvedSession.sessionId ?? remoteSession.sessionId],
2227
- ["Status", green("approved")],
2228
- ["Wallet ID", walletId],
2229
- ["EVM Address", green(evmAddress)],
2230
- ["Expires", approvedSession.expiresAt ?? remoteSession.expiresAt ?? session.expiresAt]
2423
+ const pairs = [
2424
+ ["Connection Request ID", remoteRequest.sessionId],
2425
+ ["Status", green("approved")]
2426
+ ];
2427
+ for (const chain of requestedChains) {
2428
+ const chainSession = approvedSessionsByChain[chain];
2429
+ if (!chainSession) continue;
2430
+ pairs.push(
2431
+ [`${chain.toUpperCase()} Wallet ID`, chainSession.walletId ?? ""],
2432
+ [`${chain.toUpperCase()} Address`, green(chainSession.walletAddress ?? "")]
2433
+ );
2434
+ }
2435
+ pairs.push([
2436
+ "Expires",
2437
+ firstApprovedSession.expiresAt ?? remoteRequest.expiresAt ?? session.expiresAt
2231
2438
  ]);
2439
+ printKeyValue(pairs);
2232
2440
  console.log(` ${green("\u2713")} Wallet connected`);
2233
2441
  }
2234
2442
  }
@@ -2297,17 +2505,6 @@ async function runConnectFlow(program2, opts) {
2297
2505
  }
2298
2506
  mode2 = "local";
2299
2507
  }
2300
- let chain = "both";
2301
- if (opts.chain !== void 0) {
2302
- if (opts.chain !== "evm" && opts.chain !== "solana" && opts.chain !== "both") {
2303
- throw errInvalidArgs("`--chain` must be 'evm', 'solana', or 'both'.");
2304
- }
2305
- chain = opts.chain;
2306
- }
2307
- if (importPath && chain !== "evm" && opts.chain !== void 0) {
2308
- throw errInvalidArgs("`--import` implies `--chain evm`; omit `--chain` or set it to 'evm'.");
2309
- }
2310
- if (importPath) chain = "evm";
2311
2508
  if (!mode2) {
2312
2509
  if (!isInteractiveAllowed(program2)) {
2313
2510
  throw errInvalidArgs(
@@ -2317,7 +2514,7 @@ async function runConnectFlow(program2, opts) {
2317
2514
  const choice = await promptSelect({
2318
2515
  message: "Choose a wallet to connect",
2319
2516
  options: [
2320
- { value: "session", label: "Session wallet", hint: "Recommended \u2014 Alchemy/Privy-managed, more secure" },
2517
+ { value: "session", label: "Session wallet", hint: "Recommended \u2014 Alchemy-managed, more secure" },
2321
2518
  { value: "local", label: "Local wallet", hint: "Private key stored on this machine" }
2322
2519
  ],
2323
2520
  initialValue: "session",
@@ -2327,7 +2524,11 @@ async function runConnectFlow(program2, opts) {
2327
2524
  mode2 = choice;
2328
2525
  }
2329
2526
  if (mode2 === "session") {
2330
- await runSessionConnect({ program: program2, force, instanceName: opts.instanceName });
2527
+ await runSessionConnect({
2528
+ program: program2,
2529
+ force,
2530
+ instanceName: opts.instanceName
2531
+ });
2331
2532
  printCrossModeHintAfterSessionConnect();
2332
2533
  return;
2333
2534
  }
@@ -2337,9 +2538,30 @@ async function runConnectFlow(program2, opts) {
2337
2538
  printCrossModeHintAfterLocal("evm");
2338
2539
  return;
2339
2540
  }
2340
- const result = await runLocalCreate({ chain, force, program: program2 });
2541
+ const result = await runLocalCreate({ force, program: program2 });
2341
2542
  printPostLocalCreateSummary(result);
2342
- printCrossModeHintAfterLocal(chain);
2543
+ printCrossModeHintAfterLocal("both");
2544
+ }
2545
+ function uniqueSessionIds(session) {
2546
+ const ids = Object.values(session.sessionsByChain ?? {}).map((chainSession) => chainSession?.sessionId).filter((sessionId) => Boolean(sessionId));
2547
+ if (ids.length === 0) {
2548
+ ids.push(session.sessionId);
2549
+ }
2550
+ return Array.from(new Set(ids));
2551
+ }
2552
+ function selectRemoteSessionForStatus(session, remoteSessions) {
2553
+ return remoteSessions.find((remoteSession) => {
2554
+ return remoteSession.status !== "approved";
2555
+ }) ?? remoteSessions.find((remoteSession) => {
2556
+ return remoteSession.sessionId === session.sessionId;
2557
+ }) ?? remoteSessions[0];
2558
+ }
2559
+ function mergeRemoteCapabilities(session, remoteSessions) {
2560
+ return Object.assign(
2561
+ {},
2562
+ session.capabilities ?? {},
2563
+ ...remoteSessions.map((remoteSession) => remoteSession.capabilities ?? {})
2564
+ );
2343
2565
  }
2344
2566
  async function buildSessionSnapshot(program2, verify) {
2345
2567
  let session = loadStoredSession?.() ?? loadSession();
@@ -2347,25 +2569,55 @@ async function buildSessionSnapshot(program2, verify) {
2347
2569
  if (verify && session) {
2348
2570
  const authToken = resolveAuthToken();
2349
2571
  if (!authToken) throw errAuthRequired();
2350
- const remote = await getRemoteWalletSession(authToken, session.sessionId);
2572
+ const remoteSessions = session.connectionRequestId ? await getRemoteWalletSessionRequest(authToken, session.connectionRequestId).then((request2) => request2.sessions) : await Promise.all(
2573
+ uniqueSessionIds(session).map(
2574
+ (sessionId) => getRemoteWalletSession(authToken, sessionId)
2575
+ )
2576
+ );
2577
+ const remote = selectRemoteSessionForStatus(session, remoteSessions);
2351
2578
  remoteStatus = remote.status;
2352
- const walletId2 = remote.walletId ?? remote.evmWalletId;
2579
+ const nextSessionsByChain = { ...session.sessionsByChain ?? {} };
2580
+ for (const remoteSession of remoteSessions) {
2581
+ const chain = remoteSession.chainType === "solana" ? "solana" : "evm";
2582
+ const walletIdForChain = chain === "solana" ? remoteSession.solanaWalletId ?? remoteSession.walletId : remoteSession.evmWalletId ?? remoteSession.walletId;
2583
+ const walletAddressForChain = chain === "solana" ? remoteSession.solanaAddress ?? remoteSession.address : remoteSession.evmAddress ?? remoteSession.address;
2584
+ nextSessionsByChain[chain] = {
2585
+ ...nextSessionsByChain[chain] ?? {},
2586
+ sessionId: remoteSession.sessionId ?? nextSessionsByChain[chain]?.sessionId ?? session.sessionId,
2587
+ status: normalizeRemoteStatusForStorage(remoteSession.status),
2588
+ expiresAt: remoteSession.expiresAt ?? nextSessionsByChain[chain]?.expiresAt ?? session.expiresAt,
2589
+ chainType: chain,
2590
+ ...walletIdForChain ? { walletId: walletIdForChain } : {},
2591
+ ...walletAddressForChain ? { walletAddress: walletAddressForChain } : {},
2592
+ ...remoteSession.privyKeyQuorumId ? { providerKeyQuorumId: remoteSession.privyKeyQuorumId } : {},
2593
+ ...remoteSession.privySignerId ? { providerSignerId: remoteSession.privySignerId } : {},
2594
+ ...remoteSession.capabilities ? { capabilities: remoteSession.capabilities } : {}
2595
+ };
2596
+ }
2597
+ const remoteChain = remote.chainType === "solana" ? "solana" : "evm";
2598
+ const remoteWalletId = remoteChain === "solana" ? remote.solanaWalletId ?? remote.walletId : remote.evmWalletId ?? remote.walletId;
2599
+ const evmWalletId = remoteChain === "evm" ? remoteWalletId ?? session.evmWalletId : session.evmWalletId;
2600
+ const evmAddress = remoteChain === "evm" ? remote.evmAddress ?? remote.address ?? session.evmAddress : session.evmAddress;
2601
+ const solanaWalletId = remoteChain === "solana" ? remoteWalletId ?? session.solanaWalletId : session.solanaWalletId;
2602
+ const solanaAddress = remoteChain === "solana" ? remote.solanaAddress ?? remote.address ?? session.solanaAddress : session.solanaAddress;
2603
+ const chainType = nextSessionsByChain.evm && nextSessionsByChain.solana ? "both" : remote.chainType ?? session.chainType;
2353
2604
  session = {
2354
2605
  ...session,
2355
2606
  status: normalizeRemoteStatusForStorage(remote.status),
2356
2607
  expiresAt: remote.expiresAt ?? session.expiresAt,
2357
2608
  privyAppId: remote.privyAppId ?? session.privyAppId,
2358
- walletId: walletId2 ?? session.walletId,
2359
- evmWalletId: remote.evmWalletId ?? walletId2 ?? session.evmWalletId,
2360
- evmAddress: remote.evmAddress ?? remote.address ?? session.evmAddress,
2361
- solanaWalletId: remote.solanaWalletId ?? session.solanaWalletId,
2362
- solanaAddress: remote.solanaAddress ?? session.solanaAddress,
2363
- chainType: remote.chainType ?? session.chainType,
2364
- capabilities: remote.capabilities ?? session.capabilities
2609
+ walletId: evmWalletId ?? solanaWalletId ?? session.walletId,
2610
+ evmWalletId,
2611
+ evmAddress,
2612
+ solanaWalletId,
2613
+ solanaAddress,
2614
+ chainType,
2615
+ capabilities: mergeRemoteCapabilities(session, remoteSessions),
2616
+ sessionsByChain: nextSessionsByChain
2365
2617
  };
2366
2618
  saveSession(session);
2367
2619
  }
2368
- const status = deriveWalletStatus(session, remoteStatus ?? void 0);
2620
+ const status = deriveWalletStatus(session);
2369
2621
  const walletAddress = resolveSessionAddress(session);
2370
2622
  const environment = resolveSessionEnvironment(session);
2371
2623
  const signerCapabilities = resolveEnabledCapabilities(session);
@@ -2381,14 +2633,16 @@ async function buildSessionSnapshot(program2, verify) {
2381
2633
  walletId,
2382
2634
  expiresAt,
2383
2635
  sessionId: session?.sessionId ?? null,
2636
+ connectionRequestId: session?.connectionRequestId ?? null,
2384
2637
  sessionState: session?.status ?? null,
2385
2638
  chainType: session?.chainType ?? null,
2639
+ sessionsByChain: session?.sessionsByChain ?? null,
2386
2640
  valid: session ? isSessionValid(session) : false
2387
2641
  };
2388
2642
  }
2389
2643
  function registerWallets(program2) {
2390
2644
  const cmd = program2.command("wallet").description("Manage wallets");
2391
- cmd.command("connect").description("Connect a wallet for onchain actions (session or local)").option("--mode <mode>", "session | local").option("--chain <chain>", "For --mode local: evm | solana | both (default: both)").option("--import <path>", "For --mode local: import EVM key from file (implies --chain evm)").option("--instance-name <name>", "For --mode session: name this CLI instance").option("--force", "Replace the existing signer").action(async (opts) => {
2645
+ cmd.command("connect").description("Connect a wallet for onchain actions (session or local)").option("--mode <mode>", "session | local").option("--import <path>", "For --mode local: import an EVM key from file instead of creating both wallets").option("--instance-name <name>", "For --mode session: name this CLI instance").option("--force", "Replace the existing signer").action(async (opts) => {
2392
2646
  try {
2393
2647
  await runConnectFlow(program2, opts);
2394
2648
  } catch (err) {
@@ -2418,10 +2672,12 @@ function registerWallets(program2) {
2418
2672
  expiresAt: snap.expiresAt,
2419
2673
  environment: snap.environment,
2420
2674
  signerCapabilities: snap.signerCapabilities,
2675
+ connectionRequestId: snap.connectionRequestId,
2421
2676
  sessionId: snap.sessionId,
2422
2677
  sessionState: snap.sessionState,
2423
2678
  walletId: snap.walletId,
2424
2679
  chainType: snap.chainType,
2680
+ sessionsByChain: snap.sessionsByChain,
2425
2681
  verified: Boolean(opts.verify),
2426
2682
  remoteStatus: snap.remoteStatus,
2427
2683
  valid: snap.valid,
@@ -2432,10 +2688,12 @@ function registerWallets(program2) {
2432
2688
  expiresAt: snap.expiresAt,
2433
2689
  environment: snap.environment,
2434
2690
  signerCapabilities: snap.signerCapabilities,
2691
+ connectionRequestId: snap.connectionRequestId,
2435
2692
  sessionId: snap.sessionId,
2436
2693
  sessionState: snap.sessionState,
2437
2694
  walletId: snap.walletId,
2438
2695
  chainType: snap.chainType,
2696
+ sessionsByChain: snap.sessionsByChain,
2439
2697
  valid: snap.valid
2440
2698
  },
2441
2699
  localEvm: localState.evmAddress ? { address: localState.evmAddress, keyFile: localState.evmKeyFile } : null,
@@ -2453,7 +2711,11 @@ function registerWallets(program2) {
2453
2711
  ["Environment", snap.environment ?? dim("none")],
2454
2712
  ["Signer Capabilities", snap.signerCapabilities.length > 0 ? snap.signerCapabilities.join(", ") : dim("none")]
2455
2713
  ];
2456
- if (snap.sessionId) pairs.push(["Session ID", snap.sessionId]);
2714
+ if (snap.connectionRequestId) {
2715
+ pairs.push(["Connection Request ID", snap.connectionRequestId]);
2716
+ } else if (snap.sessionId) {
2717
+ pairs.push(["Session ID", snap.sessionId]);
2718
+ }
2457
2719
  if (snap.walletId) pairs.push(["Wallet ID", snap.walletId]);
2458
2720
  if (opts.verify) {
2459
2721
  pairs.push(["Backend Status", snap.remoteStatus ?? dim("not checked")]);
@@ -2608,7 +2870,7 @@ function registerWallets(program2) {
2608
2870
  }
2609
2871
  if (target === "local" && !hasLocalEvmKey()) {
2610
2872
  throw errInvalidArgs(
2611
- "No local EVM key configured. Run `alchemy wallet connect --mode local --chain evm` first."
2873
+ "No local EVM key configured. Run `alchemy wallet connect --mode local` first."
2612
2874
  );
2613
2875
  }
2614
2876
  const cfg = load();
@@ -2623,8 +2885,11 @@ function registerWallets(program2) {
2623
2885
  exitWithError(err);
2624
2886
  }
2625
2887
  });
2626
- cmd.command("disconnect").description("Revoke the current wallet session").action(async () => {
2888
+ cmd.command("disconnect").description("Revoke the current wallet session").option("--chain <chain>", "evm | solana").action(async (opts) => {
2627
2889
  try {
2890
+ if (opts.chain !== void 0 && opts.chain !== "evm" && opts.chain !== "solana") {
2891
+ throw errInvalidArgs("`--chain` must be 'evm' or 'solana'.");
2892
+ }
2628
2893
  const session = loadStoredSession?.() ?? loadSession();
2629
2894
  if (!session) {
2630
2895
  if (isJSONMode()) {
@@ -2642,15 +2907,40 @@ function registerWallets(program2) {
2642
2907
  let revoked = false;
2643
2908
  let alreadyDisconnected = false;
2644
2909
  let remoteStatus = null;
2910
+ const sessionsToDisconnect = opts.chain ? [
2911
+ getWalletSessionByChain(
2912
+ session,
2913
+ opts.chain
2914
+ )
2915
+ ].filter(
2916
+ (value) => value !== null
2917
+ ) : Object.values(session.sessionsByChain ?? {}).filter(
2918
+ (value) => value !== void 0
2919
+ ).map(
2920
+ (chainSession) => getWalletSessionByChain(session, chainSession.chainType)
2921
+ ).filter(
2922
+ (value) => value !== null
2923
+ );
2924
+ const disconnectTargets = sessionsToDisconnect.length > 0 ? sessionsToDisconnect : [session];
2645
2925
  if (authToken) {
2646
- const remote = await disconnectRemoteWalletSession(authToken, session.sessionId);
2647
- revoked = !remote.alreadyDisconnected;
2648
- alreadyDisconnected = remote.alreadyDisconnected;
2649
- remoteStatus = remote.status;
2650
- }
2651
- const removed = clearSession();
2926
+ const results = await Promise.all(
2927
+ disconnectTargets.map(
2928
+ (target) => disconnectRemoteWalletSession(authToken, target.sessionId)
2929
+ )
2930
+ );
2931
+ revoked = results.some((remote) => !remote.alreadyDisconnected);
2932
+ alreadyDisconnected = results.every((remote) => remote.alreadyDisconnected);
2933
+ remoteStatus = results[0]?.status ?? null;
2934
+ }
2935
+ const removed = opts.chain === void 0 ? clearSession() : updateSession({
2936
+ sessionsByChain: {
2937
+ ...session.sessionsByChain ?? {},
2938
+ [opts.chain]: void 0
2939
+ },
2940
+ ...opts.chain === "evm" ? { evmWalletId: void 0, evmAddress: void 0 } : { solanaWalletId: void 0, solanaAddress: void 0 }
2941
+ }) != null;
2652
2942
  const cfg = load();
2653
- if (cfg.active_signer === "session") {
2943
+ if (cfg.active_signer === "session" && opts.chain !== "solana") {
2654
2944
  const { active_signer: _omit, ...rest } = cfg;
2655
2945
  save(rest);
2656
2946
  }
@@ -2682,6 +2972,8 @@ function registerWallets(program2) {
2682
2972
  }
2683
2973
 
2684
2974
  // src/lib/rest.ts
2975
+ var DATA_API_BASE_URL_ENV = "ALCHEMY_DATA_API_BASE_URL";
2976
+ var PRICES_API_BASE_URL_ENV = "ALCHEMY_PRICES_API_BASE_URL";
2685
2977
  function withQuery(url, query) {
2686
2978
  if (!query) return url;
2687
2979
  for (const [key, value] of Object.entries(query)) {
@@ -2715,13 +3007,15 @@ async function requestJSON(url, options) {
2715
3007
  }
2716
3008
  async function callApiData(apiKey, path, options = {}) {
2717
3009
  if (!apiKey) throw errAuthRequired();
2718
- const base2 = new URL(`https://api.g.${getBaseDomain()}/data/v1/${apiKey}/`);
3010
+ const override = parseBaseURLOverride(DATA_API_BASE_URL_ENV);
3011
+ const base2 = override ? new URL(`/data/v1/${apiKey}/`, override) : new URL(`https://api.g.${getBaseDomain()}/data/v1/${apiKey}/`);
2719
3012
  const url = withQuery(new URL(path.replace(/^\//, ""), base2), options.query);
2720
3013
  return requestJSON(url, { ...options, path });
2721
3014
  }
2722
3015
  async function callApiPrices(apiKey, path, options = {}) {
2723
3016
  if (!apiKey) throw errAuthRequired();
2724
- const base2 = new URL(`https://api.g.${getBaseDomain()}/prices/v1/${apiKey}/`);
3017
+ const override = parseBaseURLOverride(PRICES_API_BASE_URL_ENV);
3018
+ const base2 = override ? new URL(`/prices/v1/${apiKey}/`, override) : new URL(`https://api.g.${getBaseDomain()}/prices/v1/${apiKey}/`);
2725
3019
  const url = withQuery(new URL(path.replace(/^\//, ""), base2), options.query);
2726
3020
  return requestJSON(url, { ...options, path });
2727
3021
  }
@@ -3041,9 +3335,10 @@ import {
3041
3335
  createKeyPairSignerFromBytes as createKeyPairSignerFromBytes2,
3042
3336
  createKeyPairSignerFromPrivateKeyBytes as createKeyPairSignerFromPrivateKeyBytes2
3043
3337
  } from "@solana/kit";
3044
- import { getTransferSolInstruction } from "@solana-program/system";
3338
+ import { sign as signWithPrivateKey } from "crypto";
3045
3339
  var SOL_DECIMALS = 9;
3046
3340
  var SPONSOR_FEE_PAYER_PLACEHOLDER = address("Amh6quo1FcmL16Qmzdugzjq3Lv1zXzTW7ktswyLDzits");
3341
+ var SYSTEM_PROGRAM_ADDRESS = address("11111111111111111111111111111111");
3047
3342
  var SPL_TOKEN_PROGRAM_ADDRESS = address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
3048
3343
  function parseSolanaKeyBytes(secret) {
3049
3344
  const trimmed = secret.trim();
@@ -3074,11 +3369,28 @@ async function createSolanaSignerFromKeyBytes(keyBytes) {
3074
3369
  throw errInvalidArgs("Invalid Solana key: expected 64-byte secret key or 32-byte private key.");
3075
3370
  }
3076
3371
  function buildSolTransferInstruction(from, to, lamports) {
3077
- return getTransferSolInstruction({
3078
- source: from,
3079
- destination: to,
3080
- amount: lamports
3081
- });
3372
+ return {
3373
+ programAddress: SYSTEM_PROGRAM_ADDRESS,
3374
+ accounts: [
3375
+ { address: from.address, role: AccountRole.WRITABLE_SIGNER },
3376
+ { address: to, role: AccountRole.WRITABLE }
3377
+ ],
3378
+ data: new Uint8Array([
3379
+ ...encodeU32LE(2),
3380
+ ...encodeU64LE(lamports)
3381
+ ])
3382
+ };
3383
+ }
3384
+ function encodeU32LE(value) {
3385
+ if (!Number.isSafeInteger(value) || value < 0 || value > 4294967295) {
3386
+ throw errInvalidArgs("Instruction discriminator must fit in an unsigned 32-bit integer.");
3387
+ }
3388
+ const bytes = new Uint8Array(4);
3389
+ bytes[0] = value & 255;
3390
+ bytes[1] = value >> 8 & 255;
3391
+ bytes[2] = value >> 16 & 255;
3392
+ bytes[3] = value >> 24 & 255;
3393
+ return bytes;
3082
3394
  }
3083
3395
  function encodeU64LE(value) {
3084
3396
  if (value < 0n || value > 0xffffffffffffffffn) {
@@ -3159,6 +3471,38 @@ async function buildAndSendSolanaTransaction(opts) {
3159
3471
  const signature = await client.call("sendTransaction", [wireTransaction, { encoding: "base64" }]);
3160
3472
  return { signature, fromAddress: signer.address };
3161
3473
  }
3474
+ async function buildAndSendSolanaTransactionWithSession(opts) {
3475
+ const { client, instructions, session, authToken, sponsored, gasPolicyId } = opts;
3476
+ const fromAddress = getSolanaSessionAddress(session);
3477
+ const blockhashResult = await client.call("getLatestBlockhash", [{ commitment: "finalized" }]);
3478
+ const { blockhash, lastValidBlockHeight } = blockhashResult.value;
3479
+ const feePayer = sponsored ? SPONSOR_FEE_PAYER_PLACEHOLDER : address(fromAddress);
3480
+ const txMessage = pipe(
3481
+ createTransactionMessage({ version: 0 }),
3482
+ (msg) => setTransactionMessageFeePayer(feePayer, msg),
3483
+ (msg) => setTransactionMessageLifetimeUsingBlockhash(
3484
+ { blockhash, lastValidBlockHeight },
3485
+ msg
3486
+ ),
3487
+ (msg) => appendTransactionMessageInstructions(instructions, msg)
3488
+ );
3489
+ const compiledTx = compileTransaction(txMessage);
3490
+ const transactionToSign = sponsored ? await sponsorSolanaTransaction({
3491
+ client,
3492
+ compiledTransactionBase64: getBase64EncodedWireTransaction(compiledTx),
3493
+ gasPolicyId
3494
+ }) : getBase64EncodedWireTransaction(compiledTx);
3495
+ const signedTransaction = await signSolanaTransactionWithSession({
3496
+ authToken,
3497
+ session,
3498
+ transactionBase64: transactionToSign
3499
+ });
3500
+ const signature = await client.call("sendTransaction", [
3501
+ signedTransaction,
3502
+ { encoding: "base64" }
3503
+ ]);
3504
+ return { signature, fromAddress };
3505
+ }
3162
3506
  async function waitForSolanaConfirmation(client, signature, timeoutMs = 6e4, pollIntervalMs = 2e3) {
3163
3507
  const start = Date.now();
3164
3508
  while (Date.now() - start < timeoutMs) {
@@ -3175,6 +3519,76 @@ async function waitForSolanaConfirmation(client, signature, timeoutMs = 6e4, pol
3175
3519
  }
3176
3520
  return false;
3177
3521
  }
3522
+ async function sponsorSolanaTransaction(args) {
3523
+ if (!args.gasPolicyId) {
3524
+ throw errInvalidArgs("Fee sponsorship requires a fee policy ID.");
3525
+ }
3526
+ const feePayerResponse = await args.client.call("alchemy_requestFeePayer", [{
3527
+ policyId: args.gasPolicyId,
3528
+ serializedTransaction: args.compiledTransactionBase64
3529
+ }]);
3530
+ return feePayerResponse.serializedTransaction;
3531
+ }
3532
+ async function signSolanaTransactionWithSession(args) {
3533
+ const binding = getSolanaSessionBinding(args.session);
3534
+ if (args.session.capabilities?.["solana.signTransaction"] === false) {
3535
+ throw errInvalidArgs(
3536
+ "Delegated wallet session does not allow 'solana.signTransaction'. Reconnect the wallet session to refresh capabilities."
3537
+ );
3538
+ }
3539
+ const challenge = await createRemoteSolanaSignTransactionChallenge(args.authToken, {
3540
+ ...binding,
3541
+ transaction: args.transactionBase64,
3542
+ encoding: "base64"
3543
+ });
3544
+ const completion = await completeRemoteSolanaSignTransactionChallenge(args.authToken, {
3545
+ challengeId: challenge.challengeId,
3546
+ signature: signChallengePayload({
3547
+ challengePayload: challenge.challenge,
3548
+ privateKeyPem: args.session.privateKeyPem
3549
+ })
3550
+ });
3551
+ return completion.signedTransaction;
3552
+ }
3553
+ function getSolanaSessionBinding(session) {
3554
+ const walletId = session.solanaWalletId ?? session.walletId;
3555
+ const walletAddress = getSolanaSessionAddress(session);
3556
+ if (!walletId) {
3557
+ throw errInvalidArgs(
3558
+ "Delegated wallet session is missing Solana wallet ID metadata. Run 'alchemy wallet connect --mode session --force'."
3559
+ );
3560
+ }
3561
+ if (!session.privyKeyQuorumId && !session.privySignerId) {
3562
+ throw errInvalidArgs(
3563
+ "Delegated wallet session is missing signer binding metadata. Run 'alchemy wallet connect --mode session --force'."
3564
+ );
3565
+ }
3566
+ return {
3567
+ sessionId: session.sessionId,
3568
+ walletId,
3569
+ walletAddress,
3570
+ privyKeyQuorumId: session.privyKeyQuorumId,
3571
+ privySignerId: session.privySignerId
3572
+ };
3573
+ }
3574
+ function getSolanaSessionAddress(session) {
3575
+ if (!session.solanaAddress) {
3576
+ throw errInvalidArgs(
3577
+ "Delegated wallet session is missing Solana address metadata. Run 'alchemy wallet connect --mode session --force'."
3578
+ );
3579
+ }
3580
+ return session.solanaAddress;
3581
+ }
3582
+ function signChallengePayload(args) {
3583
+ return signWithPrivateKey(
3584
+ "sha256",
3585
+ Buffer.from(args.challengePayload, "utf8"),
3586
+ {
3587
+ key: args.privateKeyPem,
3588
+ dsaEncoding: "der"
3589
+ }
3590
+ ).toString("base64url");
3591
+ }
3178
3592
 
3179
3593
  // src/lib/solana-fees.ts
3180
3594
  function resolveSolanaFeeSponsorship(program2) {
@@ -3196,23 +3610,54 @@ function parseDecimals(value) {
3196
3610
  }
3197
3611
  return decimals;
3198
3612
  }
3199
- async function resolveLocalSolanaDelegateSigner(program2, signerOpt) {
3613
+ async function resolveSolanaDelegateSigner(program2, signerOpt) {
3200
3614
  const signer = parseSignerOpt(signerOpt);
3201
3615
  if (signer === "session") {
3202
- throw errInvalidArgs(
3203
- "The session wallet does not yet support Solana. Use `--signer local` or configure a local Solana wallet with `alchemy wallet connect --mode local --chain solana`."
3204
- );
3616
+ return resolveSessionSolanaDelegateSigner();
3617
+ }
3618
+ if (signer === void 0 && resolveActiveSigner(program2) === "session") {
3619
+ return resolveSessionSolanaDelegateSigner();
3205
3620
  }
3206
3621
  const solanaKey = resolveSolanaWalletKey(program2);
3207
3622
  if (!solanaKey) {
3623
+ if (signer === void 0) {
3624
+ const sessionSigner = tryResolveSessionSolanaDelegateSigner();
3625
+ if (sessionSigner) {
3626
+ return sessionSigner;
3627
+ }
3628
+ }
3208
3629
  throw errSolanaWalletKeyRequired();
3209
3630
  }
3210
3631
  const keyBytes = parseSolanaKeyBytes(solanaKey);
3211
3632
  return {
3633
+ type: "local",
3212
3634
  keyBytes,
3213
3635
  signer: await createSolanaSignerFromKeyBytes(keyBytes)
3214
3636
  };
3215
3637
  }
3638
+ function resolveSessionSolanaDelegateSigner() {
3639
+ const sessionSigner = tryResolveSessionSolanaDelegateSigner();
3640
+ if (!sessionSigner) {
3641
+ throw errInvalidArgs(
3642
+ "No active Solana wallet session. Run `alchemy wallet connect --mode session` first."
3643
+ );
3644
+ }
3645
+ return sessionSigner;
3646
+ }
3647
+ function tryResolveSessionSolanaDelegateSigner() {
3648
+ const authToken = resolveAuthToken();
3649
+ const session = resolveWalletSession();
3650
+ const solanaSession = session ? getWalletSessionByChain(session, "solana") : null;
3651
+ if (!authToken || !solanaSession?.solanaAddress) {
3652
+ return void 0;
3653
+ }
3654
+ return {
3655
+ type: "session",
3656
+ address: solanaSession.solanaAddress,
3657
+ session: solanaSession,
3658
+ authToken
3659
+ };
3660
+ }
3216
3661
  function registerSolanaDelegate(program2) {
3217
3662
  const cmd = program2.command("delegate").description("Delegate Solana token authority");
3218
3663
  const approveCmd = cmd.command("approve").description("Approve a delegate for an SPL token account").requiredOption("--token-account <address>", "SPL token account to delegate from").requiredOption("--mint <address>", "SPL token mint").requiredOption("--delegate <address>", "Delegate address").requiredOption("--amount <number>", "Amount to delegate in decimal token units").requiredOption("--decimals <n>", "Mint decimals").option("--fee-sponsored", "Enable Solana fee sponsorship").option("--fee-policy-id <id>", "Solana fee policy ID for sponsorship");
@@ -3246,7 +3691,7 @@ async function performSolanaDelegateApprove(program2, opts) {
3246
3691
  validateSolanaAddress(opts.delegate);
3247
3692
  const decimals = parseDecimals(opts.decimals);
3248
3693
  const rawAmount = parseAmount(opts.amount, decimals);
3249
- const { keyBytes, signer } = await resolveLocalSolanaDelegateSigner(program2, opts.signer);
3694
+ const signer = await resolveSolanaDelegateSigner(program2, opts.signer);
3250
3695
  const network = resolveSolanaNetwork(program2);
3251
3696
  const { sponsored, feePolicyId } = resolveSolanaFeeSponsorship(program2);
3252
3697
  const client = clientFromFlags(program2, { forceNetwork: network });
@@ -3254,20 +3699,32 @@ async function performSolanaDelegateApprove(program2, opts) {
3254
3699
  tokenAccount: solAddress(opts.tokenAccount),
3255
3700
  mint: solAddress(opts.mint),
3256
3701
  delegate: solAddress(opts.delegate),
3257
- owner: signer,
3702
+ owner: { address: solAddress(signer.type === "session" ? signer.address : signer.signer.address) },
3258
3703
  amount: rawAmount,
3259
3704
  decimals
3260
3705
  });
3261
3706
  const result = await withSpinner(
3262
3707
  "Approving delegate...",
3263
3708
  "Delegate approved",
3264
- () => buildAndSendSolanaTransaction({
3265
- client,
3266
- instructions: [instruction],
3267
- senderKeyBytes: keyBytes,
3268
- sponsored,
3269
- gasPolicyId: feePolicyId
3270
- })
3709
+ async () => {
3710
+ if (signer.type === "session") {
3711
+ return await buildAndSendSolanaTransactionWithSession({
3712
+ client,
3713
+ instructions: [instruction],
3714
+ session: signer.session,
3715
+ authToken: signer.authToken,
3716
+ sponsored,
3717
+ gasPolicyId: feePolicyId
3718
+ });
3719
+ }
3720
+ return await buildAndSendSolanaTransaction({
3721
+ client,
3722
+ instructions: [instruction],
3723
+ senderKeyBytes: signer.keyBytes,
3724
+ sponsored,
3725
+ gasPolicyId: feePolicyId
3726
+ });
3727
+ }
3271
3728
  );
3272
3729
  const confirmed = await withSpinner(
3273
3730
  "Waiting for confirmation...",
@@ -3308,24 +3765,36 @@ async function performSolanaDelegateApprove(program2, opts) {
3308
3765
  }
3309
3766
  async function performSolanaDelegateRevoke(program2, opts) {
3310
3767
  validateSolanaAddress(opts.tokenAccount);
3311
- const { keyBytes, signer } = await resolveLocalSolanaDelegateSigner(program2, opts.signer);
3768
+ const signer = await resolveSolanaDelegateSigner(program2, opts.signer);
3312
3769
  const network = resolveSolanaNetwork(program2);
3313
3770
  const { sponsored, feePolicyId } = resolveSolanaFeeSponsorship(program2);
3314
3771
  const client = clientFromFlags(program2, { forceNetwork: network });
3315
3772
  const instruction = buildSplTokenRevokeInstruction({
3316
3773
  tokenAccount: solAddress(opts.tokenAccount),
3317
- owner: signer
3774
+ owner: { address: solAddress(signer.type === "session" ? signer.address : signer.signer.address) }
3318
3775
  });
3319
3776
  const result = await withSpinner(
3320
3777
  "Revoking delegate...",
3321
3778
  "Delegate revoked",
3322
- () => buildAndSendSolanaTransaction({
3323
- client,
3324
- instructions: [instruction],
3325
- senderKeyBytes: keyBytes,
3326
- sponsored,
3327
- gasPolicyId: feePolicyId
3328
- })
3779
+ async () => {
3780
+ if (signer.type === "session") {
3781
+ return await buildAndSendSolanaTransactionWithSession({
3782
+ client,
3783
+ instructions: [instruction],
3784
+ session: signer.session,
3785
+ authToken: signer.authToken,
3786
+ sponsored,
3787
+ gasPolicyId: feePolicyId
3788
+ });
3789
+ }
3790
+ return await buildAndSendSolanaTransaction({
3791
+ client,
3792
+ instructions: [instruction],
3793
+ senderKeyBytes: signer.keyBytes,
3794
+ sponsored,
3795
+ gasPolicyId: feePolicyId
3796
+ });
3797
+ }
3329
3798
  );
3330
3799
  const confirmed = await withSpinner(
3331
3800
  "Waiting for confirmation...",
@@ -3569,35 +4038,41 @@ async function performSolanaSend(program2, toArg, amountArg, tokenAddress, opts
3569
4038
  if (tokenAddress) {
3570
4039
  throw errInvalidArgs("SPL token transfers are not yet supported. Omit --token for native SOL transfers.");
3571
4040
  }
3572
- if (opts.signer === "session") {
3573
- throw errInvalidArgs(
3574
- "The session wallet does not yet support Solana. Use `--signer local` or configure a local Solana wallet with `alchemy wallet connect --mode local --chain solana`."
3575
- );
3576
- }
3577
- const solanaKey = resolveSolanaWalletKey(program2);
3578
- if (!solanaKey) {
3579
- throw errSolanaWalletKeyRequired();
3580
- }
3581
- const keyBytes = parseSolanaKeyBytes(solanaKey);
4041
+ const signer = await resolveSolanaSigner(program2, opts.signer);
3582
4042
  validateSolanaAddress(toArg);
3583
4043
  const to = solAddress2(toArg);
3584
4044
  const network = resolveSolanaNetwork(program2);
3585
4045
  const symbol = nativeTokenSymbol(network);
3586
4046
  const lamports = parseAmount(amountArg, SOL_DECIMALS);
3587
- const signer = await createSolanaSignerFromKeyBytes(keyBytes);
3588
- const instruction = buildSolTransferInstruction(signer, to, lamports);
4047
+ const instruction = buildSolTransferInstruction(
4048
+ { address: solAddress2(signer.address) },
4049
+ to,
4050
+ lamports
4051
+ );
3589
4052
  const { sponsored, feePolicyId } = resolveSolanaFeeSponsorship(program2);
3590
4053
  const client = clientFromFlags(program2, { forceNetwork: network });
3591
4054
  const result = await withSpinner(
3592
4055
  "Sending transaction\u2026",
3593
4056
  "Transaction submitted",
3594
- () => buildAndSendSolanaTransaction({
3595
- client,
3596
- instructions: [instruction],
3597
- senderKeyBytes: keyBytes,
3598
- sponsored,
3599
- gasPolicyId: feePolicyId
3600
- })
4057
+ async () => {
4058
+ if (signer.type === "session") {
4059
+ return await buildAndSendSolanaTransactionWithSession({
4060
+ client,
4061
+ instructions: [instruction],
4062
+ session: signer.session,
4063
+ authToken: signer.authToken,
4064
+ sponsored,
4065
+ gasPolicyId: feePolicyId
4066
+ });
4067
+ }
4068
+ return await buildAndSendSolanaTransaction({
4069
+ client,
4070
+ instructions: [instruction],
4071
+ senderKeyBytes: signer.keyBytes,
4072
+ sponsored,
4073
+ gasPolicyId: feePolicyId
4074
+ });
4075
+ }
3601
4076
  );
3602
4077
  const confirmed = await withSpinner(
3603
4078
  "Waiting for confirmation\u2026",
@@ -3630,9 +4105,73 @@ async function performSolanaSend(program2, toArg, amountArg, tokenAddress, opts
3630
4105
  printKeyValue(pairs);
3631
4106
  }
3632
4107
  }
4108
+ async function resolveSolanaSigner(program2, signer) {
4109
+ if (signer === "session") {
4110
+ const authToken = resolveAuthToken();
4111
+ const session = resolveWalletSession();
4112
+ const solanaSession = session ? getWalletSessionByChain(session, "solana") : null;
4113
+ if (!authToken) {
4114
+ throw errInvalidArgs("Session Solana signing requires CLI authentication.");
4115
+ }
4116
+ if (!solanaSession?.solanaAddress) {
4117
+ throw errInvalidArgs(
4118
+ "No active Solana wallet session. Run `alchemy wallet connect --mode session` first."
4119
+ );
4120
+ }
4121
+ return {
4122
+ type: "session",
4123
+ address: solanaSession.solanaAddress,
4124
+ session: solanaSession,
4125
+ authToken
4126
+ };
4127
+ }
4128
+ if (signer === void 0 && resolveActiveSigner(program2) === "session") {
4129
+ const authToken = resolveAuthToken();
4130
+ const session = resolveWalletSession();
4131
+ const solanaSession = session ? getWalletSessionByChain(session, "solana") : null;
4132
+ if (!authToken) {
4133
+ throw errInvalidArgs("Session Solana signing requires CLI authentication.");
4134
+ }
4135
+ if (!solanaSession?.solanaAddress) {
4136
+ throw errInvalidArgs(
4137
+ "No active Solana wallet session. Run `alchemy wallet connect --mode session` first."
4138
+ );
4139
+ }
4140
+ return {
4141
+ type: "session",
4142
+ address: solanaSession.solanaAddress,
4143
+ session: solanaSession,
4144
+ authToken
4145
+ };
4146
+ }
4147
+ const solanaKey = resolveSolanaWalletKey(program2);
4148
+ if (!solanaKey) {
4149
+ if (signer === void 0) {
4150
+ const authToken = resolveAuthToken();
4151
+ const session = resolveWalletSession();
4152
+ const solanaSession = session ? getWalletSessionByChain(session, "solana") : null;
4153
+ if (authToken && solanaSession?.solanaAddress) {
4154
+ return {
4155
+ type: "session",
4156
+ address: solanaSession.solanaAddress,
4157
+ session: solanaSession,
4158
+ authToken
4159
+ };
4160
+ }
4161
+ }
4162
+ throw errSolanaWalletKeyRequired();
4163
+ }
4164
+ const keyBytes = parseSolanaKeyBytes(solanaKey);
4165
+ const localSigner = await createSolanaSignerFromKeyBytes(keyBytes);
4166
+ return {
4167
+ type: "local",
4168
+ address: localSigner.address,
4169
+ keyBytes
4170
+ };
4171
+ }
3633
4172
 
3634
4173
  // src/lib/smart-wallet.ts
3635
- import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";
4174
+ import { createSmartWalletClient } from "@alchemy/wallet-apis";
3636
4175
  import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
3637
4176
 
3638
4177
  // src/lib/chains.ts
@@ -3689,7 +4228,7 @@ function networkToChain(network) {
3689
4228
  }
3690
4229
 
3691
4230
  // src/lib/delegated-signer.ts
3692
- import { sign as signWithPrivateKey } from "crypto";
4231
+ import { sign as signWithPrivateKey2 } from "crypto";
3693
4232
  import { isAddressEqual, recoverMessageAddress, recoverTypedDataAddress } from "viem";
3694
4233
  import { toAccount } from "viem/accounts";
3695
4234
  var messageCapabilities = {
@@ -3703,8 +4242,8 @@ function toHex(bytes) {
3703
4242
  function normalizeSignedChallengeSignature(signature) {
3704
4243
  return signature.toString("base64url");
3705
4244
  }
3706
- function signChallengePayload(args) {
3707
- const signature = signWithPrivateKey(
4245
+ function signChallengePayload2(args) {
4246
+ const signature = signWithPrivateKey2(
3708
4247
  "sha256",
3709
4248
  Buffer.from(args.challengePayload, "utf8"),
3710
4249
  {
@@ -3887,7 +4426,7 @@ function createDelegatedAccount(args) {
3887
4426
  message: remoteMessage.message,
3888
4427
  encoding: remoteMessage.encoding
3889
4428
  });
3890
- const signature = signChallengePayload({
4429
+ const signature = signChallengePayload2({
3891
4430
  challengePayload: challenge.challenge,
3892
4431
  privateKeyPem: args.session.privateKeyPem
3893
4432
  });
@@ -3922,7 +4461,7 @@ function createDelegatedAccount(args) {
3922
4461
  ...sessionBinding,
3923
4462
  typedData: serializedTypedData
3924
4463
  });
3925
- const signature = signChallengePayload({
4464
+ const signature = signChallengePayload2({
3926
4465
  challengePayload: challenge.challenge,
3927
4466
  privateKeyPem: args.session.privateKeyPem
3928
4467
  });
@@ -3951,7 +4490,7 @@ function createDelegatedAccount(args) {
3951
4490
  ...sessionBinding,
3952
4491
  authorization
3953
4492
  });
3954
- const signature = signChallengePayload({
4493
+ const signature = signChallengePayload2({
3955
4494
  challengePayload: challenge.challenge,
3956
4495
  privateKeyPem: args.session.privateKeyPem
3957
4496
  });
@@ -3971,6 +4510,17 @@ function createDelegatedAccount(args) {
3971
4510
  });
3972
4511
  }
3973
4512
 
4513
+ // src/lib/wallet-transport.ts
4514
+ import { alchemyWalletTransport } from "@alchemy/wallet-apis";
4515
+ var WALLET_RPC_BASE_URL_ENV = "ALCHEMY_WALLET_RPC_BASE_URL";
4516
+ function createAlchemyWalletTransport(apiKey) {
4517
+ const override = parseBaseURLOverride(WALLET_RPC_BASE_URL_ENV);
4518
+ return alchemyWalletTransport({
4519
+ apiKey,
4520
+ ...override ? { url: override.toString() } : {}
4521
+ });
4522
+ }
4523
+
3974
4524
  // src/lib/smart-wallet.ts
3975
4525
  function normalizeKey(key) {
3976
4526
  const trimmed = key.trim();
@@ -4039,12 +4589,13 @@ function buildWalletClient(program2, options = {}) {
4039
4589
  }
4040
4590
  throw errNoActiveSession();
4041
4591
  }
4042
- assertSessionMetadata(validSession);
4043
- if (validSession.chainType && validSession.chainType !== "evm") {
4592
+ const evmSession = getWalletSessionByChain(validSession, "evm");
4593
+ if (!evmSession) {
4044
4594
  throw errInvalidArgs(
4045
- `Wallet session is configured for '${validSession.chainType}', not EVM. Run 'alchemy wallet connect --force'.`
4595
+ "Wallet session is missing EVM metadata. Run 'alchemy wallet connect --mode session --force'."
4046
4596
  );
4047
4597
  }
4598
+ assertSessionMetadata(evmSession);
4048
4599
  const authToken = resolveAuthToken(cfg);
4049
4600
  if (!authToken) {
4050
4601
  throw errAuthRequired();
@@ -4052,9 +4603,9 @@ function buildWalletClient(program2, options = {}) {
4052
4603
  return {
4053
4604
  signer: createDelegatedAccount({
4054
4605
  authToken,
4055
- session: validSession
4606
+ session: evmSession
4056
4607
  }),
4057
- address: validSession.evmAddress
4608
+ address: evmSession.evmAddress
4058
4609
  };
4059
4610
  }
4060
4611
  if (!localKey) throw errWalletKeyRequired();
@@ -4069,7 +4620,7 @@ function buildWalletClient(program2, options = {}) {
4069
4620
  }
4070
4621
  const paymaster = gasSponsored && gasPolicyId ? { policyId: gasPolicyId } : void 0;
4071
4622
  const client = createSmartWalletClient({
4072
- transport: alchemyWalletTransport({ apiKey }),
4623
+ transport: createAlchemyWalletTransport(apiKey),
4073
4624
  chain,
4074
4625
  signer: signerConfig.signer,
4075
4626
  paymaster
@@ -4640,6 +5191,7 @@ var ERROR_RECOVERY = {
4640
5191
  RATE_LIMITED: "Wait and retry; consider upgrading your Alchemy plan",
4641
5192
  PAYMENT_REQUIRED: "Fund your x402 wallet or switch to API key auth",
4642
5193
  SETUP_REQUIRED: "Run preflight: alchemy --json --no-interactive config status, then follow nextCommands",
5194
+ QUOTE_FAILED: "Quote unavailable; check data.cause for the specific reason (INSUFFICIENT_BALANCE, INSUFFICIENT_LIQUIDITY, UNSUPPORTED_ROUTE, QUOTE_REVERTED, QUOTE_FAILED) and follow the hint",
4643
5195
  INTERNAL_ERROR: "Unexpected error; retry or report a bug"
4644
5196
  };
4645
5197
  function buildCommandSchema(cmd) {
@@ -4694,11 +5246,13 @@ function buildAgentPrompt(program2) {
4694
5246
  "For general capability and usage questions, prefer npx -y @alchemy/cli@latest --json --no-interactive agent-prompt so stale local installs or PATH shims do not hide new commands",
4695
5247
  "Use the user's installed alchemy binary only when executing commands against their local config or diagnosing their installed version",
4696
5248
  "If installed alchemy help contradicts latest npm help, compare alchemy --json --no-interactive version with npx -y @alchemy/cli@latest --json --no-interactive version and check command -v alchemy",
4697
- "Run alchemy --json --no-interactive update-check when you need to detect available CLI upgrades"
5249
+ "Run alchemy --json --no-interactive update-check when you need to detect available CLI upgrades",
5250
+ "For state-changing EVM commands, prefer preview flags such as --dry-run when available before asking the user to execute",
5251
+ "For large data reads, use output-size controls such as --limit or --summary when available"
4698
5252
  ],
4699
5253
  preflight: {
4700
5254
  command: "alchemy --json --no-interactive config status",
4701
- description: "Check auth readiness before first command. If complete is false, follow nextCommands in the response to configure auth."
5255
+ description: "Check auth readiness before first command. Use capabilities.rpc_data/admin_api/notify_webhooks/wallet_signing/x402 to scope setup to the intended command family."
4702
5256
  },
4703
5257
  runtimeDiscovery: {
4704
5258
  installed: {
@@ -4790,7 +5344,7 @@ function buildAgentPrompt(program2) {
4790
5344
  method: "Local wallet + API key",
4791
5345
  envVar: "ALCHEMY_WALLET_KEY",
4792
5346
  flag: "--wallet-key-file <path> | --signer local",
4793
- setup: "alchemy wallet connect --mode local --chain evm",
5347
+ setup: "alchemy wallet connect --mode local",
4794
5348
  commandFamilies: [
4795
5349
  "evm send",
4796
5350
  "evm contract call",
@@ -4810,9 +5364,11 @@ function buildAgentPrompt(program2) {
4810
5364
  "ALCHEMY_ACCESS_KEY=ak_xxx alchemy --json --no-interactive app list",
4811
5365
  "alchemy --json --no-interactive evm rpc eth_blockNumber --api-key $ALCHEMY_API_KEY",
4812
5366
  "alchemy --json --no-interactive evm network list",
4813
- "alchemy --json --no-interactive evm send 0xRecipient 0.001 -n eth-sepolia",
5367
+ "alchemy --json --no-interactive evm send 0xRecipient 0.001 --dry-run -n eth-sepolia",
4814
5368
  `alchemy --json --no-interactive evm contract read 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 "balanceOf(address)(uint256)" --args '["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"]' -n eth-mainnet`,
4815
- "alchemy --json --no-interactive evm swap quote --from 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE --to 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --amount 1.0 -n eth-mainnet",
5369
+ "alchemy --json --no-interactive evm swap quote --from 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE --to 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --amount 1.0 --from-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n eth-mainnet",
5370
+ "alchemy --json --no-interactive evm logs --from-block latest --limit 25",
5371
+ "alchemy --json --no-interactive evm block latest --summary",
4816
5372
  "alchemy --json --no-interactive evm status 0xCallId -n eth-mainnet"
4817
5373
  ],
4818
5374
  docs: "https://www.alchemy.com/docs"
@@ -5154,15 +5710,26 @@ async function performApprove(program2, spenderArg, opts) {
5154
5710
  }
5155
5711
 
5156
5712
  // src/commands/block.ts
5713
+ function summarizeBlock(block) {
5714
+ return {
5715
+ number: block.number ?? null,
5716
+ hash: block.hash ?? null,
5717
+ timestamp: block.timestamp ?? null,
5718
+ transactionCount: Array.isArray(block.transactions) ? block.transactions.length : null,
5719
+ miner: block.miner ?? null,
5720
+ gasUsed: block.gasUsed ?? null,
5721
+ gasLimit: block.gasLimit ?? null
5722
+ };
5723
+ }
5157
5724
  function registerBlock(program2) {
5158
- program2.command("block").argument("<number>", "Block number, hex (0x...), or tag (latest, earliest, pending)").description("Get block details by number").addHelpText(
5725
+ program2.command("block").argument("<number>", "Block number, hex (0x...), or tag (latest, earliest, pending)").description("Get block details by number").option("--summary", "Return compact block summary in JSON mode").addHelpText(
5159
5726
  "after",
5160
5727
  `
5161
5728
  Examples:
5162
5729
  alchemy evm block latest
5163
5730
  alchemy evm block 17000000
5164
5731
  alchemy evm block 0x1`
5165
- ).action(async (blockId) => {
5732
+ ).action(async (blockId, opts) => {
5166
5733
  try {
5167
5734
  let blockParam;
5168
5735
  if (["latest", "earliest", "pending"].includes(blockId)) {
@@ -5189,7 +5756,7 @@ Examples:
5189
5756
  );
5190
5757
  if (!block) throw errNotFound(`block ${blockId}`);
5191
5758
  if (isJSONMode()) {
5192
- printJSON(block);
5759
+ printJSON(opts.summary ? summarizeBlock(block) : block);
5193
5760
  return;
5194
5761
  }
5195
5762
  const pairs = [];
@@ -6237,60 +6804,74 @@ async function runDataCall(program2, title, path, body) {
6237
6804
  () => x402 ? x402.callRest(`data/v1${path}`, { method: "POST", body }) : callApiData(resolveAPIKey(program2), path, { method: "POST", body })
6238
6805
  );
6239
6806
  }
6807
+ function limitPayload(value, limit) {
6808
+ if (limit === void 0) return value;
6809
+ if (Array.isArray(value)) {
6810
+ return value.slice(0, limit);
6811
+ }
6812
+ if (value && typeof value === "object") {
6813
+ return Object.fromEntries(
6814
+ Object.entries(value).map(([key, nested]) => [
6815
+ key,
6816
+ Array.isArray(nested) ? nested.slice(0, limit) : nested
6817
+ ])
6818
+ );
6819
+ }
6820
+ return value;
6821
+ }
6822
+ async function runPortfolioCommand(program2, title, path, opts) {
6823
+ const limit = parseOptionalInt(opts.limit, "--limit");
6824
+ const result = await runDataCall(program2, title, path, JSON.parse(opts.body));
6825
+ const output = limitPayload(result, limit);
6826
+ if (isJSONMode()) printJSON(output);
6827
+ else printSyntaxJSON(output);
6828
+ }
6240
6829
  function registerPortfolio(program2) {
6241
6830
  const cmd = program2.command("portfolio").description("Portfolio API wrappers");
6242
- cmd.command("tokens").description("Get token portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/by-address").action(async (opts) => {
6831
+ cmd.command("tokens").description("Get token portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/by-address").option("--limit <n>", "Limit arrays in output").action(async (opts) => {
6243
6832
  try {
6244
- const result = await runDataCall(
6833
+ await runPortfolioCommand(
6245
6834
  program2,
6246
6835
  "token portfolio",
6247
6836
  "/assets/tokens/by-address",
6248
- JSON.parse(opts.body)
6837
+ opts
6249
6838
  );
6250
- if (isJSONMode()) printJSON(result);
6251
- else printSyntaxJSON(result);
6252
6839
  } catch (err) {
6253
6840
  exitWithError(err);
6254
6841
  }
6255
6842
  });
6256
- cmd.command("token-balances").description("Get token balances by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/balances/by-address").action(async (opts) => {
6843
+ cmd.command("token-balances").description("Get token balances by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/balances/by-address").option("--limit <n>", "Limit arrays in output").action(async (opts) => {
6257
6844
  try {
6258
- const result = await runDataCall(
6845
+ await runPortfolioCommand(
6259
6846
  program2,
6260
6847
  "token balances",
6261
6848
  "/assets/tokens/balances/by-address",
6262
- JSON.parse(opts.body)
6849
+ opts
6263
6850
  );
6264
- if (isJSONMode()) printJSON(result);
6265
- else printSyntaxJSON(result);
6266
6851
  } catch (err) {
6267
6852
  exitWithError(err);
6268
6853
  }
6269
6854
  });
6270
- cmd.command("nfts").description("Get NFT portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/by-address").action(async (opts) => {
6855
+ cmd.command("nfts").description("Get NFT portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/by-address").option("--limit <n>", "Limit arrays in output").action(async (opts) => {
6271
6856
  try {
6272
- const result = await runDataCall(
6857
+ await runPortfolioCommand(
6273
6858
  program2,
6274
6859
  "NFT portfolio",
6275
6860
  "/assets/nfts/by-address",
6276
- JSON.parse(opts.body)
6861
+ opts
6277
6862
  );
6278
- if (isJSONMode()) printJSON(result);
6279
- else printSyntaxJSON(result);
6280
6863
  } catch (err) {
6281
6864
  exitWithError(err);
6282
6865
  }
6283
6866
  });
6284
- cmd.command("nft-contracts").description("Get NFT contracts by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/contracts/by-address").action(async (opts) => {
6867
+ cmd.command("nft-contracts").description("Get NFT contracts by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/contracts/by-address").option("--limit <n>", "Limit arrays in output").action(async (opts) => {
6285
6868
  try {
6286
- const result = await runDataCall(
6869
+ await runPortfolioCommand(
6287
6870
  program2,
6288
6871
  "NFT contracts",
6289
6872
  "/assets/nfts/contracts/by-address",
6290
- JSON.parse(opts.body)
6873
+ opts
6291
6874
  );
6292
- if (isJSONMode()) printJSON(result);
6293
- else printSyntaxJSON(result);
6294
6875
  } catch (err) {
6295
6876
  exitWithError(err);
6296
6877
  }
@@ -6454,8 +7035,9 @@ Examples:
6454
7035
  alchemy evm logs --address 0xdAC17F958D2ee523a2206206994597C13D831ec7 --from-block 18000000 --to-block 18000010
6455
7036
  alchemy evm logs --address 0xdAC17F958D2ee523a2206206994597C13D831ec7 --topic 0xddf252ad...
6456
7037
  alchemy evm logs --from-block latest --json`
6457
- ).option("--address <address>", "Contract address to filter logs").option("--topic <topic...>", "Event topic(s) to filter (topic0, topic1, ...)").option("--from-block <block>", "Start block (number, hex, or tag)", "latest").option("--to-block <block>", "End block (number, hex, or tag)", "latest").action(async (opts) => {
7038
+ ).option("--address <address>", "Contract address to filter logs").option("--topic <topic...>", "Event topic(s) to filter (topic0, topic1, ...)").option("--from-block <block>", "Start block (number, hex, or tag)", "latest").option("--to-block <block>", "End block (number, hex, or tag)", "latest").option("--limit <n>", "Limit returned logs in output").action(async (opts) => {
6458
7039
  try {
7040
+ const outputLimit = parseOptionalInt(opts.limit, "--limit");
6459
7041
  const filter = {
6460
7042
  fromBlock: normalizeBlockParam(opts.fromBlock),
6461
7043
  toBlock: normalizeBlockParam(opts.toBlock)
@@ -6473,20 +7055,41 @@ Examples:
6473
7055
  () => client.call("eth_getLogs", [filter])
6474
7056
  );
6475
7057
  const network = resolveNetwork(program2);
7058
+ const displayedLogs = outputLimit === void 0 ? logs : logs.slice(0, outputLimit);
6476
7059
  if (isJSONMode()) {
6477
- printJSON({ logs, count: logs.length, network });
7060
+ printJSON({
7061
+ logs: displayedLogs,
7062
+ count: displayedLogs.length,
7063
+ network,
7064
+ ...outputLimit !== void 0 ? {
7065
+ totalCount: logs.length,
7066
+ truncated: displayedLogs.length < logs.length
7067
+ } : {}
7068
+ });
6478
7069
  return;
6479
7070
  }
6480
7071
  if (logs.length === 0) {
6481
7072
  console.log(dim("No logs found for the given filter."));
6482
7073
  return;
6483
7074
  }
7075
+ if (displayedLogs.length === 0) {
7076
+ console.log(dim(`Found ${logs.length} log${logs.length === 1 ? "" : "s"}; --limit 0 hides output.`));
7077
+ return;
7078
+ }
6484
7079
  const total = logs.length;
6485
7080
  const interactive = isInteractiveAllowed(program2);
7081
+ const limitSummary = displayedLogs.length < total ? `Showing ${displayedLogs.length} of ${total} logs on ${network}
7082
+ ` : `Found ${total} log${total === 1 ? "" : "s"} on ${network}
7083
+ `;
7084
+ if (outputLimit !== void 0) {
7085
+ console.log(limitSummary);
7086
+ printTable(TABLE_HEADERS2, formatLogRows(displayedLogs));
7087
+ return;
7088
+ }
6486
7089
  if (!interactive) {
6487
7090
  console.log(`Found ${total} log${total === 1 ? "" : "s"} on ${network}
6488
7091
  `);
6489
- printTable(TABLE_HEADERS2, formatLogRows(logs));
7092
+ printTable(TABLE_HEADERS2, formatLogRows(displayedLogs));
6490
7093
  } else {
6491
7094
  let offset = 0;
6492
7095
  const firstPage = logs.slice(0, PAGE_SIZE);
@@ -6616,8 +7219,15 @@ Examples:
6616
7219
  // src/commands/send-evm.ts
6617
7220
  import { encodeFunctionData as encodeFunctionData3, erc20Abi as erc20Abi2 } from "viem";
6618
7221
  var NATIVE_EVM_DECIMALS = 18;
7222
+ function formatCallPreview(call) {
7223
+ return {
7224
+ to: call.to,
7225
+ ...call.value !== void 0 ? { value: call.value.toString() } : {},
7226
+ ...call.data ? { data: call.data } : {}
7227
+ };
7228
+ }
6619
7229
  function registerEvmSend(program2) {
6620
- const sendCmd = program2.command("send").description("Send native tokens or ERC-20 tokens to an address").argument("<to>", "Recipient address (0x... or ENS name)").argument("<amount>", "Amount to send (human-readable, e.g. 1.5)").option("--token <address>", "ERC-20 token contract address (omit for native token)");
7230
+ const sendCmd = program2.command("send").description("Send native tokens or ERC-20 tokens to an address").argument("<to>", "Recipient address (0x... or ENS name)").argument("<amount>", "Amount to send (human-readable, e.g. 1.5)").option("--token <address>", "ERC-20 token contract address (omit for native token)").option("--dry-run", "Preview transaction without signing or sending");
6621
7231
  addSignerOption(sendCmd);
6622
7232
  sendCmd.option("--gas-sponsored", "Enable gas sponsorship (env: ALCHEMY_EVM_GAS_SPONSORED)").option("--gas-policy-id <id>", "Gas policy ID for sponsorship (env: ALCHEMY_EVM_GAS_POLICY_ID)").addHelpText(
6623
7233
  "after",
@@ -6626,16 +7236,18 @@ Examples:
6626
7236
  alchemy evm send 0xAbC...123 1.5 Send 1.5 ETH
6627
7237
  alchemy evm send vitalik.eth 0.1 -n base-mainnet Send 0.1 ETH on Base
6628
7238
  alchemy evm send 0xAbC...123 100 --token 0xA0b8...USDC Send 100 USDC
7239
+ alchemy evm send 0xAbC...123 1.5 --dry-run Preview without signing or sending
6629
7240
  alchemy evm send 0xAbC...123 1 --gas-sponsored --gas-policy-id <id>
6630
7241
  alchemy evm send 0xAbC...123 1.5 --signer local Force the local wallet`
6631
7242
  ).action(async (toArg, amountArg, _opts, cmd) => {
6632
7243
  try {
6633
7244
  const opts = cmd.opts();
6634
7245
  await performEvmSend(cmd, toArg, amountArg, opts.token, {
6635
- signer: parseSignerOpt(opts.signer)
7246
+ signer: parseSignerOpt(opts.signer),
7247
+ dryRun: opts.dryRun
6636
7248
  });
6637
7249
  } catch (err) {
6638
- const { exitWithError: exitWithError2 } = await import("./errors-J6HNGXVA.js");
7250
+ const { exitWithError: exitWithError2 } = await import("./errors-UL3W4ECQ.js");
6639
7251
  exitWithError2(err);
6640
7252
  }
6641
7253
  });
@@ -6666,6 +7278,37 @@ async function performEvmSend(program2, toArg, amountArg, tokenAddress, opts = {
6666
7278
  args: [to, wei]
6667
7279
  })
6668
7280
  }] : [{ to, value: wei }];
7281
+ if (opts.dryRun) {
7282
+ const previewCalls = calls.map(formatCallPreview);
7283
+ if (isJSONMode()) {
7284
+ printJSON({
7285
+ dryRun: true,
7286
+ action: "evm-send",
7287
+ from,
7288
+ to,
7289
+ amount: amountArg,
7290
+ token: tokenAddress ?? symbol,
7291
+ tokenAddress: tokenAddress ?? null,
7292
+ network,
7293
+ sponsored: !!paymaster,
7294
+ calls: previewCalls
7295
+ });
7296
+ } else {
7297
+ const pairs = [
7298
+ ["Dry Run", "yes"],
7299
+ ["From", from],
7300
+ ["To", to],
7301
+ ["Amount", green(`${amountArg} ${symbol}`)],
7302
+ ["Network", network],
7303
+ ["Calls", String(previewCalls.length)]
7304
+ ];
7305
+ if (paymaster) {
7306
+ pairs.push(["Gas", green("Sponsored")]);
7307
+ }
7308
+ printKeyValue(pairs);
7309
+ }
7310
+ return;
7311
+ }
6669
7312
  const { id } = await withSpinner(
6670
7313
  "Sending transaction\u2026",
6671
7314
  "Transaction submitted",
@@ -6770,6 +7413,105 @@ function registerSimulate(program2) {
6770
7413
  import {
6771
7414
  swapActions
6772
7415
  } from "@alchemy/wallet-apis/experimental";
7416
+
7417
+ // src/lib/wallet-quote-client.ts
7418
+ import { createClient } from "viem";
7419
+ import { parseAccount } from "viem/accounts";
7420
+ function buildWalletQuoteClient(program2, address2) {
7421
+ const apiKey = resolveAPIKey(program2);
7422
+ if (!apiKey) throw errAuthRequired();
7423
+ const cfg = load();
7424
+ const network = resolveNetwork(program2, cfg);
7425
+ const chain = networkToChain(network);
7426
+ const client = createClient({
7427
+ account: parseAccount(address2),
7428
+ transport: createAlchemyWalletTransport(apiKey),
7429
+ chain,
7430
+ name: "alchemyQuoteClient"
7431
+ });
7432
+ if (typeof client.extend !== "function") {
7433
+ throw new Error("Quote client missing extend(); @alchemy/wallet-apis or viem may have changed.");
7434
+ }
7435
+ return {
7436
+ client,
7437
+ network,
7438
+ chain,
7439
+ address: address2,
7440
+ paymaster: void 0
7441
+ };
7442
+ }
7443
+
7444
+ // src/lib/quote-errors.ts
7445
+ function errorText(err) {
7446
+ if (err instanceof CLIError) {
7447
+ return [err.message, err.details].filter(Boolean).join(" ");
7448
+ }
7449
+ if (err instanceof Error) {
7450
+ return err.message;
7451
+ }
7452
+ return String(err);
7453
+ }
7454
+ function classifyQuoteFailure(text) {
7455
+ const lower = text.toLowerCase();
7456
+ if (/liquidity|no route|route not found|cannot find route|unable to find route|no quote/.test(lower)) {
7457
+ return "INSUFFICIENT_LIQUIDITY";
7458
+ }
7459
+ if (/insufficient (funds|balance)|exceeds balance|not enough (funds|balance)|balance too low/.test(lower)) {
7460
+ return "INSUFFICIENT_BALANCE";
7461
+ }
7462
+ if (/unsupported|not supported|unsupported route|unsupported token|unsupported chain|unsupported network/.test(lower)) {
7463
+ return "UNSUPPORTED_ROUTE";
7464
+ }
7465
+ if (/execution reverted|revert|call exception|internal json-rpc|rpc error|-32603/.test(lower)) {
7466
+ return "QUOTE_REVERTED";
7467
+ }
7468
+ return "QUOTE_FAILED";
7469
+ }
7470
+ function quoteMessage(flow, cause) {
7471
+ const label = flow === "swap" ? "Swap" : "Bridge";
7472
+ switch (cause) {
7473
+ case "INSUFFICIENT_BALANCE":
7474
+ return `${label} quote unavailable: insufficient balance for the requested amount.`;
7475
+ case "INSUFFICIENT_LIQUIDITY":
7476
+ return `${label} quote unavailable: no route or insufficient liquidity for this trade.`;
7477
+ case "UNSUPPORTED_ROUTE":
7478
+ return `${label} quote unavailable: unsupported token, chain, or route.`;
7479
+ case "QUOTE_REVERTED":
7480
+ return `${label} quote unavailable: quote simulation reverted.`;
7481
+ case "QUOTE_FAILED":
7482
+ return `${label} quote unavailable.`;
7483
+ }
7484
+ }
7485
+ function quoteHint(flow, cause) {
7486
+ switch (cause) {
7487
+ case "INSUFFICIENT_BALANCE":
7488
+ return "Lower --amount or quote from an address that holds enough source token.";
7489
+ case "INSUFFICIENT_LIQUIDITY":
7490
+ return flow === "swap" ? "Try a smaller --amount, different tokens, or a different mainnet." : "Try a smaller --amount, different tokens, or a different destination network.";
7491
+ case "UNSUPPORTED_ROUTE":
7492
+ return flow === "swap" ? "Check token addresses and use an EVM mainnet supported by swap." : "Check token addresses and source/destination EVM mainnets.";
7493
+ case "QUOTE_REVERTED":
7494
+ return "Try a smaller --amount or different route. Use --json for typed error details.";
7495
+ case "QUOTE_FAILED":
7496
+ return "Retry later or try a different amount, token, or network.";
7497
+ }
7498
+ }
7499
+ function normalizeQuoteError(err, flow) {
7500
+ if (err instanceof CLIError && err.code !== ErrorCode.RPC_ERROR && err.code !== ErrorCode.INTERNAL_ERROR) {
7501
+ return err;
7502
+ }
7503
+ const detail = errorText(err);
7504
+ const cause = classifyQuoteFailure(detail);
7505
+ return new CLIError(
7506
+ ErrorCode.QUOTE_FAILED,
7507
+ quoteMessage(flow, cause),
7508
+ quoteHint(flow, cause),
7509
+ detail || void 0,
7510
+ { cause }
7511
+ );
7512
+ }
7513
+
7514
+ // src/commands/swap.ts
6773
7515
  var NATIVE_TOKEN_ADDRESS2 = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
6774
7516
  var NATIVE_DECIMALS = 18;
6775
7517
  function isNativeToken2(address2) {
@@ -6792,11 +7534,12 @@ async function resolveTokenInfo(network, program2, tokenAddress) {
6792
7534
  throw errInvalidArgs(`Failed to resolve token info for ${tokenAddress}.${detail}`);
6793
7535
  }
6794
7536
  }
6795
- function createQuoteRequest(fromToken, toToken, fromAmount, slippagePercent, paymaster) {
7537
+ function createQuoteRequest(fromToken, toToken, fromAmount, slippagePercent, paymaster, account) {
6796
7538
  const request2 = {
6797
7539
  fromToken,
6798
7540
  toToken,
6799
7541
  fromAmount,
7542
+ ...account ? { account } : {},
6800
7543
  ...slippagePercent !== void 0 ? { slippage: slippagePercentToBasisPoints(slippagePercent) } : {},
6801
7544
  ...paymaster ? { capabilities: { paymaster } } : {}
6802
7545
  };
@@ -6843,7 +7586,7 @@ async function prepareQuoteForExecution(client, quote) {
6843
7586
  }
6844
7587
  function registerSwap(program2) {
6845
7588
  const cmd = program2.command("swap").description("Swap tokens on the same chain");
6846
- const quoteCmd = cmd.command("quote").description("Get a swap quote without executing").requiredOption("--from <token_address>", "Token address to swap from (use 0xEeee...EEeE for the native token)").requiredOption("--to <token_address>", "Token address to swap to (use 0xEeee...EEeE for the native token)").requiredOption("--amount <number>", "Amount to swap in decimal token units (for example, 1.5)").option("--slippage <percent>", "Max slippage percentage (omit to use the API default)");
7589
+ const quoteCmd = cmd.command("quote").description("Get a swap quote without executing").requiredOption("--from <token_address>", "Token address to swap from (use 0xEeee...EEeE for the native token)").requiredOption("--to <token_address>", "Token address to swap to (use 0xEeee...EEeE for the native token)").requiredOption("--amount <number>", "Amount to swap in decimal token units (for example, 1.5)").option("--from-address <address>", "Wallet/account address to quote from without changing signer selection").option("--slippage <percent>", "Max slippage percentage (omit to use the API default)");
6847
7590
  addSignerOption(quoteCmd);
6848
7591
  quoteCmd.addHelpText(
6849
7592
  "after",
@@ -6880,8 +7623,9 @@ async function performSwapQuote(program2, opts) {
6880
7623
  validateSwapNetwork(resolveNetwork(program2));
6881
7624
  validateAddress(opts.from);
6882
7625
  validateAddress(opts.to);
7626
+ if (opts.fromAddress) validateAddress(opts.fromAddress);
6883
7627
  const signer = parseSignerOpt(opts.signer);
6884
- const { client, network, paymaster } = buildWalletClient(program2, { signer });
7628
+ const { client, network, address: fromAddress, paymaster } = opts.fromAddress ? buildWalletQuoteClient(program2, opts.fromAddress) : buildWalletClient(program2, { signer });
6885
7629
  const swapClient = client.extend(swapActions);
6886
7630
  const fromInfo = await resolveTokenInfo(network, program2, opts.from);
6887
7631
  const rawAmount = parseAmount(opts.amount, fromInfo.decimals);
@@ -6889,17 +7633,23 @@ async function performSwapQuote(program2, opts) {
6889
7633
  if (slippage !== void 0 && (isNaN(slippage) || slippage < 0 || slippage > 100)) {
6890
7634
  throw errInvalidArgs("Slippage must be a number between 0 and 100.");
6891
7635
  }
6892
- const quote = await withSpinner(
6893
- "Fetching quote\u2026",
6894
- "Quote received",
6895
- () => swapClient.requestQuoteV0(createQuoteRequest(opts.from, opts.to, rawAmount, slippage, paymaster))
6896
- );
7636
+ let quote;
7637
+ try {
7638
+ quote = await withSpinner(
7639
+ "Fetching quote\u2026",
7640
+ "Quote received",
7641
+ () => swapClient.requestQuoteV0(createQuoteRequest(opts.from, opts.to, rawAmount, slippage, paymaster, fromAddress))
7642
+ );
7643
+ } catch (err) {
7644
+ throw normalizeQuoteError(err, "swap");
7645
+ }
6897
7646
  const toInfo = await resolveTokenInfo(network, program2, opts.to);
6898
7647
  const quoteData = extractQuoteData(quote);
6899
7648
  if (isJSONMode()) {
6900
7649
  printJSON({
6901
7650
  fromToken: opts.from,
6902
7651
  toToken: opts.to,
7652
+ fromAddress,
6903
7653
  fromAmount: opts.amount,
6904
7654
  fromSymbol: fromInfo.symbol,
6905
7655
  toSymbol: toInfo.symbol,
@@ -6918,6 +7668,7 @@ async function performSwapQuote(program2, opts) {
6918
7668
  pairs.push(["To", `${toInfo.symbol}`]);
6919
7669
  }
6920
7670
  pairs.push(
7671
+ ["Wallet", fromAddress],
6921
7672
  ["Slippage", slippage === void 0 ? "API default" : `${slippage}%`],
6922
7673
  ["Network", network]
6923
7674
  );
@@ -6937,11 +7688,16 @@ async function performSwapExecute(program2, opts) {
6937
7688
  if (slippage !== void 0 && (isNaN(slippage) || slippage < 0 || slippage > 100)) {
6938
7689
  throw errInvalidArgs("Slippage must be a number between 0 and 100.");
6939
7690
  }
6940
- const quote = await withSpinner(
6941
- "Fetching quote\u2026",
6942
- "Quote received",
6943
- () => swapClient.requestQuoteV0(createQuoteRequest(opts.from, opts.to, rawAmount, slippage, paymaster))
6944
- );
7691
+ let quote;
7692
+ try {
7693
+ quote = await withSpinner(
7694
+ "Fetching quote\u2026",
7695
+ "Quote received",
7696
+ () => swapClient.requestQuoteV0(createQuoteRequest(opts.from, opts.to, rawAmount, slippage, paymaster, from))
7697
+ );
7698
+ } catch (err) {
7699
+ throw normalizeQuoteError(err, "swap");
7700
+ }
6945
7701
  const preparedQuote = await prepareQuoteForExecution(client, quote);
6946
7702
  const { id } = await withSpinner(
6947
7703
  "Sending swap transaction\u2026",
@@ -7143,12 +7899,13 @@ async function resolveTokenInfo2(network, program2, tokenAddress) {
7143
7899
  throw errInvalidArgs(`Failed to resolve token info for ${tokenAddress}.${detail}`);
7144
7900
  }
7145
7901
  }
7146
- function createBridgeQuoteRequest(fromToken, toToken, fromAmount, toChainId, slippagePercent, paymaster) {
7902
+ function createBridgeQuoteRequest(fromToken, toToken, fromAmount, toChainId, slippagePercent, paymaster, account) {
7147
7903
  const request2 = {
7148
7904
  fromToken,
7149
7905
  toToken,
7150
7906
  fromAmount,
7151
7907
  toChainId,
7908
+ ...account ? { account } : {},
7152
7909
  ...slippagePercent !== void 0 ? { slippage: slippagePercentToBasisPoints2(slippagePercent) } : {},
7153
7910
  ...paymaster ? { capabilities: { paymaster } } : {}
7154
7911
  };
@@ -7214,7 +7971,7 @@ function extractQuoteData2(quote) {
7214
7971
  }
7215
7972
  function registerBridge(program2) {
7216
7973
  const cmd = program2.command("bridge").description("Bridge tokens from the source -n/--network to a destination --to-network");
7217
- const quoteCmd = cmd.command("quote").description("Get a bridge quote without executing").requiredOption("--from <token_address>", `Source token address (use ${NATIVE_TOKEN_ADDRESS3} for the native token)`).requiredOption("--to <token_address>", `Destination token address (use ${NATIVE_TOKEN_ADDRESS3} for the native token)`).requiredOption("--amount <number>", "Amount to bridge in decimal token units (for example, 1.5)").requiredOption("--to-network <network>", "Destination network (e.g. base-mainnet)").option("--slippage <percent>", "Max slippage percentage (omit to use the API default)");
7974
+ const quoteCmd = cmd.command("quote").description("Get a bridge quote without executing").requiredOption("--from <token_address>", `Source token address (use ${NATIVE_TOKEN_ADDRESS3} for the native token)`).requiredOption("--to <token_address>", `Destination token address (use ${NATIVE_TOKEN_ADDRESS3} for the native token)`).requiredOption("--amount <number>", "Amount to bridge in decimal token units (for example, 1.5)").requiredOption("--to-network <network>", "Destination network (e.g. base-mainnet)").option("--from-address <address>", "Wallet/account address to quote from without changing signer selection").option("--slippage <percent>", "Max slippage percentage (omit to use the API default)");
7218
7975
  addSignerOption(quoteCmd);
7219
7976
  quoteCmd.addHelpText(
7220
7977
  "after",
@@ -7256,8 +8013,9 @@ async function performBridgeQuote(program2, opts) {
7256
8013
  validateAddress(opts.from);
7257
8014
  const toChainId = bridgeDestinationChainId(opts.toNetwork);
7258
8015
  validateAddress(opts.to);
8016
+ if (opts.fromAddress) validateAddress(opts.fromAddress);
7259
8017
  const signer = parseSignerOpt(opts.signer);
7260
- const { client, network, paymaster } = buildWalletClient(program2, { signer });
8018
+ const { client, network, address: fromAddress, paymaster } = opts.fromAddress ? buildWalletQuoteClient(program2, opts.fromAddress) : buildWalletClient(program2, { signer });
7261
8019
  validateBridgeNetworks(network, opts.toNetwork);
7262
8020
  const swapClient = client.extend(swapActions2);
7263
8021
  const fromInfo = await resolveTokenInfo2(network, program2, opts.from);
@@ -7266,17 +8024,23 @@ async function performBridgeQuote(program2, opts) {
7266
8024
  if (slippage !== void 0 && (isNaN(slippage) || slippage < 0 || slippage > 100)) {
7267
8025
  throw errInvalidArgs("Slippage must be a number between 0 and 100.");
7268
8026
  }
7269
- const quote = await withSpinner(
7270
- "Fetching bridge quote\u2026",
7271
- "Quote received",
7272
- () => swapClient.requestQuoteV0(createBridgeQuoteRequest(opts.from, opts.to, rawAmount, toChainId, slippage, paymaster))
7273
- );
8027
+ let quote;
8028
+ try {
8029
+ quote = await withSpinner(
8030
+ "Fetching bridge quote\u2026",
8031
+ "Quote received",
8032
+ () => swapClient.requestQuoteV0(createBridgeQuoteRequest(opts.from, opts.to, rawAmount, toChainId, slippage, paymaster, fromAddress))
8033
+ );
8034
+ } catch (err) {
8035
+ throw normalizeQuoteError(err, "bridge");
8036
+ }
7274
8037
  const toInfo = await resolveTokenInfo2(opts.toNetwork, program2, opts.to);
7275
8038
  const quoteData = extractQuoteData2(quote);
7276
8039
  if (isJSONMode()) {
7277
8040
  printJSON({
7278
8041
  fromToken: opts.from,
7279
8042
  toToken: opts.to,
8043
+ fromAddress,
7280
8044
  fromAmount: opts.amount,
7281
8045
  fromSymbol: fromInfo.symbol,
7282
8046
  toSymbol: toInfo.symbol,
@@ -7296,6 +8060,7 @@ async function performBridgeQuote(program2, opts) {
7296
8060
  pairs.push(["To", toInfo.symbol]);
7297
8061
  }
7298
8062
  pairs.push(
8063
+ ["Wallet", fromAddress],
7299
8064
  ["Slippage", slippage === void 0 ? "API default" : `${slippage}%`],
7300
8065
  ["From Network", network],
7301
8066
  ["To Network", opts.toNetwork]
@@ -7317,11 +8082,16 @@ async function performBridgeExecute(program2, opts) {
7317
8082
  if (slippage !== void 0 && (isNaN(slippage) || slippage < 0 || slippage > 100)) {
7318
8083
  throw errInvalidArgs("Slippage must be a number between 0 and 100.");
7319
8084
  }
7320
- const quote = await withSpinner(
7321
- "Fetching bridge quote\u2026",
7322
- "Quote received",
7323
- () => swapClient.requestQuoteV0(createBridgeQuoteRequest(opts.from, opts.to, rawAmount, toChainId, slippage, paymaster))
7324
- );
8085
+ let quote;
8086
+ try {
8087
+ quote = await withSpinner(
8088
+ "Fetching bridge quote\u2026",
8089
+ "Quote received",
8090
+ () => swapClient.requestQuoteV0(createBridgeQuoteRequest(opts.from, opts.to, rawAmount, toChainId, slippage, paymaster, from))
8091
+ );
8092
+ } catch (err) {
8093
+ throw normalizeQuoteError(err, "bridge");
8094
+ }
7325
8095
  const preparedQuote = await prepareQuoteForExecution2(client, quote);
7326
8096
  const { id } = await withSpinner(
7327
8097
  "Sending bridge transaction\u2026",
@@ -7866,13 +8636,62 @@ function registerInstall(program2) {
7866
8636
  }
7867
8637
 
7868
8638
  // src/commands/doctor.ts
8639
+ var DOCTOR_SETUP_CAPABILITY_ORDER = SETUP_CAPABILITY_ORDER.filter(
8640
+ (capability) => capability !== "x402"
8641
+ );
8642
+ function removeX402SetupMissing(missing) {
8643
+ return missing.map(
8644
+ (item) => item.replace(" OR SIWx wallet", "").replace(" or x402 wallet", "")
8645
+ );
8646
+ }
8647
+ function removeX402NextCommands(commands) {
8648
+ return commands.filter((command) => !/x402|siwx/i.test(command));
8649
+ }
8650
+ function sanitizeCapabilityStatus(status) {
8651
+ if (status.satisfiedBy !== "x402_wallet") {
8652
+ return {
8653
+ ...status,
8654
+ missing: removeX402SetupMissing(status.missing),
8655
+ nextCommands: removeX402NextCommands(status.nextCommands)
8656
+ };
8657
+ }
8658
+ return {
8659
+ complete: false,
8660
+ satisfiedBy: null,
8661
+ missing: ["api-key"],
8662
+ nextCommands: ["alchemy config set app", "alchemy config set api-key <key>"]
8663
+ };
8664
+ }
8665
+ function doctorSetupStatus(setup) {
8666
+ const { x402: _x402, ...capabilities } = setup.capabilities;
8667
+ const sanitizedCapabilities = Object.fromEntries(
8668
+ Object.entries(capabilities).map(([capability, status]) => [
8669
+ capability,
8670
+ sanitizeCapabilityStatus(status)
8671
+ ])
8672
+ );
8673
+ const x402OnlySetup = setup.satisfiedBy === "x402_wallet";
8674
+ return {
8675
+ ...setup,
8676
+ complete: x402OnlySetup ? false : setup.complete,
8677
+ satisfiedBy: x402OnlySetup ? null : setup.satisfiedBy,
8678
+ missing: x402OnlySetup ? ["Provide one auth path: alchemy auth OR api-key OR ALCHEMY_ACCESS_KEY+app"] : removeX402SetupMissing(setup.missing),
8679
+ nextCommands: x402OnlySetup ? [
8680
+ "alchemy auth",
8681
+ "alchemy config set app",
8682
+ "alchemy config set access-key <key> && alchemy config set app <app-id>"
8683
+ ] : removeX402NextCommands(setup.nextCommands),
8684
+ capabilities: sanitizedCapabilities
8685
+ };
8686
+ }
7869
8687
  function registerDoctor(program2) {
7870
8688
  program2.command("doctor").description("Run readiness checks and print suggested fixes").action(() => {
7871
- const setup = getSetupStatus(load());
8689
+ const setup = doctorSetupStatus(getSetupStatus(load()));
7872
8690
  const payload = {
7873
8691
  ok: setup.complete,
7874
8692
  checks: {
7875
- setup
8693
+ setup,
8694
+ capabilities: setup.capabilities
7876
8695
  }
7877
8696
  };
7878
8697
  if (isJSONMode()) {
@@ -7891,6 +8710,15 @@ function registerDoctor(program2) {
7891
8710
  console.log(` ${command}`);
7892
8711
  }
7893
8712
  }
8713
+ console.log("");
8714
+ console.log(` ${dim("Capabilities:")}`);
8715
+ printKeyValue(
8716
+ DOCTOR_SETUP_CAPABILITY_ORDER.map((capability) => {
8717
+ const status = setup.capabilities[capability];
8718
+ const value = status.complete ? green(`ready${status.satisfiedBy ? ` (${status.satisfiedBy})` : ""}`) : dim(`missing ${status.missing.join(", ")}`);
8719
+ return [SETUP_CAPABILITY_LABELS[capability], value];
8720
+ })
8721
+ );
7894
8722
  });
7895
8723
  }
7896
8724
 
@@ -7987,9 +8815,15 @@ function resetUpdateNoticeState() {
7987
8815
  cachedAvailableUpdate = void 0;
7988
8816
  updateShownDuringInteractiveStartup = false;
7989
8817
  }
8818
+ async function flushProcessOutput() {
8819
+ await Promise.all([
8820
+ new Promise((resolve) => process.stdout.write("", () => resolve())),
8821
+ new Promise((resolve) => process.stderr.write("", () => resolve()))
8822
+ ]);
8823
+ }
7990
8824
  program.name("alchemy").description(
7991
8825
  "The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
7992
- ).version("0.7.2", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option(
8826
+ ).version("0.7.4-alpha.37", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option(
7993
8827
  "-n, --network <network>",
7994
8828
  "Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
7995
8829
  ).option("--x402", "Use x402 wallet-based gateway auth").option(
@@ -8176,17 +9010,17 @@ ${styledLine}`;
8176
9010
  "wallet"
8177
9011
  ];
8178
9012
  if (!skipAppPrompt.includes(cmdName) && isInteractiveAllowed(program) && !opts.apiKey && !process.env.ALCHEMY_API_KEY) {
8179
- const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-ZCR3YCHJ.js");
9013
+ const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-PAQKIAX3.js");
8180
9014
  const authToken = resolveAuthToken2(cfg);
8181
9015
  const hasApiKey = Boolean(cfg.api_key?.trim() || cfg.app?.apiKey);
8182
9016
  if (authToken && !hasApiKey) {
8183
- const { selectAppAfterAuth } = await import("./auth-7QNYRHIK.js");
9017
+ const { selectAppAfterAuth } = await import("./auth-R5QHPFMA.js");
8184
9018
  console.log("");
8185
9019
  console.log(` No app selected. Please select an app to continue.`);
8186
9020
  await selectAppAfterAuth(authToken);
8187
9021
  }
8188
9022
  }
8189
- }).hook("postAction", () => {
9023
+ }).hook("postAction", async () => {
8190
9024
  if (!isJSONMode() && !quiet) {
8191
9025
  console.log("");
8192
9026
  if (!updateShownDuringInteractiveStartup) {
@@ -8196,6 +9030,7 @@ ${styledLine}`;
8196
9030
  }
8197
9031
  resetUpdateNoticeState();
8198
9032
  if (!isReplMode()) {
9033
+ await flushProcessOutput();
8199
9034
  process.exit(0);
8200
9035
  }
8201
9036
  }).action(async (_opts, cmd) => {
@@ -8214,7 +9049,7 @@ ${styledLine}`;
8214
9049
  if (isInteractiveAllowed(program)) {
8215
9050
  let latestForInteractiveStartup = null;
8216
9051
  if (shouldRunOnboarding(program, cfg)) {
8217
- const { runOnboarding } = await import("./onboarding-WN3IMTGS.js");
9052
+ const { runOnboarding } = await import("./onboarding-Q5PBXH3M.js");
8218
9053
  const latest = getAvailableUpdateOnce();
8219
9054
  const completed = await runOnboarding(program, latest);
8220
9055
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -8228,7 +9063,7 @@ ${styledLine}`;
8228
9063
  latestForInteractiveStartup
8229
9064
  );
8230
9065
  }
8231
- const { startREPL } = await import("./interactive-JNTFVCUJ.js");
9066
+ const { startREPL } = await import("./interactive-Z2YHE6ME.js");
8232
9067
  program.exitOverride();
8233
9068
  program.configureOutput({
8234
9069
  writeErr: () => {