@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/{auth-TA3SL4BL.js → auth-D6CT363I.js} +2 -2
- package/dist/auth-R5QHPFMA.js +16 -0
- package/dist/{chunk-LQXLZLCY.js → chunk-2OUAYCVA.js} +6 -6
- package/dist/{chunk-5LCA2B6S.js → chunk-5HYOZ773.js} +610 -483
- package/dist/{chunk-MB6REYQL.js → chunk-6UHKZ5EN.js} +3 -3
- package/dist/{chunk-RE5HSYJJ.js → chunk-AUGBYMHT.js} +1 -1
- package/dist/{chunk-DGKUBK7G.js → chunk-HR2UZ6ZU.js} +1 -1
- package/dist/{chunk-KLPWJFWP.js → chunk-MYHXAACL.js} +6 -23
- package/dist/{chunk-BAZ4NGOD.js → chunk-PX2YJ7XC.js} +1 -1
- package/dist/chunk-UYZH6GSY.js +134 -0
- package/dist/{chunk-INVT5BV6.js → chunk-WCZIVY4O.js} +1 -1
- package/dist/{errors-J6HNGXVA.js → errors-UL3W4ECQ.js} +1 -1
- package/dist/index.js +1114 -279
- package/dist/{interactive-JNTFVCUJ.js → interactive-Z2YHE6ME.js} +13 -10
- package/dist/{onboarding-WN3IMTGS.js → onboarding-Q5PBXH3M.js} +6 -6
- package/dist/{resolve-ZCR3YCHJ.js → resolve-PAQKIAX3.js} +3 -3
- package/package.json +18 -24
- package/scripts/postinstall.cjs +2 -0
- package/dist/auth-7QNYRHIK.js +0 -16
- package/dist/chunk-EZ2ZD7YO.js +0 -64
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-
|
|
5
|
+
} from "./chunk-2OUAYCVA.js";
|
|
6
6
|
import {
|
|
7
7
|
openBrowser
|
|
8
|
-
} from "./chunk-
|
|
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-
|
|
15
|
+
} from "./chunk-UYZH6GSY.js";
|
|
14
16
|
import {
|
|
15
17
|
isInteractiveAllowed
|
|
16
|
-
} from "./chunk-
|
|
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-
|
|
47
|
+
} from "./chunk-5HYOZ773.js";
|
|
45
48
|
import {
|
|
46
49
|
getAvailableUpdate,
|
|
47
50
|
getUpdateStatus,
|
|
48
51
|
printUpdateNotice
|
|
49
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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([
|
|
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().
|
|
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
|
-
|
|
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(
|
|
1547
|
+
return unwrapAdminData(walletSessionRequestCreateResponseSchema, data);
|
|
1487
1548
|
} catch (err) {
|
|
1488
|
-
const retryInput = getClientInstanceCompatibilityRetryInput(
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
"
|
|
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
|
|
2006
|
-
const
|
|
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
|
|
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
|
-
|
|
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: "
|
|
2106
|
-
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
|
|
2214
|
+
const remoteRequest = await createRemoteWalletSessionRequest(authToken, {
|
|
2111
2215
|
publicKeyJwk: session.publicKeyJwk,
|
|
2112
2216
|
requestSignerVersion: session.envelopeVersion,
|
|
2113
|
-
|
|
2114
|
-
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
|
-
|
|
2122
|
-
|
|
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
|
-
["
|
|
2246
|
+
["Connection Request ID", remoteRequest.sessionId],
|
|
2127
2247
|
["Status", "pending"],
|
|
2128
|
-
["Approval URL",
|
|
2248
|
+
["Approval URL", remoteRequest.approvalUrl],
|
|
2129
2249
|
["Created", session.createdAt],
|
|
2130
|
-
["Expires",
|
|
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
|
|
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(
|
|
2154
|
-
const
|
|
2280
|
+
openBrowser(remoteRequest.approvalUrl);
|
|
2281
|
+
const approvedSessions = await withApprovalInterruptHandler({
|
|
2155
2282
|
authToken,
|
|
2156
|
-
|
|
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
|
|
2161
|
-
|
|
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 (
|
|
2165
|
-
|
|
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
|
-
|
|
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 ${
|
|
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 (!
|
|
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
|
|
2188
|
-
|
|
2189
|
-
|
|
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
|
-
|
|
2376
|
+
connectionRequestId: remoteRequest.sessionId,
|
|
2377
|
+
sessionId: firstApprovedSession.sessionId ?? remoteRequest.sessions[0].walletSessionId,
|
|
2197
2378
|
status: "approved",
|
|
2198
|
-
expiresAt:
|
|
2199
|
-
privyAppId:
|
|
2200
|
-
walletId,
|
|
2201
|
-
evmWalletId:
|
|
2202
|
-
evmAddress,
|
|
2203
|
-
solanaWalletId:
|
|
2204
|
-
solanaAddress:
|
|
2205
|
-
privyKeyQuorumId:
|
|
2206
|
-
privySignerId:
|
|
2207
|
-
chainType:
|
|
2208
|
-
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
|
-
|
|
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:
|
|
2217
|
-
walletId,
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
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
|
-
|
|
2226
|
-
["
|
|
2227
|
-
["Status", green("approved")]
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
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
|
|
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({
|
|
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({
|
|
2541
|
+
const result = await runLocalCreate({ force, program: program2 });
|
|
2341
2542
|
printPostLocalCreateSummary(result);
|
|
2342
|
-
printCrossModeHintAfterLocal(
|
|
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
|
|
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
|
|
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:
|
|
2359
|
-
evmWalletId
|
|
2360
|
-
evmAddress
|
|
2361
|
-
solanaWalletId
|
|
2362
|
-
solanaAddress
|
|
2363
|
-
chainType
|
|
2364
|
-
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
|
|
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("--
|
|
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.
|
|
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
|
|
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
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
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
|
|
3613
|
+
async function resolveSolanaDelegateSigner(program2, signerOpt) {
|
|
3200
3614
|
const signer = parseSignerOpt(signerOpt);
|
|
3201
3615
|
if (signer === "session") {
|
|
3202
|
-
|
|
3203
|
-
|
|
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
|
|
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
|
-
() =>
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
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
|
|
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
|
-
() =>
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
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
|
-
|
|
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
|
|
3588
|
-
|
|
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
|
-
() =>
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
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
|
|
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
|
|
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
|
|
3707
|
-
const signature =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
4043
|
-
if (
|
|
4592
|
+
const evmSession = getWalletSessionByChain(validSession, "evm");
|
|
4593
|
+
if (!evmSession) {
|
|
4044
4594
|
throw errInvalidArgs(
|
|
4045
|
-
|
|
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:
|
|
4606
|
+
session: evmSession
|
|
4056
4607
|
}),
|
|
4057
|
-
address:
|
|
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:
|
|
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.
|
|
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
|
|
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
|
-
|
|
6833
|
+
await runPortfolioCommand(
|
|
6245
6834
|
program2,
|
|
6246
6835
|
"token portfolio",
|
|
6247
6836
|
"/assets/tokens/by-address",
|
|
6248
|
-
|
|
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
|
-
|
|
6845
|
+
await runPortfolioCommand(
|
|
6259
6846
|
program2,
|
|
6260
6847
|
"token balances",
|
|
6261
6848
|
"/assets/tokens/balances/by-address",
|
|
6262
|
-
|
|
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
|
-
|
|
6857
|
+
await runPortfolioCommand(
|
|
6273
6858
|
program2,
|
|
6274
6859
|
"NFT portfolio",
|
|
6275
6860
|
"/assets/nfts/by-address",
|
|
6276
|
-
|
|
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
|
-
|
|
6869
|
+
await runPortfolioCommand(
|
|
6287
6870
|
program2,
|
|
6288
6871
|
"NFT contracts",
|
|
6289
6872
|
"/assets/nfts/contracts/by-address",
|
|
6290
|
-
|
|
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({
|
|
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(
|
|
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-
|
|
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
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
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
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
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
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
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
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
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.
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
9066
|
+
const { startREPL } = await import("./interactive-Z2YHE6ME.js");
|
|
8232
9067
|
program.exitOverride();
|
|
8233
9068
|
program.configureOutput({
|
|
8234
9069
|
writeErr: () => {
|