@alchemy/cli 0.7.3 → 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-GD7BJOMK.js → auth-D6CT363I.js} +2 -2
- package/dist/auth-R5QHPFMA.js +16 -0
- package/dist/{chunk-XSN4XA5Z.js → chunk-2OUAYCVA.js} +6 -6
- package/dist/{chunk-K6V3R7SH.js → chunk-5HYOZ773.js} +130 -8
- package/dist/{chunk-FD6YLNTB.js → chunk-6UHKZ5EN.js} +3 -3
- package/dist/{chunk-64A5W4M2.js → chunk-AUGBYMHT.js} +1 -1
- package/dist/{chunk-5IL2PMZ6.js → chunk-HR2UZ6ZU.js} +1 -1
- package/dist/{chunk-2BALTY22.js → chunk-MYHXAACL.js} +4 -4
- package/dist/{chunk-JUCUKTP3.js → chunk-PX2YJ7XC.js} +1 -1
- package/dist/{chunk-R4W44A6E.js → chunk-UYZH6GSY.js} +5 -5
- package/dist/{chunk-C5HNQOLB.js → chunk-WCZIVY4O.js} +1 -1
- package/dist/{errors-E2P6WHTX.js → errors-UL3W4ECQ.js} +1 -1
- package/dist/index.js +739 -214
- package/dist/{interactive-BNIJYEOV.js → interactive-Z2YHE6ME.js} +7 -7
- package/dist/{onboarding-TTUVIXM4.js → onboarding-Q5PBXH3M.js} +6 -6
- package/dist/{resolve-WXT5ZCUK.js → resolve-PAQKIAX3.js} +3 -3
- package/package.json +1 -1
- package/dist/auth-F2IXC6CM.js +0 -16
package/dist/index.js
CHANGED
|
@@ -2,26 +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
10
|
SETUP_CAPABILITY_LABELS,
|
|
11
11
|
SETUP_CAPABILITY_ORDER,
|
|
12
12
|
getSetupStatus,
|
|
13
13
|
isSetupComplete,
|
|
14
14
|
shouldRunOnboarding
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-UYZH6GSY.js";
|
|
16
16
|
import {
|
|
17
17
|
isInteractiveAllowed
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-AUGBYMHT.js";
|
|
19
19
|
import {
|
|
20
20
|
adminClientFromFlags,
|
|
21
21
|
clearSession,
|
|
22
22
|
clientFromFlags,
|
|
23
23
|
createPendingSession,
|
|
24
24
|
getRPCNetworks,
|
|
25
|
+
getWalletSessionByChain,
|
|
25
26
|
isSessionValid,
|
|
26
27
|
isSolanaNetwork,
|
|
27
28
|
loadSession,
|
|
@@ -43,12 +44,12 @@ import {
|
|
|
43
44
|
resolveX402Client,
|
|
44
45
|
saveSession,
|
|
45
46
|
updateSession
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-5HYOZ773.js";
|
|
47
48
|
import {
|
|
48
49
|
getAvailableUpdate,
|
|
49
50
|
getUpdateStatus,
|
|
50
51
|
printUpdateNotice
|
|
51
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-6UHKZ5EN.js";
|
|
52
53
|
import {
|
|
53
54
|
bold,
|
|
54
55
|
brand,
|
|
@@ -72,7 +73,7 @@ import {
|
|
|
72
73
|
weiToEth,
|
|
73
74
|
withSpinner,
|
|
74
75
|
yellow
|
|
75
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-HR2UZ6ZU.js";
|
|
76
77
|
import {
|
|
77
78
|
KEY_MAP,
|
|
78
79
|
configDir,
|
|
@@ -83,7 +84,7 @@ import {
|
|
|
83
84
|
save,
|
|
84
85
|
toMap,
|
|
85
86
|
validKeys
|
|
86
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-PX2YJ7XC.js";
|
|
87
88
|
import {
|
|
88
89
|
CLIError,
|
|
89
90
|
EXIT_CODES,
|
|
@@ -119,7 +120,7 @@ import {
|
|
|
119
120
|
setFlags,
|
|
120
121
|
setNoColor,
|
|
121
122
|
verbose
|
|
122
|
-
} from "./chunk-
|
|
123
|
+
} from "./chunk-MYHXAACL.js";
|
|
123
124
|
|
|
124
125
|
// src/index.ts
|
|
125
126
|
import { Command, Help } from "commander";
|
|
@@ -626,7 +627,7 @@ function registerConfig(program2) {
|
|
|
626
627
|
printJSON(toMap(cfg));
|
|
627
628
|
return;
|
|
628
629
|
}
|
|
629
|
-
const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-
|
|
630
|
+
const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-PAQKIAX3.js");
|
|
630
631
|
const validToken = resolveAuthToken2(cfg);
|
|
631
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");
|
|
632
633
|
const pairs = [
|
|
@@ -1305,7 +1306,19 @@ var walletSessionCreateResponseSchema = z.object({
|
|
|
1305
1306
|
approvalUrl: z.string().url(),
|
|
1306
1307
|
expiresAt: z.string().datetime().optional()
|
|
1307
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();
|
|
1308
1320
|
var rawWalletSessionStatusResponseSchema = z.object({
|
|
1321
|
+
type: z.literal("session").optional(),
|
|
1309
1322
|
sessionId: z.string().min(1).optional(),
|
|
1310
1323
|
status: rawWalletSessionStateSchema,
|
|
1311
1324
|
expiresAt: z.string().datetime().optional(),
|
|
@@ -1321,6 +1334,15 @@ var rawWalletSessionStatusResponseSchema = z.object({
|
|
|
1321
1334
|
chainType: z.string().min(1).optional(),
|
|
1322
1335
|
capabilities: z.record(z.string(), z.boolean()).optional()
|
|
1323
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();
|
|
1324
1346
|
var walletSessionDisconnectResponseSchema = z.object({
|
|
1325
1347
|
sessionId: z.string().min(1).optional(),
|
|
1326
1348
|
status: rawWalletSessionStateSchema.optional().transform(
|
|
@@ -1347,9 +1369,14 @@ var rawEvmSigningChallengeResponseSchema = z.object({
|
|
|
1347
1369
|
challenge: z.string().min(1),
|
|
1348
1370
|
expiresAt: z.string().datetime(),
|
|
1349
1371
|
requestExpiry: z.string().min(1),
|
|
1350
|
-
method: z.enum([
|
|
1372
|
+
method: z.enum([
|
|
1373
|
+
"personal_sign",
|
|
1374
|
+
"eth_signTypedData_v4",
|
|
1375
|
+
"eth_sign7702Authorization",
|
|
1376
|
+
"signTransaction"
|
|
1377
|
+
]),
|
|
1351
1378
|
walletId: z.string().min(1),
|
|
1352
|
-
walletAddress: z.string().
|
|
1379
|
+
walletAddress: z.string().min(1),
|
|
1353
1380
|
providerKeyQuorumId: z.string().min(1).optional(),
|
|
1354
1381
|
providerSignerId: z.string().min(1).optional()
|
|
1355
1382
|
}).strict();
|
|
@@ -1375,6 +1402,10 @@ var signAuthorizationCompleteResponseSchema = z.object({
|
|
|
1375
1402
|
y_parity: z.number().int()
|
|
1376
1403
|
}).strict()
|
|
1377
1404
|
}).strict();
|
|
1405
|
+
var solanaSignTransactionCompleteResponseSchema = z.object({
|
|
1406
|
+
signedTransaction: z.string().min(1),
|
|
1407
|
+
encoding: z.literal("base64")
|
|
1408
|
+
}).strict();
|
|
1378
1409
|
function baseURLOverride() {
|
|
1379
1410
|
const options = { allowedHostnames: [STAGING_ADMIN_API_HOST] };
|
|
1380
1411
|
return parseBaseURLOverride(WALLET_API_BASE_URL_ENV, options) ?? parseBaseURLOverride(ADMIN_API_BASE_URL_ENV, options);
|
|
@@ -1468,6 +1499,17 @@ function normalizeWalletSessionStatus(session) {
|
|
|
1468
1499
|
privySignerId: session.providerSignerId
|
|
1469
1500
|
};
|
|
1470
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
|
+
}
|
|
1471
1513
|
function normalizeEvmSigningChallengeResponse(response) {
|
|
1472
1514
|
return {
|
|
1473
1515
|
...response,
|
|
@@ -1483,7 +1525,15 @@ function toProviderSigningBindingInput(input) {
|
|
|
1483
1525
|
...privySignerId ? { providerSignerId: privySignerId } : {}
|
|
1484
1526
|
};
|
|
1485
1527
|
}
|
|
1486
|
-
|
|
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) {
|
|
1487
1537
|
let data;
|
|
1488
1538
|
let requestInput = input;
|
|
1489
1539
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
@@ -1494,16 +1544,19 @@ async function createRemoteWalletSession(token, input) {
|
|
|
1494
1544
|
"/wallet/sessions",
|
|
1495
1545
|
requestInput
|
|
1496
1546
|
);
|
|
1497
|
-
return unwrapAdminData(
|
|
1547
|
+
return unwrapAdminData(walletSessionRequestCreateResponseSchema, data);
|
|
1498
1548
|
} catch (err) {
|
|
1499
|
-
const retryInput = getClientInstanceCompatibilityRetryInput(
|
|
1549
|
+
const retryInput = getClientInstanceCompatibilityRetryInput(
|
|
1550
|
+
requestInput,
|
|
1551
|
+
err
|
|
1552
|
+
);
|
|
1500
1553
|
if (retryInput === void 0) {
|
|
1501
1554
|
throw err;
|
|
1502
1555
|
}
|
|
1503
1556
|
requestInput = retryInput;
|
|
1504
1557
|
}
|
|
1505
1558
|
}
|
|
1506
|
-
throw new CLIError(ErrorCode.NETWORK_ERROR, "Unable to create wallet session.");
|
|
1559
|
+
throw new CLIError(ErrorCode.NETWORK_ERROR, "Unable to create wallet session request.");
|
|
1507
1560
|
}
|
|
1508
1561
|
function getClientInstanceCompatibilityRetryInput(input, err) {
|
|
1509
1562
|
if (input.environment.clientInstanceName !== void 0 && isUnsupportedClientInstanceMetadataError(err, "clientInstanceName")) {
|
|
@@ -1538,6 +1591,16 @@ async function getRemoteWalletSession(token, sessionId) {
|
|
|
1538
1591
|
unwrapAdminData(rawWalletSessionStatusResponseSchema, data)
|
|
1539
1592
|
);
|
|
1540
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
|
+
}
|
|
1541
1604
|
async function disconnectRemoteWalletSession(token, sessionId) {
|
|
1542
1605
|
try {
|
|
1543
1606
|
const data = await request(
|
|
@@ -1616,6 +1679,24 @@ var signAuthorizationChallengeInputSchema = evmSigningSessionBindingBaseSchema.e
|
|
|
1616
1679
|
executor: z.literal("self").optional()
|
|
1617
1680
|
}).strict()
|
|
1618
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
|
+
});
|
|
1619
1700
|
async function createRemoteSignTypedDataChallenge(token, input) {
|
|
1620
1701
|
const parsedInput = signTypedDataChallengeInputSchema.parse(input);
|
|
1621
1702
|
return createEvmSigningChallenge(
|
|
@@ -1654,6 +1735,25 @@ async function completeRemoteSignAuthorizationChallenge(token, input) {
|
|
|
1654
1735
|
adminApiResponseSchema(signAuthorizationCompleteResponseSchema).transform((resp) => resp.data)
|
|
1655
1736
|
);
|
|
1656
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
|
+
}
|
|
1657
1757
|
|
|
1658
1758
|
// src/commands/wallet.ts
|
|
1659
1759
|
import QRCode from "qrcode";
|
|
@@ -1664,6 +1764,9 @@ var DEFAULT_WALLET_CAPABILITIES = {
|
|
|
1664
1764
|
"evm.signTypedData": true,
|
|
1665
1765
|
"evm.signAuthorization": true
|
|
1666
1766
|
};
|
|
1767
|
+
var DEFAULT_SOLANA_SESSION_CAPABILITIES = {
|
|
1768
|
+
"solana.signTransaction": true
|
|
1769
|
+
};
|
|
1667
1770
|
function createEvmWallet() {
|
|
1668
1771
|
const privateKey = generatePrivateKey();
|
|
1669
1772
|
const account = privateKeyToAccount(privateKey);
|
|
@@ -1787,7 +1890,13 @@ async function withApprovalInterruptHandler(args) {
|
|
|
1787
1890
|
return await args.run(controller.signal);
|
|
1788
1891
|
} catch (err) {
|
|
1789
1892
|
if (isWalletConnectInterruptedError(err)) {
|
|
1790
|
-
await
|
|
1893
|
+
await Promise.all(
|
|
1894
|
+
args.sessionIds.map(
|
|
1895
|
+
(sessionId) => disconnectRemoteWalletSession(args.authToken, sessionId).catch(
|
|
1896
|
+
() => void 0
|
|
1897
|
+
)
|
|
1898
|
+
)
|
|
1899
|
+
);
|
|
1791
1900
|
clearSession();
|
|
1792
1901
|
}
|
|
1793
1902
|
throw err;
|
|
@@ -1798,23 +1907,9 @@ async function withApprovalInterruptHandler(args) {
|
|
|
1798
1907
|
}
|
|
1799
1908
|
}
|
|
1800
1909
|
}
|
|
1801
|
-
function
|
|
1802
|
-
const date = new Date(value);
|
|
1803
|
-
if (Number.isNaN(date.getTime())) {
|
|
1804
|
-
return false;
|
|
1805
|
-
}
|
|
1806
|
-
return date > /* @__PURE__ */ new Date();
|
|
1807
|
-
}
|
|
1808
|
-
function isActiveWalletSession(session, remoteStatus) {
|
|
1809
|
-
if (!session) return false;
|
|
1810
|
-
if (session.status !== "approved") return false;
|
|
1811
|
-
if (!isFutureIsoDate(session.expiresAt)) return false;
|
|
1812
|
-
if (remoteStatus && remoteStatus !== "approved") return false;
|
|
1813
|
-
return true;
|
|
1814
|
-
}
|
|
1815
|
-
function deriveWalletStatus(session, remoteStatus) {
|
|
1910
|
+
function deriveWalletStatus(session) {
|
|
1816
1911
|
if (!session) return "none";
|
|
1817
|
-
if (
|
|
1912
|
+
if (isSessionValid(session)) return "active";
|
|
1818
1913
|
if (session.status === "pending") return "none";
|
|
1819
1914
|
return "expired";
|
|
1820
1915
|
}
|
|
@@ -1924,7 +2019,7 @@ function resolveQrAddress(args) {
|
|
|
1924
2019
|
function missingQrWalletError(type, source) {
|
|
1925
2020
|
if (source === "session") {
|
|
1926
2021
|
return type === "solana" ? errInvalidArgs(
|
|
1927
|
-
"
|
|
2022
|
+
"No Solana session wallet is configured. Run `alchemy wallet connect --mode session --force`."
|
|
1928
2023
|
) : errNoActiveSession();
|
|
1929
2024
|
}
|
|
1930
2025
|
if (source === "local") {
|
|
@@ -1935,20 +2030,6 @@ function missingQrWalletError(type, source) {
|
|
|
1935
2030
|
function indentBlock(text, prefix = " ") {
|
|
1936
2031
|
return text.split("\n").map((line) => line ? `${prefix}${line}` : line).join("\n");
|
|
1937
2032
|
}
|
|
1938
|
-
function generateAndPersistWallet() {
|
|
1939
|
-
const wallet = createEvmWallet();
|
|
1940
|
-
const keyPath = persistWalletKey("wallet-key", wallet.privateKey, wallet.address);
|
|
1941
|
-
const cfg = load();
|
|
1942
|
-
save({ ...cfg, wallet_key_file: keyPath, wallet_address: wallet.address });
|
|
1943
|
-
return { address: wallet.address, keyFile: keyPath };
|
|
1944
|
-
}
|
|
1945
|
-
async function generateAndPersistSolanaWallet() {
|
|
1946
|
-
const wallet = await createSolanaWallet();
|
|
1947
|
-
const keyPath = persistWalletKey("solana-wallet-key", wallet.secretKey, wallet.address);
|
|
1948
|
-
const cfg = load();
|
|
1949
|
-
save({ ...cfg, solana_wallet_key_file: keyPath, solana_wallet_address: wallet.address });
|
|
1950
|
-
return { address: wallet.address, keyFile: keyPath };
|
|
1951
|
-
}
|
|
1952
2033
|
async function createAndPersistWallets() {
|
|
1953
2034
|
const evm = createEvmWallet();
|
|
1954
2035
|
const solana = await createSolanaWallet();
|
|
@@ -2013,14 +2094,12 @@ function getEffectiveLocalWalletState(program2, cfg) {
|
|
|
2013
2094
|
}
|
|
2014
2095
|
async function runLocalCreate(opts) {
|
|
2015
2096
|
const cfg = load();
|
|
2016
|
-
const
|
|
2017
|
-
const
|
|
2018
|
-
const evmConflict = wantsEvm && Boolean(cfg.wallet_key_file);
|
|
2019
|
-
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);
|
|
2020
2099
|
if ((evmConflict || solanaConflict) && !opts.force) {
|
|
2021
2100
|
if (!isInteractiveAllowed(opts.program)) {
|
|
2022
2101
|
throw errInvalidArgs(
|
|
2023
|
-
"A local key is already configured
|
|
2102
|
+
"A local key is already configured. Re-run with --force to replace it."
|
|
2024
2103
|
);
|
|
2025
2104
|
}
|
|
2026
2105
|
const kinds = [evmConflict ? "EVM" : null, solanaConflict ? "Solana" : null].filter(Boolean).join(" and ");
|
|
@@ -2033,16 +2112,7 @@ async function runLocalCreate(opts) {
|
|
|
2033
2112
|
throw errInvalidArgs("Aborted. Existing local key preserved.");
|
|
2034
2113
|
}
|
|
2035
2114
|
}
|
|
2036
|
-
|
|
2037
|
-
const { evm, solana: solana2 } = await createAndPersistWallets();
|
|
2038
|
-
return { evm, solana: solana2 };
|
|
2039
|
-
}
|
|
2040
|
-
if (opts.chain === "evm") {
|
|
2041
|
-
const evm = generateAndPersistWallet();
|
|
2042
|
-
return { evm };
|
|
2043
|
-
}
|
|
2044
|
-
const solana = await generateAndPersistSolanaWallet();
|
|
2045
|
-
return { solana };
|
|
2115
|
+
return await createAndPersistWallets();
|
|
2046
2116
|
}
|
|
2047
2117
|
function runLocalImport(opts) {
|
|
2048
2118
|
const cfg = load();
|
|
@@ -2075,7 +2145,27 @@ async function runLocalImportInteractive(opts) {
|
|
|
2075
2145
|
return runLocalImport({ ...opts, force: true });
|
|
2076
2146
|
}
|
|
2077
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
|
+
}
|
|
2078
2167
|
async function runSessionConnect(opts) {
|
|
2168
|
+
const requestedChains = ["evm", "solana"];
|
|
2079
2169
|
const authToken = resolveAuthToken();
|
|
2080
2170
|
if (!authToken) throw errAuthRequired();
|
|
2081
2171
|
const existing = loadSession();
|
|
@@ -2113,32 +2203,51 @@ async function runSessionConnect(opts) {
|
|
|
2113
2203
|
...clientInstance.name === void 0 ? {} : { clientInstanceName: clientInstance.name }
|
|
2114
2204
|
};
|
|
2115
2205
|
updateSession({
|
|
2116
|
-
chainType: "
|
|
2117
|
-
capabilities:
|
|
2206
|
+
chainType: "both",
|
|
2207
|
+
capabilities: Object.assign(
|
|
2208
|
+
{},
|
|
2209
|
+
...requestedChains.map((chain) => capabilitiesForChain(chain))
|
|
2210
|
+
),
|
|
2118
2211
|
backendBaseUrl: getWalletApiBaseUrl(),
|
|
2119
2212
|
environment: sessionEnvironment
|
|
2120
2213
|
});
|
|
2121
|
-
const
|
|
2214
|
+
const remoteRequest = await createRemoteWalletSessionRequest(authToken, {
|
|
2122
2215
|
publicKeyJwk: session.publicKeyJwk,
|
|
2123
2216
|
requestSignerVersion: session.envelopeVersion,
|
|
2124
|
-
|
|
2125
|
-
capabilities:
|
|
2217
|
+
chains: requestedChains,
|
|
2218
|
+
capabilities: capabilitiesForChains(requestedChains),
|
|
2126
2219
|
environment: sessionEnvironment
|
|
2127
2220
|
}).catch((err) => {
|
|
2128
2221
|
clearSession();
|
|
2129
2222
|
throw err;
|
|
2130
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
|
+
);
|
|
2131
2238
|
updateSession({
|
|
2132
|
-
|
|
2133
|
-
|
|
2239
|
+
connectionRequestId: remoteRequest.sessionId,
|
|
2240
|
+
sessionId: remoteRequest.sessions[0].walletSessionId,
|
|
2241
|
+
expiresAt: remoteRequest.expiresAt ?? session.expiresAt,
|
|
2242
|
+
sessionsByChain: pendingSessionsByChain
|
|
2134
2243
|
});
|
|
2135
2244
|
if (!isJSONMode()) {
|
|
2136
2245
|
const summaryPairs = [
|
|
2137
|
-
["
|
|
2246
|
+
["Connection Request ID", remoteRequest.sessionId],
|
|
2138
2247
|
["Status", "pending"],
|
|
2139
|
-
["Approval URL",
|
|
2248
|
+
["Approval URL", remoteRequest.approvalUrl],
|
|
2140
2249
|
["Created", session.createdAt],
|
|
2141
|
-
["Expires",
|
|
2250
|
+
["Expires", remoteRequest.expiresAt ?? session.expiresAt]
|
|
2142
2251
|
];
|
|
2143
2252
|
if (clientInstance.name !== void 0) {
|
|
2144
2253
|
summaryPairs.splice(1, 0, ["Instance", clientInstance.name]);
|
|
@@ -2153,7 +2262,14 @@ async function runSessionConnect(opts) {
|
|
|
2153
2262
|
});
|
|
2154
2263
|
if (answer === null) {
|
|
2155
2264
|
clearSession();
|
|
2156
|
-
await
|
|
2265
|
+
await Promise.all(
|
|
2266
|
+
remoteRequest.sessions.map(
|
|
2267
|
+
(remoteSession) => disconnectRemoteWalletSession(
|
|
2268
|
+
authToken,
|
|
2269
|
+
remoteSession.walletSessionId
|
|
2270
|
+
).catch(() => void 0)
|
|
2271
|
+
)
|
|
2272
|
+
);
|
|
2157
2273
|
throw new WalletConnectInterruptedError();
|
|
2158
2274
|
}
|
|
2159
2275
|
}
|
|
@@ -2161,25 +2277,41 @@ async function runSessionConnect(opts) {
|
|
|
2161
2277
|
console.log(` Opening browser for approval...`);
|
|
2162
2278
|
console.log(` ${dim("Waiting for dashboard approval to complete connection.")}`);
|
|
2163
2279
|
}
|
|
2164
|
-
openBrowser(
|
|
2165
|
-
const
|
|
2280
|
+
openBrowser(remoteRequest.approvalUrl);
|
|
2281
|
+
const approvedSessions = await withApprovalInterruptHandler({
|
|
2166
2282
|
authToken,
|
|
2167
|
-
|
|
2283
|
+
sessionIds: remoteRequest.sessions.map(
|
|
2284
|
+
(remoteSession) => remoteSession.walletSessionId
|
|
2285
|
+
),
|
|
2168
2286
|
run: async (signal) => {
|
|
2169
2287
|
const startedAt = Date.now();
|
|
2170
2288
|
while (Date.now() - startedAt < CONNECT_TIMEOUT_MS) {
|
|
2171
|
-
const
|
|
2172
|
-
|
|
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
|
+
),
|
|
2173
2298
|
signal
|
|
2174
2299
|
);
|
|
2175
|
-
if (
|
|
2176
|
-
|
|
2300
|
+
if (requestedChains.every(
|
|
2301
|
+
(chain) => currentSessions.some((current) => {
|
|
2302
|
+
return current.chainType === chain && current.status === "approved";
|
|
2303
|
+
})
|
|
2304
|
+
)) {
|
|
2305
|
+
return currentSessions;
|
|
2177
2306
|
}
|
|
2178
|
-
|
|
2307
|
+
const terminalSession = currentSessions.find((current) => {
|
|
2308
|
+
return current.status === "denied" || current.status === "revoked" || current.status === "expired";
|
|
2309
|
+
});
|
|
2310
|
+
if (terminalSession) {
|
|
2179
2311
|
clearSession();
|
|
2180
2312
|
throw new CLIError(
|
|
2181
2313
|
ErrorCode.INTERNAL_ERROR,
|
|
2182
|
-
`Wallet session ${
|
|
2314
|
+
`Wallet session ${terminalSession.status}.`,
|
|
2183
2315
|
"Run 'alchemy wallet connect' to start a new approval flow."
|
|
2184
2316
|
);
|
|
2185
2317
|
}
|
|
@@ -2188,58 +2320,123 @@ async function runSessionConnect(opts) {
|
|
|
2188
2320
|
return null;
|
|
2189
2321
|
}
|
|
2190
2322
|
});
|
|
2191
|
-
if (!
|
|
2323
|
+
if (!approvedSessions) {
|
|
2192
2324
|
throw new CLIError(
|
|
2193
2325
|
ErrorCode.NETWORK_ERROR,
|
|
2194
2326
|
"Timed out waiting for wallet approval.",
|
|
2195
2327
|
"Finish approval in the dashboard and rerun 'alchemy wallet connect --force' if needed."
|
|
2196
2328
|
);
|
|
2197
2329
|
}
|
|
2198
|
-
const
|
|
2199
|
-
|
|
2200
|
-
|
|
2330
|
+
const missingProviderAppId = approvedSessions.find((approvedSession) => {
|
|
2331
|
+
return !approvedSession.privyAppId;
|
|
2332
|
+
});
|
|
2333
|
+
if (missingProviderAppId) {
|
|
2201
2334
|
throw new CLIError(
|
|
2202
2335
|
ErrorCode.INTERNAL_ERROR,
|
|
2203
2336
|
"Approved wallet session response missing required metadata."
|
|
2204
2337
|
);
|
|
2205
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;
|
|
2206
2375
|
updateSession({
|
|
2207
|
-
|
|
2376
|
+
connectionRequestId: remoteRequest.sessionId,
|
|
2377
|
+
sessionId: firstApprovedSession.sessionId ?? remoteRequest.sessions[0].walletSessionId,
|
|
2208
2378
|
status: "approved",
|
|
2209
|
-
expiresAt:
|
|
2210
|
-
privyAppId:
|
|
2211
|
-
walletId,
|
|
2212
|
-
evmWalletId:
|
|
2213
|
-
evmAddress,
|
|
2214
|
-
solanaWalletId:
|
|
2215
|
-
solanaAddress:
|
|
2216
|
-
privyKeyQuorumId:
|
|
2217
|
-
privySignerId:
|
|
2218
|
-
chainType:
|
|
2219
|
-
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,
|
|
2220
2395
|
backendBaseUrl: getWalletApiBaseUrl(),
|
|
2221
2396
|
environment: sessionEnvironment
|
|
2222
2397
|
});
|
|
2223
2398
|
if (isJSONMode()) {
|
|
2224
2399
|
printJSON({
|
|
2225
|
-
|
|
2400
|
+
connectionRequestId: remoteRequest.sessionId,
|
|
2401
|
+
sessionId: firstApprovedSession.sessionId ?? remoteRequest.sessions[0].walletSessionId,
|
|
2402
|
+
walletSessionId: firstApprovedSession.sessionId ?? remoteRequest.sessions[0].walletSessionId,
|
|
2226
2403
|
status: "approved",
|
|
2227
|
-
privyAppId:
|
|
2228
|
-
walletId,
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
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
|
|
2234
2421
|
});
|
|
2235
2422
|
} else {
|
|
2236
|
-
|
|
2237
|
-
["
|
|
2238
|
-
["Status", green("approved")]
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
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
|
|
2242
2438
|
]);
|
|
2439
|
+
printKeyValue(pairs);
|
|
2243
2440
|
console.log(` ${green("\u2713")} Wallet connected`);
|
|
2244
2441
|
}
|
|
2245
2442
|
}
|
|
@@ -2308,17 +2505,6 @@ async function runConnectFlow(program2, opts) {
|
|
|
2308
2505
|
}
|
|
2309
2506
|
mode2 = "local";
|
|
2310
2507
|
}
|
|
2311
|
-
let chain = "both";
|
|
2312
|
-
if (opts.chain !== void 0) {
|
|
2313
|
-
if (opts.chain !== "evm" && opts.chain !== "solana" && opts.chain !== "both") {
|
|
2314
|
-
throw errInvalidArgs("`--chain` must be 'evm', 'solana', or 'both'.");
|
|
2315
|
-
}
|
|
2316
|
-
chain = opts.chain;
|
|
2317
|
-
}
|
|
2318
|
-
if (importPath && chain !== "evm" && opts.chain !== void 0) {
|
|
2319
|
-
throw errInvalidArgs("`--import` implies `--chain evm`; omit `--chain` or set it to 'evm'.");
|
|
2320
|
-
}
|
|
2321
|
-
if (importPath) chain = "evm";
|
|
2322
2508
|
if (!mode2) {
|
|
2323
2509
|
if (!isInteractiveAllowed(program2)) {
|
|
2324
2510
|
throw errInvalidArgs(
|
|
@@ -2328,7 +2514,7 @@ async function runConnectFlow(program2, opts) {
|
|
|
2328
2514
|
const choice = await promptSelect({
|
|
2329
2515
|
message: "Choose a wallet to connect",
|
|
2330
2516
|
options: [
|
|
2331
|
-
{ value: "session", label: "Session wallet", hint: "Recommended \u2014 Alchemy
|
|
2517
|
+
{ value: "session", label: "Session wallet", hint: "Recommended \u2014 Alchemy-managed, more secure" },
|
|
2332
2518
|
{ value: "local", label: "Local wallet", hint: "Private key stored on this machine" }
|
|
2333
2519
|
],
|
|
2334
2520
|
initialValue: "session",
|
|
@@ -2338,7 +2524,11 @@ async function runConnectFlow(program2, opts) {
|
|
|
2338
2524
|
mode2 = choice;
|
|
2339
2525
|
}
|
|
2340
2526
|
if (mode2 === "session") {
|
|
2341
|
-
await runSessionConnect({
|
|
2527
|
+
await runSessionConnect({
|
|
2528
|
+
program: program2,
|
|
2529
|
+
force,
|
|
2530
|
+
instanceName: opts.instanceName
|
|
2531
|
+
});
|
|
2342
2532
|
printCrossModeHintAfterSessionConnect();
|
|
2343
2533
|
return;
|
|
2344
2534
|
}
|
|
@@ -2348,9 +2538,30 @@ async function runConnectFlow(program2, opts) {
|
|
|
2348
2538
|
printCrossModeHintAfterLocal("evm");
|
|
2349
2539
|
return;
|
|
2350
2540
|
}
|
|
2351
|
-
const result = await runLocalCreate({
|
|
2541
|
+
const result = await runLocalCreate({ force, program: program2 });
|
|
2352
2542
|
printPostLocalCreateSummary(result);
|
|
2353
|
-
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
|
+
);
|
|
2354
2565
|
}
|
|
2355
2566
|
async function buildSessionSnapshot(program2, verify) {
|
|
2356
2567
|
let session = loadStoredSession?.() ?? loadSession();
|
|
@@ -2358,25 +2569,55 @@ async function buildSessionSnapshot(program2, verify) {
|
|
|
2358
2569
|
if (verify && session) {
|
|
2359
2570
|
const authToken = resolveAuthToken();
|
|
2360
2571
|
if (!authToken) throw errAuthRequired();
|
|
2361
|
-
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);
|
|
2362
2578
|
remoteStatus = remote.status;
|
|
2363
|
-
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;
|
|
2364
2604
|
session = {
|
|
2365
2605
|
...session,
|
|
2366
2606
|
status: normalizeRemoteStatusForStorage(remote.status),
|
|
2367
2607
|
expiresAt: remote.expiresAt ?? session.expiresAt,
|
|
2368
2608
|
privyAppId: remote.privyAppId ?? session.privyAppId,
|
|
2369
|
-
walletId:
|
|
2370
|
-
evmWalletId
|
|
2371
|
-
evmAddress
|
|
2372
|
-
solanaWalletId
|
|
2373
|
-
solanaAddress
|
|
2374
|
-
chainType
|
|
2375
|
-
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
|
|
2376
2617
|
};
|
|
2377
2618
|
saveSession(session);
|
|
2378
2619
|
}
|
|
2379
|
-
const status = deriveWalletStatus(session
|
|
2620
|
+
const status = deriveWalletStatus(session);
|
|
2380
2621
|
const walletAddress = resolveSessionAddress(session);
|
|
2381
2622
|
const environment = resolveSessionEnvironment(session);
|
|
2382
2623
|
const signerCapabilities = resolveEnabledCapabilities(session);
|
|
@@ -2392,14 +2633,16 @@ async function buildSessionSnapshot(program2, verify) {
|
|
|
2392
2633
|
walletId,
|
|
2393
2634
|
expiresAt,
|
|
2394
2635
|
sessionId: session?.sessionId ?? null,
|
|
2636
|
+
connectionRequestId: session?.connectionRequestId ?? null,
|
|
2395
2637
|
sessionState: session?.status ?? null,
|
|
2396
2638
|
chainType: session?.chainType ?? null,
|
|
2639
|
+
sessionsByChain: session?.sessionsByChain ?? null,
|
|
2397
2640
|
valid: session ? isSessionValid(session) : false
|
|
2398
2641
|
};
|
|
2399
2642
|
}
|
|
2400
2643
|
function registerWallets(program2) {
|
|
2401
2644
|
const cmd = program2.command("wallet").description("Manage wallets");
|
|
2402
|
-
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) => {
|
|
2403
2646
|
try {
|
|
2404
2647
|
await runConnectFlow(program2, opts);
|
|
2405
2648
|
} catch (err) {
|
|
@@ -2429,10 +2672,12 @@ function registerWallets(program2) {
|
|
|
2429
2672
|
expiresAt: snap.expiresAt,
|
|
2430
2673
|
environment: snap.environment,
|
|
2431
2674
|
signerCapabilities: snap.signerCapabilities,
|
|
2675
|
+
connectionRequestId: snap.connectionRequestId,
|
|
2432
2676
|
sessionId: snap.sessionId,
|
|
2433
2677
|
sessionState: snap.sessionState,
|
|
2434
2678
|
walletId: snap.walletId,
|
|
2435
2679
|
chainType: snap.chainType,
|
|
2680
|
+
sessionsByChain: snap.sessionsByChain,
|
|
2436
2681
|
verified: Boolean(opts.verify),
|
|
2437
2682
|
remoteStatus: snap.remoteStatus,
|
|
2438
2683
|
valid: snap.valid,
|
|
@@ -2443,10 +2688,12 @@ function registerWallets(program2) {
|
|
|
2443
2688
|
expiresAt: snap.expiresAt,
|
|
2444
2689
|
environment: snap.environment,
|
|
2445
2690
|
signerCapabilities: snap.signerCapabilities,
|
|
2691
|
+
connectionRequestId: snap.connectionRequestId,
|
|
2446
2692
|
sessionId: snap.sessionId,
|
|
2447
2693
|
sessionState: snap.sessionState,
|
|
2448
2694
|
walletId: snap.walletId,
|
|
2449
2695
|
chainType: snap.chainType,
|
|
2696
|
+
sessionsByChain: snap.sessionsByChain,
|
|
2450
2697
|
valid: snap.valid
|
|
2451
2698
|
},
|
|
2452
2699
|
localEvm: localState.evmAddress ? { address: localState.evmAddress, keyFile: localState.evmKeyFile } : null,
|
|
@@ -2464,7 +2711,11 @@ function registerWallets(program2) {
|
|
|
2464
2711
|
["Environment", snap.environment ?? dim("none")],
|
|
2465
2712
|
["Signer Capabilities", snap.signerCapabilities.length > 0 ? snap.signerCapabilities.join(", ") : dim("none")]
|
|
2466
2713
|
];
|
|
2467
|
-
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
|
+
}
|
|
2468
2719
|
if (snap.walletId) pairs.push(["Wallet ID", snap.walletId]);
|
|
2469
2720
|
if (opts.verify) {
|
|
2470
2721
|
pairs.push(["Backend Status", snap.remoteStatus ?? dim("not checked")]);
|
|
@@ -2619,7 +2870,7 @@ function registerWallets(program2) {
|
|
|
2619
2870
|
}
|
|
2620
2871
|
if (target === "local" && !hasLocalEvmKey()) {
|
|
2621
2872
|
throw errInvalidArgs(
|
|
2622
|
-
"No local EVM key configured. Run `alchemy wallet connect --mode local
|
|
2873
|
+
"No local EVM key configured. Run `alchemy wallet connect --mode local` first."
|
|
2623
2874
|
);
|
|
2624
2875
|
}
|
|
2625
2876
|
const cfg = load();
|
|
@@ -2634,8 +2885,11 @@ function registerWallets(program2) {
|
|
|
2634
2885
|
exitWithError(err);
|
|
2635
2886
|
}
|
|
2636
2887
|
});
|
|
2637
|
-
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) => {
|
|
2638
2889
|
try {
|
|
2890
|
+
if (opts.chain !== void 0 && opts.chain !== "evm" && opts.chain !== "solana") {
|
|
2891
|
+
throw errInvalidArgs("`--chain` must be 'evm' or 'solana'.");
|
|
2892
|
+
}
|
|
2639
2893
|
const session = loadStoredSession?.() ?? loadSession();
|
|
2640
2894
|
if (!session) {
|
|
2641
2895
|
if (isJSONMode()) {
|
|
@@ -2653,15 +2907,40 @@ function registerWallets(program2) {
|
|
|
2653
2907
|
let revoked = false;
|
|
2654
2908
|
let alreadyDisconnected = false;
|
|
2655
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];
|
|
2656
2925
|
if (authToken) {
|
|
2657
|
-
const
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
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;
|
|
2663
2942
|
const cfg = load();
|
|
2664
|
-
if (cfg.active_signer === "session") {
|
|
2943
|
+
if (cfg.active_signer === "session" && opts.chain !== "solana") {
|
|
2665
2944
|
const { active_signer: _omit, ...rest } = cfg;
|
|
2666
2945
|
save(rest);
|
|
2667
2946
|
}
|
|
@@ -3056,9 +3335,10 @@ import {
|
|
|
3056
3335
|
createKeyPairSignerFromBytes as createKeyPairSignerFromBytes2,
|
|
3057
3336
|
createKeyPairSignerFromPrivateKeyBytes as createKeyPairSignerFromPrivateKeyBytes2
|
|
3058
3337
|
} from "@solana/kit";
|
|
3059
|
-
import {
|
|
3338
|
+
import { sign as signWithPrivateKey } from "crypto";
|
|
3060
3339
|
var SOL_DECIMALS = 9;
|
|
3061
3340
|
var SPONSOR_FEE_PAYER_PLACEHOLDER = address("Amh6quo1FcmL16Qmzdugzjq3Lv1zXzTW7ktswyLDzits");
|
|
3341
|
+
var SYSTEM_PROGRAM_ADDRESS = address("11111111111111111111111111111111");
|
|
3062
3342
|
var SPL_TOKEN_PROGRAM_ADDRESS = address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
3063
3343
|
function parseSolanaKeyBytes(secret) {
|
|
3064
3344
|
const trimmed = secret.trim();
|
|
@@ -3089,11 +3369,28 @@ async function createSolanaSignerFromKeyBytes(keyBytes) {
|
|
|
3089
3369
|
throw errInvalidArgs("Invalid Solana key: expected 64-byte secret key or 32-byte private key.");
|
|
3090
3370
|
}
|
|
3091
3371
|
function buildSolTransferInstruction(from, to, lamports) {
|
|
3092
|
-
return
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
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;
|
|
3097
3394
|
}
|
|
3098
3395
|
function encodeU64LE(value) {
|
|
3099
3396
|
if (value < 0n || value > 0xffffffffffffffffn) {
|
|
@@ -3174,6 +3471,38 @@ async function buildAndSendSolanaTransaction(opts) {
|
|
|
3174
3471
|
const signature = await client.call("sendTransaction", [wireTransaction, { encoding: "base64" }]);
|
|
3175
3472
|
return { signature, fromAddress: signer.address };
|
|
3176
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
|
+
}
|
|
3177
3506
|
async function waitForSolanaConfirmation(client, signature, timeoutMs = 6e4, pollIntervalMs = 2e3) {
|
|
3178
3507
|
const start = Date.now();
|
|
3179
3508
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -3190,6 +3519,76 @@ async function waitForSolanaConfirmation(client, signature, timeoutMs = 6e4, pol
|
|
|
3190
3519
|
}
|
|
3191
3520
|
return false;
|
|
3192
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
|
+
}
|
|
3193
3592
|
|
|
3194
3593
|
// src/lib/solana-fees.ts
|
|
3195
3594
|
function resolveSolanaFeeSponsorship(program2) {
|
|
@@ -3211,23 +3610,54 @@ function parseDecimals(value) {
|
|
|
3211
3610
|
}
|
|
3212
3611
|
return decimals;
|
|
3213
3612
|
}
|
|
3214
|
-
async function
|
|
3613
|
+
async function resolveSolanaDelegateSigner(program2, signerOpt) {
|
|
3215
3614
|
const signer = parseSignerOpt(signerOpt);
|
|
3216
3615
|
if (signer === "session") {
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3616
|
+
return resolveSessionSolanaDelegateSigner();
|
|
3617
|
+
}
|
|
3618
|
+
if (signer === void 0 && resolveActiveSigner(program2) === "session") {
|
|
3619
|
+
return resolveSessionSolanaDelegateSigner();
|
|
3220
3620
|
}
|
|
3221
3621
|
const solanaKey = resolveSolanaWalletKey(program2);
|
|
3222
3622
|
if (!solanaKey) {
|
|
3623
|
+
if (signer === void 0) {
|
|
3624
|
+
const sessionSigner = tryResolveSessionSolanaDelegateSigner();
|
|
3625
|
+
if (sessionSigner) {
|
|
3626
|
+
return sessionSigner;
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3223
3629
|
throw errSolanaWalletKeyRequired();
|
|
3224
3630
|
}
|
|
3225
3631
|
const keyBytes = parseSolanaKeyBytes(solanaKey);
|
|
3226
3632
|
return {
|
|
3633
|
+
type: "local",
|
|
3227
3634
|
keyBytes,
|
|
3228
3635
|
signer: await createSolanaSignerFromKeyBytes(keyBytes)
|
|
3229
3636
|
};
|
|
3230
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
|
+
}
|
|
3231
3661
|
function registerSolanaDelegate(program2) {
|
|
3232
3662
|
const cmd = program2.command("delegate").description("Delegate Solana token authority");
|
|
3233
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");
|
|
@@ -3261,7 +3691,7 @@ async function performSolanaDelegateApprove(program2, opts) {
|
|
|
3261
3691
|
validateSolanaAddress(opts.delegate);
|
|
3262
3692
|
const decimals = parseDecimals(opts.decimals);
|
|
3263
3693
|
const rawAmount = parseAmount(opts.amount, decimals);
|
|
3264
|
-
const
|
|
3694
|
+
const signer = await resolveSolanaDelegateSigner(program2, opts.signer);
|
|
3265
3695
|
const network = resolveSolanaNetwork(program2);
|
|
3266
3696
|
const { sponsored, feePolicyId } = resolveSolanaFeeSponsorship(program2);
|
|
3267
3697
|
const client = clientFromFlags(program2, { forceNetwork: network });
|
|
@@ -3269,20 +3699,32 @@ async function performSolanaDelegateApprove(program2, opts) {
|
|
|
3269
3699
|
tokenAccount: solAddress(opts.tokenAccount),
|
|
3270
3700
|
mint: solAddress(opts.mint),
|
|
3271
3701
|
delegate: solAddress(opts.delegate),
|
|
3272
|
-
owner: signer,
|
|
3702
|
+
owner: { address: solAddress(signer.type === "session" ? signer.address : signer.signer.address) },
|
|
3273
3703
|
amount: rawAmount,
|
|
3274
3704
|
decimals
|
|
3275
3705
|
});
|
|
3276
3706
|
const result = await withSpinner(
|
|
3277
3707
|
"Approving delegate...",
|
|
3278
3708
|
"Delegate approved",
|
|
3279
|
-
() =>
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
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
|
+
}
|
|
3286
3728
|
);
|
|
3287
3729
|
const confirmed = await withSpinner(
|
|
3288
3730
|
"Waiting for confirmation...",
|
|
@@ -3323,24 +3765,36 @@ async function performSolanaDelegateApprove(program2, opts) {
|
|
|
3323
3765
|
}
|
|
3324
3766
|
async function performSolanaDelegateRevoke(program2, opts) {
|
|
3325
3767
|
validateSolanaAddress(opts.tokenAccount);
|
|
3326
|
-
const
|
|
3768
|
+
const signer = await resolveSolanaDelegateSigner(program2, opts.signer);
|
|
3327
3769
|
const network = resolveSolanaNetwork(program2);
|
|
3328
3770
|
const { sponsored, feePolicyId } = resolveSolanaFeeSponsorship(program2);
|
|
3329
3771
|
const client = clientFromFlags(program2, { forceNetwork: network });
|
|
3330
3772
|
const instruction = buildSplTokenRevokeInstruction({
|
|
3331
3773
|
tokenAccount: solAddress(opts.tokenAccount),
|
|
3332
|
-
owner: signer
|
|
3774
|
+
owner: { address: solAddress(signer.type === "session" ? signer.address : signer.signer.address) }
|
|
3333
3775
|
});
|
|
3334
3776
|
const result = await withSpinner(
|
|
3335
3777
|
"Revoking delegate...",
|
|
3336
3778
|
"Delegate revoked",
|
|
3337
|
-
() =>
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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
|
+
}
|
|
3344
3798
|
);
|
|
3345
3799
|
const confirmed = await withSpinner(
|
|
3346
3800
|
"Waiting for confirmation...",
|
|
@@ -3584,35 +4038,41 @@ async function performSolanaSend(program2, toArg, amountArg, tokenAddress, opts
|
|
|
3584
4038
|
if (tokenAddress) {
|
|
3585
4039
|
throw errInvalidArgs("SPL token transfers are not yet supported. Omit --token for native SOL transfers.");
|
|
3586
4040
|
}
|
|
3587
|
-
|
|
3588
|
-
throw errInvalidArgs(
|
|
3589
|
-
"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`."
|
|
3590
|
-
);
|
|
3591
|
-
}
|
|
3592
|
-
const solanaKey = resolveSolanaWalletKey(program2);
|
|
3593
|
-
if (!solanaKey) {
|
|
3594
|
-
throw errSolanaWalletKeyRequired();
|
|
3595
|
-
}
|
|
3596
|
-
const keyBytes = parseSolanaKeyBytes(solanaKey);
|
|
4041
|
+
const signer = await resolveSolanaSigner(program2, opts.signer);
|
|
3597
4042
|
validateSolanaAddress(toArg);
|
|
3598
4043
|
const to = solAddress2(toArg);
|
|
3599
4044
|
const network = resolveSolanaNetwork(program2);
|
|
3600
4045
|
const symbol = nativeTokenSymbol(network);
|
|
3601
4046
|
const lamports = parseAmount(amountArg, SOL_DECIMALS);
|
|
3602
|
-
const
|
|
3603
|
-
|
|
4047
|
+
const instruction = buildSolTransferInstruction(
|
|
4048
|
+
{ address: solAddress2(signer.address) },
|
|
4049
|
+
to,
|
|
4050
|
+
lamports
|
|
4051
|
+
);
|
|
3604
4052
|
const { sponsored, feePolicyId } = resolveSolanaFeeSponsorship(program2);
|
|
3605
4053
|
const client = clientFromFlags(program2, { forceNetwork: network });
|
|
3606
4054
|
const result = await withSpinner(
|
|
3607
4055
|
"Sending transaction\u2026",
|
|
3608
4056
|
"Transaction submitted",
|
|
3609
|
-
() =>
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
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
|
+
}
|
|
3616
4076
|
);
|
|
3617
4077
|
const confirmed = await withSpinner(
|
|
3618
4078
|
"Waiting for confirmation\u2026",
|
|
@@ -3645,6 +4105,70 @@ async function performSolanaSend(program2, toArg, amountArg, tokenAddress, opts
|
|
|
3645
4105
|
printKeyValue(pairs);
|
|
3646
4106
|
}
|
|
3647
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
|
+
}
|
|
3648
4172
|
|
|
3649
4173
|
// src/lib/smart-wallet.ts
|
|
3650
4174
|
import { createSmartWalletClient } from "@alchemy/wallet-apis";
|
|
@@ -3704,7 +4228,7 @@ function networkToChain(network) {
|
|
|
3704
4228
|
}
|
|
3705
4229
|
|
|
3706
4230
|
// src/lib/delegated-signer.ts
|
|
3707
|
-
import { sign as
|
|
4231
|
+
import { sign as signWithPrivateKey2 } from "crypto";
|
|
3708
4232
|
import { isAddressEqual, recoverMessageAddress, recoverTypedDataAddress } from "viem";
|
|
3709
4233
|
import { toAccount } from "viem/accounts";
|
|
3710
4234
|
var messageCapabilities = {
|
|
@@ -3718,8 +4242,8 @@ function toHex(bytes) {
|
|
|
3718
4242
|
function normalizeSignedChallengeSignature(signature) {
|
|
3719
4243
|
return signature.toString("base64url");
|
|
3720
4244
|
}
|
|
3721
|
-
function
|
|
3722
|
-
const signature =
|
|
4245
|
+
function signChallengePayload2(args) {
|
|
4246
|
+
const signature = signWithPrivateKey2(
|
|
3723
4247
|
"sha256",
|
|
3724
4248
|
Buffer.from(args.challengePayload, "utf8"),
|
|
3725
4249
|
{
|
|
@@ -3902,7 +4426,7 @@ function createDelegatedAccount(args) {
|
|
|
3902
4426
|
message: remoteMessage.message,
|
|
3903
4427
|
encoding: remoteMessage.encoding
|
|
3904
4428
|
});
|
|
3905
|
-
const signature =
|
|
4429
|
+
const signature = signChallengePayload2({
|
|
3906
4430
|
challengePayload: challenge.challenge,
|
|
3907
4431
|
privateKeyPem: args.session.privateKeyPem
|
|
3908
4432
|
});
|
|
@@ -3937,7 +4461,7 @@ function createDelegatedAccount(args) {
|
|
|
3937
4461
|
...sessionBinding,
|
|
3938
4462
|
typedData: serializedTypedData
|
|
3939
4463
|
});
|
|
3940
|
-
const signature =
|
|
4464
|
+
const signature = signChallengePayload2({
|
|
3941
4465
|
challengePayload: challenge.challenge,
|
|
3942
4466
|
privateKeyPem: args.session.privateKeyPem
|
|
3943
4467
|
});
|
|
@@ -3966,7 +4490,7 @@ function createDelegatedAccount(args) {
|
|
|
3966
4490
|
...sessionBinding,
|
|
3967
4491
|
authorization
|
|
3968
4492
|
});
|
|
3969
|
-
const signature =
|
|
4493
|
+
const signature = signChallengePayload2({
|
|
3970
4494
|
challengePayload: challenge.challenge,
|
|
3971
4495
|
privateKeyPem: args.session.privateKeyPem
|
|
3972
4496
|
});
|
|
@@ -4065,12 +4589,13 @@ function buildWalletClient(program2, options = {}) {
|
|
|
4065
4589
|
}
|
|
4066
4590
|
throw errNoActiveSession();
|
|
4067
4591
|
}
|
|
4068
|
-
|
|
4069
|
-
if (
|
|
4592
|
+
const evmSession = getWalletSessionByChain(validSession, "evm");
|
|
4593
|
+
if (!evmSession) {
|
|
4070
4594
|
throw errInvalidArgs(
|
|
4071
|
-
|
|
4595
|
+
"Wallet session is missing EVM metadata. Run 'alchemy wallet connect --mode session --force'."
|
|
4072
4596
|
);
|
|
4073
4597
|
}
|
|
4598
|
+
assertSessionMetadata(evmSession);
|
|
4074
4599
|
const authToken = resolveAuthToken(cfg);
|
|
4075
4600
|
if (!authToken) {
|
|
4076
4601
|
throw errAuthRequired();
|
|
@@ -4078,9 +4603,9 @@ function buildWalletClient(program2, options = {}) {
|
|
|
4078
4603
|
return {
|
|
4079
4604
|
signer: createDelegatedAccount({
|
|
4080
4605
|
authToken,
|
|
4081
|
-
session:
|
|
4606
|
+
session: evmSession
|
|
4082
4607
|
}),
|
|
4083
|
-
address:
|
|
4608
|
+
address: evmSession.evmAddress
|
|
4084
4609
|
};
|
|
4085
4610
|
}
|
|
4086
4611
|
if (!localKey) throw errWalletKeyRequired();
|
|
@@ -4819,7 +5344,7 @@ function buildAgentPrompt(program2) {
|
|
|
4819
5344
|
method: "Local wallet + API key",
|
|
4820
5345
|
envVar: "ALCHEMY_WALLET_KEY",
|
|
4821
5346
|
flag: "--wallet-key-file <path> | --signer local",
|
|
4822
|
-
setup: "alchemy wallet connect --mode local
|
|
5347
|
+
setup: "alchemy wallet connect --mode local",
|
|
4823
5348
|
commandFamilies: [
|
|
4824
5349
|
"evm send",
|
|
4825
5350
|
"evm contract call",
|
|
@@ -6722,7 +7247,7 @@ Examples:
|
|
|
6722
7247
|
dryRun: opts.dryRun
|
|
6723
7248
|
});
|
|
6724
7249
|
} catch (err) {
|
|
6725
|
-
const { exitWithError: exitWithError2 } = await import("./errors-
|
|
7250
|
+
const { exitWithError: exitWithError2 } = await import("./errors-UL3W4ECQ.js");
|
|
6726
7251
|
exitWithError2(err);
|
|
6727
7252
|
}
|
|
6728
7253
|
});
|
|
@@ -8298,7 +8823,7 @@ async function flushProcessOutput() {
|
|
|
8298
8823
|
}
|
|
8299
8824
|
program.name("alchemy").description(
|
|
8300
8825
|
"The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
|
|
8301
|
-
).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(
|
|
8302
8827
|
"-n, --network <network>",
|
|
8303
8828
|
"Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
|
|
8304
8829
|
).option("--x402", "Use x402 wallet-based gateway auth").option(
|
|
@@ -8485,11 +9010,11 @@ ${styledLine}`;
|
|
|
8485
9010
|
"wallet"
|
|
8486
9011
|
];
|
|
8487
9012
|
if (!skipAppPrompt.includes(cmdName) && isInteractiveAllowed(program) && !opts.apiKey && !process.env.ALCHEMY_API_KEY) {
|
|
8488
|
-
const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-
|
|
9013
|
+
const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-PAQKIAX3.js");
|
|
8489
9014
|
const authToken = resolveAuthToken2(cfg);
|
|
8490
9015
|
const hasApiKey = Boolean(cfg.api_key?.trim() || cfg.app?.apiKey);
|
|
8491
9016
|
if (authToken && !hasApiKey) {
|
|
8492
|
-
const { selectAppAfterAuth } = await import("./auth-
|
|
9017
|
+
const { selectAppAfterAuth } = await import("./auth-R5QHPFMA.js");
|
|
8493
9018
|
console.log("");
|
|
8494
9019
|
console.log(` No app selected. Please select an app to continue.`);
|
|
8495
9020
|
await selectAppAfterAuth(authToken);
|
|
@@ -8524,7 +9049,7 @@ ${styledLine}`;
|
|
|
8524
9049
|
if (isInteractiveAllowed(program)) {
|
|
8525
9050
|
let latestForInteractiveStartup = null;
|
|
8526
9051
|
if (shouldRunOnboarding(program, cfg)) {
|
|
8527
|
-
const { runOnboarding } = await import("./onboarding-
|
|
9052
|
+
const { runOnboarding } = await import("./onboarding-Q5PBXH3M.js");
|
|
8528
9053
|
const latest = getAvailableUpdateOnce();
|
|
8529
9054
|
const completed = await runOnboarding(program, latest);
|
|
8530
9055
|
updateShownDuringInteractiveStartup = Boolean(latest);
|
|
@@ -8538,7 +9063,7 @@ ${styledLine}`;
|
|
|
8538
9063
|
latestForInteractiveStartup
|
|
8539
9064
|
);
|
|
8540
9065
|
}
|
|
8541
|
-
const { startREPL } = await import("./interactive-
|
|
9066
|
+
const { startREPL } = await import("./interactive-Z2YHE6ME.js");
|
|
8542
9067
|
program.exitOverride();
|
|
8543
9068
|
program.configureOutput({
|
|
8544
9069
|
writeErr: () => {
|