@bounded-sh/client 0.0.26 → 0.0.27

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.
Files changed (34) hide show
  1. package/REACT_NATIVE.md +144 -0
  2. package/README.md +24 -18
  3. package/dist/auth/index.d.ts +18 -21
  4. package/dist/auth/providers/email-auth-provider.d.ts +8 -19
  5. package/dist/auth/providers/privy-expo-provider.d.ts +1 -1
  6. package/dist/global.d.ts +2 -2
  7. package/dist/index.d.ts +0 -3
  8. package/dist/index.js +84 -4617
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +94 -4621
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/index.native.d.ts +2 -2
  13. package/dist/index.native.js +5184 -78
  14. package/dist/index.native.js.map +1 -1
  15. package/dist/index.native.mjs +5095 -4
  16. package/dist/index.native.mjs.map +1 -1
  17. package/dist/platform.d.ts +1 -1
  18. package/package.json +7 -3
  19. package/dist/index.native-BfAzgVuY.js +0 -5802
  20. package/dist/index.native-BfAzgVuY.js.map +0 -1
  21. package/dist/index.native-DEWlFCQV.mjs +0 -5742
  22. package/dist/index.native-DEWlFCQV.mjs.map +0 -1
  23. package/dist/phantom-wallet-provider-3xKduOOY.js +0 -1452
  24. package/dist/phantom-wallet-provider-3xKduOOY.js.map +0 -1
  25. package/dist/phantom-wallet-provider-B3ztJGu2.mjs +0 -1431
  26. package/dist/phantom-wallet-provider-B3ztJGu2.mjs.map +0 -1
  27. package/dist/privy-wallet-provider-3LnqlNI-.js +0 -949
  28. package/dist/privy-wallet-provider-3LnqlNI-.js.map +0 -1
  29. package/dist/privy-wallet-provider-D2i_xwYW.mjs +0 -926
  30. package/dist/privy-wallet-provider-D2i_xwYW.mjs.map +0 -1
  31. package/dist/solana-mobile-wallet-provider-D2nTRj-4.js +0 -1492
  32. package/dist/solana-mobile-wallet-provider-D2nTRj-4.js.map +0 -1
  33. package/dist/solana-mobile-wallet-provider-dOWiujmo.mjs +0 -1471
  34. package/dist/solana-mobile-wallet-provider-dOWiujmo.mjs.map +0 -1
@@ -1,1452 +0,0 @@
1
- 'use strict';
2
-
3
- var index_native = require('./index.native-BfAzgVuY.js');
4
- var core = require('@bounded-sh/core');
5
- var web3_js = require('@solana/web3.js');
6
- var anchor = require('@coral-xyz/anchor');
7
- require('react');
8
-
9
- function _interopNamespaceDefault(e) {
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
20
- });
21
- }
22
- n.default = e;
23
- return Object.freeze(n);
24
- }
25
-
26
- var anchor__namespace = /*#__PURE__*/_interopNamespaceDefault(anchor);
27
-
28
- const VALID_PROVIDERS = ['injected', 'google', 'apple', 'deeplink', 'phantom'];
29
- // When the dApp runs inside an iframe whose parent origin is different from
30
- // (or can't inject into) the child frame — notably Phantom's Android in-app
31
- // browser, which injects window.phantom.solana only into the top frame —
32
- // the Phantom deeplink path tries to navigate phantom.app/ul/browse/... from
33
- // inside the iframe. Android App Links only hand off on top-level navigations,
34
- // and phantom.app refuses to be framed (X-Frame-Options: DENY), so the connect
35
- // fails. To get back to a working context we navigate the top window to the
36
- // dApp's own URL, dropping the iframe shell. Once top-level, the wallet
37
- // browser injects its provider and the normal injected flow succeeds.
38
- function isInIframe() {
39
- if (typeof window === 'undefined')
40
- return false;
41
- try {
42
- return window.top !== window.self;
43
- }
44
- catch (_a) {
45
- // Cross-origin access to window.top throws — if we can't read it, we're iframed.
46
- return true;
47
- }
48
- }
49
- function shouldBreakOutOfIframe(provider) {
50
- // Only break out when the user chose the deeplink provider. The modal only
51
- // offers deeplink on mobile when no injected Phantom is present, so this is
52
- // exactly the iframe-without-provider case we need to escape.
53
- return provider === 'deeplink' && isInIframe();
54
- }
55
- // In a cross-origin iframe (e.g. the poof.new build-page preview) on mobile,
56
- // Mobile Wallet Adapter can't run: its localhost association handshake needs
57
- // Chrome's Local Network Access, which is denied to cross-origin sub-frames.
58
- // So open the app's own URL TOP-LEVEL (a new tab / Custom Tab), where MWA works
59
- // natively; the connect happens there and the session — written to same-origin
60
- // storage — reflects back into this iframe (awaitTopLevelConnect picks it up).
61
- // Must be called synchronously inside a user-gesture handler so window.open
62
- // isn't popup-blocked. Returns true if the tab was opened.
63
- function openTopLevelForConnect() {
64
- if (typeof window === 'undefined' || !isInIframe())
65
- return false;
66
- try {
67
- const url = new URL(window.location.href);
68
- url.searchParams.set('poofConnect', '1');
69
- const w = window.open(url.toString(), '_blank');
70
- return !!w;
71
- }
72
- catch (_a) {
73
- return false;
74
- }
75
- }
76
- // Dynamically import React and Phantom SDK - only when needed
77
- let React;
78
- let ReactDOM;
79
- let phantomReactSdk;
80
- let sdkLoaded = false;
81
- let loadingPromise = null;
82
- // Lazy load React and Phantom dependencies only when PhantomWalletProvider is instantiated
83
- // Uses dynamic import() to ensure code splitting and prevent side effects
84
- async function loadDependencies() {
85
- if (sdkLoaded)
86
- return;
87
- if (typeof window === 'undefined')
88
- return;
89
- // Prevent multiple concurrent loads
90
- if (loadingPromise)
91
- return loadingPromise;
92
- loadingPromise = (async () => {
93
- const [reactModule, reactDomModule, phantomModule] = await Promise.all([
94
- import('react'),
95
- import('react-dom/client'),
96
- import('@phantom/react-sdk')
97
- ]);
98
- // Extract default export from ESM module namespace
99
- // Dynamic import() returns { default: Module, ...exports }, not the module directly
100
- React = reactModule.default || reactModule;
101
- ReactDOM = reactDomModule.default || reactDomModule;
102
- phantomReactSdk = phantomModule;
103
- sdkLoaded = true;
104
- })();
105
- return loadingPromise;
106
- }
107
- class PhantomWalletProvider {
108
- /** Temporarily override modal appearance for a single login() call. Cleared automatically after login settles. */
109
- setLoginOverrides(opts) {
110
- this.loginOverrides = opts;
111
- }
112
- constructor(networkUrl = null, config = {}, configuredNetwork = null) {
113
- this.containerElement = null;
114
- this.root = null;
115
- this.phantomMethods = null;
116
- this.resolvedProviders = ['injected'];
117
- this.pendingLogin = null;
118
- this.loginInProgress = false;
119
- this.autoLoginInProgress = false;
120
- this.initPromise = null;
121
- this.loginOverrides = null;
122
- /** Callback to swap to a Mobile Wallet Adapter provider when the user clicks "Connect Mobile Wallet" */
123
- this.onSwitchToMWA = null;
124
- this.networkUrl = networkUrl;
125
- this.configuredNetwork = configuredNetwork;
126
- this.config = config;
127
- if (typeof window === 'undefined') {
128
- throw new Error('PhantomWalletProvider can only be instantiated in a browser environment');
129
- }
130
- // Singleton pattern - reuse existing instance
131
- if (PhantomWalletProvider.instance) {
132
- return PhantomWalletProvider.instance;
133
- }
134
- // Resolve providers from config (doesn't need SDK)
135
- this.resolveProviders();
136
- // Start async initialization (load SDK and initialize React component)
137
- this.initPromise = this.initializeAsync();
138
- PhantomWalletProvider.instance = this;
139
- }
140
- async initializeAsync() {
141
- // Lazy load dependencies only when actually instantiating
142
- await loadDependencies();
143
- // Initialize React component
144
- this.initialize();
145
- }
146
- /**
147
- * Check if social login providers are configured (non-injected providers).
148
- */
149
- hasSocialProviders() {
150
- return this.resolvedProviders.some(p => p !== 'injected');
151
- }
152
- static getInstance(networkUrl, config, configuredNetwork = null) {
153
- if (!PhantomWalletProvider.instance) {
154
- new PhantomWalletProvider(networkUrl, config, configuredNetwork);
155
- }
156
- return PhantomWalletProvider.instance;
157
- }
158
- resolveProviders() {
159
- if (this.config.providers && this.config.providers.length > 0) {
160
- const configProviders = this.config.providers;
161
- this.resolvedProviders = configProviders.filter((p) => VALID_PROVIDERS.includes(p));
162
- if (this.resolvedProviders.length === 0) {
163
- this.resolvedProviders = ['injected'];
164
- }
165
- }
166
- else if (this.config.appId) {
167
- this.resolvedProviders = ['injected', 'google', 'apple', 'deeplink'];
168
- }
169
- else {
170
- this.resolvedProviders = ['injected'];
171
- }
172
- }
173
- /**
174
- * Patch the Phantom extension's provider to gracefully handle `phantom_getFeatures`
175
- * instead of throwing. Some SDKs probe this method to check feature support, but the
176
- * extension doesn't implement it, causing noisy console errors.
177
- */
178
- patchPhantomGetFeatures() {
179
- var _a;
180
- try {
181
- const provider = (_a = window.phantom) === null || _a === void 0 ? void 0 : _a.solana;
182
- if (!(provider === null || provider === void 0 ? void 0 : provider.request) || provider.__featuresPatched)
183
- return;
184
- const originalRequest = provider.request.bind(provider);
185
- provider.request = async (args) => {
186
- if ((args === null || args === void 0 ? void 0 : args.method) === 'phantom_getFeatures') {
187
- return { features: {} };
188
- }
189
- return originalRequest(args);
190
- };
191
- provider.__featuresPatched = true;
192
- }
193
- catch (_b) {
194
- // Phantom extension not present — nothing to patch
195
- }
196
- }
197
- initialize() {
198
- if (this.containerElement)
199
- return;
200
- // Suppress "phantom_getFeatures isn't implemented" errors from SDKs probing
201
- // the Phantom extension for features it doesn't support yet.
202
- this.patchPhantomGetFeatures();
203
- // Remove any existing Phantom providers
204
- const existingProviders = document.querySelectorAll('[data-phantom-provider]');
205
- existingProviders.forEach(el => el.remove());
206
- this.containerElement = document.createElement('div');
207
- this.containerElement.setAttribute('data-phantom-provider', 'true');
208
- // Keep Phantom UI above host overlays while avoiding global click-capture.
209
- this.containerElement.style.position = 'fixed';
210
- this.containerElement.style.zIndex = '2147483647';
211
- // Full-viewport overlay for custom modal; pointer-events: none lets
212
- // clicks pass through to page except where the modal sets 'auto'
213
- this.containerElement.style.inset = '0';
214
- this.containerElement.style.pointerEvents = 'none';
215
- document.body.appendChild(this.containerElement);
216
- const that = this;
217
- const { PhantomProvider: ReactPhantomProvider, usePhantom, useConnect, useDisconnect, useModal, useSolana, useDiscoveredWallets, AddressType, darkTheme, lightTheme } = phantomReactSdk;
218
- // The SDK expects providers as an array of strings: 'injected' | 'google' | 'apple' | 'phantom' | 'deeplink'
219
- // Ensure we have a valid non-empty array
220
- const sdkProviders = this.resolvedProviders.length > 0 ? [...this.resolvedProviders] : ['injected'];
221
- // Inner component that uses hooks
222
- const PhantomHooksComponent = () => {
223
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
224
- const phantom = usePhantom();
225
- const { connect, error: connectError } = useConnect();
226
- const { disconnect, isDisconnecting } = useDisconnect();
227
- const modal = useModal();
228
- const solanaHook = useSolana();
229
- const { solana } = solanaHook;
230
- const [showWalletModal, setShowWalletModal] = React.useState(false);
231
- const [hoveredBtn, setHoveredBtn] = React.useState(null);
232
- // Discover all available wallets via Wallet Standard + EIP-6963
233
- const { wallets: discoveredWallets } = useDiscoveredWallets();
234
- const isMobile = index_native.detectMobile();
235
- const hasPhantomInjected = discoveredWallets.some((w) => w.id === 'phantom');
236
- const showDeeplink = isMobile && sdkProviders.includes('deeplink') && !hasPhantomInjected;
237
- // Treat MWA's own wallet-standard registrations as not-injected, so the MWA
238
- // button still appears when only MWA is present (e.g. Android Chrome on Seeker).
239
- const hasInjectedWallet = discoveredWallets.some((w) => {
240
- var _a, _b;
241
- const id = String((_a = w.id) !== null && _a !== void 0 ? _a : '').toLowerCase();
242
- const name = String((_b = w.name) !== null && _b !== void 0 ? _b : '').toLowerCase();
243
- return (id !== 'mobile-wallet-adapter' &&
244
- id !== 'remote-mobile-wallet-adapter' &&
245
- name !== 'mobile wallet adapter' &&
246
- name !== 'remote mobile wallet adapter');
247
- });
248
- // Track previous modal state to detect closes
249
- const prevModalOpen = React.useRef(false);
250
- // Track when modal was closed because user selected a wallet (not dismissed)
251
- const walletClickedRef = React.useRef(false);
252
- // Set up effect to expose methods to the class
253
- React.useEffect(() => {
254
- if (phantom) {
255
- that.phantomMethods = {
256
- ready: !phantom.isLoading,
257
- isConnected: phantom.isConnected,
258
- addresses: phantom.addresses || [],
259
- user: phantom.user,
260
- connect: async () => {
261
- return await connect();
262
- },
263
- disconnect: async () => {
264
- await disconnect();
265
- },
266
- isDisconnecting,
267
- openModal: () => modal.open(),
268
- closeModal: () => modal.close(),
269
- isModalOpen: modal.isOpened,
270
- connectError,
271
- showCustomModal: () => setShowWalletModal(true),
272
- hideCustomModal: () => setShowWalletModal(false),
273
- solana: solana && solanaHook.isAvailable ? {
274
- // Wrap methods to preserve 'this' context - direct references lose binding
275
- signMessage: (message) => solana.signMessage(message),
276
- signTransaction: (tx) => solana.signTransaction(tx),
277
- signAllTransactions: (txs) => solana.signAllTransactions(txs),
278
- signAndSendTransaction: (tx) => solana.signAndSendTransaction(tx),
279
- getPublicKey: () => solana.getPublicKey(),
280
- isAvailable: solanaHook.isAvailable,
281
- connected: solana.connected,
282
- connect: (options) => solana.connect(options),
283
- } : null,
284
- };
285
- }
286
- }, [phantom, phantom === null || phantom === void 0 ? void 0 : phantom.isConnected, phantom === null || phantom === void 0 ? void 0 : phantom.addresses, phantom === null || phantom === void 0 ? void 0 : phantom.isLoading, connect, disconnect, isDisconnecting, modal, modal.isOpened, solana, solana === null || solana === void 0 ? void 0 : solana.connected, solanaHook.isAvailable, connectError]);
287
- // Auto-login: If connected but no Bounded session, try to create one.
288
- // This handles social login callbacks where user returns from OAuth already connected.
289
- React.useEffect(() => {
290
- const autoCreateSession = async () => {
291
- var _a;
292
- // Only proceed when SDK is ready, connected, has addresses, and not already in a login flow
293
- if (!(phantom === null || phantom === void 0 ? void 0 : phantom.isConnected) || (phantom === null || phantom === void 0 ? void 0 : phantom.isLoading) || that.loginInProgress || that.autoLoginInProgress || that.pendingLogin) {
294
- return;
295
- }
296
- // Don't clobber a session owned by MWA on Seeker. MWA marks the
297
- // auth method as soon as login starts (see solana-mobile-wallet-provider),
298
- // and clears it on logout, so this guard is correct in both
299
- // steady-state and post-logout cases.
300
- try {
301
- if (index_native.getPlatform().storage.getItem('bounded_last_auth_method') === 'mobile-wallet-adapter') {
302
- return;
303
- }
304
- }
305
- catch (_b) { }
306
- // Need solana to be available AND connected for signing
307
- if (!solana || !solanaHook.isAvailable || !solana.connected) {
308
- return;
309
- }
310
- // Find Solana address
311
- const solAddress = (_a = phantom.addresses) === null || _a === void 0 ? void 0 : _a.find((addr) => addr.addressType === AddressType.solana);
312
- if (!solAddress) {
313
- return;
314
- }
315
- const publicKey = solAddress.address;
316
- // Check if we already have a valid session for this address
317
- const existingSession = await core.WebSessionManager.getSession();
318
- if (existingSession && existingSession.address === publicKey) {
319
- // Already have a valid session, just set user
320
- index_native.setCurrentUser({ provider: that, address: publicKey });
321
- return;
322
- }
323
- // No valid session - try to create one automatically
324
- that.autoLoginInProgress = true;
325
- index_native.setAuthLoading(true);
326
- try {
327
- const nonce = await core.genAuthNonce();
328
- const messageText = await core.genSolanaMessage(publicKey, nonce);
329
- // signMessage returns { signature: Uint8Array, publicKey: string }
330
- const signResult = await solana.signMessage(messageText);
331
- // Convert Uint8Array signature to base64 string
332
- const signatureBytes = signResult.signature;
333
- const signature = index_native.bufferExports.Buffer.from(signatureBytes).toString('base64');
334
- const createSessionResult = await core.createSessionWithSignature(publicKey, messageText, signature);
335
- // Pre-write guard: MWA may have started a login while we were
336
- // in flight. If MWA owns the auth method now, abort before we
337
- // overwrite its session (or its in-flight session).
338
- try {
339
- if (index_native.getPlatform().storage.getItem('bounded_last_auth_method') === 'mobile-wallet-adapter') {
340
- return;
341
- }
342
- }
343
- catch (_c) { }
344
- await core.WebSessionManager.storeSession(publicKey, createSessionResult.accessToken, createSessionResult.idToken, createSessionResult.refreshToken);
345
- // Mark auth method so clearIncompatibleSession() doesn't wipe this session
346
- try {
347
- index_native.getPlatform().storage.setItem('bounded_last_auth_method', 'phantom');
348
- }
349
- catch (_d) { }
350
- index_native.setCurrentUser({ provider: that, address: publicKey });
351
- }
352
- catch (error) {
353
- // User rejected signing or other error - disconnect fully
354
- try {
355
- await disconnect();
356
- }
357
- catch (e) {
358
- // Ignore disconnect errors
359
- }
360
- }
361
- finally {
362
- that.autoLoginInProgress = false;
363
- index_native.setAuthLoading(false);
364
- }
365
- };
366
- autoCreateSession();
367
- }, [phantom === null || phantom === void 0 ? void 0 : phantom.isConnected, phantom === null || phantom === void 0 ? void 0 : phantom.isLoading, phantom === null || phantom === void 0 ? void 0 : phantom.addresses, solana, solana === null || solana === void 0 ? void 0 : solana.connected, solanaHook.isAvailable, disconnect]);
368
- // Suppress SDK modal if it opens during custom modal login flow
369
- React.useEffect(() => {
370
- if (modal.isOpened && that.loginInProgress) {
371
- modal.close();
372
- }
373
- }, [modal.isOpened]);
374
- // Handle modal close without connection
375
- React.useEffect(() => {
376
- // Detect when modal was open and is now closed
377
- if (prevModalOpen.current && !showWalletModal && !modal.isOpened) {
378
- if (walletClickedRef.current) {
379
- // Modal closed because user selected a wallet — don't reject
380
- walletClickedRef.current = false;
381
- }
382
- else if (that.loginInProgress && that.pendingLogin && !(phantom === null || phantom === void 0 ? void 0 : phantom.isConnected)) {
383
- // User dismissed modal without connecting
384
- that.pendingLogin.reject(new Error('User cancelled login'));
385
- that.pendingLogin = null;
386
- that.loginInProgress = false;
387
- }
388
- }
389
- prevModalOpen.current = showWalletModal || modal.isOpened;
390
- }, [showWalletModal, modal.isOpened, phantom === null || phantom === void 0 ? void 0 : phantom.isConnected]);
391
- // Handle connection errors
392
- React.useEffect(() => {
393
- if (connectError && that.pendingLogin) {
394
- that.pendingLogin.reject(connectError);
395
- that.pendingLogin = null;
396
- that.loginInProgress = false;
397
- }
398
- }, [connectError]);
399
- // Handle connection success
400
- React.useEffect(() => {
401
- const handleConnectionSuccess = async () => {
402
- var _a;
403
- // All conditions must be met for login to proceed
404
- if (!(phantom === null || phantom === void 0 ? void 0 : phantom.isConnected) || !((_a = phantom === null || phantom === void 0 ? void 0 : phantom.addresses) === null || _a === void 0 ? void 0 : _a.length) || !that.pendingLogin || !that.loginInProgress) {
405
- return;
406
- }
407
- // Solana must be available AND connected for signing
408
- // If not ready yet, just return - this effect will re-run when solana.connected changes
409
- if (!solana || !solanaHook.isAvailable || !solana.connected) {
410
- return;
411
- }
412
- try {
413
- // Immediately set loginInProgress to false to prevent double execution
414
- // if the effect runs again before async operations complete
415
- that.loginInProgress = false;
416
- // Find Solana address
417
- const solAddress = phantom.addresses.find((addr) => addr.addressType === AddressType.solana);
418
- if (!solAddress) {
419
- that.pendingLogin.reject(new Error('No Solana address returned'));
420
- that.pendingLogin = null;
421
- return;
422
- }
423
- const publicKey = solAddress.address;
424
- // Check if we already have a valid session
425
- const existingSession = await core.WebSessionManager.getSession();
426
- if (existingSession && existingSession.address === publicKey) {
427
- const user = { provider: that, address: publicKey };
428
- index_native.setCurrentUser(user);
429
- that.pendingLogin.resolve(user);
430
- that.pendingLogin = null;
431
- return;
432
- }
433
- // Create new session with signature
434
- const nonce = await core.genAuthNonce();
435
- const messageText = await core.genSolanaMessage(publicKey, nonce);
436
- // signMessage returns { signature: Uint8Array, publicKey: string }
437
- const signResult = await solana.signMessage(messageText);
438
- // Convert Uint8Array signature to base64 string
439
- const signatureBytes = signResult.signature;
440
- const signature = index_native.bufferExports.Buffer.from(signatureBytes).toString('base64');
441
- const createSessionResult = await core.createSessionWithSignature(publicKey, messageText, signature);
442
- await core.WebSessionManager.storeSession(publicKey, createSessionResult.accessToken, createSessionResult.idToken, createSessionResult.refreshToken);
443
- // Mark auth method so clearIncompatibleSession() doesn't wipe this session
444
- try {
445
- index_native.getPlatform().storage.setItem('bounded_last_auth_method', 'phantom');
446
- }
447
- catch (_b) { }
448
- const user = { provider: that, address: publicKey };
449
- index_native.setCurrentUser(user);
450
- that.pendingLogin.resolve(user);
451
- that.pendingLogin = null;
452
- }
453
- catch (error) {
454
- // Disconnect Phantom since login failed - don't leave in half-connected state
455
- try {
456
- await disconnect();
457
- }
458
- catch (e) {
459
- // Ignore disconnect errors
460
- }
461
- if (that.pendingLogin) {
462
- that.pendingLogin.reject(error);
463
- that.pendingLogin = null;
464
- that.loginInProgress = false;
465
- }
466
- }
467
- };
468
- handleConnectionSuccess();
469
- }, [phantom === null || phantom === void 0 ? void 0 : phantom.isConnected, phantom === null || phantom === void 0 ? void 0 : phantom.addresses, solana, solana === null || solana === void 0 ? void 0 : solana.connected, solanaHook.isAvailable]);
470
- // --- Custom wallet modal handlers ---
471
- const handleWalletClick = async (options) => {
472
- walletClickedRef.current = true;
473
- setShowWalletModal(false);
474
- // Cross-origin preview iframe + deeplink: the old approach navigated
475
- // the top frame to this URL, but in a TWA that lands out-of-scope and
476
- // shows a chrome-error ("bad page"). Instead, open the app TOP-LEVEL
477
- // in a new tab where the wallet connect works natively; the session
478
- // reflects back into this iframe (see openTopLevelForConnect /
479
- // awaitTopLevelConnect).
480
- if (shouldBreakOutOfIframe(options === null || options === void 0 ? void 0 : options.provider)) {
481
- if (openTopLevelForConnect()) {
482
- that.awaitTopLevelConnect();
483
- return;
484
- }
485
- // Popup blocked — fall through to the original deeplink path
486
- // (better a broken-ish button than a silently-dead one).
487
- }
488
- try {
489
- if (options === null || options === void 0 ? void 0 : options.walletId) {
490
- await connect({ provider: 'injected', walletId: options.walletId });
491
- }
492
- else if (options === null || options === void 0 ? void 0 : options.provider) {
493
- await connect({ provider: options.provider });
494
- }
495
- else {
496
- await connect();
497
- }
498
- }
499
- catch (err) {
500
- // connectError effect handles this
501
- }
502
- };
503
- const handleMobileWalletClick = async () => {
504
- that.loginInProgress = false;
505
- walletClickedRef.current = true;
506
- setShowWalletModal(false);
507
- // Cross-origin preview iframe (e.g. poof.new build page) on mobile:
508
- // MWA can't run here (Chrome denies Local Network Access to sub-frames,
509
- // and its localhost handshake fails). Open the app top-level for the
510
- // connect; the session reflects back into this iframe.
511
- if (isInIframe() && openTopLevelForConnect()) {
512
- that.awaitTopLevelConnect();
513
- return;
514
- }
515
- // LNA permission is handled by @solana-mobile/wallet-standard-mobile
516
- // >=0.5.0's checkLocalNetworkAccessPermission (three-stage UX, fired
517
- // before the localhost reflector opens). No homemade pre-warm needed.
518
- if (that.onSwitchToMWA) {
519
- try {
520
- const mwaProvider = await that.onSwitchToMWA();
521
- const user = await mwaProvider.login();
522
- if (that.pendingLogin && user) {
523
- that.pendingLogin.resolve(user);
524
- that.pendingLogin = null;
525
- }
526
- else if (that.pendingLogin) {
527
- that.pendingLogin.reject(new Error('User cancelled login'));
528
- that.pendingLogin = null;
529
- }
530
- }
531
- catch (error) {
532
- if (that.pendingLogin) {
533
- that.pendingLogin.reject(error);
534
- that.pendingLogin = null;
535
- }
536
- }
537
- }
538
- };
539
- const handleCloseModal = () => {
540
- setShowWalletModal(false);
541
- if (that.loginInProgress && that.pendingLogin && !(phantom === null || phantom === void 0 ? void 0 : phantom.isConnected)) {
542
- that.pendingLogin.reject(new Error('User cancelled login'));
543
- that.pendingLogin = null;
544
- that.loginInProgress = false;
545
- }
546
- };
547
- // --- Custom wallet modal ---
548
- if (!showWalletModal) {
549
- return null;
550
- }
551
- const effectiveTheme = (_c = (_b = (_a = that.loginOverrides) === null || _a === void 0 ? void 0 : _a.theme) !== null && _b !== void 0 ? _b : that.config.theme) !== null && _c !== void 0 ? _c : 'dark';
552
- const isLight = effectiveTheme === 'light';
553
- const bgColor = isLight ? '#FFFFFF' : '#111113';
554
- const cardBorder = isLight ? '1px solid rgba(0,0,0,0.08)' : '1px solid rgba(255,255,255,0.07)';
555
- const textColor = isLight ? '#0f0f0f' : '#f5f5f5';
556
- const subTextColor = isLight ? 'rgba(0,0,0,0.45)' : 'rgba(255,255,255,0.4)';
557
- const btnBg = isLight ? 'rgba(0,0,0,0.04)' : 'rgba(255,255,255,0.05)';
558
- const btnHoverBg = isLight ? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.09)';
559
- const btnBorder = isLight ? '1px solid rgba(0,0,0,0.10)' : '1px solid rgba(255,255,255,0.09)';
560
- const fontFamily = '"SF Pro Text",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif';
561
- const buttonStyle = (id) => ({
562
- display: 'flex',
563
- alignItems: 'center',
564
- justifyContent: 'center',
565
- gap: '10px',
566
- width: '100%',
567
- height: '52px',
568
- padding: '0 20px',
569
- border: btnBorder,
570
- borderRadius: '12px',
571
- backgroundColor: hoveredBtn === id ? btnHoverBg : btnBg,
572
- color: textColor,
573
- fontFamily,
574
- fontSize: '14px',
575
- fontWeight: '500',
576
- lineHeight: '1',
577
- letterSpacing: '0',
578
- cursor: 'pointer',
579
- transition: 'background-color 0.15s, border-color 0.15s',
580
- boxSizing: 'border-box',
581
- });
582
- const walletButtons = [];
583
- // Google OAuth button — shown when 'google' is in resolved providers
584
- if (sdkProviders.includes('google')) {
585
- walletButtons.push(React.createElement('button', {
586
- key: 'google-oauth',
587
- style: buttonStyle('google-oauth'),
588
- onClick: () => handleWalletClick({ provider: 'google' }),
589
- onMouseEnter: () => setHoveredBtn('google-oauth'),
590
- onMouseLeave: () => setHoveredBtn(null),
591
- }, React.createElement('svg', {
592
- width: '18', height: '18', viewBox: '0 0 24 24',
593
- style: { flexShrink: '0' },
594
- }, React.createElement('path', {
595
- d: 'M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z',
596
- fill: '#4285F4',
597
- }), React.createElement('path', {
598
- d: 'M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z',
599
- fill: '#34A853',
600
- }), React.createElement('path', {
601
- d: 'M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18A11.96 11.96 0 0 0 1 12c0 1.94.46 3.77 1.18 5.41l3.66-2.84z',
602
- fill: '#FBBC05',
603
- }), React.createElement('path', {
604
- d: 'M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z',
605
- fill: '#EA4335',
606
- })), 'Continue with Google'));
607
- }
608
- // Apple OAuth button — shown when 'apple' is in resolved providers
609
- if (sdkProviders.includes('apple')) {
610
- walletButtons.push(React.createElement('button', {
611
- key: 'apple-oauth',
612
- style: buttonStyle('apple-oauth'),
613
- onClick: () => handleWalletClick({ provider: 'apple' }),
614
- onMouseEnter: () => setHoveredBtn('apple-oauth'),
615
- onMouseLeave: () => setHoveredBtn(null),
616
- }, React.createElement('svg', {
617
- width: '18', height: '18', viewBox: '0 0 24 24',
618
- fill: textColor,
619
- style: { flexShrink: '0' },
620
- }, React.createElement('path', {
621
- d: 'M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z',
622
- })), 'Continue with Apple'));
623
- }
624
- // Fallback icon for Phantom — uses the official SVG path from @phantom/wallet-sdk-ui
625
- // (SDK intentionally leaves wallet.icon empty for Phantom, expecting its own UI to render it)
626
- const PHANTOM_ICON = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHJ4PSI2IiBmaWxsPSIjQUI5RkYyIi8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCw0KSI+PHBhdGggZD0iTTIuMzY2NTUgMTguMzI3MUM0LjkxODcxIDE4LjMyNzEgNi44MzY3IDE2LjEwNzYgNy45ODEzMSAxNC4zNTM3QzcuODQyMSAxNC43NDE3IDcuNzY0NzYgMTUuMTI5OCA3Ljc2NDc2IDE1LjUwMjNDNy43NjQ3NiAxNi41MjY3IDguMzUyNTMgMTcuMjU2MiA5LjUxMjYgMTcuMjU2MkMxMS4xMDU4IDE3LjI1NjIgMTIuODA3MiAxNS44NTkzIDEzLjY4ODkgMTQuMzUzN0MxMy42MjcgMTQuNTcxIDEzLjU5NjEgMTQuNzcyOCAxMy41OTYxIDE0Ljk1OUMxMy41OTYxIDE1LjY3MyAxMy45OTgyIDE2LjEyMzEgMTQuODE4IDE2LjEyMzFDMTcuNDAxMSAxNi4xMjMxIDE5Ljk5OTcgMTEuNTQ0NCAxOS45OTk3IDcuNTM5ODdDMTkuOTk5NyA0LjQyMDExIDE4LjQyMiAxLjY3Mjg1IDE0LjQ2MjIgMS42NzI4NUM3LjUwMTgxIDEuNjcyODUgMCAxMC4xNzg1IDAgMTUuNjczQzAgMTcuODMwNSAxLjE2MDA3IDE4LjMyNzEgMi4zNjY1NSAxOC4zMjcxWk0xMi4wNjQ4IDcuMTk4NDFDMTIuMDY0OCA2LjQyMjM1IDEyLjQ5NzkgNS44NzkxIDEzLjEzMiA1Ljg3OTFDMTMuNzUwNyA1Ljg3OTEgMTQuMTgzOCA2LjQyMjM1IDE0LjE4MzggNy4xOTg0MUMxNC4xODM4IDcuOTc0NDcgMTMuNzUwNyA4LjUzMzIzIDEzLjEzMiA4LjUzMzIzQzEyLjQ5NzkgOC41MzMyMyAxMi4wNjQ4IDcuOTc0NDcgMTIuMDY0OCA3LjE5ODQxWk0xNS4zNzQ4IDcuMTk4NDFDMTUuMzc0OCA2LjQyMjM1IDE1LjgwNzkgNS44NzkxIDE2LjQ0MjEgNS44NzkxQzE3LjA2MDggNS44NzkxIDE3LjQ5MzkgNi40MjIzNSAxNy40OTM5IDcuMTk4NDFDMTcuNDkzOSA3Ljk3NDQ3IDE3LjA2MDggOC41MzMyMyAxNi40NDIxIDguNTMzMjNDMTUuODA3OSA4LjUzMzIzIDE1LjM3NDggNy45NzQ0NyAxNS4zNzQ4IDcuMTk4NDFaIiBmaWxsPSJ3aGl0ZSIvPjwvZz48L3N2Zz4=';
627
- // Show all discovered injected wallets (Phantom, Solflare, Jupiter, etc.)
628
- if (sdkProviders.includes('injected')) {
629
- for (const wallet of discoveredWallets) {
630
- const walletKey = `wallet-${wallet.id}`;
631
- const iconSrc = wallet.icon || (wallet.id === 'phantom' ? PHANTOM_ICON : null);
632
- walletButtons.push(React.createElement('button', {
633
- key: walletKey,
634
- style: buttonStyle(walletKey),
635
- onClick: () => handleWalletClick({ walletId: wallet.id }),
636
- onMouseEnter: () => setHoveredBtn(walletKey),
637
- onMouseLeave: () => setHoveredBtn(null),
638
- }, iconSrc
639
- ? React.createElement('img', {
640
- src: iconSrc,
641
- alt: wallet.name,
642
- style: { width: '28px', height: '28px', borderRadius: '6px' },
643
- })
644
- : null, wallet.name));
645
- }
646
- }
647
- if (showDeeplink) {
648
- walletButtons.push(React.createElement('button', {
649
- key: 'phantom-deeplink',
650
- style: buttonStyle('phantom-deeplink'),
651
- onClick: () => handleWalletClick({ provider: 'deeplink' }),
652
- onMouseEnter: () => setHoveredBtn('phantom-deeplink'),
653
- onMouseLeave: () => setHoveredBtn(null),
654
- }, React.createElement('img', {
655
- src: PHANTOM_ICON,
656
- alt: 'Phantom',
657
- style: { width: '28px', height: '28px', borderRadius: '6px' },
658
- }), 'Open Phantom app'));
659
- }
660
- // Mobile Wallet Adapter button — shown on Android when MWA callback is available
661
- // and no injected wallet is present (hides button inside Phantom/Solflare/etc. in-app browsers).
662
- const isAndroid = index_native.detectAndroid();
663
- if (isAndroid && that.onSwitchToMWA && !hasInjectedWallet) {
664
- walletButtons.push(React.createElement('button', {
665
- key: 'mobile-wallet',
666
- style: buttonStyle('mobile-wallet'),
667
- onClick: handleMobileWalletClick,
668
- onMouseEnter: () => setHoveredBtn('mobile-wallet'),
669
- onMouseLeave: () => setHoveredBtn(null),
670
- }, React.createElement('svg', {
671
- width: '18', height: '18', viewBox: '0 0 24 24', fill: 'none',
672
- style: { flexShrink: '0', opacity: '0.85' },
673
- },
674
- // Phone outline
675
- React.createElement('rect', {
676
- x: '5', y: '2', width: '14', height: '20', rx: '2',
677
- stroke: textColor, strokeWidth: '2', fill: 'none',
678
- }),
679
- // Screen line
680
- React.createElement('line', {
681
- x1: '5', y1: '6', x2: '19', y2: '6',
682
- stroke: textColor, strokeWidth: '1.5',
683
- }),
684
- // Wallet/shield checkmark
685
- React.createElement('path', {
686
- d: 'M9.5 12.5l2 2 3.5-3.5',
687
- stroke: textColor, strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', fill: 'none',
688
- })), 'Connect Mobile Wallet'));
689
- }
690
- const mainButtons = walletButtons;
691
- // Render custom modal
692
- return React.createElement('div', {
693
- // Overlay
694
- style: {
695
- position: 'fixed',
696
- top: '0',
697
- left: '0',
698
- right: '0',
699
- bottom: '0',
700
- backgroundColor: isLight ? 'rgba(0,0,0,0.35)' : 'rgba(0,0,0,0.65)',
701
- display: 'flex',
702
- alignItems: 'center',
703
- justifyContent: 'center',
704
- padding: '16px',
705
- boxSizing: 'border-box',
706
- overflowY: 'auto',
707
- zIndex: 2147483647,
708
- pointerEvents: 'auto',
709
- backdropFilter: 'blur(4px)',
710
- WebkitBackdropFilter: 'blur(4px)',
711
- },
712
- onClick: (e) => {
713
- if (e.target === e.currentTarget)
714
- handleCloseModal();
715
- },
716
- onMouseDown: (e) => {
717
- e.stopPropagation();
718
- },
719
- onPointerDown: (e) => {
720
- e.stopPropagation();
721
- },
722
- },
723
- // Card
724
- React.createElement('div', {
725
- style: {
726
- backgroundColor: bgColor,
727
- border: cardBorder,
728
- borderRadius: '20px',
729
- width: '100%',
730
- maxWidth: '380px',
731
- maxHeight: '100%',
732
- overflowY: 'auto',
733
- padding: '28px 24px 24px',
734
- position: 'relative',
735
- boxShadow: isLight
736
- ? '0 8px 40px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06)'
737
- : '0 8px 40px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.3)',
738
- pointerEvents: 'auto',
739
- boxSizing: 'border-box',
740
- },
741
- onMouseDown: (e) => {
742
- e.stopPropagation();
743
- },
744
- onPointerDown: (e) => {
745
- e.stopPropagation();
746
- },
747
- },
748
- // Close button
749
- React.createElement('button', {
750
- onClick: handleCloseModal,
751
- style: {
752
- position: 'absolute',
753
- top: '14px',
754
- right: '14px',
755
- background: isLight ? 'rgba(0,0,0,0.05)' : 'rgba(255,255,255,0.07)',
756
- border: isLight ? '1px solid rgba(0,0,0,0.08)' : '1px solid rgba(255,255,255,0.08)',
757
- borderRadius: '50%',
758
- width: '28px',
759
- height: '28px',
760
- cursor: 'pointer',
761
- display: 'flex',
762
- alignItems: 'center',
763
- justifyContent: 'center',
764
- padding: '0',
765
- flexShrink: '0',
766
- },
767
- }, React.createElement('svg', {
768
- width: '12', height: '12', viewBox: '0 0 12 12', fill: 'none',
769
- }, React.createElement('path', {
770
- d: 'M11 1L1 11M1 1l10 10',
771
- stroke: subTextColor, strokeWidth: '1.75', strokeLinecap: 'round',
772
- }))),
773
- // App icon (if provided)
774
- that.config.appIcon ? React.createElement('div', {
775
- style: { display: 'flex', justifyContent: 'center', marginBottom: '16px' },
776
- }, React.createElement('img', {
777
- src: that.config.appIcon,
778
- alt: that.config.appName || 'App',
779
- style: { width: '44px', height: '44px', borderRadius: '10px' },
780
- })) : null,
781
- // Title
782
- React.createElement('div', {
783
- style: {
784
- color: textColor,
785
- fontFamily,
786
- fontSize: '17px',
787
- fontWeight: '600',
788
- textAlign: 'center',
789
- marginBottom: '6px',
790
- letterSpacing: '-0.2px',
791
- },
792
- }, (_f = (_e = (_d = that.loginOverrides) === null || _d === void 0 ? void 0 : _d.modalTitle) !== null && _e !== void 0 ? _e : that.config.modalTitle) !== null && _f !== void 0 ? _f : (that.config.appName ? `Log in to ${that.config.appName}` : 'Log in to your account')),
793
- // Subtitle
794
- React.createElement('div', {
795
- style: {
796
- color: subTextColor,
797
- fontFamily,
798
- fontSize: '13px',
799
- fontWeight: '400',
800
- textAlign: 'center',
801
- marginBottom: '24px',
802
- lineHeight: '1.4',
803
- },
804
- }, (_j = (_h = (_g = that.loginOverrides) === null || _g === void 0 ? void 0 : _g.modalSubtitle) !== null && _h !== void 0 ? _h : that.config.modalSubtitle) !== null && _j !== void 0 ? _j : 'Choose how you\'d like to continue'),
805
- // Wallet / social login buttons
806
- React.createElement('div', {
807
- style: {
808
- display: 'flex',
809
- flexDirection: 'column',
810
- gap: '8px',
811
- },
812
- }, ...mainButtons)));
813
- };
814
- // Wrapper component with PhantomProvider
815
- const PhantomProviderWrapper = () => {
816
- // Build SDK config
817
- const sdkConfig = React.useMemo(() => {
818
- var _a;
819
- const config = {
820
- providers: sdkProviders,
821
- addressTypes: [AddressType.solana],
822
- autoConnect: (_a = that.config.autoConnect) !== null && _a !== void 0 ? _a : true,
823
- };
824
- if (that.config.appId) {
825
- config.appId = that.config.appId;
826
- }
827
- return config;
828
- }, []);
829
- const theme = that.config.theme === 'light' ? lightTheme : darkTheme;
830
- return React.createElement(ReactPhantomProvider, {
831
- config: sdkConfig,
832
- theme: theme,
833
- appName: that.config.appName || 'App',
834
- appIcon: that.config.appIcon,
835
- }, React.createElement(PhantomHooksComponent));
836
- };
837
- this.root = ReactDOM.createRoot(this.containerElement);
838
- this.root.render(React.createElement(PhantomProviderWrapper));
839
- }
840
- async ensureReady() {
841
- // Wait for async initialization to complete first
842
- if (this.initPromise) {
843
- await this.initPromise;
844
- }
845
- // Wait for SDK to be ready
846
- await new Promise((resolve) => {
847
- const check = () => {
848
- var _a;
849
- if ((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.ready) {
850
- resolve();
851
- }
852
- else {
853
- setTimeout(check, 100);
854
- }
855
- };
856
- check();
857
- });
858
- }
859
- async ensureSolanaReady() {
860
- var _a;
861
- await this.ensureReady();
862
- // If connected at the Phantom level, ensure solana methods are ready
863
- if ((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected) {
864
- const timeoutMs = 10000; // 10 second timeout
865
- const startTime = Date.now();
866
- // First, wait for solana to be available
867
- await new Promise((resolve, reject) => {
868
- const check = () => {
869
- var _a, _b;
870
- if ((_b = (_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.solana) === null || _b === void 0 ? void 0 : _b.isAvailable) {
871
- resolve();
872
- }
873
- else if (Date.now() - startTime > timeoutMs) {
874
- reject(new Error('Timeout waiting for Solana provider to be available'));
875
- }
876
- else {
877
- setTimeout(check, 100);
878
- }
879
- };
880
- check();
881
- });
882
- // Now check if the solana provider is connected, if not try to reconnect
883
- // Note: We check our wrapper's `connected` which may be stale, but this is just
884
- // an optimization to skip reconnect if we know we're already connected.
885
- // The actual signing methods use the SDK's bound methods which check live state.
886
- if (!this.phantomMethods.solana.connected) {
887
- console.log('Solana provider not connected, attempting to reconnect...');
888
- try {
889
- // onlyIfTrusted: true means it will reconnect silently if previously approved
890
- // If not trusted, it will throw and we'll catch it below
891
- await this.phantomMethods.solana.connect({ onlyIfTrusted: true });
892
- console.log('Solana provider reconnected successfully');
893
- }
894
- catch (error) {
895
- // If onlyIfTrusted fails, the user may need to re-approve via modal
896
- console.error('Failed to reconnect solana provider (silent reconnect failed):', error === null || error === void 0 ? void 0 : error.message);
897
- throw new Error('Failed to reconnect Solana provider. Please try logging in again.');
898
- }
899
- }
900
- // Note: We don't do a final connected check here because our wrapper's `connected`
901
- // is a snapshot that won't update until React re-renders. If connect() succeeded,
902
- // the SDK is connected and signing will work.
903
- }
904
- }
905
- /**
906
- * Get the list of available (configured) providers for this wallet.
907
- */
908
- getAvailableProviders() {
909
- return [...this.resolvedProviders];
910
- }
911
- /**
912
- * Login using the Phantom connect modal. A custom modal is rendered with the
913
- * discovered wallets and configured social providers instead of the SDK's
914
- * built-in modal.
915
- */
916
- async login() {
917
- var _a, _b;
918
- await this.ensureReady();
919
- // Check if already connected with valid session
920
- if (((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected) && ((_b = this.phantomMethods.addresses) === null || _b === void 0 ? void 0 : _b.length) > 0) {
921
- const solAddress = this.phantomMethods.addresses.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
922
- if (solAddress) {
923
- const session = await core.WebSessionManager.getSession();
924
- if (session && session.address === solAddress.address) {
925
- const user = { provider: this, address: solAddress.address };
926
- index_native.setCurrentUser(user);
927
- return user;
928
- }
929
- }
930
- }
931
- // If already pending, wait for it
932
- if (this.pendingLogin) {
933
- return new Promise((resolve, reject) => {
934
- this.pendingLogin.resolve = resolve;
935
- this.pendingLogin.reject = reject;
936
- });
937
- }
938
- const loginPromise = new Promise((resolve, reject) => {
939
- this.pendingLogin = { resolve, reject };
940
- this.loginInProgress = true;
941
- // Always use our custom wallet modal
942
- this.phantomMethods.showCustomModal();
943
- // Safety timeout
944
- setTimeout(() => {
945
- if (this.pendingLogin) {
946
- this.pendingLogin.reject(new Error('Login timed out'));
947
- this.pendingLogin = null;
948
- this.loginInProgress = false;
949
- }
950
- }, 180000);
951
- });
952
- return loginPromise.finally(() => {
953
- this.loginOverrides = null;
954
- });
955
- }
956
- async restoreSession() {
957
- await this.ensureReady();
958
- const session = await core.WebSessionManager.getSession();
959
- if (session) {
960
- return { provider: this, address: session.address };
961
- }
962
- return null;
963
- }
964
- // When connect is delegated to a top-level tab (openTopLevelForConnect — used
965
- // for the cross-origin preview iframe on mobile, where MWA can't run), the
966
- // actual connect happens in that tab and writes the session to same-origin
967
- // storage. This watches for that session to appear and resolves the in-flight
968
- // login() with it. It listens for `storage` events and visibility regain (the
969
- // user returning from the tab), with a polling fallback and a hard timeout.
970
- awaitTopLevelConnect() {
971
- if (typeof window === 'undefined')
972
- return;
973
- let done = false;
974
- const cleanup = () => {
975
- clearInterval(poll);
976
- clearTimeout(timeout);
977
- window.removeEventListener('storage', onChange);
978
- document.removeEventListener('visibilitychange', onVisible);
979
- };
980
- const tryResolve = async () => {
981
- if (done)
982
- return;
983
- try {
984
- const user = await this.restoreSession();
985
- if (user && this.pendingLogin) {
986
- done = true;
987
- cleanup();
988
- this.pendingLogin.resolve(user);
989
- this.pendingLogin = null;
990
- this.loginInProgress = false;
991
- }
992
- }
993
- catch (_a) {
994
- // not ready yet — keep waiting
995
- }
996
- };
997
- const onChange = () => { void tryResolve(); };
998
- const onVisible = () => { if (document.visibilityState === 'visible')
999
- void tryResolve(); };
1000
- const poll = setInterval(() => { void tryResolve(); }, 1500);
1001
- const timeout = setTimeout(() => {
1002
- cleanup();
1003
- if (!done && this.pendingLogin) {
1004
- this.pendingLogin.reject(new Error('Wallet connection timed out'));
1005
- this.pendingLogin = null;
1006
- this.loginInProgress = false;
1007
- }
1008
- }, 180000);
1009
- window.addEventListener('storage', onChange);
1010
- document.addEventListener('visibilitychange', onVisible);
1011
- }
1012
- async address() {
1013
- var _a, _b, _c, _d;
1014
- await this.ensureReady();
1015
- if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected) || !((_b = this.phantomMethods.addresses) === null || _b === void 0 ? void 0 : _b.length)) {
1016
- // Try to connect first
1017
- const user = await this.login();
1018
- if (!user)
1019
- return null;
1020
- }
1021
- const solAddress = (_d = (_c = this.phantomMethods) === null || _c === void 0 ? void 0 : _c.addresses) === null || _d === void 0 ? void 0 : _d.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
1022
- return (solAddress === null || solAddress === void 0 ? void 0 : solAddress.address) || null;
1023
- }
1024
- async runTransaction(_evmTransactionData, solTransactionData, options) {
1025
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1026
- if (!solTransactionData) {
1027
- throw new Error("Solana transaction data is required for Phantom wallet");
1028
- }
1029
- try {
1030
- await this.ensureSolanaReady();
1031
- }
1032
- catch (error) {
1033
- console.error('Solana provider reconnection failed, logging out:', error.message);
1034
- await this.logout();
1035
- throw new Error('Wallet connection lost. Please reconnect.');
1036
- }
1037
- if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected)) {
1038
- const user = await this.login();
1039
- if (!user) {
1040
- throw new Error('Failed to connect wallet');
1041
- }
1042
- try {
1043
- await this.ensureSolanaReady();
1044
- }
1045
- catch (error) {
1046
- console.error('Solana provider reconnection failed after login, logging out:', error.message);
1047
- await this.logout();
1048
- throw new Error('Wallet connection lost. Please reconnect.');
1049
- }
1050
- }
1051
- if (!((_b = this.phantomMethods) === null || _b === void 0 ? void 0 : _b.solana)) {
1052
- throw new Error('Solana signing not available');
1053
- }
1054
- const rpcUrl = this.getRpcUrl(solTransactionData.network);
1055
- const connection = new web3_js.Connection(rpcUrl, 'confirmed');
1056
- const isSurfnet = rpcUrl === index_native.SURFNET_RPC_URL;
1057
- try {
1058
- const remainingAccounts = core.convertRemainingAccounts(solTransactionData.txArgs[0].remainingAccounts);
1059
- const app_id = solTransactionData.appId;
1060
- if (!app_id) {
1061
- throw new Error("App ID is required");
1062
- }
1063
- // Get address
1064
- const solAddress = this.phantomMethods.addresses.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
1065
- if (!solAddress) {
1066
- throw new Error('Failed to get Solana address');
1067
- }
1068
- const publicKey = new web3_js.PublicKey(solAddress.address);
1069
- // Create wallet adapter using SDK methods
1070
- const solana = this.phantomMethods.solana;
1071
- const walletAdapter = {
1072
- publicKey,
1073
- signTransaction: async (tx) => {
1074
- return await solana.signTransaction(tx);
1075
- },
1076
- signAllTransactions: async (txs) => {
1077
- return await solana.signAllTransactions(txs);
1078
- }
1079
- };
1080
- const anchorProvider = new anchor__namespace.AnchorProvider(connection, walletAdapter, anchor__namespace.AnchorProvider.defaultOptions());
1081
- const finalDeduped = [];
1082
- for (const acc of remainingAccounts) {
1083
- const existing = finalDeduped.find((d) => d.pubkey.equals(acc.pubkey));
1084
- if (existing) {
1085
- existing.isSigner = existing.isSigner || acc.isSigner;
1086
- existing.isWritable = existing.isWritable || acc.isWritable;
1087
- }
1088
- else {
1089
- finalDeduped.push(acc);
1090
- }
1091
- }
1092
- // When the server has co-signed a transaction (CPI attestation),
1093
- // deserialize it and just add the wallet signature instead of
1094
- // building from components.
1095
- let tx;
1096
- if (solTransactionData.signedTransaction) {
1097
- tx = web3_js.VersionedTransaction.deserialize(index_native.bufferExports.Buffer.from(solTransactionData.signedTransaction, 'base64'));
1098
- }
1099
- else {
1100
- const result = await core.buildSetDocumentsTransaction(connection, solTransactionData.txArgs[0].idl, anchorProvider, publicKey, {
1101
- app_id,
1102
- documents: solTransactionData.txArgs[0].setDocumentData,
1103
- delete_paths: solTransactionData.txArgs[0].deletePaths,
1104
- txData: solTransactionData.txArgs[0].txData
1105
- }, finalDeduped, solTransactionData.lutKey, solTransactionData.preInstructions, false, solTransactionData.additionalLutAddresses);
1106
- tx = result.tx;
1107
- }
1108
- if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false) {
1109
- const signedTx = await walletAdapter.signTransaction(tx);
1110
- return {
1111
- signedTransaction: signedTx,
1112
- blockNumber: 0,
1113
- gasUsed: "0",
1114
- data: ""
1115
- };
1116
- }
1117
- // Keep manual submission on surfnet, use Phantom's recommended signAndSend for other networks.
1118
- if (isSurfnet) {
1119
- const signedTx = await walletAdapter.signTransaction(tx);
1120
- const { signature, txInfo } = await this.submitSignedTransaction(signedTx, connection);
1121
- return {
1122
- transactionSignature: signature,
1123
- blockNumber: (txInfo === null || txInfo === void 0 ? void 0 : txInfo.slot) || 0,
1124
- gasUsed: ((_c = txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta) === null || _c === void 0 ? void 0 : _c.fee.toString()) || '0',
1125
- data: txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta,
1126
- };
1127
- }
1128
- const signature = await this.signAndSendViaPhantom(tx);
1129
- const txInfo = await index_native.confirmAndCheckTransaction(connection, signature);
1130
- return {
1131
- transactionSignature: signature,
1132
- blockNumber: (txInfo === null || txInfo === void 0 ? void 0 : txInfo.slot) || 0,
1133
- gasUsed: ((_d = txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta) === null || _d === void 0 ? void 0 : _d.fee.toString()) || '0',
1134
- data: txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta,
1135
- };
1136
- }
1137
- catch (error) {
1138
- // Check if this is a connection error - if so, log out
1139
- if (((_e = error === null || error === void 0 ? void 0 : error.message) === null || _e === void 0 ? void 0 : _e.includes('not connected')) || ((_f = error === null || error === void 0 ? void 0 : error.message) === null || _f === void 0 ? void 0 : _f.includes('connect first'))) {
1140
- console.error('Solana provider connection lost during transaction, logging out');
1141
- await this.logout();
1142
- throw new Error('Wallet connection lost. Please reconnect.');
1143
- }
1144
- const isUserRejection = (error === null || error === void 0 ? void 0 : error.code) === 4001 ||
1145
- ((_g = error === null || error === void 0 ? void 0 : error.message) === null || _g === void 0 ? void 0 : _g.toLowerCase().includes('user rejected')) ||
1146
- ((_h = error === null || error === void 0 ? void 0 : error.message) === null || _h === void 0 ? void 0 : _h.toLowerCase().includes('user denied')) ||
1147
- ((_j = error === null || error === void 0 ? void 0 : error.message) === null || _j === void 0 ? void 0 : _j.toLowerCase().includes('user cancelled')) ||
1148
- ((_k = error === null || error === void 0 ? void 0 : error.message) === null || _k === void 0 ? void 0 : _k.toLowerCase().includes('user canceled'));
1149
- if (!isUserRejection) {
1150
- console.error('Failed to execute transaction', error);
1151
- }
1152
- throw new Error(`Failed to execute transaction: ${error.message}`);
1153
- }
1154
- }
1155
- /**
1156
- * Signs a Solana transaction without submitting it.
1157
- *
1158
- * This method handles blockhash automatically if not set - you do NOT need to
1159
- * set recentBlockhash on the transaction before calling this method.
1160
- * The network/RPC URL is derived from the provider's configuration (set during initialization).
1161
- *
1162
- * @param transaction - The transaction to sign (Transaction or VersionedTransaction)
1163
- * @returns The signed transaction
1164
- */
1165
- async signTransaction(transaction) {
1166
- var _a, _b, _c, _d;
1167
- try {
1168
- await this.ensureSolanaReady();
1169
- }
1170
- catch (error) {
1171
- // Reconnection failed - log user out and throw
1172
- console.error('Solana provider reconnection failed, logging out:', error.message);
1173
- await this.logout();
1174
- throw new Error('Wallet connection lost. Please reconnect.');
1175
- }
1176
- if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected)) {
1177
- const user = await this.login();
1178
- if (!user) {
1179
- throw new Error('Failed to connect wallet');
1180
- }
1181
- try {
1182
- await this.ensureSolanaReady();
1183
- }
1184
- catch (error) {
1185
- console.error('Solana provider reconnection failed after login, logging out:', error.message);
1186
- await this.logout();
1187
- throw new Error('Wallet connection lost. Please reconnect.');
1188
- }
1189
- }
1190
- if (!((_b = this.phantomMethods) === null || _b === void 0 ? void 0 : _b.solana)) {
1191
- throw new Error('Solana signing not available');
1192
- }
1193
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
1194
- const isLegacyTransaction = 'recentBlockhash' in transaction && !('message' in transaction && 'staticAccountKeys' in transaction.message);
1195
- // Ensure blockhash is set before signing
1196
- if (isLegacyTransaction) {
1197
- const legacyTx = transaction;
1198
- if (!legacyTx.recentBlockhash) {
1199
- const rpcUrl = this.getRpcUrl();
1200
- const connection = new web3_js.Connection(rpcUrl, 'confirmed');
1201
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
1202
- legacyTx.recentBlockhash = blockhash;
1203
- legacyTx.lastValidBlockHeight = lastValidBlockHeight;
1204
- }
1205
- // Set feePayer if not already set
1206
- if (!legacyTx.feePayer) {
1207
- const solAddress = this.phantomMethods.addresses.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
1208
- if (solAddress) {
1209
- legacyTx.feePayer = new web3_js.PublicKey(solAddress.address);
1210
- }
1211
- }
1212
- }
1213
- else {
1214
- // VersionedTransaction
1215
- const versionedTx = transaction;
1216
- if (!versionedTx.message.recentBlockhash) {
1217
- const rpcUrl = this.getRpcUrl();
1218
- const connection = new web3_js.Connection(rpcUrl, 'confirmed');
1219
- const { blockhash } = await connection.getLatestBlockhash('confirmed');
1220
- versionedTx.message.recentBlockhash = blockhash;
1221
- }
1222
- }
1223
- try {
1224
- return await this.phantomMethods.solana.signTransaction(transaction);
1225
- }
1226
- catch (error) {
1227
- // Check if this is a connection error - if so, log out
1228
- if (((_c = error === null || error === void 0 ? void 0 : error.message) === null || _c === void 0 ? void 0 : _c.includes('not connected')) || ((_d = error === null || error === void 0 ? void 0 : error.message) === null || _d === void 0 ? void 0 : _d.includes('connect first'))) {
1229
- console.error('Solana provider connection lost during signing, logging out');
1230
- await this.logout();
1231
- throw new Error('Wallet connection lost. Please reconnect.');
1232
- }
1233
- throw new Error(`Failed to sign transaction: ${error.message}`);
1234
- }
1235
- }
1236
- /**
1237
- * Signs and submits a Solana transaction to the network.
1238
- *
1239
- * This method handles blockhash and transaction confirmation automatically - you do NOT need to
1240
- * set recentBlockhash or lastValidBlockHeight on the transaction before calling this method.
1241
- * The network/RPC URL is derived from the provider's configuration (set during initialization).
1242
- *
1243
- * @param transaction - The transaction to sign and submit (Transaction or VersionedTransaction)
1244
- * @param feePayer - Optional fee payer public key. If not provided and the transaction doesn't
1245
- * already have a feePayer set, the connected wallet address will be used.
1246
- * Useful for co-signing scenarios where a different account pays the fees.
1247
- * @returns The transaction signature
1248
- */
1249
- async signAndSubmitTransaction(transaction, feePayer) {
1250
- var _a, _b, _c, _d, _e, _f, _g, _h;
1251
- try {
1252
- await this.ensureSolanaReady();
1253
- }
1254
- catch (error) {
1255
- console.error('Solana provider reconnection failed, logging out:', error.message);
1256
- await this.logout();
1257
- throw new Error('Wallet connection lost. Please reconnect.');
1258
- }
1259
- if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected)) {
1260
- const user = await this.login();
1261
- if (!user) {
1262
- throw new Error('Failed to connect wallet');
1263
- }
1264
- try {
1265
- await this.ensureSolanaReady();
1266
- }
1267
- catch (error) {
1268
- console.error('Solana provider reconnection failed after login, logging out:', error.message);
1269
- await this.logout();
1270
- throw new Error('Wallet connection lost. Please reconnect.');
1271
- }
1272
- }
1273
- if (!((_b = this.phantomMethods) === null || _b === void 0 ? void 0 : _b.solana)) {
1274
- throw new Error('Solana signing not available');
1275
- }
1276
- const rpcUrl = this.getRpcUrl();
1277
- const connection = new web3_js.Connection(rpcUrl, 'confirmed');
1278
- const isSurfnet = rpcUrl === index_native.SURFNET_RPC_URL;
1279
- try {
1280
- // Get fresh blockhash and set it on the transaction before signing
1281
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
1282
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
1283
- const isLegacyTransaction = 'recentBlockhash' in transaction && !('message' in transaction && 'staticAccountKeys' in transaction.message);
1284
- if (isLegacyTransaction) {
1285
- const legacyTx = transaction;
1286
- legacyTx.recentBlockhash = blockhash;
1287
- legacyTx.lastValidBlockHeight = lastValidBlockHeight;
1288
- // Set feePayer if not already set
1289
- if (!legacyTx.feePayer) {
1290
- if (feePayer) {
1291
- legacyTx.feePayer = feePayer;
1292
- }
1293
- else {
1294
- // Use connected wallet address as feePayer
1295
- const solAddress = this.phantomMethods.addresses.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
1296
- if (solAddress) {
1297
- legacyTx.feePayer = new web3_js.PublicKey(solAddress.address);
1298
- }
1299
- }
1300
- }
1301
- }
1302
- else {
1303
- // VersionedTransaction
1304
- const versionedTx = transaction;
1305
- versionedTx.message.recentBlockhash = blockhash;
1306
- // Note: VersionedTransaction feePayer is set in the message at creation time
1307
- // and cannot be modified after creation
1308
- }
1309
- // Keep manual submission on surfnet, use Phantom's recommended signAndSend for other networks.
1310
- if (isSurfnet) {
1311
- const signedTx = await this.phantomMethods.solana.signTransaction(transaction);
1312
- const { signature } = await this.submitSignedTransactionWithBlockhash(signedTx, connection, blockhash, lastValidBlockHeight);
1313
- return signature;
1314
- }
1315
- const signature = await this.signAndSendViaPhantom(transaction);
1316
- await index_native.confirmAndCheckTransaction(connection, signature);
1317
- return signature;
1318
- }
1319
- catch (error) {
1320
- // Check if this is a connection error - if so, log out
1321
- if (((_c = error === null || error === void 0 ? void 0 : error.message) === null || _c === void 0 ? void 0 : _c.includes('not connected')) || ((_d = error === null || error === void 0 ? void 0 : error.message) === null || _d === void 0 ? void 0 : _d.includes('connect first'))) {
1322
- console.error('Solana provider connection lost during transaction, logging out');
1323
- await this.logout();
1324
- throw new Error('Wallet connection lost. Please reconnect.');
1325
- }
1326
- const isUserRejection = (error === null || error === void 0 ? void 0 : error.code) === 4001 ||
1327
- ((_e = error === null || error === void 0 ? void 0 : error.message) === null || _e === void 0 ? void 0 : _e.toLowerCase().includes('user rejected')) ||
1328
- ((_f = error === null || error === void 0 ? void 0 : error.message) === null || _f === void 0 ? void 0 : _f.toLowerCase().includes('user denied')) ||
1329
- ((_g = error === null || error === void 0 ? void 0 : error.message) === null || _g === void 0 ? void 0 : _g.toLowerCase().includes('user cancelled')) ||
1330
- ((_h = error === null || error === void 0 ? void 0 : error.message) === null || _h === void 0 ? void 0 : _h.toLowerCase().includes('user canceled'));
1331
- if (!isUserRejection) {
1332
- console.error('Failed to execute transaction', error);
1333
- }
1334
- throw new Error(`Failed to execute transaction: ${error.message}`);
1335
- }
1336
- }
1337
- async signMessage(message) {
1338
- var _a, _b, _c, _d, _e, _f, _g, _h;
1339
- try {
1340
- await this.ensureSolanaReady();
1341
- }
1342
- catch (error) {
1343
- // Reconnection failed - log user out and throw
1344
- console.error('Solana provider reconnection failed, logging out:', error.message);
1345
- await this.logout();
1346
- throw new Error('Wallet connection lost. Please reconnect.');
1347
- }
1348
- if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected)) {
1349
- const user = await this.login();
1350
- if (!user) {
1351
- throw new Error('Failed to connect wallet');
1352
- }
1353
- try {
1354
- await this.ensureSolanaReady();
1355
- }
1356
- catch (error) {
1357
- console.error('Solana provider reconnection failed after login, logging out:', error.message);
1358
- await this.logout();
1359
- throw new Error('Wallet connection lost. Please reconnect.');
1360
- }
1361
- }
1362
- if (!((_b = this.phantomMethods) === null || _b === void 0 ? void 0 : _b.solana)) {
1363
- throw new Error('Solana signing not available');
1364
- }
1365
- try {
1366
- // signMessage returns { signature: Uint8Array, publicKey: string }
1367
- const result = await this.phantomMethods.solana.signMessage(message);
1368
- return index_native.bufferExports.Buffer.from(result.signature).toString('base64');
1369
- }
1370
- catch (error) {
1371
- // Check if this is a connection error - if so, log out
1372
- if (((_c = error === null || error === void 0 ? void 0 : error.message) === null || _c === void 0 ? void 0 : _c.includes('not connected')) || ((_d = error === null || error === void 0 ? void 0 : error.message) === null || _d === void 0 ? void 0 : _d.includes('connect first'))) {
1373
- console.error('Solana provider connection lost during signing, logging out');
1374
- await this.logout();
1375
- throw new Error('Wallet connection lost. Please reconnect.');
1376
- }
1377
- const isUserRejection = (error === null || error === void 0 ? void 0 : error.code) === 4001 ||
1378
- ((_e = error === null || error === void 0 ? void 0 : error.message) === null || _e === void 0 ? void 0 : _e.toLowerCase().includes('user rejected')) ||
1379
- ((_f = error === null || error === void 0 ? void 0 : error.message) === null || _f === void 0 ? void 0 : _f.toLowerCase().includes('user denied')) ||
1380
- ((_g = error === null || error === void 0 ? void 0 : error.message) === null || _g === void 0 ? void 0 : _g.toLowerCase().includes('user cancelled')) ||
1381
- ((_h = error === null || error === void 0 ? void 0 : error.message) === null || _h === void 0 ? void 0 : _h.toLowerCase().includes('user canceled'));
1382
- if (!isUserRejection) {
1383
- console.error('Failed to sign message', error);
1384
- }
1385
- throw new Error('Failed to sign message');
1386
- }
1387
- }
1388
- async logout() {
1389
- var _a;
1390
- await this.ensureReady();
1391
- try {
1392
- if ((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected) {
1393
- await this.phantomMethods.disconnect();
1394
- }
1395
- }
1396
- catch (error) {
1397
- console.error('Failed to disconnect from Phantom wallet', error);
1398
- }
1399
- // Always clear local state
1400
- core.WebSessionManager.clearSession();
1401
- index_native.setCurrentUser(null);
1402
- }
1403
- async getNativeMethods() {
1404
- await this.ensureReady();
1405
- return this.phantomMethods;
1406
- }
1407
- /* ----------------------------------------------------------- *
1408
- * Private Helpers
1409
- * ----------------------------------------------------------- */
1410
- getRpcUrl(network) {
1411
- return index_native.resolveSolanaRpcUrl(this.networkUrl, network !== null && network !== void 0 ? network : this.configuredNetwork, "Phantom wallet Solana transaction");
1412
- }
1413
- async signAndSendViaPhantom(transaction) {
1414
- var _a, _b, _c;
1415
- const result = await ((_b = (_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.solana) === null || _b === void 0 ? void 0 : _b.signAndSendTransaction(transaction));
1416
- const signature = (_c = result === null || result === void 0 ? void 0 : result.signature) !== null && _c !== void 0 ? _c : result === null || result === void 0 ? void 0 : result.hash;
1417
- if (!signature) {
1418
- throw new Error('Phantom did not return a transaction signature');
1419
- }
1420
- return signature;
1421
- }
1422
- async submitSignedTransaction(signedTx, connection) {
1423
- // Get fresh blockhash for confirmation
1424
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
1425
- return this.submitSignedTransactionWithBlockhash(signedTx, connection, blockhash, lastValidBlockHeight);
1426
- }
1427
- async submitSignedTransactionWithBlockhash(signedTx, connection, blockhash, lastValidBlockHeight) {
1428
- // Submit the transaction
1429
- const signature = await connection.sendRawTransaction(signedTx.serialize(), {
1430
- preflightCommitment: 'confirmed'
1431
- });
1432
- // Confirm using blockhash strategy (same as runTransaction)
1433
- const confirmation = await connection.confirmTransaction({
1434
- signature,
1435
- blockhash,
1436
- lastValidBlockHeight,
1437
- }, 'confirmed');
1438
- if (confirmation.value.err) {
1439
- throw new Error(`Transaction failed: ${confirmation.value.err.toString()}`);
1440
- }
1441
- // Get transaction info
1442
- const txInfo = await connection.getParsedTransaction(signature, {
1443
- maxSupportedTransactionVersion: 0,
1444
- commitment: 'confirmed'
1445
- });
1446
- return { signature, txInfo };
1447
- }
1448
- }
1449
- PhantomWalletProvider.instance = null;
1450
-
1451
- exports.PhantomWalletProvider = PhantomWalletProvider;
1452
- //# sourceMappingURL=phantom-wallet-provider-3xKduOOY.js.map