@solana/kit-plugin-wallet 0.0.0 → 0.13.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/LICENSE +22 -0
- package/README.md +401 -0
- package/dist/index.browser.cjs +622 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.mjs +614 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.node.cjs +100 -0
- package/dist/index.node.cjs.map +1 -0
- package/dist/index.node.mjs +91 -0
- package/dist/index.node.mjs.map +1 -0
- package/dist/index.react-native.mjs +91 -0
- package/dist/index.react-native.mjs.map +1 -0
- package/dist/react/index.browser.cjs +77 -0
- package/dist/react/index.browser.cjs.map +1 -0
- package/dist/react/index.browser.mjs +59 -0
- package/dist/react/index.browser.mjs.map +1 -0
- package/dist/react/index.node.cjs +77 -0
- package/dist/react/index.node.cjs.map +1 -0
- package/dist/react/index.node.mjs +59 -0
- package/dist/react/index.node.mjs.map +1 -0
- package/dist/react/index.react-native.mjs +59 -0
- package/dist/react/index.react-native.mjs.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/react/index.d.ts +235 -0
- package/dist/types/react/index.d.ts.map +1 -0
- package/dist/types/status.d.ts +31 -0
- package/dist/types/status.d.ts.map +1 -0
- package/dist/types/store.d.ts +16 -0
- package/dist/types/store.d.ts.map +1 -0
- package/dist/types/types.d.ts +384 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/wallet.d.ts +133 -0
- package/dist/types/wallet.d.ts.map +1 -0
- package/package.json +103 -7
- package/src/__typetests__/wallet-typetest.ts +129 -0
- package/src/index.ts +3 -0
- package/src/react/__typetests__/index.ts +108 -0
- package/src/react/index.ts +301 -0
- package/src/status.ts +33 -0
- package/src/store.ts +990 -0
- package/src/types/global.d.ts +6 -0
- package/src/types.ts +407 -0
- package/src/wallet.ts +218 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import { isWalletWarmingUp } from './chunk-MSZ5MCI7.browser.mjs';
|
|
2
|
+
export { isWalletWarmingUp } from './chunk-MSZ5MCI7.browser.mjs';
|
|
3
|
+
import { withCleanup, extendClient, SolanaError, SOLANA_ERROR__WALLET__NO_SIGNER_CONNECTED, SOLANA_ERROR__WALLET__SIGNER_NOT_AVAILABLE, SOLANA_ERROR__WALLET__NOT_CONNECTED, SOLANA_ERROR__WALLET__ACCOUNT_NOT_AVAILABLE } from '@solana/kit';
|
|
4
|
+
import { createSignerFromWalletAccount } from '@solana/wallet-account-signer';
|
|
5
|
+
import { SolanaSignMessage, SolanaSignIn } from '@solana/wallet-standard-features';
|
|
6
|
+
import { getWallets } from '@wallet-standard/app';
|
|
7
|
+
import { StandardConnect, StandardDisconnect, StandardEvents } from '@wallet-standard/features';
|
|
8
|
+
import { getWalletFeature, getWalletAccountFeature } from '@wallet-standard/ui-features';
|
|
9
|
+
import { getOrCreateUiWalletForStandardWallet, getWalletForHandle } from '@wallet-standard/ui-registry';
|
|
10
|
+
|
|
11
|
+
function createWalletStore(config) {
|
|
12
|
+
let state = {
|
|
13
|
+
account: null,
|
|
14
|
+
connectedWallet: null,
|
|
15
|
+
signer: null,
|
|
16
|
+
status: "pending",
|
|
17
|
+
wallets: []
|
|
18
|
+
};
|
|
19
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
20
|
+
const signerListeners = /* @__PURE__ */ new Set();
|
|
21
|
+
const walletEventSubscriptions = /* @__PURE__ */ new Map();
|
|
22
|
+
let reconnectCleanup = null;
|
|
23
|
+
let userHasSelected = false;
|
|
24
|
+
let connectGeneration = 0;
|
|
25
|
+
let disposed = false;
|
|
26
|
+
let disconnectingWalletName = null;
|
|
27
|
+
let readyDeferred = null;
|
|
28
|
+
function resolveDefaultStorage() {
|
|
29
|
+
try {
|
|
30
|
+
return localStorage;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const storage = config.storage === null ? null : config.storage ?? resolveDefaultStorage();
|
|
36
|
+
const storageKey = config.storageKey ?? "kit-wallet";
|
|
37
|
+
function deriveSnapshot(s) {
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
connected: s.connectedWallet && s.account ? Object.freeze({
|
|
40
|
+
account: s.account,
|
|
41
|
+
signer: s.signer,
|
|
42
|
+
wallet: s.connectedWallet
|
|
43
|
+
}) : null,
|
|
44
|
+
status: s.status,
|
|
45
|
+
wallets: s.wallets
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
let snapshot = deriveSnapshot(state);
|
|
49
|
+
function observableSigner(s) {
|
|
50
|
+
return s.connectedWallet && s.account ? s.signer : void 0;
|
|
51
|
+
}
|
|
52
|
+
function updateState(updates) {
|
|
53
|
+
const prev = state;
|
|
54
|
+
state = { ...state, ...updates };
|
|
55
|
+
if (isWalletWarmingUp(prev.status) && !isWalletWarmingUp(state.status)) {
|
|
56
|
+
readyDeferred?.resolve();
|
|
57
|
+
readyDeferred = null;
|
|
58
|
+
}
|
|
59
|
+
if (state.wallets !== prev.wallets) {
|
|
60
|
+
syncWalletEventSubscriptions(state.wallets);
|
|
61
|
+
}
|
|
62
|
+
const signerChanged = observableSigner(state) !== observableSigner(prev);
|
|
63
|
+
if (state.connectedWallet !== prev.connectedWallet || state.account !== prev.account || state.status !== prev.status || state.signer !== prev.signer || state.wallets !== prev.wallets) {
|
|
64
|
+
snapshot = deriveSnapshot(state);
|
|
65
|
+
listeners.forEach((l) => l());
|
|
66
|
+
}
|
|
67
|
+
if (signerChanged) {
|
|
68
|
+
signerListeners.forEach((l) => l());
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function whenReady() {
|
|
72
|
+
if (!isWalletWarmingUp(state.status)) {
|
|
73
|
+
return Promise.resolve();
|
|
74
|
+
}
|
|
75
|
+
readyDeferred ??= Promise.withResolvers();
|
|
76
|
+
return readyDeferred.promise;
|
|
77
|
+
}
|
|
78
|
+
function tryCreateSigner(account) {
|
|
79
|
+
try {
|
|
80
|
+
return createSignerFromWalletAccount(account, config.chain);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const registry = getWallets();
|
|
86
|
+
function filterWallet(uiWallet) {
|
|
87
|
+
const supportsChain = uiWallet.chains.includes(config.chain);
|
|
88
|
+
const supportsConnect = uiWallet.features.includes(StandardConnect);
|
|
89
|
+
if (!supportsChain || !supportsConnect) return false;
|
|
90
|
+
return config.filter ? config.filter(uiWallet) : true;
|
|
91
|
+
}
|
|
92
|
+
function buildWalletList() {
|
|
93
|
+
return Object.freeze(registry.get().map(getOrCreateUiWalletForStandardWallet).filter(filterWallet));
|
|
94
|
+
}
|
|
95
|
+
function reconcileWalletList() {
|
|
96
|
+
const next = buildWalletList();
|
|
97
|
+
const prev = state.wallets;
|
|
98
|
+
if (next.length === prev.length && next.every((w, i) => w === prev[i])) {
|
|
99
|
+
return prev;
|
|
100
|
+
}
|
|
101
|
+
return next;
|
|
102
|
+
}
|
|
103
|
+
function syncWalletEventSubscriptions(wallets) {
|
|
104
|
+
const names = new Set(wallets.map((w) => w.name));
|
|
105
|
+
for (const [name, cleanup] of walletEventSubscriptions) {
|
|
106
|
+
if (!names.has(name)) {
|
|
107
|
+
cleanup();
|
|
108
|
+
walletEventSubscriptions.delete(name);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const w of wallets) {
|
|
112
|
+
if (!walletEventSubscriptions.has(w.name)) {
|
|
113
|
+
walletEventSubscriptions.set(w.name, subscribeToWalletEvents(w));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function isWalletStillAvailable(uiWallet) {
|
|
118
|
+
return buildWalletList().some((w) => w.name === uiWallet.name);
|
|
119
|
+
}
|
|
120
|
+
updateState({ wallets: buildWalletList() });
|
|
121
|
+
const unsubRegister = registry.on("register", () => {
|
|
122
|
+
updateState({ wallets: reconcileWalletList() });
|
|
123
|
+
});
|
|
124
|
+
const unsubUnregister = registry.on("unregister", () => {
|
|
125
|
+
const newWallets = reconcileWalletList();
|
|
126
|
+
const updates = { wallets: newWallets };
|
|
127
|
+
if (state.connectedWallet && !newWallets.some((w) => w.name === state.connectedWallet.name)) {
|
|
128
|
+
updates.connectedWallet = null;
|
|
129
|
+
updates.account = null;
|
|
130
|
+
updates.signer = null;
|
|
131
|
+
updates.status = "disconnected";
|
|
132
|
+
clearPersistedAccount();
|
|
133
|
+
}
|
|
134
|
+
updateState(updates);
|
|
135
|
+
});
|
|
136
|
+
function cancelReconnect() {
|
|
137
|
+
reconnectCleanup?.();
|
|
138
|
+
reconnectCleanup = null;
|
|
139
|
+
}
|
|
140
|
+
function setConnected(account, wallet, options) {
|
|
141
|
+
if (disposed) return;
|
|
142
|
+
const signer = tryCreateSigner(account);
|
|
143
|
+
disconnectingWalletName = null;
|
|
144
|
+
updateState({
|
|
145
|
+
account,
|
|
146
|
+
connectedWallet: wallet,
|
|
147
|
+
signer,
|
|
148
|
+
status: "connected",
|
|
149
|
+
wallets: reconcileWalletList()
|
|
150
|
+
});
|
|
151
|
+
if (options?.persist !== false) {
|
|
152
|
+
persistAccount(account, wallet);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function refreshUiWallet(staleUiWallet) {
|
|
156
|
+
const rawWallet = getWalletForHandle(staleUiWallet);
|
|
157
|
+
return getOrCreateUiWalletForStandardWallet(rawWallet);
|
|
158
|
+
}
|
|
159
|
+
function subscribeToWalletEvents(uiWallet) {
|
|
160
|
+
if (!uiWallet.features.includes(StandardEvents)) {
|
|
161
|
+
return () => {
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const eventsFeature = getWalletFeature(
|
|
165
|
+
uiWallet,
|
|
166
|
+
StandardEvents
|
|
167
|
+
);
|
|
168
|
+
const walletName = uiWallet.name;
|
|
169
|
+
return eventsFeature.on("change", () => {
|
|
170
|
+
const newWallets = reconcileWalletList();
|
|
171
|
+
if (!state.connectedWallet || state.connectedWallet.name !== walletName) {
|
|
172
|
+
updateState({ wallets: newWallets });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const refreshed = refreshUiWallet(state.connectedWallet);
|
|
176
|
+
if (!filterWallet(refreshed)) {
|
|
177
|
+
disconnectLocally();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const result = reconcileActiveAccount(refreshed);
|
|
181
|
+
if (result === null) {
|
|
182
|
+
disconnectLocally();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
updateState({ connectedWallet: refreshed, wallets: newWallets, ...result });
|
|
186
|
+
if (result.account) {
|
|
187
|
+
persistAccount(result.account, refreshed);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function reconcileActiveAccount(wallet) {
|
|
192
|
+
const newAccounts = wallet.accounts;
|
|
193
|
+
if (newAccounts.length === 0) return null;
|
|
194
|
+
const currentAddress = state.account?.address;
|
|
195
|
+
const stillPresent = currentAddress ? newAccounts.find((a) => a.address === currentAddress) : null;
|
|
196
|
+
const activeAccount = stillPresent ?? newAccounts[0];
|
|
197
|
+
if (activeAccount === state.account) return {};
|
|
198
|
+
return { account: activeAccount, signer: tryCreateSigner(activeAccount) };
|
|
199
|
+
}
|
|
200
|
+
async function connect(uiWallet, options) {
|
|
201
|
+
options?.abortSignal?.throwIfAborted();
|
|
202
|
+
const connectFeature = getWalletFeature(
|
|
203
|
+
uiWallet,
|
|
204
|
+
StandardConnect
|
|
205
|
+
);
|
|
206
|
+
userHasSelected = true;
|
|
207
|
+
cancelReconnect();
|
|
208
|
+
const generation = ++connectGeneration;
|
|
209
|
+
updateState({ status: "connecting" });
|
|
210
|
+
try {
|
|
211
|
+
const existingAccounts = [...uiWallet.accounts];
|
|
212
|
+
await connectFeature.connect();
|
|
213
|
+
if (generation !== connectGeneration) {
|
|
214
|
+
throw new DOMException("Wallet connect was superseded by a newer connect or sign-in", "AbortError");
|
|
215
|
+
}
|
|
216
|
+
if (!isWalletStillAvailable(uiWallet)) {
|
|
217
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED, { operation: "connect" });
|
|
218
|
+
}
|
|
219
|
+
const refreshedWallet = refreshUiWallet(uiWallet);
|
|
220
|
+
const allAccounts = refreshedWallet.accounts;
|
|
221
|
+
if (allAccounts.length === 0) {
|
|
222
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED, { operation: "connect" });
|
|
223
|
+
}
|
|
224
|
+
const newAccount = allAccounts.find((a) => !existingAccounts.some((e) => e.address === a.address));
|
|
225
|
+
const activeAccount = newAccount ?? allAccounts[0];
|
|
226
|
+
setConnected(activeAccount, refreshedWallet);
|
|
227
|
+
return allAccounts;
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (generation === connectGeneration) {
|
|
230
|
+
revertToPreviousConnectionOrDisconnect();
|
|
231
|
+
}
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function disconnect(wallet, options) {
|
|
236
|
+
options?.abortSignal?.throwIfAborted();
|
|
237
|
+
const target = wallet ?? state.connectedWallet;
|
|
238
|
+
if (!target) {
|
|
239
|
+
userHasSelected = true;
|
|
240
|
+
cancelReconnect();
|
|
241
|
+
connectGeneration++;
|
|
242
|
+
updateState({ status: "disconnected" });
|
|
243
|
+
clearPersistedAccount();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const isActive = state.connectedWallet != null && state.connectedWallet.name === target.name;
|
|
247
|
+
if (isActive) {
|
|
248
|
+
const currentWallet = state.connectedWallet;
|
|
249
|
+
const generation = ++connectGeneration;
|
|
250
|
+
disconnectingWalletName = currentWallet.name;
|
|
251
|
+
updateState({ status: "disconnecting" });
|
|
252
|
+
try {
|
|
253
|
+
if (currentWallet.features.includes(StandardDisconnect)) {
|
|
254
|
+
const disconnectFeature = getWalletFeature(
|
|
255
|
+
currentWallet,
|
|
256
|
+
StandardDisconnect
|
|
257
|
+
);
|
|
258
|
+
await disconnectFeature.disconnect();
|
|
259
|
+
}
|
|
260
|
+
} finally {
|
|
261
|
+
if (generation === connectGeneration) {
|
|
262
|
+
disconnectLocally();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!isWalletStillAvailable(target)) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const refreshedTarget = refreshUiWallet(target);
|
|
271
|
+
if (!refreshedTarget.features.includes(StandardDisconnect)) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const prevDisconnecting = disconnectingWalletName;
|
|
275
|
+
disconnectingWalletName = refreshedTarget.name;
|
|
276
|
+
try {
|
|
277
|
+
const disconnectFeature = getWalletFeature(
|
|
278
|
+
refreshedTarget,
|
|
279
|
+
StandardDisconnect
|
|
280
|
+
);
|
|
281
|
+
await disconnectFeature.disconnect();
|
|
282
|
+
} finally {
|
|
283
|
+
if (disconnectingWalletName === refreshedTarget.name) {
|
|
284
|
+
disconnectingWalletName = prevDisconnecting;
|
|
285
|
+
}
|
|
286
|
+
if (!disposed) {
|
|
287
|
+
updateState({ wallets: reconcileWalletList() });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function disconnectLocally() {
|
|
292
|
+
disconnectingWalletName = null;
|
|
293
|
+
updateState({
|
|
294
|
+
account: null,
|
|
295
|
+
connectedWallet: null,
|
|
296
|
+
signer: null,
|
|
297
|
+
status: "disconnected",
|
|
298
|
+
// Reconcile the list too: when this runs from the change handler
|
|
299
|
+
// because the connected wallet dropped the configured chain or
|
|
300
|
+
// failed the filter, that wallet must leave `wallets` immediately
|
|
301
|
+
// rather than linger until an unrelated register/unregister event.
|
|
302
|
+
// `reconcileWalletList` returns the existing reference when the
|
|
303
|
+
// visible set is unchanged (the common case for a plain disconnect),
|
|
304
|
+
// so this stays churn-free.
|
|
305
|
+
wallets: reconcileWalletList()
|
|
306
|
+
});
|
|
307
|
+
clearPersistedAccount();
|
|
308
|
+
}
|
|
309
|
+
function revertToPreviousConnectionOrDisconnect() {
|
|
310
|
+
if (state.connectedWallet && state.account && state.connectedWallet.name !== disconnectingWalletName) {
|
|
311
|
+
updateState({ status: "connected" });
|
|
312
|
+
} else {
|
|
313
|
+
disconnectLocally();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function selectAccount(account) {
|
|
317
|
+
let owner;
|
|
318
|
+
try {
|
|
319
|
+
owner = getOrCreateUiWalletForStandardWallet(getWalletForHandle(account));
|
|
320
|
+
} catch {
|
|
321
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__ACCOUNT_NOT_AVAILABLE, {
|
|
322
|
+
account: account.address,
|
|
323
|
+
operation: "selectAccount"
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (!isWalletStillAvailable(owner)) {
|
|
327
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__ACCOUNT_NOT_AVAILABLE, {
|
|
328
|
+
account: account.address,
|
|
329
|
+
operation: "selectAccount"
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (owner.name === disconnectingWalletName) {
|
|
333
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED, { operation: "selectAccount" });
|
|
334
|
+
}
|
|
335
|
+
const refreshed = refreshUiWallet(owner);
|
|
336
|
+
const selectedAccount = refreshed.accounts.find((a) => a.address === account.address);
|
|
337
|
+
if (!selectedAccount) {
|
|
338
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__ACCOUNT_NOT_AVAILABLE, {
|
|
339
|
+
account: account.address,
|
|
340
|
+
operation: "selectAccount"
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
userHasSelected = true;
|
|
344
|
+
++connectGeneration;
|
|
345
|
+
if (selectedAccount === state.account && refreshed === state.connectedWallet) {
|
|
346
|
+
updateState({ status: "connected" });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const signer = tryCreateSigner(selectedAccount);
|
|
350
|
+
updateState({ account: selectedAccount, connectedWallet: refreshed, signer, status: "connected" });
|
|
351
|
+
persistAccount(selectedAccount, refreshed);
|
|
352
|
+
}
|
|
353
|
+
async function signMessage(message, options) {
|
|
354
|
+
options?.abortSignal?.throwIfAborted();
|
|
355
|
+
const { connectedWallet, account } = state;
|
|
356
|
+
if (!connectedWallet || !account) {
|
|
357
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED, { operation: "signMessage" });
|
|
358
|
+
}
|
|
359
|
+
const signMessageFeature = getWalletAccountFeature(
|
|
360
|
+
account,
|
|
361
|
+
SolanaSignMessage
|
|
362
|
+
);
|
|
363
|
+
const [output] = await signMessageFeature.signMessage({ account, message });
|
|
364
|
+
return output.signature;
|
|
365
|
+
}
|
|
366
|
+
async function signIn(uiWallet, input, options) {
|
|
367
|
+
options?.abortSignal?.throwIfAborted();
|
|
368
|
+
const signInFeature = getWalletFeature(uiWallet, SolanaSignIn);
|
|
369
|
+
userHasSelected = true;
|
|
370
|
+
cancelReconnect();
|
|
371
|
+
const generation = ++connectGeneration;
|
|
372
|
+
updateState({ status: "connecting" });
|
|
373
|
+
try {
|
|
374
|
+
const [result] = await signInFeature.signIn(input);
|
|
375
|
+
if (generation !== connectGeneration) {
|
|
376
|
+
throw new DOMException("Wallet sign-in was superseded by a newer connect or sign-in", "AbortError");
|
|
377
|
+
}
|
|
378
|
+
if (!isWalletStillAvailable(uiWallet)) {
|
|
379
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED, { operation: "signIn" });
|
|
380
|
+
}
|
|
381
|
+
const refreshedWallet = refreshUiWallet(uiWallet);
|
|
382
|
+
const activeAccount = refreshedWallet.accounts.find((a) => a.address === result.account.address);
|
|
383
|
+
if (!activeAccount) {
|
|
384
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__NOT_CONNECTED, { operation: "signIn" });
|
|
385
|
+
}
|
|
386
|
+
setConnected(activeAccount, refreshedWallet);
|
|
387
|
+
return result;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (generation === connectGeneration) {
|
|
390
|
+
revertToPreviousConnectionOrDisconnect();
|
|
391
|
+
}
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function ignoreStorageFailure(operation) {
|
|
396
|
+
void (async () => {
|
|
397
|
+
try {
|
|
398
|
+
await operation();
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
})();
|
|
402
|
+
}
|
|
403
|
+
function persistAccount(account, wallet) {
|
|
404
|
+
if (!storage) return;
|
|
405
|
+
ignoreStorageFailure(() => storage.setItem(storageKey, `${wallet.name}:${account.address}`));
|
|
406
|
+
}
|
|
407
|
+
function clearPersistedAccount() {
|
|
408
|
+
if (!storage) return;
|
|
409
|
+
ignoreStorageFailure(() => storage.removeItem(storageKey));
|
|
410
|
+
}
|
|
411
|
+
if (config.autoConnect !== false && storage) {
|
|
412
|
+
const generation = connectGeneration;
|
|
413
|
+
(async () => {
|
|
414
|
+
const savedKey = await storage.getItem(storageKey);
|
|
415
|
+
if (userHasSelected || disposed) return;
|
|
416
|
+
if (!savedKey) {
|
|
417
|
+
updateState({ status: "disconnected" });
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const separatorIndex = savedKey.lastIndexOf(":");
|
|
421
|
+
if (separatorIndex === -1) {
|
|
422
|
+
updateState({ status: "disconnected" });
|
|
423
|
+
clearPersistedAccount();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const walletName = savedKey.slice(0, separatorIndex);
|
|
427
|
+
const savedAddress = savedKey.slice(separatorIndex + 1);
|
|
428
|
+
const existing = state.wallets.find((w) => w.name === walletName);
|
|
429
|
+
if (existing) {
|
|
430
|
+
await attemptSilentReconnect(savedAddress, existing);
|
|
431
|
+
} else if (registry.get().some((w) => {
|
|
432
|
+
const ui = getOrCreateUiWalletForStandardWallet(w);
|
|
433
|
+
return ui.name === walletName;
|
|
434
|
+
})) {
|
|
435
|
+
updateState({ status: "disconnected" });
|
|
436
|
+
clearPersistedAccount();
|
|
437
|
+
} else {
|
|
438
|
+
updateState({ status: "reconnecting" });
|
|
439
|
+
const statusTimeout = setTimeout(() => {
|
|
440
|
+
if (!userHasSelected && state.status === "reconnecting") {
|
|
441
|
+
updateState({ status: "disconnected" });
|
|
442
|
+
}
|
|
443
|
+
}, 3e3);
|
|
444
|
+
const unsubRegisterForReconnect = registry.on("register", () => {
|
|
445
|
+
const generation2 = connectGeneration;
|
|
446
|
+
void (async () => {
|
|
447
|
+
if (userHasSelected) {
|
|
448
|
+
clearTimeout(statusTimeout);
|
|
449
|
+
unsubRegisterForReconnect();
|
|
450
|
+
reconnectCleanup = null;
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const found = buildWalletList().find((w) => w.name === walletName);
|
|
454
|
+
if (found) {
|
|
455
|
+
clearTimeout(statusTimeout);
|
|
456
|
+
unsubRegisterForReconnect();
|
|
457
|
+
reconnectCleanup = null;
|
|
458
|
+
await attemptSilentReconnect(savedAddress, found);
|
|
459
|
+
} else if (registry.get().some((w) => {
|
|
460
|
+
const ui = getOrCreateUiWalletForStandardWallet(w);
|
|
461
|
+
return ui.name === walletName;
|
|
462
|
+
})) {
|
|
463
|
+
clearTimeout(statusTimeout);
|
|
464
|
+
unsubRegisterForReconnect();
|
|
465
|
+
reconnectCleanup = null;
|
|
466
|
+
updateState({ status: "disconnected" });
|
|
467
|
+
clearPersistedAccount();
|
|
468
|
+
}
|
|
469
|
+
})().catch(() => {
|
|
470
|
+
if (generation2 === connectGeneration && !userHasSelected) {
|
|
471
|
+
updateState({ status: "disconnected" });
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
reconnectCleanup = () => {
|
|
476
|
+
clearTimeout(statusTimeout);
|
|
477
|
+
unsubRegisterForReconnect();
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
})().catch(() => {
|
|
481
|
+
if (generation === connectGeneration && !userHasSelected) {
|
|
482
|
+
updateState({ status: "disconnected" });
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
} else {
|
|
486
|
+
updateState({ status: "disconnected" });
|
|
487
|
+
}
|
|
488
|
+
async function attemptSilentReconnect(savedAddress, uiWallet) {
|
|
489
|
+
const generation = ++connectGeneration;
|
|
490
|
+
updateState({ status: "reconnecting" });
|
|
491
|
+
try {
|
|
492
|
+
const connectFeature = getWalletFeature(
|
|
493
|
+
uiWallet,
|
|
494
|
+
StandardConnect
|
|
495
|
+
);
|
|
496
|
+
await connectFeature.connect({ silent: true });
|
|
497
|
+
if (generation !== connectGeneration) return;
|
|
498
|
+
if (!isWalletStillAvailable(uiWallet)) {
|
|
499
|
+
updateState({ status: "disconnected" });
|
|
500
|
+
clearPersistedAccount();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const refreshedWallet = refreshUiWallet(uiWallet);
|
|
504
|
+
const allAccounts = refreshedWallet.accounts;
|
|
505
|
+
if (allAccounts.length === 0) {
|
|
506
|
+
updateState({ status: "disconnected" });
|
|
507
|
+
clearPersistedAccount();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const activeAccount = allAccounts.find((a) => a.address === savedAddress) ?? allAccounts[0];
|
|
511
|
+
setConnected(activeAccount, refreshedWallet, {
|
|
512
|
+
persist: activeAccount.address !== savedAddress
|
|
513
|
+
});
|
|
514
|
+
} catch {
|
|
515
|
+
if (generation === connectGeneration) {
|
|
516
|
+
updateState({ status: "disconnected" });
|
|
517
|
+
clearPersistedAccount();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
connect,
|
|
523
|
+
disconnect,
|
|
524
|
+
getState: () => snapshot,
|
|
525
|
+
selectAccount,
|
|
526
|
+
signIn,
|
|
527
|
+
signMessage,
|
|
528
|
+
subscribe: (listener) => {
|
|
529
|
+
listeners.add(listener);
|
|
530
|
+
return () => {
|
|
531
|
+
listeners.delete(listener);
|
|
532
|
+
};
|
|
533
|
+
},
|
|
534
|
+
subscribeSigner: (listener) => {
|
|
535
|
+
signerListeners.add(listener);
|
|
536
|
+
return () => {
|
|
537
|
+
signerListeners.delete(listener);
|
|
538
|
+
};
|
|
539
|
+
},
|
|
540
|
+
whenReady,
|
|
541
|
+
[Symbol.dispose]: () => {
|
|
542
|
+
disposed = true;
|
|
543
|
+
connectGeneration++;
|
|
544
|
+
unsubRegister();
|
|
545
|
+
unsubUnregister();
|
|
546
|
+
for (const cleanup of walletEventSubscriptions.values()) {
|
|
547
|
+
cleanup();
|
|
548
|
+
}
|
|
549
|
+
walletEventSubscriptions.clear();
|
|
550
|
+
cancelReconnect();
|
|
551
|
+
listeners.clear();
|
|
552
|
+
signerListeners.clear();
|
|
553
|
+
if (isWalletWarmingUp(state.status)) {
|
|
554
|
+
updateState({ status: "disconnected" });
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/wallet.ts
|
|
561
|
+
function defineSignerGetter(additions, property, store) {
|
|
562
|
+
Object.defineProperty(additions, property, {
|
|
563
|
+
configurable: true,
|
|
564
|
+
// Non-enumerable on purpose: this getter throws when no signer is connected, so we
|
|
565
|
+
// don't want things like Object.entries to read it.
|
|
566
|
+
enumerable: false,
|
|
567
|
+
get() {
|
|
568
|
+
const state = store.getState();
|
|
569
|
+
if (!state.connected) {
|
|
570
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__NO_SIGNER_CONNECTED, { status: state.status });
|
|
571
|
+
}
|
|
572
|
+
if (!state.connected.signer) {
|
|
573
|
+
throw new SolanaError(SOLANA_ERROR__WALLET__SIGNER_NOT_AVAILABLE);
|
|
574
|
+
}
|
|
575
|
+
return state.connected.signer;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
var SUBSCRIBE_PROPERTY = {
|
|
580
|
+
identity: "subscribeToIdentity",
|
|
581
|
+
payer: "subscribeToPayer"
|
|
582
|
+
};
|
|
583
|
+
function createPlugin(config, signerProperties) {
|
|
584
|
+
return (client) => {
|
|
585
|
+
if ("wallet" in client) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
"Only one wallet plugin can be used per client. Use walletSigner, walletPayer, walletIdentity, or walletWithoutSigner \u2014 not multiple."
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const store = createWalletStore(config);
|
|
591
|
+
const additions = { wallet: store };
|
|
592
|
+
for (const prop of signerProperties) {
|
|
593
|
+
defineSignerGetter(additions, prop, store);
|
|
594
|
+
additions[SUBSCRIBE_PROPERTY[prop]] = store.subscribeSigner;
|
|
595
|
+
}
|
|
596
|
+
return withCleanup(extendClient(client, additions), () => store[Symbol.dispose]());
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function walletSigner(config) {
|
|
600
|
+
return createPlugin(config, ["payer", "identity"]);
|
|
601
|
+
}
|
|
602
|
+
function walletIdentity(config) {
|
|
603
|
+
return createPlugin(config, ["identity"]);
|
|
604
|
+
}
|
|
605
|
+
function walletPayer(config) {
|
|
606
|
+
return createPlugin(config, ["payer"]);
|
|
607
|
+
}
|
|
608
|
+
function walletWithoutSigner(config) {
|
|
609
|
+
return createPlugin(config, []);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export { walletIdentity, walletPayer, walletSigner, walletWithoutSigner };
|
|
613
|
+
//# sourceMappingURL=index.browser.mjs.map
|
|
614
|
+
//# sourceMappingURL=index.browser.mjs.map
|