@vue-solana/core 0.3.3 → 0.4.1
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 +2 -2
- package/dist/index.cjs +12 -2
- package/dist/index.d.cts +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +4 -2
- package/dist/ios-wallet.cjs +543 -0
- package/dist/ios-wallet.d.cts +42 -0
- package/dist/ios-wallet.d.mts +42 -0
- package/dist/ios-wallet.d.ts +42 -0
- package/dist/ios-wallet.mjs +517 -0
- package/dist/types.d.cts +7 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -112,17 +112,17 @@ const wallet: SolanaWallet = {
|
|
|
112
112
|
};
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
Browser extension wallets discovered through the Solana Wallet Standard are adapted into `SolanaWallet`. Android Mobile Wallet Adapter is registered through `@solana-mobile/wallet-standard-mobile` and then adapted through the same Wallet Standard adapter on supported Android Chrome clients. You can also provide a custom object that implements `SolanaWallet` for tests or custom adapters.
|
|
115
|
+
Browser extension wallets discovered through the Solana Wallet Standard are adapted into `SolanaWallet`. Android Mobile Wallet Adapter is registered through `@solana-mobile/wallet-standard-mobile` and then adapted through the same Wallet Standard adapter on supported Android Chrome clients. iOS browser wallet entries for Phantom, Solflare, and Backpack are adapted through wallet-specific universal links. You can also provide a custom object that implements `SolanaWallet` for tests or custom adapters.
|
|
116
116
|
|
|
117
117
|
Current wallet support:
|
|
118
118
|
|
|
119
119
|
- Browser extension wallets through Wallet Standard packages.
|
|
120
120
|
- Android native mobile wallets through `@solana-mobile/wallet-standard-mobile` on Android Chrome and Chrome PWAs.
|
|
121
|
+
- iOS browser wallets for Phantom, Solflare, and Backpack through wallet-specific universal links.
|
|
121
122
|
- Manual/custom wallet objects that implement `SolanaWallet`.
|
|
122
123
|
|
|
123
124
|
Planned but not supported yet:
|
|
124
125
|
|
|
125
|
-
- iOS browser wallets through wallet-specific universal link or deep link adapters.
|
|
126
126
|
- Desktop native app wallets through wallet-specific protocol links or future native Wallet Standard registration.
|
|
127
127
|
|
|
128
128
|
## Examples
|
package/dist/index.cjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const clusters = require('./clusters.cjs');
|
|
4
|
+
const iosWallet = require('./ios-wallet.cjs');
|
|
4
5
|
const mobileWallet = require('./mobile-wallet.cjs');
|
|
5
6
|
const rpc = require('./rpc.cjs');
|
|
6
7
|
const transaction = require('./transaction.cjs');
|
|
7
8
|
const wallet = require('./wallet.cjs');
|
|
8
9
|
const walletStandard = require('./wallet-standard.cjs');
|
|
9
|
-
require('@solana-mobile/wallet-standard-mobile');
|
|
10
10
|
require('@solana/web3-compat');
|
|
11
|
+
require('bs58');
|
|
12
|
+
require('tweetnacl');
|
|
13
|
+
require('@solana-mobile/wallet-standard-mobile');
|
|
11
14
|
require('@wallet-standard/app');
|
|
12
15
|
require('@wallet-standard/features');
|
|
13
16
|
require('@solana/wallet-standard-features');
|
|
14
|
-
require('bs58');
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
|
|
@@ -19,6 +21,14 @@ exports.DEFAULT_CLUSTER = clusters.DEFAULT_CLUSTER;
|
|
|
19
21
|
exports.getClusterEndpoint = clusters.getClusterEndpoint;
|
|
20
22
|
exports.getClusterWebSocketEndpoint = clusters.getClusterWebSocketEndpoint;
|
|
21
23
|
exports.getWebSocketEndpoint = clusters.getWebSocketEndpoint;
|
|
24
|
+
exports.adaptSolanaIosWallet = iosWallet.adaptSolanaIosWallet;
|
|
25
|
+
exports.getDefaultIosWalletAppIdentity = iosWallet.getDefaultIosWalletAppIdentity;
|
|
26
|
+
exports.getDefaultIosWalletRedirectUrl = iosWallet.getDefaultIosWalletRedirectUrl;
|
|
27
|
+
exports.getSolanaIosWallets = iosWallet.getSolanaIosWallets;
|
|
28
|
+
exports.handleSolanaIosWalletCallback = iosWallet.handleSolanaIosWalletCallback;
|
|
29
|
+
exports.isIosBrowser = iosWallet.isIosBrowser;
|
|
30
|
+
exports.isSolanaIosBrowserWalletSupported = iosWallet.isSolanaIosBrowserWalletSupported;
|
|
31
|
+
exports.isSolanaIosWalletInfo = iosWallet.isSolanaIosWalletInfo;
|
|
22
32
|
exports.getDefaultMobileWalletAppIdentity = mobileWallet.getDefaultMobileWalletAppIdentity;
|
|
23
33
|
exports.isSolanaMobileWalletSupported = mobileWallet.isSolanaMobileWalletSupported;
|
|
24
34
|
exports.registerSolanaMobileWallet = mobileWallet.registerSolanaMobileWallet;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { DEFAULT_CLUSTER, getClusterEndpoint, getClusterWebSocketEndpoint, getWebSocketEndpoint } from './clusters.cjs';
|
|
2
|
+
export { AdaptSolanaIosWalletOptions, GetSolanaIosWalletsOptions, SolanaIosWalletAppIdentity, adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo } from './ios-wallet.cjs';
|
|
2
3
|
export { RegisterSolanaMobileWalletOptions, SolanaMobileWalletAppIdentity, getDefaultMobileWalletAppIdentity, isSolanaMobileWalletSupported, registerSolanaMobileWallet } from './mobile-wallet.cjs';
|
|
3
4
|
export { createSolanaConnection, createSolanaContext } from './rpc.cjs';
|
|
4
5
|
export { signAndSendTransaction } from './transaction.cjs';
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { DEFAULT_CLUSTER, getClusterEndpoint, getClusterWebSocketEndpoint, getWebSocketEndpoint } from './clusters.mjs';
|
|
2
|
+
export { AdaptSolanaIosWalletOptions, GetSolanaIosWalletsOptions, SolanaIosWalletAppIdentity, adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo } from './ios-wallet.mjs';
|
|
2
3
|
export { RegisterSolanaMobileWalletOptions, SolanaMobileWalletAppIdentity, getDefaultMobileWalletAppIdentity, isSolanaMobileWalletSupported, registerSolanaMobileWallet } from './mobile-wallet.mjs';
|
|
3
4
|
export { createSolanaConnection, createSolanaContext } from './rpc.mjs';
|
|
4
5
|
export { signAndSendTransaction } from './transaction.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { DEFAULT_CLUSTER, getClusterEndpoint, getClusterWebSocketEndpoint, getWebSocketEndpoint } from './clusters.js';
|
|
2
|
+
export { AdaptSolanaIosWalletOptions, GetSolanaIosWalletsOptions, SolanaIosWalletAppIdentity, adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo } from './ios-wallet.js';
|
|
2
3
|
export { RegisterSolanaMobileWalletOptions, SolanaMobileWalletAppIdentity, getDefaultMobileWalletAppIdentity, isSolanaMobileWalletSupported, registerSolanaMobileWallet } from './mobile-wallet.js';
|
|
3
4
|
export { createSolanaConnection, createSolanaContext } from './rpc.js';
|
|
4
5
|
export { signAndSendTransaction } from './transaction.js';
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export { DEFAULT_CLUSTER, getClusterEndpoint, getClusterWebSocketEndpoint, getWebSocketEndpoint } from './clusters.mjs';
|
|
2
|
+
export { adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo } from './ios-wallet.mjs';
|
|
2
3
|
export { getDefaultMobileWalletAppIdentity, isSolanaMobileWalletSupported, registerSolanaMobileWallet } from './mobile-wallet.mjs';
|
|
3
4
|
export { createSolanaConnection, createSolanaContext } from './rpc.mjs';
|
|
4
5
|
export { signAndSendTransaction } from './transaction.mjs';
|
|
5
6
|
export { assertWalletCanSign, assertWalletConnected, isWalletConnected } from './wallet.mjs';
|
|
6
7
|
export { SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, adaptSolanaStandardWallet, getRegisteredSolanaWallets, getSolanaChain, isSolanaStandardWallet, subscribeSolanaWallets } from './wallet-standard.mjs';
|
|
7
|
-
import '@solana-mobile/wallet-standard-mobile';
|
|
8
8
|
import '@solana/web3-compat';
|
|
9
|
+
import 'bs58';
|
|
10
|
+
import 'tweetnacl';
|
|
11
|
+
import '@solana-mobile/wallet-standard-mobile';
|
|
9
12
|
import '@wallet-standard/app';
|
|
10
13
|
import '@wallet-standard/features';
|
|
11
14
|
import '@solana/wallet-standard-features';
|
|
12
|
-
import 'bs58';
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const web3Compat = require('@solana/web3-compat');
|
|
4
|
+
const bs58 = require('bs58');
|
|
5
|
+
const naclModule = require('tweetnacl');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
8
|
+
|
|
9
|
+
function _interopNamespaceCompat(e) {
|
|
10
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
11
|
+
const n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
for (const k in e) {
|
|
14
|
+
n[k] = e[k];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
n.default = e;
|
|
18
|
+
return n;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const bs58__default = /*#__PURE__*/_interopDefaultCompat(bs58);
|
|
22
|
+
const naclModule__namespace = /*#__PURE__*/_interopNamespaceCompat(naclModule);
|
|
23
|
+
|
|
24
|
+
const nacl = resolveTweetNaCl();
|
|
25
|
+
function resolveTweetNaCl() {
|
|
26
|
+
const moduleDefault = naclModule__namespace.default;
|
|
27
|
+
const globalNacl = globalThis.nacl;
|
|
28
|
+
return "box" in naclModule__namespace ? naclModule__namespace : moduleDefault ?? globalNacl;
|
|
29
|
+
}
|
|
30
|
+
const DEFAULT_IOS_WALLET_CHAINS = ["solana:mainnet", "solana:devnet"];
|
|
31
|
+
const PENDING_REQUEST_KEY = "vue-solana:ios-wallet:pending";
|
|
32
|
+
const SESSION_PREFIX = "vue-solana:ios-wallet:session:";
|
|
33
|
+
const CALLBACK_PARAMS = /* @__PURE__ */ new Set([
|
|
34
|
+
"data",
|
|
35
|
+
"nonce",
|
|
36
|
+
"errorCode",
|
|
37
|
+
"errorMessage",
|
|
38
|
+
"phantom_encryption_public_key",
|
|
39
|
+
"solflare_encryption_public_key",
|
|
40
|
+
"wallet_encryption_public_key"
|
|
41
|
+
]);
|
|
42
|
+
const IOS_WALLETS = [
|
|
43
|
+
{
|
|
44
|
+
id: "phantom",
|
|
45
|
+
name: "Phantom",
|
|
46
|
+
icon: "https://phantom.app/img/phantom-logo.svg",
|
|
47
|
+
appUrl: "https://phantom.app",
|
|
48
|
+
installUrl: "https://phantom.app/download",
|
|
49
|
+
encryptionPublicKeyParam: "phantom_encryption_public_key",
|
|
50
|
+
connectUrl: "https://phantom.app/ul/v1/connect",
|
|
51
|
+
signTransactionUrl: "https://phantom.app/ul/v1/signTransaction",
|
|
52
|
+
signAllTransactionsUrl: "https://phantom.app/ul/v1/signAllTransactions"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "solflare",
|
|
56
|
+
name: "Solflare",
|
|
57
|
+
icon: "https://solflare.com/favicon.ico",
|
|
58
|
+
appUrl: "https://solflare.com",
|
|
59
|
+
installUrl: "https://solflare.com/download",
|
|
60
|
+
encryptionPublicKeyParam: "solflare_encryption_public_key",
|
|
61
|
+
connectUrl: "https://solflare.com/ul/v1/connect",
|
|
62
|
+
signTransactionUrl: "https://solflare.com/ul/v1/signTransaction",
|
|
63
|
+
signAllTransactionsUrl: "https://solflare.com/ul/v1/signAllTransactions",
|
|
64
|
+
signAndSendTransactionUrl: "https://solflare.com/ul/v1/signAndSendTransaction"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "backpack",
|
|
68
|
+
name: "Backpack",
|
|
69
|
+
icon: "https://backpack.app/favicon.ico",
|
|
70
|
+
appUrl: "https://backpack.app",
|
|
71
|
+
installUrl: "https://backpack.app/download",
|
|
72
|
+
encryptionPublicKeyParam: "wallet_encryption_public_key",
|
|
73
|
+
connectUrl: "https://backpack.app/ul/v1/connect",
|
|
74
|
+
signTransactionUrl: "https://backpack.app/ul/v1/signTransaction",
|
|
75
|
+
signAllTransactionsUrl: "https://backpack.app/ul/v1/signAllTransactions",
|
|
76
|
+
signAndSendTransactionUrl: "https://backpack.app/ul/v1/signAndSendTransaction"
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
function getSolanaIosWallets(options = {}) {
|
|
80
|
+
if (!isSolanaIosBrowserWalletSupported()) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const chains = options.chains?.length ? options.chains : DEFAULT_IOS_WALLET_CHAINS;
|
|
84
|
+
const callbackUrl = options.redirectUrl ?? getDefaultIosWalletRedirectUrl();
|
|
85
|
+
return IOS_WALLETS.map((wallet) => ({
|
|
86
|
+
name: wallet.name,
|
|
87
|
+
icon: wallet.icon,
|
|
88
|
+
chains,
|
|
89
|
+
platform: "mobile",
|
|
90
|
+
source: "deep-link",
|
|
91
|
+
appUrl: wallet.appUrl,
|
|
92
|
+
installUrl: wallet.installUrl,
|
|
93
|
+
callbackUrl,
|
|
94
|
+
capabilities: {
|
|
95
|
+
connect: true,
|
|
96
|
+
signTransaction: Boolean(wallet.signTransactionUrl),
|
|
97
|
+
signAllTransactions: Boolean(wallet.signAllTransactionsUrl),
|
|
98
|
+
signAndSendTransaction: Boolean(wallet.signAndSendTransactionUrl)
|
|
99
|
+
},
|
|
100
|
+
accounts: getStoredIosWalletAccount(wallet.id, chains),
|
|
101
|
+
wallet
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
function adaptSolanaIosWallet(walletInfo, options = {}) {
|
|
105
|
+
const definition = getIosWalletDefinition(walletInfo);
|
|
106
|
+
let connecting = false;
|
|
107
|
+
let disconnecting = false;
|
|
108
|
+
let session = getStoredSession(definition.id);
|
|
109
|
+
function refreshSession() {
|
|
110
|
+
session = getStoredSession(definition.id);
|
|
111
|
+
options.onChange?.();
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
get publicKey() {
|
|
115
|
+
return session?.publicKey ? new web3Compat.PublicKey(session.publicKey) : null;
|
|
116
|
+
},
|
|
117
|
+
get connected() {
|
|
118
|
+
return Boolean(session?.publicKey && session.session);
|
|
119
|
+
},
|
|
120
|
+
get connecting() {
|
|
121
|
+
return connecting;
|
|
122
|
+
},
|
|
123
|
+
get disconnecting() {
|
|
124
|
+
return disconnecting;
|
|
125
|
+
},
|
|
126
|
+
async connect() {
|
|
127
|
+
const callback = handleSolanaIosWalletCallback({ clearUrl: true });
|
|
128
|
+
if (callback?.walletId === definition.id) {
|
|
129
|
+
refreshSession();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
connecting = true;
|
|
133
|
+
options.onChange?.();
|
|
134
|
+
try {
|
|
135
|
+
launchConnect(definition, options);
|
|
136
|
+
await waitForRedirect();
|
|
137
|
+
} finally {
|
|
138
|
+
connecting = false;
|
|
139
|
+
options.onChange?.();
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
async disconnect() {
|
|
143
|
+
disconnecting = true;
|
|
144
|
+
options.onChange?.();
|
|
145
|
+
try {
|
|
146
|
+
removeStoredSession(definition.id);
|
|
147
|
+
refreshSession();
|
|
148
|
+
} finally {
|
|
149
|
+
disconnecting = false;
|
|
150
|
+
options.onChange?.();
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
signTransaction: definition.signTransactionUrl ? async (transaction) => {
|
|
154
|
+
const signedTransaction = await launchSignTransaction(definition, transaction, options);
|
|
155
|
+
return deserializeTransaction(transaction, signedTransaction);
|
|
156
|
+
} : void 0,
|
|
157
|
+
signAllTransactions: definition.signAllTransactionsUrl ? async (transactions) => {
|
|
158
|
+
const signedTransactions = await launchSignAllTransactions(
|
|
159
|
+
definition,
|
|
160
|
+
transactions,
|
|
161
|
+
options
|
|
162
|
+
);
|
|
163
|
+
return signedTransactions.map(
|
|
164
|
+
(transaction, index) => deserializeTransaction(transactions[index], transaction)
|
|
165
|
+
);
|
|
166
|
+
} : void 0,
|
|
167
|
+
signAndSendTransaction: definition.signAndSendTransactionUrl ? async (transaction, sendOptions) => launchSignAndSendTransaction(definition, transaction, sendOptions, options) : void 0
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function handleSolanaIosWalletCallback(options = {}) {
|
|
171
|
+
if (typeof window === "undefined") {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const url = new URL(window.location.href);
|
|
175
|
+
if (!hasIosWalletCallbackParams(url)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const errorCode = url.searchParams.get("errorCode");
|
|
179
|
+
const errorMessage = url.searchParams.get("errorMessage");
|
|
180
|
+
if (errorCode || errorMessage) {
|
|
181
|
+
clearPendingRequest();
|
|
182
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
183
|
+
throw new Error(errorMessage || `iOS wallet request failed: ${errorCode}`);
|
|
184
|
+
}
|
|
185
|
+
const pending = getPendingRequest();
|
|
186
|
+
if (!pending) {
|
|
187
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
188
|
+
throw new Error("Received an iOS wallet callback without a pending request");
|
|
189
|
+
}
|
|
190
|
+
const definition = getIosWalletDefinitionById(pending.walletId);
|
|
191
|
+
const walletEncryptionPublicKey = url.searchParams.get(definition.encryptionPublicKeyParam);
|
|
192
|
+
const nonce = url.searchParams.get("nonce");
|
|
193
|
+
const data = url.searchParams.get("data");
|
|
194
|
+
if (!nonce || !data) {
|
|
195
|
+
throw new Error("Received an incomplete iOS wallet callback");
|
|
196
|
+
}
|
|
197
|
+
const sharedSecret = getSharedSecret(
|
|
198
|
+
walletEncryptionPublicKey ?? getStoredSession(pending.walletId)?.walletEncryptionPublicKey,
|
|
199
|
+
pending.dappEncryptionSecretKey
|
|
200
|
+
);
|
|
201
|
+
const payload = decryptPayload(data, nonce, sharedSecret);
|
|
202
|
+
const result = createCallbackResult(pending, payload);
|
|
203
|
+
if (pending.method === "connect") {
|
|
204
|
+
if (!walletEncryptionPublicKey) {
|
|
205
|
+
throw new Error("iOS wallet connect callback did not include an encryption public key");
|
|
206
|
+
}
|
|
207
|
+
storeSession({
|
|
208
|
+
walletId: pending.walletId,
|
|
209
|
+
publicKey: getStringField(payload, "public_key"),
|
|
210
|
+
session: getStringField(payload, "session"),
|
|
211
|
+
dappEncryptionPublicKey: pending.dappEncryptionPublicKey,
|
|
212
|
+
dappEncryptionSecretKey: pending.dappEncryptionSecretKey,
|
|
213
|
+
walletEncryptionPublicKey,
|
|
214
|
+
sharedSecret: bs58__default.encode(sharedSecret)
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
clearPendingRequest();
|
|
218
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
function isSolanaIosWalletInfo(walletInfo) {
|
|
222
|
+
return walletInfo.source === "deep-link" && isIosWalletDefinition(walletInfo.wallet);
|
|
223
|
+
}
|
|
224
|
+
function isSolanaIosBrowserWalletSupported() {
|
|
225
|
+
return isIosBrowser();
|
|
226
|
+
}
|
|
227
|
+
function isIosBrowser() {
|
|
228
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const userAgent = navigator.userAgent;
|
|
232
|
+
const platform = navigator.platform;
|
|
233
|
+
const maxTouchPoints = navigator.maxTouchPoints ?? 0;
|
|
234
|
+
return /iPad|iPhone|iPod/i.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1;
|
|
235
|
+
}
|
|
236
|
+
function getDefaultIosWalletAppIdentity() {
|
|
237
|
+
if (typeof window === "undefined") {
|
|
238
|
+
return {
|
|
239
|
+
name: "Vue Solana App",
|
|
240
|
+
uri: "https://localhost"
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
name: document.title || "Vue Solana App",
|
|
245
|
+
uri: window.location.origin,
|
|
246
|
+
icon: getDocumentIconUrl()
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function getDefaultIosWalletRedirectUrl() {
|
|
250
|
+
if (typeof window === "undefined") {
|
|
251
|
+
return "https://localhost";
|
|
252
|
+
}
|
|
253
|
+
const url = new URL(window.location.href);
|
|
254
|
+
for (const param of CALLBACK_PARAMS) {
|
|
255
|
+
url.searchParams.delete(param);
|
|
256
|
+
}
|
|
257
|
+
return url.toString();
|
|
258
|
+
}
|
|
259
|
+
function launchConnect(definition, options) {
|
|
260
|
+
const keyPair = nacl.box.keyPair();
|
|
261
|
+
const pending = createPendingRequest(definition.id, "connect", keyPair, options.redirectUrl);
|
|
262
|
+
const appIdentity = options.appIdentity ?? getDefaultIosWalletAppIdentity();
|
|
263
|
+
const url = new URL(definition.connectUrl);
|
|
264
|
+
url.searchParams.set("app_url", appIdentity.uri);
|
|
265
|
+
url.searchParams.set("dapp_encryption_public_key", pending.dappEncryptionPublicKey);
|
|
266
|
+
url.searchParams.set("redirect_link", pending.redirectUrl);
|
|
267
|
+
if (options.cluster && options.cluster !== "localnet") {
|
|
268
|
+
url.searchParams.set("cluster", options.cluster);
|
|
269
|
+
}
|
|
270
|
+
storePendingRequest(pending);
|
|
271
|
+
openIosWalletUrl(url.toString());
|
|
272
|
+
}
|
|
273
|
+
async function launchSignTransaction(definition, transaction, options) {
|
|
274
|
+
const response = await launchEncryptedWalletRequest(definition, "signTransaction", options, {
|
|
275
|
+
transaction: bs58__default.encode(serializeTransaction(transaction))
|
|
276
|
+
});
|
|
277
|
+
if (!response.transaction) {
|
|
278
|
+
throw new Error("iOS wallet did not return a signed transaction");
|
|
279
|
+
}
|
|
280
|
+
return response.transaction;
|
|
281
|
+
}
|
|
282
|
+
async function launchSignAllTransactions(definition, transactions, options) {
|
|
283
|
+
const response = await launchEncryptedWalletRequest(definition, "signAllTransactions", options, {
|
|
284
|
+
transactions: transactions.map((transaction) => bs58__default.encode(serializeTransaction(transaction)))
|
|
285
|
+
});
|
|
286
|
+
if (!response.transactions) {
|
|
287
|
+
throw new Error("iOS wallet did not return signed transactions");
|
|
288
|
+
}
|
|
289
|
+
return response.transactions;
|
|
290
|
+
}
|
|
291
|
+
async function launchSignAndSendTransaction(definition, transaction, sendOptions, options) {
|
|
292
|
+
const response = await launchEncryptedWalletRequest(
|
|
293
|
+
definition,
|
|
294
|
+
"signAndSendTransaction",
|
|
295
|
+
options,
|
|
296
|
+
{
|
|
297
|
+
transaction: bs58__default.encode(serializeTransaction(transaction)),
|
|
298
|
+
sendOptions
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
if (!response.signature) {
|
|
302
|
+
throw new Error("iOS wallet did not return a transaction signature");
|
|
303
|
+
}
|
|
304
|
+
return { signature: response.signature };
|
|
305
|
+
}
|
|
306
|
+
async function launchEncryptedWalletRequest(definition, method, options, payload) {
|
|
307
|
+
const session = getStoredSession(definition.id);
|
|
308
|
+
if (!session) {
|
|
309
|
+
throw new Error("Connect the iOS wallet before signing");
|
|
310
|
+
}
|
|
311
|
+
const methodUrl = getIosWalletMethodUrl(definition, method);
|
|
312
|
+
const pending = createPendingRequest(
|
|
313
|
+
definition.id,
|
|
314
|
+
method,
|
|
315
|
+
{
|
|
316
|
+
publicKey: bs58__default.decode(session.dappEncryptionPublicKey),
|
|
317
|
+
secretKey: bs58__default.decode(session.dappEncryptionSecretKey)
|
|
318
|
+
},
|
|
319
|
+
options.redirectUrl
|
|
320
|
+
);
|
|
321
|
+
const nonce = nacl.randomBytes(nacl.box.nonceLength);
|
|
322
|
+
const encryptedPayload = encryptPayload(
|
|
323
|
+
{ ...payload, session: session.session },
|
|
324
|
+
nonce,
|
|
325
|
+
bs58__default.decode(session.sharedSecret)
|
|
326
|
+
);
|
|
327
|
+
const url = new URL(methodUrl);
|
|
328
|
+
url.searchParams.set("dapp_encryption_public_key", session.dappEncryptionPublicKey);
|
|
329
|
+
url.searchParams.set("nonce", bs58__default.encode(nonce));
|
|
330
|
+
url.searchParams.set("redirect_link", pending.redirectUrl);
|
|
331
|
+
url.searchParams.set("payload", encryptedPayload);
|
|
332
|
+
storePendingRequest(pending);
|
|
333
|
+
openIosWalletUrl(url.toString());
|
|
334
|
+
return waitForRedirect();
|
|
335
|
+
}
|
|
336
|
+
function createPendingRequest(walletId, method, keyPair, redirectUrl) {
|
|
337
|
+
return {
|
|
338
|
+
id: createRequestId(),
|
|
339
|
+
walletId,
|
|
340
|
+
method,
|
|
341
|
+
dappEncryptionPublicKey: bs58__default.encode(keyPair.publicKey),
|
|
342
|
+
dappEncryptionSecretKey: bs58__default.encode(keyPair.secretKey),
|
|
343
|
+
redirectUrl: redirectUrl ?? getDefaultIosWalletRedirectUrl(),
|
|
344
|
+
createdAt: Date.now()
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function createCallbackResult(pending, payload) {
|
|
348
|
+
if (pending.method === "connect") {
|
|
349
|
+
return {
|
|
350
|
+
walletId: pending.walletId,
|
|
351
|
+
method: pending.method,
|
|
352
|
+
publicKey: getStringField(payload, "public_key")
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (pending.method === "signTransaction") {
|
|
356
|
+
return {
|
|
357
|
+
walletId: pending.walletId,
|
|
358
|
+
method: pending.method,
|
|
359
|
+
transaction: bs58__default.decode(getStringField(payload, "transaction"))
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (pending.method === "signAllTransactions") {
|
|
363
|
+
const transactions = payload.transactions;
|
|
364
|
+
if (!Array.isArray(transactions) || transactions.some((transaction) => typeof transaction !== "string")) {
|
|
365
|
+
throw new Error("iOS wallet returned invalid signed transactions");
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
walletId: pending.walletId,
|
|
369
|
+
method: pending.method,
|
|
370
|
+
transactions: transactions.map((transaction) => bs58__default.decode(transaction))
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
walletId: pending.walletId,
|
|
375
|
+
method: pending.method,
|
|
376
|
+
signature: getStringField(payload, "signature")
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function getIosWalletMethodUrl(definition, method) {
|
|
380
|
+
const url = definition[`${method}Url`];
|
|
381
|
+
if (!url) {
|
|
382
|
+
throw new Error(`${definition.name} does not support ${method} through iOS deeplinks`);
|
|
383
|
+
}
|
|
384
|
+
return url;
|
|
385
|
+
}
|
|
386
|
+
function getSharedSecret(walletEncryptionPublicKey, dappSecretKey) {
|
|
387
|
+
if (!walletEncryptionPublicKey) {
|
|
388
|
+
throw new Error("Missing iOS wallet encryption public key");
|
|
389
|
+
}
|
|
390
|
+
return nacl.box.before(bs58__default.decode(walletEncryptionPublicKey), bs58__default.decode(dappSecretKey));
|
|
391
|
+
}
|
|
392
|
+
function encryptPayload(payload, nonce, sharedSecret) {
|
|
393
|
+
return bs58__default.encode(nacl.box.after(encodeJson(payload), nonce, sharedSecret));
|
|
394
|
+
}
|
|
395
|
+
function decryptPayload(data, nonce, sharedSecret) {
|
|
396
|
+
const decrypted = nacl.box.open.after(bs58__default.decode(data), bs58__default.decode(nonce), sharedSecret);
|
|
397
|
+
if (!decrypted) {
|
|
398
|
+
throw new Error("Unable to decrypt iOS wallet callback");
|
|
399
|
+
}
|
|
400
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
401
|
+
}
|
|
402
|
+
function encodeJson(value) {
|
|
403
|
+
return new TextEncoder().encode(JSON.stringify(value));
|
|
404
|
+
}
|
|
405
|
+
function serializeTransaction(transaction) {
|
|
406
|
+
if (transaction instanceof web3Compat.Transaction) {
|
|
407
|
+
return transaction.serialize({ requireAllSignatures: false, verifySignatures: false });
|
|
408
|
+
}
|
|
409
|
+
return transaction.serialize();
|
|
410
|
+
}
|
|
411
|
+
function deserializeTransaction(source, bytes) {
|
|
412
|
+
if (source instanceof web3Compat.Transaction) {
|
|
413
|
+
return web3Compat.Transaction.from(bytes);
|
|
414
|
+
}
|
|
415
|
+
return web3Compat.VersionedTransaction.deserialize(bytes);
|
|
416
|
+
}
|
|
417
|
+
function getStoredIosWalletAccount(walletId, chains) {
|
|
418
|
+
const session = getStoredSession(walletId);
|
|
419
|
+
if (!session) {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
return [
|
|
423
|
+
{
|
|
424
|
+
address: session.publicKey,
|
|
425
|
+
publicKey: new web3Compat.PublicKey(session.publicKey).toBytes(),
|
|
426
|
+
chains
|
|
427
|
+
}
|
|
428
|
+
];
|
|
429
|
+
}
|
|
430
|
+
function getStoredSession(walletId) {
|
|
431
|
+
const value = getStorage()?.getItem(`${SESSION_PREFIX}${walletId}`);
|
|
432
|
+
if (!value) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
return JSON.parse(value);
|
|
437
|
+
} catch {
|
|
438
|
+
removeStoredSession(walletId);
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function storeSession(session) {
|
|
443
|
+
getStorage()?.setItem(`${SESSION_PREFIX}${session.walletId}`, JSON.stringify(session));
|
|
444
|
+
}
|
|
445
|
+
function removeStoredSession(walletId) {
|
|
446
|
+
getStorage()?.removeItem(`${SESSION_PREFIX}${walletId}`);
|
|
447
|
+
}
|
|
448
|
+
function getPendingRequest() {
|
|
449
|
+
const value = getStorage()?.getItem(PENDING_REQUEST_KEY);
|
|
450
|
+
if (!value) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
return JSON.parse(value);
|
|
455
|
+
} catch {
|
|
456
|
+
clearPendingRequest();
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function storePendingRequest(request) {
|
|
461
|
+
getStorage()?.setItem(PENDING_REQUEST_KEY, JSON.stringify(request));
|
|
462
|
+
}
|
|
463
|
+
function clearPendingRequest() {
|
|
464
|
+
getStorage()?.removeItem(PENDING_REQUEST_KEY);
|
|
465
|
+
}
|
|
466
|
+
function getStorage() {
|
|
467
|
+
if (typeof window === "undefined") {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
return window.sessionStorage;
|
|
472
|
+
} catch {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function openIosWalletUrl(url) {
|
|
477
|
+
if (typeof window === "undefined") {
|
|
478
|
+
throw new Error("iOS wallet links are only available in browser runtimes");
|
|
479
|
+
}
|
|
480
|
+
window.location.assign(url);
|
|
481
|
+
}
|
|
482
|
+
function waitForRedirect() {
|
|
483
|
+
return new Promise(() => {
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
function hasIosWalletCallbackParams(url) {
|
|
487
|
+
for (const param of CALLBACK_PARAMS) {
|
|
488
|
+
if (url.searchParams.has(param)) {
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
function cleanCallbackUrl(url, clearUrl) {
|
|
495
|
+
if (!clearUrl || typeof window === "undefined") {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
for (const param of CALLBACK_PARAMS) {
|
|
499
|
+
url.searchParams.delete(param);
|
|
500
|
+
}
|
|
501
|
+
window.history.replaceState(window.history.state, document.title, url.toString());
|
|
502
|
+
}
|
|
503
|
+
function createRequestId() {
|
|
504
|
+
return bs58__default.encode(nacl.randomBytes(16));
|
|
505
|
+
}
|
|
506
|
+
function getStringField(payload, field) {
|
|
507
|
+
const value = payload[field];
|
|
508
|
+
if (typeof value !== "string" || !value) {
|
|
509
|
+
throw new Error(`iOS wallet callback is missing ${field}`);
|
|
510
|
+
}
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
513
|
+
function getIosWalletDefinition(walletInfo) {
|
|
514
|
+
if (!isIosWalletDefinition(walletInfo.wallet)) {
|
|
515
|
+
throw new Error("Solana wallet info is not an iOS wallet");
|
|
516
|
+
}
|
|
517
|
+
return walletInfo.wallet;
|
|
518
|
+
}
|
|
519
|
+
function getIosWalletDefinitionById(walletId) {
|
|
520
|
+
const definition = IOS_WALLETS.find((wallet) => wallet.id === walletId);
|
|
521
|
+
if (!definition) {
|
|
522
|
+
throw new Error(`Unknown iOS wallet: ${walletId}`);
|
|
523
|
+
}
|
|
524
|
+
return definition;
|
|
525
|
+
}
|
|
526
|
+
function isIosWalletDefinition(value) {
|
|
527
|
+
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && IOS_WALLETS.some((wallet) => wallet.id === value.id);
|
|
528
|
+
}
|
|
529
|
+
function getDocumentIconUrl() {
|
|
530
|
+
const icon = document.querySelector(
|
|
531
|
+
'link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'
|
|
532
|
+
);
|
|
533
|
+
return icon?.href;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
exports.adaptSolanaIosWallet = adaptSolanaIosWallet;
|
|
537
|
+
exports.getDefaultIosWalletAppIdentity = getDefaultIosWalletAppIdentity;
|
|
538
|
+
exports.getDefaultIosWalletRedirectUrl = getDefaultIosWalletRedirectUrl;
|
|
539
|
+
exports.getSolanaIosWallets = getSolanaIosWallets;
|
|
540
|
+
exports.handleSolanaIosWalletCallback = handleSolanaIosWalletCallback;
|
|
541
|
+
exports.isIosBrowser = isIosBrowser;
|
|
542
|
+
exports.isSolanaIosBrowserWalletSupported = isSolanaIosBrowserWalletSupported;
|
|
543
|
+
exports.isSolanaIosWalletInfo = isSolanaIosWalletInfo;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SolanaChain, SolanaCluster, SolanaWalletInfo, SolanaWallet } from './types.cjs';
|
|
2
|
+
import '@solana/web3-compat';
|
|
3
|
+
|
|
4
|
+
interface SolanaIosWalletAppIdentity {
|
|
5
|
+
name: string;
|
|
6
|
+
uri: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
}
|
|
9
|
+
interface GetSolanaIosWalletsOptions {
|
|
10
|
+
appIdentity?: SolanaIosWalletAppIdentity;
|
|
11
|
+
redirectUrl?: string;
|
|
12
|
+
chains?: readonly SolanaChain[];
|
|
13
|
+
cluster?: SolanaCluster;
|
|
14
|
+
}
|
|
15
|
+
interface AdaptSolanaIosWalletOptions {
|
|
16
|
+
appIdentity?: SolanaIosWalletAppIdentity;
|
|
17
|
+
redirectUrl?: string;
|
|
18
|
+
chain?: SolanaChain;
|
|
19
|
+
cluster?: SolanaCluster;
|
|
20
|
+
onChange?: () => void;
|
|
21
|
+
}
|
|
22
|
+
type IosWalletMethod = "connect" | "signTransaction" | "signAllTransactions" | "signAndSendTransaction";
|
|
23
|
+
declare function getSolanaIosWallets(options?: GetSolanaIosWalletsOptions): SolanaWalletInfo[];
|
|
24
|
+
declare function adaptSolanaIosWallet(walletInfo: SolanaWalletInfo, options?: AdaptSolanaIosWalletOptions): SolanaWallet;
|
|
25
|
+
declare function handleSolanaIosWalletCallback(options?: {
|
|
26
|
+
clearUrl?: boolean;
|
|
27
|
+
}): {
|
|
28
|
+
walletId: string;
|
|
29
|
+
method: IosWalletMethod;
|
|
30
|
+
publicKey?: string;
|
|
31
|
+
signature?: string;
|
|
32
|
+
transaction?: Uint8Array;
|
|
33
|
+
transactions?: Uint8Array[];
|
|
34
|
+
} | null;
|
|
35
|
+
declare function isSolanaIosWalletInfo(walletInfo: SolanaWalletInfo): boolean;
|
|
36
|
+
declare function isSolanaIosBrowserWalletSupported(): boolean;
|
|
37
|
+
declare function isIosBrowser(): boolean;
|
|
38
|
+
declare function getDefaultIosWalletAppIdentity(): SolanaIosWalletAppIdentity;
|
|
39
|
+
declare function getDefaultIosWalletRedirectUrl(): string;
|
|
40
|
+
|
|
41
|
+
export { adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo };
|
|
42
|
+
export type { AdaptSolanaIosWalletOptions, GetSolanaIosWalletsOptions, SolanaIosWalletAppIdentity };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SolanaChain, SolanaCluster, SolanaWalletInfo, SolanaWallet } from './types.mjs';
|
|
2
|
+
import '@solana/web3-compat';
|
|
3
|
+
|
|
4
|
+
interface SolanaIosWalletAppIdentity {
|
|
5
|
+
name: string;
|
|
6
|
+
uri: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
}
|
|
9
|
+
interface GetSolanaIosWalletsOptions {
|
|
10
|
+
appIdentity?: SolanaIosWalletAppIdentity;
|
|
11
|
+
redirectUrl?: string;
|
|
12
|
+
chains?: readonly SolanaChain[];
|
|
13
|
+
cluster?: SolanaCluster;
|
|
14
|
+
}
|
|
15
|
+
interface AdaptSolanaIosWalletOptions {
|
|
16
|
+
appIdentity?: SolanaIosWalletAppIdentity;
|
|
17
|
+
redirectUrl?: string;
|
|
18
|
+
chain?: SolanaChain;
|
|
19
|
+
cluster?: SolanaCluster;
|
|
20
|
+
onChange?: () => void;
|
|
21
|
+
}
|
|
22
|
+
type IosWalletMethod = "connect" | "signTransaction" | "signAllTransactions" | "signAndSendTransaction";
|
|
23
|
+
declare function getSolanaIosWallets(options?: GetSolanaIosWalletsOptions): SolanaWalletInfo[];
|
|
24
|
+
declare function adaptSolanaIosWallet(walletInfo: SolanaWalletInfo, options?: AdaptSolanaIosWalletOptions): SolanaWallet;
|
|
25
|
+
declare function handleSolanaIosWalletCallback(options?: {
|
|
26
|
+
clearUrl?: boolean;
|
|
27
|
+
}): {
|
|
28
|
+
walletId: string;
|
|
29
|
+
method: IosWalletMethod;
|
|
30
|
+
publicKey?: string;
|
|
31
|
+
signature?: string;
|
|
32
|
+
transaction?: Uint8Array;
|
|
33
|
+
transactions?: Uint8Array[];
|
|
34
|
+
} | null;
|
|
35
|
+
declare function isSolanaIosWalletInfo(walletInfo: SolanaWalletInfo): boolean;
|
|
36
|
+
declare function isSolanaIosBrowserWalletSupported(): boolean;
|
|
37
|
+
declare function isIosBrowser(): boolean;
|
|
38
|
+
declare function getDefaultIosWalletAppIdentity(): SolanaIosWalletAppIdentity;
|
|
39
|
+
declare function getDefaultIosWalletRedirectUrl(): string;
|
|
40
|
+
|
|
41
|
+
export { adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo };
|
|
42
|
+
export type { AdaptSolanaIosWalletOptions, GetSolanaIosWalletsOptions, SolanaIosWalletAppIdentity };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SolanaChain, SolanaCluster, SolanaWalletInfo, SolanaWallet } from './types.js';
|
|
2
|
+
import '@solana/web3-compat';
|
|
3
|
+
|
|
4
|
+
interface SolanaIosWalletAppIdentity {
|
|
5
|
+
name: string;
|
|
6
|
+
uri: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
}
|
|
9
|
+
interface GetSolanaIosWalletsOptions {
|
|
10
|
+
appIdentity?: SolanaIosWalletAppIdentity;
|
|
11
|
+
redirectUrl?: string;
|
|
12
|
+
chains?: readonly SolanaChain[];
|
|
13
|
+
cluster?: SolanaCluster;
|
|
14
|
+
}
|
|
15
|
+
interface AdaptSolanaIosWalletOptions {
|
|
16
|
+
appIdentity?: SolanaIosWalletAppIdentity;
|
|
17
|
+
redirectUrl?: string;
|
|
18
|
+
chain?: SolanaChain;
|
|
19
|
+
cluster?: SolanaCluster;
|
|
20
|
+
onChange?: () => void;
|
|
21
|
+
}
|
|
22
|
+
type IosWalletMethod = "connect" | "signTransaction" | "signAllTransactions" | "signAndSendTransaction";
|
|
23
|
+
declare function getSolanaIosWallets(options?: GetSolanaIosWalletsOptions): SolanaWalletInfo[];
|
|
24
|
+
declare function adaptSolanaIosWallet(walletInfo: SolanaWalletInfo, options?: AdaptSolanaIosWalletOptions): SolanaWallet;
|
|
25
|
+
declare function handleSolanaIosWalletCallback(options?: {
|
|
26
|
+
clearUrl?: boolean;
|
|
27
|
+
}): {
|
|
28
|
+
walletId: string;
|
|
29
|
+
method: IosWalletMethod;
|
|
30
|
+
publicKey?: string;
|
|
31
|
+
signature?: string;
|
|
32
|
+
transaction?: Uint8Array;
|
|
33
|
+
transactions?: Uint8Array[];
|
|
34
|
+
} | null;
|
|
35
|
+
declare function isSolanaIosWalletInfo(walletInfo: SolanaWalletInfo): boolean;
|
|
36
|
+
declare function isSolanaIosBrowserWalletSupported(): boolean;
|
|
37
|
+
declare function isIosBrowser(): boolean;
|
|
38
|
+
declare function getDefaultIosWalletAppIdentity(): SolanaIosWalletAppIdentity;
|
|
39
|
+
declare function getDefaultIosWalletRedirectUrl(): string;
|
|
40
|
+
|
|
41
|
+
export { adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo };
|
|
42
|
+
export type { AdaptSolanaIosWalletOptions, GetSolanaIosWalletsOptions, SolanaIosWalletAppIdentity };
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { PublicKey, Transaction, VersionedTransaction } from '@solana/web3-compat';
|
|
2
|
+
import bs58 from 'bs58';
|
|
3
|
+
import * as naclModule from 'tweetnacl';
|
|
4
|
+
|
|
5
|
+
const nacl = resolveTweetNaCl();
|
|
6
|
+
function resolveTweetNaCl() {
|
|
7
|
+
const moduleDefault = naclModule.default;
|
|
8
|
+
const globalNacl = globalThis.nacl;
|
|
9
|
+
return "box" in naclModule ? naclModule : moduleDefault ?? globalNacl;
|
|
10
|
+
}
|
|
11
|
+
const DEFAULT_IOS_WALLET_CHAINS = ["solana:mainnet", "solana:devnet"];
|
|
12
|
+
const PENDING_REQUEST_KEY = "vue-solana:ios-wallet:pending";
|
|
13
|
+
const SESSION_PREFIX = "vue-solana:ios-wallet:session:";
|
|
14
|
+
const CALLBACK_PARAMS = /* @__PURE__ */ new Set([
|
|
15
|
+
"data",
|
|
16
|
+
"nonce",
|
|
17
|
+
"errorCode",
|
|
18
|
+
"errorMessage",
|
|
19
|
+
"phantom_encryption_public_key",
|
|
20
|
+
"solflare_encryption_public_key",
|
|
21
|
+
"wallet_encryption_public_key"
|
|
22
|
+
]);
|
|
23
|
+
const IOS_WALLETS = [
|
|
24
|
+
{
|
|
25
|
+
id: "phantom",
|
|
26
|
+
name: "Phantom",
|
|
27
|
+
icon: "https://phantom.app/img/phantom-logo.svg",
|
|
28
|
+
appUrl: "https://phantom.app",
|
|
29
|
+
installUrl: "https://phantom.app/download",
|
|
30
|
+
encryptionPublicKeyParam: "phantom_encryption_public_key",
|
|
31
|
+
connectUrl: "https://phantom.app/ul/v1/connect",
|
|
32
|
+
signTransactionUrl: "https://phantom.app/ul/v1/signTransaction",
|
|
33
|
+
signAllTransactionsUrl: "https://phantom.app/ul/v1/signAllTransactions"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "solflare",
|
|
37
|
+
name: "Solflare",
|
|
38
|
+
icon: "https://solflare.com/favicon.ico",
|
|
39
|
+
appUrl: "https://solflare.com",
|
|
40
|
+
installUrl: "https://solflare.com/download",
|
|
41
|
+
encryptionPublicKeyParam: "solflare_encryption_public_key",
|
|
42
|
+
connectUrl: "https://solflare.com/ul/v1/connect",
|
|
43
|
+
signTransactionUrl: "https://solflare.com/ul/v1/signTransaction",
|
|
44
|
+
signAllTransactionsUrl: "https://solflare.com/ul/v1/signAllTransactions",
|
|
45
|
+
signAndSendTransactionUrl: "https://solflare.com/ul/v1/signAndSendTransaction"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "backpack",
|
|
49
|
+
name: "Backpack",
|
|
50
|
+
icon: "https://backpack.app/favicon.ico",
|
|
51
|
+
appUrl: "https://backpack.app",
|
|
52
|
+
installUrl: "https://backpack.app/download",
|
|
53
|
+
encryptionPublicKeyParam: "wallet_encryption_public_key",
|
|
54
|
+
connectUrl: "https://backpack.app/ul/v1/connect",
|
|
55
|
+
signTransactionUrl: "https://backpack.app/ul/v1/signTransaction",
|
|
56
|
+
signAllTransactionsUrl: "https://backpack.app/ul/v1/signAllTransactions",
|
|
57
|
+
signAndSendTransactionUrl: "https://backpack.app/ul/v1/signAndSendTransaction"
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
function getSolanaIosWallets(options = {}) {
|
|
61
|
+
if (!isSolanaIosBrowserWalletSupported()) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const chains = options.chains?.length ? options.chains : DEFAULT_IOS_WALLET_CHAINS;
|
|
65
|
+
const callbackUrl = options.redirectUrl ?? getDefaultIosWalletRedirectUrl();
|
|
66
|
+
return IOS_WALLETS.map((wallet) => ({
|
|
67
|
+
name: wallet.name,
|
|
68
|
+
icon: wallet.icon,
|
|
69
|
+
chains,
|
|
70
|
+
platform: "mobile",
|
|
71
|
+
source: "deep-link",
|
|
72
|
+
appUrl: wallet.appUrl,
|
|
73
|
+
installUrl: wallet.installUrl,
|
|
74
|
+
callbackUrl,
|
|
75
|
+
capabilities: {
|
|
76
|
+
connect: true,
|
|
77
|
+
signTransaction: Boolean(wallet.signTransactionUrl),
|
|
78
|
+
signAllTransactions: Boolean(wallet.signAllTransactionsUrl),
|
|
79
|
+
signAndSendTransaction: Boolean(wallet.signAndSendTransactionUrl)
|
|
80
|
+
},
|
|
81
|
+
accounts: getStoredIosWalletAccount(wallet.id, chains),
|
|
82
|
+
wallet
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
function adaptSolanaIosWallet(walletInfo, options = {}) {
|
|
86
|
+
const definition = getIosWalletDefinition(walletInfo);
|
|
87
|
+
let connecting = false;
|
|
88
|
+
let disconnecting = false;
|
|
89
|
+
let session = getStoredSession(definition.id);
|
|
90
|
+
function refreshSession() {
|
|
91
|
+
session = getStoredSession(definition.id);
|
|
92
|
+
options.onChange?.();
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
get publicKey() {
|
|
96
|
+
return session?.publicKey ? new PublicKey(session.publicKey) : null;
|
|
97
|
+
},
|
|
98
|
+
get connected() {
|
|
99
|
+
return Boolean(session?.publicKey && session.session);
|
|
100
|
+
},
|
|
101
|
+
get connecting() {
|
|
102
|
+
return connecting;
|
|
103
|
+
},
|
|
104
|
+
get disconnecting() {
|
|
105
|
+
return disconnecting;
|
|
106
|
+
},
|
|
107
|
+
async connect() {
|
|
108
|
+
const callback = handleSolanaIosWalletCallback({ clearUrl: true });
|
|
109
|
+
if (callback?.walletId === definition.id) {
|
|
110
|
+
refreshSession();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
connecting = true;
|
|
114
|
+
options.onChange?.();
|
|
115
|
+
try {
|
|
116
|
+
launchConnect(definition, options);
|
|
117
|
+
await waitForRedirect();
|
|
118
|
+
} finally {
|
|
119
|
+
connecting = false;
|
|
120
|
+
options.onChange?.();
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
async disconnect() {
|
|
124
|
+
disconnecting = true;
|
|
125
|
+
options.onChange?.();
|
|
126
|
+
try {
|
|
127
|
+
removeStoredSession(definition.id);
|
|
128
|
+
refreshSession();
|
|
129
|
+
} finally {
|
|
130
|
+
disconnecting = false;
|
|
131
|
+
options.onChange?.();
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
signTransaction: definition.signTransactionUrl ? async (transaction) => {
|
|
135
|
+
const signedTransaction = await launchSignTransaction(definition, transaction, options);
|
|
136
|
+
return deserializeTransaction(transaction, signedTransaction);
|
|
137
|
+
} : void 0,
|
|
138
|
+
signAllTransactions: definition.signAllTransactionsUrl ? async (transactions) => {
|
|
139
|
+
const signedTransactions = await launchSignAllTransactions(
|
|
140
|
+
definition,
|
|
141
|
+
transactions,
|
|
142
|
+
options
|
|
143
|
+
);
|
|
144
|
+
return signedTransactions.map(
|
|
145
|
+
(transaction, index) => deserializeTransaction(transactions[index], transaction)
|
|
146
|
+
);
|
|
147
|
+
} : void 0,
|
|
148
|
+
signAndSendTransaction: definition.signAndSendTransactionUrl ? async (transaction, sendOptions) => launchSignAndSendTransaction(definition, transaction, sendOptions, options) : void 0
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function handleSolanaIosWalletCallback(options = {}) {
|
|
152
|
+
if (typeof window === "undefined") {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const url = new URL(window.location.href);
|
|
156
|
+
if (!hasIosWalletCallbackParams(url)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const errorCode = url.searchParams.get("errorCode");
|
|
160
|
+
const errorMessage = url.searchParams.get("errorMessage");
|
|
161
|
+
if (errorCode || errorMessage) {
|
|
162
|
+
clearPendingRequest();
|
|
163
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
164
|
+
throw new Error(errorMessage || `iOS wallet request failed: ${errorCode}`);
|
|
165
|
+
}
|
|
166
|
+
const pending = getPendingRequest();
|
|
167
|
+
if (!pending) {
|
|
168
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
169
|
+
throw new Error("Received an iOS wallet callback without a pending request");
|
|
170
|
+
}
|
|
171
|
+
const definition = getIosWalletDefinitionById(pending.walletId);
|
|
172
|
+
const walletEncryptionPublicKey = url.searchParams.get(definition.encryptionPublicKeyParam);
|
|
173
|
+
const nonce = url.searchParams.get("nonce");
|
|
174
|
+
const data = url.searchParams.get("data");
|
|
175
|
+
if (!nonce || !data) {
|
|
176
|
+
throw new Error("Received an incomplete iOS wallet callback");
|
|
177
|
+
}
|
|
178
|
+
const sharedSecret = getSharedSecret(
|
|
179
|
+
walletEncryptionPublicKey ?? getStoredSession(pending.walletId)?.walletEncryptionPublicKey,
|
|
180
|
+
pending.dappEncryptionSecretKey
|
|
181
|
+
);
|
|
182
|
+
const payload = decryptPayload(data, nonce, sharedSecret);
|
|
183
|
+
const result = createCallbackResult(pending, payload);
|
|
184
|
+
if (pending.method === "connect") {
|
|
185
|
+
if (!walletEncryptionPublicKey) {
|
|
186
|
+
throw new Error("iOS wallet connect callback did not include an encryption public key");
|
|
187
|
+
}
|
|
188
|
+
storeSession({
|
|
189
|
+
walletId: pending.walletId,
|
|
190
|
+
publicKey: getStringField(payload, "public_key"),
|
|
191
|
+
session: getStringField(payload, "session"),
|
|
192
|
+
dappEncryptionPublicKey: pending.dappEncryptionPublicKey,
|
|
193
|
+
dappEncryptionSecretKey: pending.dappEncryptionSecretKey,
|
|
194
|
+
walletEncryptionPublicKey,
|
|
195
|
+
sharedSecret: bs58.encode(sharedSecret)
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
clearPendingRequest();
|
|
199
|
+
cleanCallbackUrl(url, options.clearUrl);
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
function isSolanaIosWalletInfo(walletInfo) {
|
|
203
|
+
return walletInfo.source === "deep-link" && isIosWalletDefinition(walletInfo.wallet);
|
|
204
|
+
}
|
|
205
|
+
function isSolanaIosBrowserWalletSupported() {
|
|
206
|
+
return isIosBrowser();
|
|
207
|
+
}
|
|
208
|
+
function isIosBrowser() {
|
|
209
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
const userAgent = navigator.userAgent;
|
|
213
|
+
const platform = navigator.platform;
|
|
214
|
+
const maxTouchPoints = navigator.maxTouchPoints ?? 0;
|
|
215
|
+
return /iPad|iPhone|iPod/i.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1;
|
|
216
|
+
}
|
|
217
|
+
function getDefaultIosWalletAppIdentity() {
|
|
218
|
+
if (typeof window === "undefined") {
|
|
219
|
+
return {
|
|
220
|
+
name: "Vue Solana App",
|
|
221
|
+
uri: "https://localhost"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
name: document.title || "Vue Solana App",
|
|
226
|
+
uri: window.location.origin,
|
|
227
|
+
icon: getDocumentIconUrl()
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function getDefaultIosWalletRedirectUrl() {
|
|
231
|
+
if (typeof window === "undefined") {
|
|
232
|
+
return "https://localhost";
|
|
233
|
+
}
|
|
234
|
+
const url = new URL(window.location.href);
|
|
235
|
+
for (const param of CALLBACK_PARAMS) {
|
|
236
|
+
url.searchParams.delete(param);
|
|
237
|
+
}
|
|
238
|
+
return url.toString();
|
|
239
|
+
}
|
|
240
|
+
function launchConnect(definition, options) {
|
|
241
|
+
const keyPair = nacl.box.keyPair();
|
|
242
|
+
const pending = createPendingRequest(definition.id, "connect", keyPair, options.redirectUrl);
|
|
243
|
+
const appIdentity = options.appIdentity ?? getDefaultIosWalletAppIdentity();
|
|
244
|
+
const url = new URL(definition.connectUrl);
|
|
245
|
+
url.searchParams.set("app_url", appIdentity.uri);
|
|
246
|
+
url.searchParams.set("dapp_encryption_public_key", pending.dappEncryptionPublicKey);
|
|
247
|
+
url.searchParams.set("redirect_link", pending.redirectUrl);
|
|
248
|
+
if (options.cluster && options.cluster !== "localnet") {
|
|
249
|
+
url.searchParams.set("cluster", options.cluster);
|
|
250
|
+
}
|
|
251
|
+
storePendingRequest(pending);
|
|
252
|
+
openIosWalletUrl(url.toString());
|
|
253
|
+
}
|
|
254
|
+
async function launchSignTransaction(definition, transaction, options) {
|
|
255
|
+
const response = await launchEncryptedWalletRequest(definition, "signTransaction", options, {
|
|
256
|
+
transaction: bs58.encode(serializeTransaction(transaction))
|
|
257
|
+
});
|
|
258
|
+
if (!response.transaction) {
|
|
259
|
+
throw new Error("iOS wallet did not return a signed transaction");
|
|
260
|
+
}
|
|
261
|
+
return response.transaction;
|
|
262
|
+
}
|
|
263
|
+
async function launchSignAllTransactions(definition, transactions, options) {
|
|
264
|
+
const response = await launchEncryptedWalletRequest(definition, "signAllTransactions", options, {
|
|
265
|
+
transactions: transactions.map((transaction) => bs58.encode(serializeTransaction(transaction)))
|
|
266
|
+
});
|
|
267
|
+
if (!response.transactions) {
|
|
268
|
+
throw new Error("iOS wallet did not return signed transactions");
|
|
269
|
+
}
|
|
270
|
+
return response.transactions;
|
|
271
|
+
}
|
|
272
|
+
async function launchSignAndSendTransaction(definition, transaction, sendOptions, options) {
|
|
273
|
+
const response = await launchEncryptedWalletRequest(
|
|
274
|
+
definition,
|
|
275
|
+
"signAndSendTransaction",
|
|
276
|
+
options,
|
|
277
|
+
{
|
|
278
|
+
transaction: bs58.encode(serializeTransaction(transaction)),
|
|
279
|
+
sendOptions
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
if (!response.signature) {
|
|
283
|
+
throw new Error("iOS wallet did not return a transaction signature");
|
|
284
|
+
}
|
|
285
|
+
return { signature: response.signature };
|
|
286
|
+
}
|
|
287
|
+
async function launchEncryptedWalletRequest(definition, method, options, payload) {
|
|
288
|
+
const session = getStoredSession(definition.id);
|
|
289
|
+
if (!session) {
|
|
290
|
+
throw new Error("Connect the iOS wallet before signing");
|
|
291
|
+
}
|
|
292
|
+
const methodUrl = getIosWalletMethodUrl(definition, method);
|
|
293
|
+
const pending = createPendingRequest(
|
|
294
|
+
definition.id,
|
|
295
|
+
method,
|
|
296
|
+
{
|
|
297
|
+
publicKey: bs58.decode(session.dappEncryptionPublicKey),
|
|
298
|
+
secretKey: bs58.decode(session.dappEncryptionSecretKey)
|
|
299
|
+
},
|
|
300
|
+
options.redirectUrl
|
|
301
|
+
);
|
|
302
|
+
const nonce = nacl.randomBytes(nacl.box.nonceLength);
|
|
303
|
+
const encryptedPayload = encryptPayload(
|
|
304
|
+
{ ...payload, session: session.session },
|
|
305
|
+
nonce,
|
|
306
|
+
bs58.decode(session.sharedSecret)
|
|
307
|
+
);
|
|
308
|
+
const url = new URL(methodUrl);
|
|
309
|
+
url.searchParams.set("dapp_encryption_public_key", session.dappEncryptionPublicKey);
|
|
310
|
+
url.searchParams.set("nonce", bs58.encode(nonce));
|
|
311
|
+
url.searchParams.set("redirect_link", pending.redirectUrl);
|
|
312
|
+
url.searchParams.set("payload", encryptedPayload);
|
|
313
|
+
storePendingRequest(pending);
|
|
314
|
+
openIosWalletUrl(url.toString());
|
|
315
|
+
return waitForRedirect();
|
|
316
|
+
}
|
|
317
|
+
function createPendingRequest(walletId, method, keyPair, redirectUrl) {
|
|
318
|
+
return {
|
|
319
|
+
id: createRequestId(),
|
|
320
|
+
walletId,
|
|
321
|
+
method,
|
|
322
|
+
dappEncryptionPublicKey: bs58.encode(keyPair.publicKey),
|
|
323
|
+
dappEncryptionSecretKey: bs58.encode(keyPair.secretKey),
|
|
324
|
+
redirectUrl: redirectUrl ?? getDefaultIosWalletRedirectUrl(),
|
|
325
|
+
createdAt: Date.now()
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function createCallbackResult(pending, payload) {
|
|
329
|
+
if (pending.method === "connect") {
|
|
330
|
+
return {
|
|
331
|
+
walletId: pending.walletId,
|
|
332
|
+
method: pending.method,
|
|
333
|
+
publicKey: getStringField(payload, "public_key")
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
if (pending.method === "signTransaction") {
|
|
337
|
+
return {
|
|
338
|
+
walletId: pending.walletId,
|
|
339
|
+
method: pending.method,
|
|
340
|
+
transaction: bs58.decode(getStringField(payload, "transaction"))
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
if (pending.method === "signAllTransactions") {
|
|
344
|
+
const transactions = payload.transactions;
|
|
345
|
+
if (!Array.isArray(transactions) || transactions.some((transaction) => typeof transaction !== "string")) {
|
|
346
|
+
throw new Error("iOS wallet returned invalid signed transactions");
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
walletId: pending.walletId,
|
|
350
|
+
method: pending.method,
|
|
351
|
+
transactions: transactions.map((transaction) => bs58.decode(transaction))
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
walletId: pending.walletId,
|
|
356
|
+
method: pending.method,
|
|
357
|
+
signature: getStringField(payload, "signature")
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function getIosWalletMethodUrl(definition, method) {
|
|
361
|
+
const url = definition[`${method}Url`];
|
|
362
|
+
if (!url) {
|
|
363
|
+
throw new Error(`${definition.name} does not support ${method} through iOS deeplinks`);
|
|
364
|
+
}
|
|
365
|
+
return url;
|
|
366
|
+
}
|
|
367
|
+
function getSharedSecret(walletEncryptionPublicKey, dappSecretKey) {
|
|
368
|
+
if (!walletEncryptionPublicKey) {
|
|
369
|
+
throw new Error("Missing iOS wallet encryption public key");
|
|
370
|
+
}
|
|
371
|
+
return nacl.box.before(bs58.decode(walletEncryptionPublicKey), bs58.decode(dappSecretKey));
|
|
372
|
+
}
|
|
373
|
+
function encryptPayload(payload, nonce, sharedSecret) {
|
|
374
|
+
return bs58.encode(nacl.box.after(encodeJson(payload), nonce, sharedSecret));
|
|
375
|
+
}
|
|
376
|
+
function decryptPayload(data, nonce, sharedSecret) {
|
|
377
|
+
const decrypted = nacl.box.open.after(bs58.decode(data), bs58.decode(nonce), sharedSecret);
|
|
378
|
+
if (!decrypted) {
|
|
379
|
+
throw new Error("Unable to decrypt iOS wallet callback");
|
|
380
|
+
}
|
|
381
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
382
|
+
}
|
|
383
|
+
function encodeJson(value) {
|
|
384
|
+
return new TextEncoder().encode(JSON.stringify(value));
|
|
385
|
+
}
|
|
386
|
+
function serializeTransaction(transaction) {
|
|
387
|
+
if (transaction instanceof Transaction) {
|
|
388
|
+
return transaction.serialize({ requireAllSignatures: false, verifySignatures: false });
|
|
389
|
+
}
|
|
390
|
+
return transaction.serialize();
|
|
391
|
+
}
|
|
392
|
+
function deserializeTransaction(source, bytes) {
|
|
393
|
+
if (source instanceof Transaction) {
|
|
394
|
+
return Transaction.from(bytes);
|
|
395
|
+
}
|
|
396
|
+
return VersionedTransaction.deserialize(bytes);
|
|
397
|
+
}
|
|
398
|
+
function getStoredIosWalletAccount(walletId, chains) {
|
|
399
|
+
const session = getStoredSession(walletId);
|
|
400
|
+
if (!session) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
return [
|
|
404
|
+
{
|
|
405
|
+
address: session.publicKey,
|
|
406
|
+
publicKey: new PublicKey(session.publicKey).toBytes(),
|
|
407
|
+
chains
|
|
408
|
+
}
|
|
409
|
+
];
|
|
410
|
+
}
|
|
411
|
+
function getStoredSession(walletId) {
|
|
412
|
+
const value = getStorage()?.getItem(`${SESSION_PREFIX}${walletId}`);
|
|
413
|
+
if (!value) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
return JSON.parse(value);
|
|
418
|
+
} catch {
|
|
419
|
+
removeStoredSession(walletId);
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function storeSession(session) {
|
|
424
|
+
getStorage()?.setItem(`${SESSION_PREFIX}${session.walletId}`, JSON.stringify(session));
|
|
425
|
+
}
|
|
426
|
+
function removeStoredSession(walletId) {
|
|
427
|
+
getStorage()?.removeItem(`${SESSION_PREFIX}${walletId}`);
|
|
428
|
+
}
|
|
429
|
+
function getPendingRequest() {
|
|
430
|
+
const value = getStorage()?.getItem(PENDING_REQUEST_KEY);
|
|
431
|
+
if (!value) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
return JSON.parse(value);
|
|
436
|
+
} catch {
|
|
437
|
+
clearPendingRequest();
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function storePendingRequest(request) {
|
|
442
|
+
getStorage()?.setItem(PENDING_REQUEST_KEY, JSON.stringify(request));
|
|
443
|
+
}
|
|
444
|
+
function clearPendingRequest() {
|
|
445
|
+
getStorage()?.removeItem(PENDING_REQUEST_KEY);
|
|
446
|
+
}
|
|
447
|
+
function getStorage() {
|
|
448
|
+
if (typeof window === "undefined") {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
return window.sessionStorage;
|
|
453
|
+
} catch {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function openIosWalletUrl(url) {
|
|
458
|
+
if (typeof window === "undefined") {
|
|
459
|
+
throw new Error("iOS wallet links are only available in browser runtimes");
|
|
460
|
+
}
|
|
461
|
+
window.location.assign(url);
|
|
462
|
+
}
|
|
463
|
+
function waitForRedirect() {
|
|
464
|
+
return new Promise(() => {
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function hasIosWalletCallbackParams(url) {
|
|
468
|
+
for (const param of CALLBACK_PARAMS) {
|
|
469
|
+
if (url.searchParams.has(param)) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
function cleanCallbackUrl(url, clearUrl) {
|
|
476
|
+
if (!clearUrl || typeof window === "undefined") {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
for (const param of CALLBACK_PARAMS) {
|
|
480
|
+
url.searchParams.delete(param);
|
|
481
|
+
}
|
|
482
|
+
window.history.replaceState(window.history.state, document.title, url.toString());
|
|
483
|
+
}
|
|
484
|
+
function createRequestId() {
|
|
485
|
+
return bs58.encode(nacl.randomBytes(16));
|
|
486
|
+
}
|
|
487
|
+
function getStringField(payload, field) {
|
|
488
|
+
const value = payload[field];
|
|
489
|
+
if (typeof value !== "string" || !value) {
|
|
490
|
+
throw new Error(`iOS wallet callback is missing ${field}`);
|
|
491
|
+
}
|
|
492
|
+
return value;
|
|
493
|
+
}
|
|
494
|
+
function getIosWalletDefinition(walletInfo) {
|
|
495
|
+
if (!isIosWalletDefinition(walletInfo.wallet)) {
|
|
496
|
+
throw new Error("Solana wallet info is not an iOS wallet");
|
|
497
|
+
}
|
|
498
|
+
return walletInfo.wallet;
|
|
499
|
+
}
|
|
500
|
+
function getIosWalletDefinitionById(walletId) {
|
|
501
|
+
const definition = IOS_WALLETS.find((wallet) => wallet.id === walletId);
|
|
502
|
+
if (!definition) {
|
|
503
|
+
throw new Error(`Unknown iOS wallet: ${walletId}`);
|
|
504
|
+
}
|
|
505
|
+
return definition;
|
|
506
|
+
}
|
|
507
|
+
function isIosWalletDefinition(value) {
|
|
508
|
+
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && IOS_WALLETS.some((wallet) => wallet.id === value.id);
|
|
509
|
+
}
|
|
510
|
+
function getDocumentIconUrl() {
|
|
511
|
+
const icon = document.querySelector(
|
|
512
|
+
'link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'
|
|
513
|
+
);
|
|
514
|
+
return icon?.href;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export { adaptSolanaIosWallet, getDefaultIosWalletAppIdentity, getDefaultIosWalletRedirectUrl, getSolanaIosWallets, handleSolanaIosWalletCallback, isIosBrowser, isSolanaIosBrowserWalletSupported, isSolanaIosWalletInfo };
|
package/dist/types.d.cts
CHANGED
|
@@ -39,6 +39,13 @@ interface SolanaWalletInfo {
|
|
|
39
39
|
source?: "wallet-standard" | "mobile-wallet-adapter" | "deep-link" | "protocol-link";
|
|
40
40
|
appUrl?: string;
|
|
41
41
|
installUrl?: string;
|
|
42
|
+
callbackUrl?: string;
|
|
43
|
+
capabilities?: {
|
|
44
|
+
connect?: boolean;
|
|
45
|
+
signTransaction?: boolean;
|
|
46
|
+
signAllTransactions?: boolean;
|
|
47
|
+
signAndSendTransaction?: boolean;
|
|
48
|
+
};
|
|
42
49
|
accounts: readonly {
|
|
43
50
|
address: string;
|
|
44
51
|
publicKey: Uint8Array;
|
package/dist/types.d.mts
CHANGED
|
@@ -39,6 +39,13 @@ interface SolanaWalletInfo {
|
|
|
39
39
|
source?: "wallet-standard" | "mobile-wallet-adapter" | "deep-link" | "protocol-link";
|
|
40
40
|
appUrl?: string;
|
|
41
41
|
installUrl?: string;
|
|
42
|
+
callbackUrl?: string;
|
|
43
|
+
capabilities?: {
|
|
44
|
+
connect?: boolean;
|
|
45
|
+
signTransaction?: boolean;
|
|
46
|
+
signAllTransactions?: boolean;
|
|
47
|
+
signAndSendTransaction?: boolean;
|
|
48
|
+
};
|
|
42
49
|
accounts: readonly {
|
|
43
50
|
address: string;
|
|
44
51
|
publicKey: Uint8Array;
|
package/dist/types.d.ts
CHANGED
|
@@ -39,6 +39,13 @@ interface SolanaWalletInfo {
|
|
|
39
39
|
source?: "wallet-standard" | "mobile-wallet-adapter" | "deep-link" | "protocol-link";
|
|
40
40
|
appUrl?: string;
|
|
41
41
|
installUrl?: string;
|
|
42
|
+
callbackUrl?: string;
|
|
43
|
+
capabilities?: {
|
|
44
|
+
connect?: boolean;
|
|
45
|
+
signTransaction?: boolean;
|
|
46
|
+
signAllTransactions?: boolean;
|
|
47
|
+
signAndSendTransaction?: boolean;
|
|
48
|
+
};
|
|
42
49
|
accounts: readonly {
|
|
43
50
|
address: string;
|
|
44
51
|
publicKey: Uint8Array;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vue-solana/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Framework-agnostic primitives for Vue Solana libraries.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"import": "./dist/clusters.mjs",
|
|
41
41
|
"require": "./dist/clusters.cjs"
|
|
42
42
|
},
|
|
43
|
+
"./ios-wallet": {
|
|
44
|
+
"types": "./dist/ios-wallet.d.ts",
|
|
45
|
+
"import": "./dist/ios-wallet.mjs",
|
|
46
|
+
"require": "./dist/ios-wallet.cjs"
|
|
47
|
+
},
|
|
43
48
|
"./mobile-wallet": {
|
|
44
49
|
"types": "./dist/mobile-wallet.d.ts",
|
|
45
50
|
"import": "./dist/mobile-wallet.mjs",
|
|
@@ -79,7 +84,8 @@
|
|
|
79
84
|
"@wallet-standard/app": "^1.1.1",
|
|
80
85
|
"@wallet-standard/base": "^1.1.1",
|
|
81
86
|
"@wallet-standard/features": "^1.1.1",
|
|
82
|
-
"bs58": "^6.0.0"
|
|
87
|
+
"bs58": "^6.0.0",
|
|
88
|
+
"tweetnacl": "^1.0.3"
|
|
83
89
|
},
|
|
84
90
|
"devDependencies": {
|
|
85
91
|
"typescript": "^5.8.3",
|