@vue-solana/core 3.0.0 → 4.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -2
- package/dist/action.d.ts +10 -3
- package/dist/action.mjs +4 -4
- package/dist/address.d.ts +7 -2
- package/dist/address.mjs +8 -2
- package/dist/clusters.d.ts +1 -1
- package/dist/clusters.mjs +10 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.mjs +4 -1
- package/dist/index.d.ts +4 -10
- package/dist/index.mjs +2 -14
- package/dist/ios-wallet.d.ts +1 -2
- package/dist/ios-wallet.mjs +659 -4
- package/dist/kit.d.ts +1 -1
- package/dist/kit.mjs +22 -9
- package/dist/mobile-wallet-support.d.ts +3 -0
- package/dist/mobile-wallet-support.mjs +5 -0
- package/dist/mobile-wallet.d.ts +13 -3
- package/dist/mobile-wallet.mjs +4 -9
- package/dist/rpc.d.ts +1 -1
- package/dist/rpc.mjs +0 -2
- package/dist/shared/{core.C9vxHRXl.d.mts → core.BhF1J_tt.d.ts} +40 -2
- package/dist/shared/{core.DaUlAmcN.mjs → core.BlqTJRXd.mjs} +56 -5
- package/dist/timeout.d.ts +22 -1
- package/dist/timeout.mjs +53 -17
- package/dist/token-accounts.d.ts +1 -1
- package/dist/token-accounts.mjs +31 -2
- package/dist/transaction.d.ts +9 -1
- package/dist/transaction.mjs +49 -10
- package/dist/types.d.ts +1 -1
- package/dist/wallet-standard.d.ts +11 -3
- package/dist/wallet-standard.mjs +1 -5
- package/dist/wallet.d.ts +1 -1
- package/package.json +17 -5
- package/dist/action.d.mts +0 -28
- package/dist/address.d.mts +0 -16
- package/dist/buffer-polyfill.d.mts +0 -7
- package/dist/clusters.d.mts +0 -10
- package/dist/errors.d.mts +0 -17
- package/dist/index.d.mts +0 -20
- package/dist/ios-wallet.d.mts +0 -62
- package/dist/kit.d.mts +0 -3
- package/dist/mobile-wallet.d.mts +0 -20
- package/dist/rpc.d.mts +0 -7
- package/dist/shared/core.C9vxHRXl.d.ts +0 -193
- package/dist/shared/core.DuQGQqnc.mjs +0 -575
- package/dist/timeout.d.mts +0 -4
- package/dist/token-accounts.d.mts +0 -36
- package/dist/transaction.d.mts +0 -18
- package/dist/types.d.mts +0 -3
- package/dist/wallet-standard.d.mts +0 -22
- package/dist/wallet.d.mts +0 -25
package/dist/ios-wallet.mjs
CHANGED
|
@@ -1,4 +1,659 @@
|
|
|
1
|
-
|
|
2
|
-
import '@solana/kit';
|
|
3
|
-
import '
|
|
4
|
-
|
|
1
|
+
import { createSolanaError } from './errors.mjs';
|
|
2
|
+
import { getBase58Encoder, getBase58Decoder, isAddress } from '@solana/kit';
|
|
3
|
+
import * as naclModule from 'tweetnacl';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_IOS_WALLET_CHAINS = [
|
|
6
|
+
"solana:mainnet",
|
|
7
|
+
"solana:devnet"
|
|
8
|
+
];
|
|
9
|
+
const CALLBACK_PARAMS = /* @__PURE__ */ new Set([
|
|
10
|
+
"data",
|
|
11
|
+
"nonce",
|
|
12
|
+
"errorCode",
|
|
13
|
+
"errorMessage",
|
|
14
|
+
"phantom_encryption_public_key",
|
|
15
|
+
"solflare_encryption_public_key",
|
|
16
|
+
"wallet_encryption_public_key"
|
|
17
|
+
]);
|
|
18
|
+
function isSolanaIosBrowserWalletSupported() {
|
|
19
|
+
return isIosBrowser();
|
|
20
|
+
}
|
|
21
|
+
function isIosBrowser() {
|
|
22
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const userAgentDataPlatform = getNavigatorUserAgentDataPlatform();
|
|
26
|
+
if (isIosPlatform(userAgentDataPlatform)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const userAgent = navigator.userAgent;
|
|
30
|
+
const platform = getNavigatorPlatform();
|
|
31
|
+
const maxTouchPoints = navigator.maxTouchPoints ?? 0;
|
|
32
|
+
return /iPad|iPhone|iPod/i.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1;
|
|
33
|
+
}
|
|
34
|
+
function getDefaultIosWalletAppIdentity() {
|
|
35
|
+
if (typeof window === "undefined") {
|
|
36
|
+
return {
|
|
37
|
+
name: "Vue Solana App",
|
|
38
|
+
uri: "https://localhost"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
name: document.title || "Vue Solana App",
|
|
43
|
+
uri: window.location.origin,
|
|
44
|
+
icon: getDocumentIconUrl()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function getDefaultIosWalletRedirectUrl() {
|
|
48
|
+
if (typeof window === "undefined") {
|
|
49
|
+
return "https://localhost";
|
|
50
|
+
}
|
|
51
|
+
const url = new URL(window.location.href);
|
|
52
|
+
for (const param of CALLBACK_PARAMS) {
|
|
53
|
+
url.searchParams.delete(param);
|
|
54
|
+
}
|
|
55
|
+
return url.toString();
|
|
56
|
+
}
|
|
57
|
+
function openIosWalletUrl(url) {
|
|
58
|
+
if (typeof window === "undefined") {
|
|
59
|
+
throw new Error("iOS wallet links are only available in browser runtimes");
|
|
60
|
+
}
|
|
61
|
+
window.location.assign(url);
|
|
62
|
+
}
|
|
63
|
+
function waitForRedirect(options = {}) {
|
|
64
|
+
const { timeoutMs = 10 * 60 * 1e3, signal } = options;
|
|
65
|
+
if (typeof timeoutMs !== "number" || Number.isNaN(timeoutMs) || timeoutMs < 0) {
|
|
66
|
+
throw createSolanaError(
|
|
67
|
+
"INVALID_INPUT",
|
|
68
|
+
`Invalid redirect timeout: expected a non-negative number of milliseconds, received ${String(timeoutMs)}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
signal?.throwIfAborted();
|
|
72
|
+
return new Promise((_, reject) => {
|
|
73
|
+
const timeoutId = setTimeout(() => {
|
|
74
|
+
signal?.removeEventListener("abort", onAbort);
|
|
75
|
+
reject(
|
|
76
|
+
createSolanaError(
|
|
77
|
+
"REDIRECT_TIMEOUT",
|
|
78
|
+
`Timed out waiting for the iOS wallet to redirect back after ${timeoutMs} ms.`
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
}, timeoutMs);
|
|
82
|
+
function onAbort() {
|
|
83
|
+
clearTimeout(timeoutId);
|
|
84
|
+
reject(signal.reason);
|
|
85
|
+
}
|
|
86
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function hasIosWalletCallbackParams(url) {
|
|
90
|
+
for (const param of CALLBACK_PARAMS) {
|
|
91
|
+
if (url.searchParams.has(param)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
function cleanCallbackUrl(url, clearUrl) {
|
|
98
|
+
if (!clearUrl || typeof window === "undefined") {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const param of CALLBACK_PARAMS) {
|
|
102
|
+
url.searchParams.delete(param);
|
|
103
|
+
}
|
|
104
|
+
window.history.replaceState(window.history.state, document.title, url.toString());
|
|
105
|
+
}
|
|
106
|
+
function getDocumentIconUrl() {
|
|
107
|
+
const icon = document.querySelector(
|
|
108
|
+
'link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'
|
|
109
|
+
);
|
|
110
|
+
return icon?.href;
|
|
111
|
+
}
|
|
112
|
+
function getNavigatorUserAgentDataPlatform() {
|
|
113
|
+
const userAgentData = Reflect.get(navigator, "userAgentData");
|
|
114
|
+
if (!isObjectRecord(userAgentData)) {
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
const platform = Reflect.get(userAgentData, "platform");
|
|
118
|
+
return typeof platform === "string" ? platform : void 0;
|
|
119
|
+
}
|
|
120
|
+
function getNavigatorPlatform() {
|
|
121
|
+
const platform = Reflect.get(navigator, "platform");
|
|
122
|
+
return typeof platform === "string" ? platform : "";
|
|
123
|
+
}
|
|
124
|
+
function isIosPlatform(platform) {
|
|
125
|
+
return platform?.toLowerCase() === "ios";
|
|
126
|
+
}
|
|
127
|
+
function isObjectRecord(value) {
|
|
128
|
+
return typeof value === "object" && value !== null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let cachedNacl;
|
|
132
|
+
function getNacl() {
|
|
133
|
+
return cachedNacl ??= resolveTweetNaCl();
|
|
134
|
+
}
|
|
135
|
+
function createRequestId() {
|
|
136
|
+
return getBase58Decoder().decode(getNacl().randomBytes(16));
|
|
137
|
+
}
|
|
138
|
+
function getSharedSecret(walletEncryptionPublicKey, dappSecretKey) {
|
|
139
|
+
if (!walletEncryptionPublicKey) {
|
|
140
|
+
throw createSolanaError("INVALID_INPUT", "Missing iOS wallet encryption public key");
|
|
141
|
+
}
|
|
142
|
+
let walletPk;
|
|
143
|
+
let dappSk;
|
|
144
|
+
try {
|
|
145
|
+
walletPk = Uint8Array.from(getBase58Encoder().encode(walletEncryptionPublicKey));
|
|
146
|
+
dappSk = Uint8Array.from(getBase58Encoder().encode(dappSecretKey));
|
|
147
|
+
} catch (cause) {
|
|
148
|
+
throw createSolanaError("INVALID_INPUT", "Invalid iOS wallet encryption key encoding", {
|
|
149
|
+
cause
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (walletPk.length !== getNacl().box.publicKeyLength) {
|
|
153
|
+
throw createSolanaError("INVALID_INPUT", "Invalid iOS wallet encryption public key length");
|
|
154
|
+
}
|
|
155
|
+
if (dappSk.length !== getNacl().box.secretKeyLength) {
|
|
156
|
+
throw createSolanaError("INVALID_INPUT", "Invalid iOS wallet dapp secret key length");
|
|
157
|
+
}
|
|
158
|
+
return getNacl().box.before(walletPk, dappSk);
|
|
159
|
+
}
|
|
160
|
+
function encryptPayload(payload, nonce, sharedSecret) {
|
|
161
|
+
if (sharedSecret.length !== getNacl().box.sharedKeyLength) {
|
|
162
|
+
throw createSolanaError("INVALID_INPUT", "Invalid iOS wallet shared secret length");
|
|
163
|
+
}
|
|
164
|
+
if (nonce.length !== getNacl().box.nonceLength) {
|
|
165
|
+
throw createSolanaError("INVALID_INPUT", "Invalid iOS wallet nonce length");
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
return getBase58Decoder().decode(getNacl().box.after(encodeJson(payload), nonce, sharedSecret));
|
|
169
|
+
} catch (cause) {
|
|
170
|
+
throw createSolanaError("INVALID_INPUT", "Failed to encrypt iOS wallet payload", { cause });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function decryptPayload(data, nonce, sharedSecret) {
|
|
174
|
+
let decodedData;
|
|
175
|
+
let decodedNonce;
|
|
176
|
+
try {
|
|
177
|
+
decodedData = Uint8Array.from(getBase58Encoder().encode(data));
|
|
178
|
+
decodedNonce = Uint8Array.from(getBase58Encoder().encode(nonce));
|
|
179
|
+
} catch (cause) {
|
|
180
|
+
throw createSolanaError("INVALID_INPUT", "Invalid iOS wallet callback encoding", { cause });
|
|
181
|
+
}
|
|
182
|
+
if (sharedSecret.length !== getNacl().box.sharedKeyLength) {
|
|
183
|
+
throw createSolanaError("INVALID_INPUT", "Invalid iOS wallet shared secret length");
|
|
184
|
+
}
|
|
185
|
+
if (decodedNonce.length !== getNacl().box.nonceLength) {
|
|
186
|
+
throw createSolanaError("INVALID_INPUT", "Invalid iOS wallet callback nonce length");
|
|
187
|
+
}
|
|
188
|
+
const decrypted = getNacl().box.open.after(decodedData, decodedNonce, sharedSecret);
|
|
189
|
+
if (!decrypted) {
|
|
190
|
+
throw createSolanaError("DECRYPTION_FAILED", "Unable to decrypt iOS wallet callback");
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
194
|
+
} catch (cause) {
|
|
195
|
+
throw createSolanaError("DECRYPTION_FAILED", "Unable to decode iOS wallet callback payload", {
|
|
196
|
+
cause
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function resolveTweetNaCl() {
|
|
201
|
+
const moduleDefault = naclModule.default;
|
|
202
|
+
const globalNacl = globalThis.nacl;
|
|
203
|
+
return "box" in naclModule ? naclModule : moduleDefault ?? globalNacl;
|
|
204
|
+
}
|
|
205
|
+
function encodeJson(value) {
|
|
206
|
+
return new TextEncoder().encode(JSON.stringify(value));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const IOS_WALLETS = [
|
|
210
|
+
{
|
|
211
|
+
id: "phantom",
|
|
212
|
+
name: "Phantom",
|
|
213
|
+
icon: "https://phantom.app/img/phantom-logo.svg",
|
|
214
|
+
appUrl: "https://phantom.app",
|
|
215
|
+
installUrl: "https://phantom.app/download",
|
|
216
|
+
encryptionPublicKeyParam: "phantom_encryption_public_key",
|
|
217
|
+
connectUrl: "https://phantom.app/ul/v1/connect",
|
|
218
|
+
signTransactionUrl: "https://phantom.app/ul/v1/signTransaction",
|
|
219
|
+
signAllTransactionsUrl: "https://phantom.app/ul/v1/signAllTransactions"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "solflare",
|
|
223
|
+
name: "Solflare",
|
|
224
|
+
icon: "https://solflare.com/favicon.ico",
|
|
225
|
+
appUrl: "https://solflare.com",
|
|
226
|
+
installUrl: "https://solflare.com/download",
|
|
227
|
+
encryptionPublicKeyParam: "solflare_encryption_public_key",
|
|
228
|
+
connectUrl: "https://solflare.com/ul/v1/connect",
|
|
229
|
+
signTransactionUrl: "https://solflare.com/ul/v1/signTransaction",
|
|
230
|
+
signAllTransactionsUrl: "https://solflare.com/ul/v1/signAllTransactions",
|
|
231
|
+
signAndSendTransactionUrl: "https://solflare.com/ul/v1/signAndSendTransaction"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: "backpack",
|
|
235
|
+
name: "Backpack",
|
|
236
|
+
icon: "https://backpack.app/favicon.ico",
|
|
237
|
+
appUrl: "https://backpack.app",
|
|
238
|
+
installUrl: "https://backpack.app/download",
|
|
239
|
+
encryptionPublicKeyParam: "wallet_encryption_public_key",
|
|
240
|
+
connectUrl: "https://backpack.app/ul/v1/connect",
|
|
241
|
+
signTransactionUrl: "https://backpack.app/ul/v1/signTransaction",
|
|
242
|
+
signAllTransactionsUrl: "https://backpack.app/ul/v1/signAllTransactions",
|
|
243
|
+
signAndSendTransactionUrl: "https://backpack.app/ul/v1/signAndSendTransaction"
|
|
244
|
+
}
|
|
245
|
+
];
|
|
246
|
+
function getIosWalletDefinitionById(walletId) {
|
|
247
|
+
const definition = IOS_WALLETS.find((wallet) => wallet.id === walletId);
|
|
248
|
+
if (!definition) {
|
|
249
|
+
throw new Error(`Unknown iOS wallet: ${walletId}`);
|
|
250
|
+
}
|
|
251
|
+
return definition;
|
|
252
|
+
}
|
|
253
|
+
function isIosWalletDefinition(value) {
|
|
254
|
+
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && IOS_WALLETS.some((wallet) => wallet.id === value.id);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const PENDING_REQUEST_KEY = "vue-solana:ios-wallet:pending";
|
|
258
|
+
const PENDING_REQUEST_TTL_MS = 10 * 60 * 1e3;
|
|
259
|
+
const SESSION_PREFIX = "vue-solana:ios-wallet:session:";
|
|
260
|
+
function createPendingRequest(walletId, method, keyPair, redirectUrl, requestedTransactionCount) {
|
|
261
|
+
return {
|
|
262
|
+
id: createRequestId(),
|
|
263
|
+
walletId,
|
|
264
|
+
method,
|
|
265
|
+
dappEncryptionPublicKey: getBase58Decoder().decode(keyPair.publicKey),
|
|
266
|
+
dappEncryptionSecretKey: getBase58Decoder().decode(keyPair.secretKey),
|
|
267
|
+
redirectUrl: redirectUrl ?? getDefaultIosWalletRedirectUrl(),
|
|
268
|
+
createdAt: Date.now(),
|
|
269
|
+
requestedTransactionCount
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function getStoredIosWalletAccount(walletId, chains) {
|
|
273
|
+
const session = getStoredSession(walletId);
|
|
274
|
+
if (!session) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
let publicKey;
|
|
278
|
+
try {
|
|
279
|
+
publicKey = Uint8Array.from(getBase58Encoder().encode(session.publicKey));
|
|
280
|
+
} catch {
|
|
281
|
+
removeStoredSession(walletId);
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
return [
|
|
285
|
+
{
|
|
286
|
+
address: session.publicKey,
|
|
287
|
+
publicKey,
|
|
288
|
+
chains
|
|
289
|
+
}
|
|
290
|
+
];
|
|
291
|
+
}
|
|
292
|
+
function getStoredSession(walletId) {
|
|
293
|
+
return readJson(
|
|
294
|
+
`${SESSION_PREFIX}${walletId}`,
|
|
295
|
+
(session) => isAddress(session.publicKey)
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
function storeSession(session) {
|
|
299
|
+
writeJson(`${SESSION_PREFIX}${session.walletId}`, session);
|
|
300
|
+
}
|
|
301
|
+
function removeStoredSession(walletId) {
|
|
302
|
+
removeJson(`${SESSION_PREFIX}${walletId}`);
|
|
303
|
+
}
|
|
304
|
+
function getPendingRequest() {
|
|
305
|
+
return readJson(PENDING_REQUEST_KEY);
|
|
306
|
+
}
|
|
307
|
+
function storePendingRequest(request) {
|
|
308
|
+
writeJson(PENDING_REQUEST_KEY, request);
|
|
309
|
+
}
|
|
310
|
+
function clearPendingRequest() {
|
|
311
|
+
removeJson(PENDING_REQUEST_KEY);
|
|
312
|
+
}
|
|
313
|
+
function isPendingRequestExpired(request) {
|
|
314
|
+
return Date.now() - request.createdAt > PENDING_REQUEST_TTL_MS;
|
|
315
|
+
}
|
|
316
|
+
function readJson(key, validate) {
|
|
317
|
+
const value = getStorage()?.getItem(key);
|
|
318
|
+
if (!value) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const parsed = JSON.parse(value);
|
|
323
|
+
if (validate && !validate(parsed)) {
|
|
324
|
+
throw new Error("Invalid stored value");
|
|
325
|
+
}
|
|
326
|
+
return parsed;
|
|
327
|
+
} catch {
|
|
328
|
+
getStorage()?.removeItem(key);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function writeJson(key, value) {
|
|
333
|
+
getStorage()?.setItem(key, JSON.stringify(value));
|
|
334
|
+
}
|
|
335
|
+
function removeJson(key) {
|
|
336
|
+
getStorage()?.removeItem(key);
|
|
337
|
+
}
|
|
338
|
+
function getStorage() {
|
|
339
|
+
if (typeof window === "undefined") {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
return window.sessionStorage;
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function handleSolanaIosWalletCallback(options = {}) {
|
|
350
|
+
if (typeof window === "undefined") {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const url = new URL(window.location.href);
|
|
354
|
+
if (!hasIosWalletCallbackParams(url)) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const errorCode = url.searchParams.get("errorCode");
|
|
358
|
+
const errorMessage = url.searchParams.get("errorMessage");
|
|
359
|
+
if (errorCode || errorMessage) {
|
|
360
|
+
clearPendingRequest();
|
|
361
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
362
|
+
throw new Error(errorMessage || `iOS wallet request failed: ${errorCode}`);
|
|
363
|
+
}
|
|
364
|
+
const pending = getPendingRequest();
|
|
365
|
+
if (!pending) {
|
|
366
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
367
|
+
throw new Error("Received an iOS wallet callback without a pending request");
|
|
368
|
+
}
|
|
369
|
+
if (isPendingRequestExpired(pending)) {
|
|
370
|
+
clearPendingRequest();
|
|
371
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
372
|
+
throw new Error("Received an expired iOS wallet callback");
|
|
373
|
+
}
|
|
374
|
+
const definition = getIosWalletDefinitionById(pending.walletId);
|
|
375
|
+
const walletEncryptionPublicKey = url.searchParams.get(definition.encryptionPublicKeyParam);
|
|
376
|
+
const nonce = url.searchParams.get("nonce");
|
|
377
|
+
const data = url.searchParams.get("data");
|
|
378
|
+
if (!nonce || !data) {
|
|
379
|
+
clearPendingRequest();
|
|
380
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
381
|
+
throw new Error("Received an incomplete iOS wallet callback");
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const sharedSecret = getSharedSecret(
|
|
385
|
+
walletEncryptionPublicKey ?? getStoredSession(pending.walletId)?.walletEncryptionPublicKey,
|
|
386
|
+
pending.dappEncryptionSecretKey
|
|
387
|
+
);
|
|
388
|
+
const payload = decryptPayload(data, nonce, sharedSecret);
|
|
389
|
+
const result = createCallbackResult(pending, payload);
|
|
390
|
+
if (pending.method === "connect") {
|
|
391
|
+
if (!walletEncryptionPublicKey) {
|
|
392
|
+
throw new Error("iOS wallet connect callback did not include an encryption public key");
|
|
393
|
+
}
|
|
394
|
+
const publicKey = getPublicKeyField(payload, "public_key");
|
|
395
|
+
storeSession({
|
|
396
|
+
walletId: pending.walletId,
|
|
397
|
+
publicKey,
|
|
398
|
+
session: getStringField(payload, "session"),
|
|
399
|
+
dappEncryptionPublicKey: pending.dappEncryptionPublicKey,
|
|
400
|
+
dappEncryptionSecretKey: pending.dappEncryptionSecretKey,
|
|
401
|
+
walletEncryptionPublicKey,
|
|
402
|
+
sharedSecret: getBase58Decoder().decode(sharedSecret)
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return result;
|
|
406
|
+
} finally {
|
|
407
|
+
clearPendingRequest();
|
|
408
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function createCallbackResult(pending, payload) {
|
|
412
|
+
if (pending.method === "connect") {
|
|
413
|
+
return {
|
|
414
|
+
walletId: pending.walletId,
|
|
415
|
+
method: pending.method,
|
|
416
|
+
publicKey: getPublicKeyField(payload, "public_key")
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
if (pending.method === "signTransaction") {
|
|
420
|
+
return {
|
|
421
|
+
walletId: pending.walletId,
|
|
422
|
+
method: pending.method,
|
|
423
|
+
transaction: Uint8Array.from(
|
|
424
|
+
getBase58Encoder().encode(getStringField(payload, "transaction"))
|
|
425
|
+
)
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (pending.method === "signAllTransactions") {
|
|
429
|
+
const transactions = payload.transactions;
|
|
430
|
+
if (!Array.isArray(transactions) || transactions.some((transaction) => typeof transaction !== "string")) {
|
|
431
|
+
throw new Error("iOS wallet returned invalid signed transactions");
|
|
432
|
+
}
|
|
433
|
+
if (typeof pending.requestedTransactionCount === "number" && transactions.length !== pending.requestedTransactionCount) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
`iOS wallet returned ${transactions.length} signed transactions for ${pending.requestedTransactionCount} requested transactions`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
walletId: pending.walletId,
|
|
440
|
+
method: pending.method,
|
|
441
|
+
transactions: transactions.map(
|
|
442
|
+
(transaction) => Uint8Array.from(getBase58Encoder().encode(transaction))
|
|
443
|
+
)
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
walletId: pending.walletId,
|
|
448
|
+
method: pending.method,
|
|
449
|
+
signature: getStringField(payload, "signature")
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function getStringField(payload, field) {
|
|
453
|
+
const value = payload[field];
|
|
454
|
+
if (typeof value !== "string" || !value) {
|
|
455
|
+
throw new Error(`iOS wallet callback is missing ${field}`);
|
|
456
|
+
}
|
|
457
|
+
return value;
|
|
458
|
+
}
|
|
459
|
+
function getPublicKeyField(payload, field) {
|
|
460
|
+
const value = getStringField(payload, field);
|
|
461
|
+
if (!isAddress(value)) {
|
|
462
|
+
throw new Error(`iOS wallet callback returned an invalid ${field}`);
|
|
463
|
+
}
|
|
464
|
+
return value;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function launchConnect(definition, options) {
|
|
468
|
+
const keyPair = getNacl().box.keyPair();
|
|
469
|
+
const pending = createPendingRequest(definition.id, "connect", keyPair, options.redirectUrl);
|
|
470
|
+
const appIdentity = options.appIdentity ?? getDefaultIosWalletAppIdentity();
|
|
471
|
+
const url = new URL(definition.connectUrl);
|
|
472
|
+
url.searchParams.set("app_url", appIdentity.uri);
|
|
473
|
+
url.searchParams.set("dapp_encryption_public_key", pending.dappEncryptionPublicKey);
|
|
474
|
+
url.searchParams.set("redirect_link", pending.redirectUrl);
|
|
475
|
+
if (options.cluster && options.cluster !== "localnet") {
|
|
476
|
+
url.searchParams.set("cluster", options.cluster);
|
|
477
|
+
}
|
|
478
|
+
storePendingRequest(pending);
|
|
479
|
+
openIosWalletUrl(url.toString());
|
|
480
|
+
}
|
|
481
|
+
async function launchSignTransaction(definition, transaction, options) {
|
|
482
|
+
const response = await launchEncryptedWalletRequest(definition, "signTransaction", options, {
|
|
483
|
+
transaction: getBase58Decoder().decode(new Uint8Array(transaction))
|
|
484
|
+
});
|
|
485
|
+
if (!response.transaction) {
|
|
486
|
+
throw new Error("iOS wallet did not return a signed transaction");
|
|
487
|
+
}
|
|
488
|
+
return response.transaction;
|
|
489
|
+
}
|
|
490
|
+
async function launchSignAllTransactions(definition, transactions, options) {
|
|
491
|
+
const response = await launchEncryptedWalletRequest(definition, "signAllTransactions", options, {
|
|
492
|
+
transactions: transactions.map(
|
|
493
|
+
(transaction) => getBase58Decoder().decode(new Uint8Array(transaction))
|
|
494
|
+
)
|
|
495
|
+
});
|
|
496
|
+
if (!response.transactions) {
|
|
497
|
+
throw new Error("iOS wallet did not return signed transactions");
|
|
498
|
+
}
|
|
499
|
+
return response.transactions;
|
|
500
|
+
}
|
|
501
|
+
async function launchSignAndSendTransaction(definition, transaction, sendOptions, options) {
|
|
502
|
+
const response = await launchEncryptedWalletRequest(
|
|
503
|
+
definition,
|
|
504
|
+
"signAndSendTransaction",
|
|
505
|
+
options,
|
|
506
|
+
{
|
|
507
|
+
transaction: getBase58Decoder().decode(new Uint8Array(transaction)),
|
|
508
|
+
sendOptions
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
if (!response.signature) {
|
|
512
|
+
throw new Error("iOS wallet did not return a transaction signature");
|
|
513
|
+
}
|
|
514
|
+
return { signature: response.signature };
|
|
515
|
+
}
|
|
516
|
+
async function launchEncryptedWalletRequest(definition, method, options, payload) {
|
|
517
|
+
const session = getStoredSession(definition.id);
|
|
518
|
+
if (!session) {
|
|
519
|
+
throw new Error("Connect the iOS wallet before signing");
|
|
520
|
+
}
|
|
521
|
+
const methodUrl = getIosWalletMethodUrl(definition, method);
|
|
522
|
+
const pending = createPendingRequest(
|
|
523
|
+
definition.id,
|
|
524
|
+
method,
|
|
525
|
+
{
|
|
526
|
+
publicKey: Uint8Array.from(getBase58Encoder().encode(session.dappEncryptionPublicKey)),
|
|
527
|
+
secretKey: Uint8Array.from(getBase58Encoder().encode(session.dappEncryptionSecretKey))
|
|
528
|
+
},
|
|
529
|
+
options.redirectUrl,
|
|
530
|
+
method === "signAllTransactions" && Array.isArray(payload.transactions) ? payload.transactions.length : void 0
|
|
531
|
+
);
|
|
532
|
+
const nonce = getNacl().randomBytes(getNacl().box.nonceLength);
|
|
533
|
+
const encryptedPayload = encryptPayload(
|
|
534
|
+
{ ...payload, session: session.session },
|
|
535
|
+
nonce,
|
|
536
|
+
Uint8Array.from(getBase58Encoder().encode(session.sharedSecret))
|
|
537
|
+
);
|
|
538
|
+
const url = new URL(methodUrl);
|
|
539
|
+
url.searchParams.set("dapp_encryption_public_key", session.dappEncryptionPublicKey);
|
|
540
|
+
url.searchParams.set("nonce", getBase58Decoder().decode(nonce));
|
|
541
|
+
url.searchParams.set("redirect_link", pending.redirectUrl);
|
|
542
|
+
url.searchParams.set("payload", encryptedPayload);
|
|
543
|
+
storePendingRequest(pending);
|
|
544
|
+
openIosWalletUrl(url.toString());
|
|
545
|
+
return waitForRedirect();
|
|
546
|
+
}
|
|
547
|
+
function getIosWalletMethodUrl(definition, method) {
|
|
548
|
+
const url = definition[`${method}Url`];
|
|
549
|
+
if (!url) {
|
|
550
|
+
throw new Error(`${definition.name} does not support ${method} through iOS deeplinks`);
|
|
551
|
+
}
|
|
552
|
+
return url;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function getSolanaIosWallets(options = {}) {
|
|
556
|
+
if (!isSolanaIosBrowserWalletSupported()) {
|
|
557
|
+
return [];
|
|
558
|
+
}
|
|
559
|
+
const chains = options.chains?.length ? options.chains : DEFAULT_IOS_WALLET_CHAINS;
|
|
560
|
+
const callbackUrl = options.redirectUrl ?? getDefaultIosWalletRedirectUrl();
|
|
561
|
+
return IOS_WALLETS.map((wallet) => ({
|
|
562
|
+
name: wallet.name,
|
|
563
|
+
icon: wallet.icon,
|
|
564
|
+
chains,
|
|
565
|
+
platform: "mobile",
|
|
566
|
+
source: "deep-link",
|
|
567
|
+
appUrl: wallet.appUrl,
|
|
568
|
+
installUrl: wallet.installUrl,
|
|
569
|
+
callbackUrl,
|
|
570
|
+
capabilities: {
|
|
571
|
+
connect: true,
|
|
572
|
+
disconnect: true,
|
|
573
|
+
signTransaction: Boolean(wallet.signTransactionUrl),
|
|
574
|
+
signAllTransactions: Boolean(wallet.signAllTransactionsUrl),
|
|
575
|
+
signAndSendTransaction: Boolean(wallet.signAndSendTransactionUrl)
|
|
576
|
+
},
|
|
577
|
+
accounts: getStoredIosWalletAccount(wallet.id, chains),
|
|
578
|
+
wallet
|
|
579
|
+
}));
|
|
580
|
+
}
|
|
581
|
+
function adaptSolanaIosWallet(walletInfo, options = {}) {
|
|
582
|
+
const definition = getIosWalletDefinition(walletInfo);
|
|
583
|
+
let connecting = false;
|
|
584
|
+
let disconnecting = false;
|
|
585
|
+
let session = getStoredSession(definition.id);
|
|
586
|
+
function refreshSession() {
|
|
587
|
+
session = getStoredSession(definition.id);
|
|
588
|
+
options.onChange?.();
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
get publicKey() {
|
|
592
|
+
return session?.publicKey ? session.publicKey : null;
|
|
593
|
+
},
|
|
594
|
+
get connected() {
|
|
595
|
+
return Boolean(session?.publicKey && session.session);
|
|
596
|
+
},
|
|
597
|
+
get connecting() {
|
|
598
|
+
return connecting;
|
|
599
|
+
},
|
|
600
|
+
get disconnecting() {
|
|
601
|
+
return disconnecting;
|
|
602
|
+
},
|
|
603
|
+
async connect() {
|
|
604
|
+
const callback = handleSolanaIosWalletCallback({ clearUrl: true });
|
|
605
|
+
if (callback?.walletId === definition.id) {
|
|
606
|
+
refreshSession();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
connecting = true;
|
|
610
|
+
options.onChange?.();
|
|
611
|
+
try {
|
|
612
|
+
launchConnect(definition, options);
|
|
613
|
+
await waitForRedirect();
|
|
614
|
+
} finally {
|
|
615
|
+
connecting = false;
|
|
616
|
+
options.onChange?.();
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
async disconnect() {
|
|
620
|
+
disconnecting = true;
|
|
621
|
+
options.onChange?.();
|
|
622
|
+
try {
|
|
623
|
+
removeStoredSession(definition.id);
|
|
624
|
+
refreshSession();
|
|
625
|
+
} finally {
|
|
626
|
+
disconnecting = false;
|
|
627
|
+
options.onChange?.();
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
signTransaction: definition.signTransactionUrl ? async (transaction) => {
|
|
631
|
+
return launchSignTransaction(definition, transaction, options);
|
|
632
|
+
} : void 0,
|
|
633
|
+
signAllTransactions: definition.signAllTransactionsUrl ? async (transactions) => {
|
|
634
|
+
const signedTransactions = await launchSignAllTransactions(
|
|
635
|
+
definition,
|
|
636
|
+
transactions,
|
|
637
|
+
options
|
|
638
|
+
);
|
|
639
|
+
if (signedTransactions.length !== transactions.length) {
|
|
640
|
+
throw new Error(
|
|
641
|
+
`iOS wallet returned ${signedTransactions.length} signed transactions for ${transactions.length} requested transactions`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return signedTransactions;
|
|
645
|
+
} : void 0,
|
|
646
|
+
signAndSendTransaction: definition.signAndSendTransactionUrl ? async (transaction, sendOptions) => launchSignAndSendTransaction(definition, transaction, sendOptions, options) : void 0
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function isSolanaIosWalletInfo(walletInfo) {
|
|
650
|
+
return walletInfo.source === "deep-link" && isIosWalletDefinition(walletInfo.wallet);
|
|
651
|
+
}
|
|
652
|
+
function getIosWalletDefinition(walletInfo) {
|
|
653
|
+
if (!isIosWalletDefinition(walletInfo.wallet)) {
|
|
654
|
+
throw new Error("Solana wallet info is not an iOS wallet");
|
|
655
|
+
}
|
|
656
|
+
return walletInfo.wallet;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
export { adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo };
|
package/dist/kit.d.ts
CHANGED