@pooflabs/web 0.0.73 → 0.0.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.d.ts +4 -5
- package/dist/auth/providers/mock-auth-provider.d.ts +3 -3
- package/dist/auth/providers/privy-expo-provider.d.ts +105 -0
- package/dist/{index-BFJJZKXQ.js → index-BHYrnHi6.js} +357 -7
- package/dist/index-BHYrnHi6.js.map +1 -0
- package/dist/index-BcDe_euX.js +15928 -0
- package/dist/index-BcDe_euX.js.map +1 -0
- package/dist/index-Bdcc5821.js +2375 -0
- package/dist/index-Bdcc5821.js.map +1 -0
- package/dist/{index-TfCOBCez.esm.js → index-BqDvUK9s.esm.js} +352 -2
- package/dist/index-BqDvUK9s.esm.js.map +1 -0
- package/dist/index-CMeewi-G.js +21553 -0
- package/dist/index-CMeewi-G.js.map +1 -0
- package/dist/index-CrOVJFX9.esm.js +2373 -0
- package/dist/index-CrOVJFX9.esm.js.map +1 -0
- package/dist/index-Dho2J3X6.esm.js +21477 -0
- package/dist/index-Dho2J3X6.esm.js.map +1 -0
- package/dist/index-_vhjpl1l.esm.js +15887 -0
- package/dist/index-_vhjpl1l.esm.js.map +1 -0
- package/dist/{index.browser-ChrwVq76.esm.js → index.browser-Br0p4bjw.esm.js} +2 -3
- package/dist/{index.browser-ChrwVq76.esm.js.map → index.browser-Br0p4bjw.esm.js.map} +1 -1
- package/dist/{index.browser-BuIgwfvv.esm.js → index.browser-Btm3sRKb.esm.js} +2 -3
- package/dist/{index.browser-BuIgwfvv.esm.js.map → index.browser-Btm3sRKb.esm.js.map} +1 -1
- package/dist/{index.browser-wsb8xknL.js → index.browser-BzHjnrpD.js} +2 -3
- package/dist/{index.browser-wsb8xknL.js.map → index.browser-BzHjnrpD.js.map} +1 -1
- package/dist/index.browser-CGfjPfzM.esm.js +1468 -0
- package/dist/index.browser-CGfjPfzM.esm.js.map +1 -0
- package/dist/{index.browser-BO1XxDi0.js → index.browser-Dapjfbl6.js} +2 -3
- package/dist/{index.browser-BO1XxDi0.js.map → index.browser-Dapjfbl6.js.map} +1 -1
- package/dist/index.browser-JX3F6oPV.js +1471 -0
- package/dist/index.browser-JX3F6oPV.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +1 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/index.native-BB7er4-z.esm.js +13269 -0
- package/dist/index.native-BB7er4-z.esm.js.map +1 -0
- package/dist/index.native-DcKDTqvq.js +13348 -0
- package/dist/index.native-DcKDTqvq.js.map +1 -0
- package/dist/index.native.d.ts +24 -0
- package/dist/index.native.esm.js +6 -0
- package/dist/index.native.esm.js.map +1 -0
- package/dist/index.native.js +59 -0
- package/dist/index.native.js.map +1 -0
- package/dist/phantom-wallet-provider-DHok8ui3.esm.js +1307 -0
- package/dist/phantom-wallet-provider-DHok8ui3.esm.js.map +1 -0
- package/dist/phantom-wallet-provider-DMxFAUC4.js +1328 -0
- package/dist/phantom-wallet-provider-DMxFAUC4.js.map +1 -0
- package/dist/platform.d.ts +81 -0
- package/dist/privy-wallet-provider-Bhvw0t1d.js +3942 -0
- package/dist/privy-wallet-provider-Bhvw0t1d.js.map +1 -0
- package/dist/privy-wallet-provider-CFuoQYuv.esm.js +3921 -0
- package/dist/privy-wallet-provider-CFuoQYuv.esm.js.map +1 -0
- package/dist/solana-mobile-wallet-provider-BpQghAgC.esm.js +558 -0
- package/dist/solana-mobile-wallet-provider-BpQghAgC.esm.js.map +1 -0
- package/dist/solana-mobile-wallet-provider-D8b5y-By.js +579 -0
- package/dist/solana-mobile-wallet-provider-D8b5y-By.js.map +1 -0
- package/package.json +19 -3
- package/dist/index-BFJJZKXQ.js.map +0 -1
- package/dist/index-BV8MOXXy.js +0 -36033
- package/dist/index-BV8MOXXy.js.map +0 -1
- package/dist/index-D0yz-P8G.esm.js +0 -35962
- package/dist/index-D0yz-P8G.esm.js.map +0 -1
- package/dist/index-TfCOBCez.esm.js.map +0 -1
|
@@ -0,0 +1,1328 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('./index-Bdcc5821.js');
|
|
4
|
+
var index_native = require('./index.native-DcKDTqvq.js');
|
|
5
|
+
var web3_js = require('@solana/web3.js');
|
|
6
|
+
var anchor = require('@coral-xyz/anchor');
|
|
7
|
+
require('axios');
|
|
8
|
+
require('react');
|
|
9
|
+
|
|
10
|
+
function _interopNamespaceDefault(e) {
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var anchor__namespace = /*#__PURE__*/_interopNamespaceDefault(anchor);
|
|
28
|
+
|
|
29
|
+
const VALID_PROVIDERS = ['injected', 'google', 'apple', 'deeplink', 'phantom'];
|
|
30
|
+
// Dynamically import React and Phantom SDK - only when needed
|
|
31
|
+
let React;
|
|
32
|
+
let ReactDOM;
|
|
33
|
+
let phantomReactSdk;
|
|
34
|
+
let sdkLoaded = false;
|
|
35
|
+
let loadingPromise = null;
|
|
36
|
+
// Lazy load React and Phantom dependencies only when PhantomWalletProvider is instantiated
|
|
37
|
+
// Uses dynamic import() to ensure code splitting and prevent side effects
|
|
38
|
+
async function loadDependencies() {
|
|
39
|
+
if (sdkLoaded)
|
|
40
|
+
return;
|
|
41
|
+
if (typeof window === 'undefined')
|
|
42
|
+
return;
|
|
43
|
+
// Prevent multiple concurrent loads
|
|
44
|
+
if (loadingPromise)
|
|
45
|
+
return loadingPromise;
|
|
46
|
+
loadingPromise = (async () => {
|
|
47
|
+
const [reactModule, reactDomModule, phantomModule] = await Promise.all([
|
|
48
|
+
import('react'),
|
|
49
|
+
import('react-dom/client'),
|
|
50
|
+
Promise.resolve().then(function () { return require('./index-BcDe_euX.js'); })
|
|
51
|
+
]);
|
|
52
|
+
// Extract default export from ESM module namespace
|
|
53
|
+
// Dynamic import() returns { default: Module, ...exports }, not the module directly
|
|
54
|
+
React = reactModule.default || reactModule;
|
|
55
|
+
ReactDOM = reactDomModule.default || reactDomModule;
|
|
56
|
+
phantomReactSdk = phantomModule;
|
|
57
|
+
sdkLoaded = true;
|
|
58
|
+
})();
|
|
59
|
+
return loadingPromise;
|
|
60
|
+
}
|
|
61
|
+
class PhantomWalletProvider {
|
|
62
|
+
constructor(networkUrl = null, config = {}) {
|
|
63
|
+
this.containerElement = null;
|
|
64
|
+
this.root = null;
|
|
65
|
+
this.phantomMethods = null;
|
|
66
|
+
this.resolvedProviders = ['injected'];
|
|
67
|
+
this.pendingLogin = null;
|
|
68
|
+
this.loginInProgress = false;
|
|
69
|
+
this.autoLoginInProgress = false;
|
|
70
|
+
this.initPromise = null;
|
|
71
|
+
/** Callback to swap to a Privy provider when the user clicks "Log in with email" */
|
|
72
|
+
this.onSwitchToPrivy = null;
|
|
73
|
+
/** Callback to swap to a Mobile Wallet Adapter provider when the user clicks "Connect Mobile Wallet" */
|
|
74
|
+
this.onSwitchToMWA = null;
|
|
75
|
+
this.networkUrl = networkUrl;
|
|
76
|
+
this.config = config;
|
|
77
|
+
if (typeof window === 'undefined') {
|
|
78
|
+
throw new Error('PhantomWalletProvider can only be instantiated in a browser environment');
|
|
79
|
+
}
|
|
80
|
+
// Singleton pattern - reuse existing instance
|
|
81
|
+
if (PhantomWalletProvider.instance) {
|
|
82
|
+
return PhantomWalletProvider.instance;
|
|
83
|
+
}
|
|
84
|
+
// Resolve providers from config (doesn't need SDK)
|
|
85
|
+
this.resolveProviders();
|
|
86
|
+
// Start async initialization (load SDK and initialize React component)
|
|
87
|
+
this.initPromise = this.initializeAsync();
|
|
88
|
+
PhantomWalletProvider.instance = this;
|
|
89
|
+
}
|
|
90
|
+
async initializeAsync() {
|
|
91
|
+
// Lazy load dependencies only when actually instantiating
|
|
92
|
+
await loadDependencies();
|
|
93
|
+
// Initialize React component
|
|
94
|
+
this.initialize();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if social login providers are configured (non-injected providers).
|
|
98
|
+
*/
|
|
99
|
+
hasSocialProviders() {
|
|
100
|
+
return this.resolvedProviders.some(p => p !== 'injected');
|
|
101
|
+
}
|
|
102
|
+
static getInstance(networkUrl, config) {
|
|
103
|
+
if (!PhantomWalletProvider.instance) {
|
|
104
|
+
new PhantomWalletProvider(networkUrl, config);
|
|
105
|
+
}
|
|
106
|
+
return PhantomWalletProvider.instance;
|
|
107
|
+
}
|
|
108
|
+
resolveProviders() {
|
|
109
|
+
if (this.config.providers && this.config.providers.length > 0) {
|
|
110
|
+
const configProviders = this.config.providers;
|
|
111
|
+
this.resolvedProviders = configProviders.filter((p) => VALID_PROVIDERS.includes(p));
|
|
112
|
+
if (this.resolvedProviders.length === 0) {
|
|
113
|
+
this.resolvedProviders = ['injected'];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (this.config.appId) {
|
|
117
|
+
this.resolvedProviders = ['injected', 'google', 'apple', 'deeplink'];
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
this.resolvedProviders = ['injected'];
|
|
121
|
+
}
|
|
122
|
+
// When Privy handles social logins, strip them from Phantom
|
|
123
|
+
if (this.config.enablePrivyFallback) {
|
|
124
|
+
this.resolvedProviders = this.resolvedProviders.filter(p => p !== 'google' && p !== 'apple');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Patch the Phantom extension's provider to gracefully handle `phantom_getFeatures`
|
|
129
|
+
* instead of throwing. Privy's SDK probes this method to check feature support,
|
|
130
|
+
* but the extension doesn't implement it, causing noisy console errors.
|
|
131
|
+
*/
|
|
132
|
+
patchPhantomGetFeatures() {
|
|
133
|
+
var _a;
|
|
134
|
+
try {
|
|
135
|
+
const provider = (_a = window.phantom) === null || _a === void 0 ? void 0 : _a.solana;
|
|
136
|
+
if (!(provider === null || provider === void 0 ? void 0 : provider.request) || provider.__featuresPatched)
|
|
137
|
+
return;
|
|
138
|
+
const originalRequest = provider.request.bind(provider);
|
|
139
|
+
provider.request = async (args) => {
|
|
140
|
+
if ((args === null || args === void 0 ? void 0 : args.method) === 'phantom_getFeatures') {
|
|
141
|
+
return { features: {} };
|
|
142
|
+
}
|
|
143
|
+
return originalRequest(args);
|
|
144
|
+
};
|
|
145
|
+
provider.__featuresPatched = true;
|
|
146
|
+
}
|
|
147
|
+
catch (_b) {
|
|
148
|
+
// Phantom extension not present — nothing to patch
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
initialize() {
|
|
152
|
+
if (this.containerElement)
|
|
153
|
+
return;
|
|
154
|
+
// Suppress "phantom_getFeatures isn't implemented" errors from Privy probing
|
|
155
|
+
// the Phantom extension for features it doesn't support yet.
|
|
156
|
+
this.patchPhantomGetFeatures();
|
|
157
|
+
// Remove any existing Phantom providers
|
|
158
|
+
const existingProviders = document.querySelectorAll('[data-phantom-provider]');
|
|
159
|
+
existingProviders.forEach(el => el.remove());
|
|
160
|
+
this.containerElement = document.createElement('div');
|
|
161
|
+
this.containerElement.setAttribute('data-phantom-provider', 'true');
|
|
162
|
+
// Keep Phantom UI above host overlays while avoiding global click-capture.
|
|
163
|
+
this.containerElement.style.position = 'fixed';
|
|
164
|
+
this.containerElement.style.zIndex = '2147483647';
|
|
165
|
+
// Full-viewport overlay for custom modal; pointer-events: none lets
|
|
166
|
+
// clicks pass through to page except where the modal sets 'auto'
|
|
167
|
+
this.containerElement.style.inset = '0';
|
|
168
|
+
this.containerElement.style.pointerEvents = 'none';
|
|
169
|
+
document.body.appendChild(this.containerElement);
|
|
170
|
+
const that = this;
|
|
171
|
+
const { PhantomProvider: ReactPhantomProvider, usePhantom, useConnect, useDisconnect, useModal, useSolana, useDiscoveredWallets, AddressType, darkTheme, lightTheme } = phantomReactSdk;
|
|
172
|
+
// The SDK expects providers as an array of strings: 'injected' | 'google' | 'apple' | 'phantom' | 'deeplink'
|
|
173
|
+
// Ensure we have a valid non-empty array
|
|
174
|
+
const sdkProviders = this.resolvedProviders.length > 0 ? [...this.resolvedProviders] : ['injected'];
|
|
175
|
+
// Inner component that uses hooks
|
|
176
|
+
const PhantomHooksComponent = () => {
|
|
177
|
+
var _a, _b;
|
|
178
|
+
const phantom = usePhantom();
|
|
179
|
+
const { connect, error: connectError } = useConnect();
|
|
180
|
+
const { disconnect, isDisconnecting } = useDisconnect();
|
|
181
|
+
const modal = useModal();
|
|
182
|
+
const solanaHook = useSolana();
|
|
183
|
+
const { solana } = solanaHook;
|
|
184
|
+
const [showWalletModal, setShowWalletModal] = React.useState(false);
|
|
185
|
+
const [hoveredBtn, setHoveredBtn] = React.useState(null);
|
|
186
|
+
// Discover all available wallets via Wallet Standard + EIP-6963
|
|
187
|
+
const { wallets: discoveredWallets } = useDiscoveredWallets();
|
|
188
|
+
const isMobile = index_native.detectMobile();
|
|
189
|
+
const hasPhantomInjected = discoveredWallets.some((w) => w.id === 'phantom');
|
|
190
|
+
const showDeeplink = isMobile && sdkProviders.includes('deeplink') && !hasPhantomInjected;
|
|
191
|
+
// Track previous modal state to detect closes
|
|
192
|
+
const prevModalOpen = React.useRef(false);
|
|
193
|
+
// Track when modal was closed because user selected a wallet (not dismissed)
|
|
194
|
+
const walletClickedRef = React.useRef(false);
|
|
195
|
+
// Set up effect to expose methods to the class
|
|
196
|
+
React.useEffect(() => {
|
|
197
|
+
if (phantom) {
|
|
198
|
+
that.phantomMethods = {
|
|
199
|
+
ready: !phantom.isLoading,
|
|
200
|
+
isConnected: phantom.isConnected,
|
|
201
|
+
addresses: phantom.addresses || [],
|
|
202
|
+
user: phantom.user,
|
|
203
|
+
connect: async () => {
|
|
204
|
+
return await connect();
|
|
205
|
+
},
|
|
206
|
+
disconnect: async () => {
|
|
207
|
+
await disconnect();
|
|
208
|
+
},
|
|
209
|
+
isDisconnecting,
|
|
210
|
+
openModal: () => modal.open(),
|
|
211
|
+
closeModal: () => modal.close(),
|
|
212
|
+
isModalOpen: modal.isOpened,
|
|
213
|
+
connectError,
|
|
214
|
+
showCustomModal: () => setShowWalletModal(true),
|
|
215
|
+
hideCustomModal: () => setShowWalletModal(false),
|
|
216
|
+
solana: solana && solanaHook.isAvailable ? {
|
|
217
|
+
// Wrap methods to preserve 'this' context - direct references lose binding
|
|
218
|
+
signMessage: (message) => solana.signMessage(message),
|
|
219
|
+
signTransaction: (tx) => solana.signTransaction(tx),
|
|
220
|
+
signAllTransactions: (txs) => solana.signAllTransactions(txs),
|
|
221
|
+
signAndSendTransaction: (tx) => solana.signAndSendTransaction(tx),
|
|
222
|
+
getPublicKey: () => solana.getPublicKey(),
|
|
223
|
+
isAvailable: solanaHook.isAvailable,
|
|
224
|
+
connected: solana.connected,
|
|
225
|
+
connect: (options) => solana.connect(options),
|
|
226
|
+
} : null,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}, [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]);
|
|
230
|
+
// Auto-login: If connected but no Tarobase session, try to create one.
|
|
231
|
+
// This handles social login callbacks where user returns from OAuth already connected.
|
|
232
|
+
React.useEffect(() => {
|
|
233
|
+
const autoCreateSession = async () => {
|
|
234
|
+
var _a;
|
|
235
|
+
// Only proceed when SDK is ready, connected, has addresses, and not already in a login flow
|
|
236
|
+
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) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// Need solana to be available AND connected for signing
|
|
240
|
+
if (!solana || !solanaHook.isAvailable || !solana.connected) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// Find Solana address
|
|
244
|
+
const solAddress = (_a = phantom.addresses) === null || _a === void 0 ? void 0 : _a.find((addr) => addr.addressType === AddressType.solana);
|
|
245
|
+
if (!solAddress) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const publicKey = solAddress.address;
|
|
249
|
+
// Check if we already have a valid session for this address
|
|
250
|
+
const existingSession = await index_native.WebSessionManager.getSession();
|
|
251
|
+
if (existingSession && existingSession.address === publicKey) {
|
|
252
|
+
// Already have a valid session, just set user
|
|
253
|
+
index_native.setCurrentUser({ provider: that, address: publicKey });
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// No valid session - try to create one automatically
|
|
257
|
+
that.autoLoginInProgress = true;
|
|
258
|
+
index_native.setAuthLoading(true);
|
|
259
|
+
try {
|
|
260
|
+
const nonce = await index_native.genAuthNonce();
|
|
261
|
+
const messageText = await index_native.genSolanaMessage(publicKey, nonce);
|
|
262
|
+
// signMessage returns { signature: Uint8Array, publicKey: string }
|
|
263
|
+
const signResult = await solana.signMessage(messageText);
|
|
264
|
+
// Convert Uint8Array signature to base64 string
|
|
265
|
+
const signatureBytes = signResult.signature;
|
|
266
|
+
const signature = index.bufferExports.Buffer.from(signatureBytes).toString('base64');
|
|
267
|
+
const createSessionResult = await index_native.createSessionWithSignature(publicKey, messageText, signature);
|
|
268
|
+
await index_native.WebSessionManager.storeSession(publicKey, createSessionResult.accessToken, createSessionResult.idToken, createSessionResult.refreshToken);
|
|
269
|
+
// Mark auth method so clearIncompatibleSession() doesn't wipe this session
|
|
270
|
+
try {
|
|
271
|
+
index_native.getPlatform().storage.setItem('tarobase_last_auth_method', 'phantom');
|
|
272
|
+
}
|
|
273
|
+
catch (_b) { }
|
|
274
|
+
index_native.setCurrentUser({ provider: that, address: publicKey });
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
// User rejected signing or other error - disconnect fully
|
|
278
|
+
try {
|
|
279
|
+
await disconnect();
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
// Ignore disconnect errors
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
that.autoLoginInProgress = false;
|
|
287
|
+
index_native.setAuthLoading(false);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
autoCreateSession();
|
|
291
|
+
}, [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]);
|
|
292
|
+
// Suppress SDK modal if it opens during custom modal login flow
|
|
293
|
+
React.useEffect(() => {
|
|
294
|
+
if (modal.isOpened && that.loginInProgress) {
|
|
295
|
+
modal.close();
|
|
296
|
+
}
|
|
297
|
+
}, [modal.isOpened]);
|
|
298
|
+
// Handle modal close without connection
|
|
299
|
+
React.useEffect(() => {
|
|
300
|
+
// Detect when modal was open and is now closed
|
|
301
|
+
if (prevModalOpen.current && !showWalletModal && !modal.isOpened) {
|
|
302
|
+
if (walletClickedRef.current) {
|
|
303
|
+
// Modal closed because user selected a wallet — don't reject
|
|
304
|
+
walletClickedRef.current = false;
|
|
305
|
+
}
|
|
306
|
+
else if (that.loginInProgress && that.pendingLogin && !(phantom === null || phantom === void 0 ? void 0 : phantom.isConnected)) {
|
|
307
|
+
// User dismissed modal without connecting
|
|
308
|
+
that.pendingLogin.reject(new Error('User cancelled login'));
|
|
309
|
+
that.pendingLogin = null;
|
|
310
|
+
that.loginInProgress = false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
prevModalOpen.current = showWalletModal || modal.isOpened;
|
|
314
|
+
}, [showWalletModal, modal.isOpened, phantom === null || phantom === void 0 ? void 0 : phantom.isConnected]);
|
|
315
|
+
// Handle connection errors
|
|
316
|
+
React.useEffect(() => {
|
|
317
|
+
if (connectError && that.pendingLogin) {
|
|
318
|
+
that.pendingLogin.reject(connectError);
|
|
319
|
+
that.pendingLogin = null;
|
|
320
|
+
that.loginInProgress = false;
|
|
321
|
+
}
|
|
322
|
+
}, [connectError]);
|
|
323
|
+
// Handle connection success
|
|
324
|
+
React.useEffect(() => {
|
|
325
|
+
const handleConnectionSuccess = async () => {
|
|
326
|
+
var _a;
|
|
327
|
+
// All conditions must be met for login to proceed
|
|
328
|
+
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) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
// Solana must be available AND connected for signing
|
|
332
|
+
// If not ready yet, just return - this effect will re-run when solana.connected changes
|
|
333
|
+
if (!solana || !solanaHook.isAvailable || !solana.connected) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
// Immediately set loginInProgress to false to prevent double execution
|
|
338
|
+
// if the effect runs again before async operations complete
|
|
339
|
+
that.loginInProgress = false;
|
|
340
|
+
// Find Solana address
|
|
341
|
+
const solAddress = phantom.addresses.find((addr) => addr.addressType === AddressType.solana);
|
|
342
|
+
if (!solAddress) {
|
|
343
|
+
that.pendingLogin.reject(new Error('No Solana address returned'));
|
|
344
|
+
that.pendingLogin = null;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const publicKey = solAddress.address;
|
|
348
|
+
// Check if we already have a valid session
|
|
349
|
+
const existingSession = await index_native.WebSessionManager.getSession();
|
|
350
|
+
if (existingSession && existingSession.address === publicKey) {
|
|
351
|
+
const user = { provider: that, address: publicKey };
|
|
352
|
+
index_native.setCurrentUser(user);
|
|
353
|
+
that.pendingLogin.resolve(user);
|
|
354
|
+
that.pendingLogin = null;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// Create new session with signature
|
|
358
|
+
const nonce = await index_native.genAuthNonce();
|
|
359
|
+
const messageText = await index_native.genSolanaMessage(publicKey, nonce);
|
|
360
|
+
// signMessage returns { signature: Uint8Array, publicKey: string }
|
|
361
|
+
const signResult = await solana.signMessage(messageText);
|
|
362
|
+
// Convert Uint8Array signature to base64 string
|
|
363
|
+
const signatureBytes = signResult.signature;
|
|
364
|
+
const signature = index.bufferExports.Buffer.from(signatureBytes).toString('base64');
|
|
365
|
+
const createSessionResult = await index_native.createSessionWithSignature(publicKey, messageText, signature);
|
|
366
|
+
await index_native.WebSessionManager.storeSession(publicKey, createSessionResult.accessToken, createSessionResult.idToken, createSessionResult.refreshToken);
|
|
367
|
+
// Mark auth method so clearIncompatibleSession() doesn't wipe this session
|
|
368
|
+
try {
|
|
369
|
+
index_native.getPlatform().storage.setItem('tarobase_last_auth_method', 'phantom');
|
|
370
|
+
}
|
|
371
|
+
catch (_b) { }
|
|
372
|
+
const user = { provider: that, address: publicKey };
|
|
373
|
+
index_native.setCurrentUser(user);
|
|
374
|
+
that.pendingLogin.resolve(user);
|
|
375
|
+
that.pendingLogin = null;
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
// Disconnect Phantom since login failed - don't leave in half-connected state
|
|
379
|
+
try {
|
|
380
|
+
await disconnect();
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
// Ignore disconnect errors
|
|
384
|
+
}
|
|
385
|
+
if (that.pendingLogin) {
|
|
386
|
+
that.pendingLogin.reject(error);
|
|
387
|
+
that.pendingLogin = null;
|
|
388
|
+
that.loginInProgress = false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
handleConnectionSuccess();
|
|
393
|
+
}, [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]);
|
|
394
|
+
// --- Custom wallet modal handlers ---
|
|
395
|
+
const handleWalletClick = async (options) => {
|
|
396
|
+
walletClickedRef.current = true;
|
|
397
|
+
setShowWalletModal(false);
|
|
398
|
+
try {
|
|
399
|
+
if (options === null || options === void 0 ? void 0 : options.walletId) {
|
|
400
|
+
await connect({ provider: 'injected', walletId: options.walletId });
|
|
401
|
+
}
|
|
402
|
+
else if (options === null || options === void 0 ? void 0 : options.provider) {
|
|
403
|
+
await connect({ provider: options.provider });
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
await connect();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
// connectError effect handles this
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
const handleEmailClick = async () => {
|
|
414
|
+
that.loginInProgress = false;
|
|
415
|
+
walletClickedRef.current = true;
|
|
416
|
+
setShowWalletModal(false);
|
|
417
|
+
if (that.onSwitchToPrivy) {
|
|
418
|
+
try {
|
|
419
|
+
const privyProvider = await that.onSwitchToPrivy();
|
|
420
|
+
const user = await privyProvider.login();
|
|
421
|
+
if (that.pendingLogin && user) {
|
|
422
|
+
that.pendingLogin.resolve(user);
|
|
423
|
+
that.pendingLogin = null;
|
|
424
|
+
}
|
|
425
|
+
else if (that.pendingLogin) {
|
|
426
|
+
that.pendingLogin.reject(new Error('User cancelled login'));
|
|
427
|
+
that.pendingLogin = null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
if (that.pendingLogin) {
|
|
432
|
+
that.pendingLogin.reject(error);
|
|
433
|
+
that.pendingLogin = null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const handleMobileWalletClick = async () => {
|
|
439
|
+
that.loginInProgress = false;
|
|
440
|
+
walletClickedRef.current = true;
|
|
441
|
+
setShowWalletModal(false);
|
|
442
|
+
if (that.onSwitchToMWA) {
|
|
443
|
+
try {
|
|
444
|
+
const mwaProvider = await that.onSwitchToMWA();
|
|
445
|
+
const user = await mwaProvider.login();
|
|
446
|
+
if (that.pendingLogin && user) {
|
|
447
|
+
that.pendingLogin.resolve(user);
|
|
448
|
+
that.pendingLogin = null;
|
|
449
|
+
}
|
|
450
|
+
else if (that.pendingLogin) {
|
|
451
|
+
that.pendingLogin.reject(new Error('User cancelled login'));
|
|
452
|
+
that.pendingLogin = null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
if (that.pendingLogin) {
|
|
457
|
+
that.pendingLogin.reject(error);
|
|
458
|
+
that.pendingLogin = null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
const handleCloseModal = () => {
|
|
464
|
+
setShowWalletModal(false);
|
|
465
|
+
if (that.loginInProgress && that.pendingLogin && !(phantom === null || phantom === void 0 ? void 0 : phantom.isConnected)) {
|
|
466
|
+
that.pendingLogin.reject(new Error('User cancelled login'));
|
|
467
|
+
that.pendingLogin = null;
|
|
468
|
+
that.loginInProgress = false;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
// --- Custom wallet modal ---
|
|
472
|
+
if (!showWalletModal) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
const theme = that.config.theme === 'light' ? lightTheme : darkTheme;
|
|
476
|
+
const bgColor = ((_a = theme === null || theme === void 0 ? void 0 : theme.background) === null || _a === void 0 ? void 0 : _a.primary) || (that.config.theme === 'light' ? '#FFFFFF' : '#1A1A2E');
|
|
477
|
+
const textColor = ((_b = theme === null || theme === void 0 ? void 0 : theme.text) === null || _b === void 0 ? void 0 : _b.primary) || (that.config.theme === 'light' ? '#000000' : '#FFFFFF');
|
|
478
|
+
const btnBg = 'rgba(152,151,156,0.1)';
|
|
479
|
+
const btnHoverBg = 'rgba(152,151,156,0.15)';
|
|
480
|
+
const fontFamily = '"SF Pro Text",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif';
|
|
481
|
+
const buttonStyle = (id) => ({
|
|
482
|
+
display: 'flex',
|
|
483
|
+
alignItems: 'center',
|
|
484
|
+
justifyContent: 'center',
|
|
485
|
+
gap: '12px',
|
|
486
|
+
width: '100%',
|
|
487
|
+
height: '56px',
|
|
488
|
+
padding: '12px 16px',
|
|
489
|
+
border: 'none',
|
|
490
|
+
borderRadius: '16px',
|
|
491
|
+
backgroundColor: hoveredBtn === id ? btnHoverBg : btnBg,
|
|
492
|
+
color: textColor,
|
|
493
|
+
fontFamily,
|
|
494
|
+
fontSize: '14px',
|
|
495
|
+
fontWeight: '600',
|
|
496
|
+
lineHeight: '17px',
|
|
497
|
+
letterSpacing: '-0.14px',
|
|
498
|
+
cursor: 'pointer',
|
|
499
|
+
transition: 'background-color 0.2s',
|
|
500
|
+
});
|
|
501
|
+
const walletButtons = [];
|
|
502
|
+
// Google OAuth button — shown when 'google' is in resolved providers
|
|
503
|
+
if (sdkProviders.includes('google')) {
|
|
504
|
+
walletButtons.push(React.createElement('button', {
|
|
505
|
+
key: 'google-oauth',
|
|
506
|
+
style: buttonStyle('google-oauth'),
|
|
507
|
+
onClick: () => handleWalletClick({ provider: 'google' }),
|
|
508
|
+
onMouseEnter: () => setHoveredBtn('google-oauth'),
|
|
509
|
+
onMouseLeave: () => setHoveredBtn(null),
|
|
510
|
+
}, React.createElement('svg', {
|
|
511
|
+
width: '20', height: '20', viewBox: '0 0 24 24',
|
|
512
|
+
style: { flexShrink: '0' },
|
|
513
|
+
}, React.createElement('path', {
|
|
514
|
+
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',
|
|
515
|
+
fill: '#4285F4',
|
|
516
|
+
}), React.createElement('path', {
|
|
517
|
+
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',
|
|
518
|
+
fill: '#34A853',
|
|
519
|
+
}), React.createElement('path', {
|
|
520
|
+
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',
|
|
521
|
+
fill: '#FBBC05',
|
|
522
|
+
}), React.createElement('path', {
|
|
523
|
+
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',
|
|
524
|
+
fill: '#EA4335',
|
|
525
|
+
})), 'Continue with Google'));
|
|
526
|
+
}
|
|
527
|
+
// Apple OAuth button — shown when 'apple' is in resolved providers
|
|
528
|
+
if (sdkProviders.includes('apple')) {
|
|
529
|
+
walletButtons.push(React.createElement('button', {
|
|
530
|
+
key: 'apple-oauth',
|
|
531
|
+
style: buttonStyle('apple-oauth'),
|
|
532
|
+
onClick: () => handleWalletClick({ provider: 'apple' }),
|
|
533
|
+
onMouseEnter: () => setHoveredBtn('apple-oauth'),
|
|
534
|
+
onMouseLeave: () => setHoveredBtn(null),
|
|
535
|
+
}, React.createElement('svg', {
|
|
536
|
+
width: '20', height: '20', viewBox: '0 0 24 24',
|
|
537
|
+
fill: textColor,
|
|
538
|
+
style: { flexShrink: '0' },
|
|
539
|
+
}, React.createElement('path', {
|
|
540
|
+
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',
|
|
541
|
+
})), 'Continue with Apple'));
|
|
542
|
+
}
|
|
543
|
+
// Fallback icon for Phantom — uses the official SVG path from @phantom/wallet-sdk-ui
|
|
544
|
+
// (SDK intentionally leaves wallet.icon empty for Phantom, expecting its own UI to render it)
|
|
545
|
+
const PHANTOM_ICON = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHJ4PSI2IiBmaWxsPSIjQUI5RkYyIi8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCw0KSI+PHBhdGggZD0iTTIuMzY2NTUgMTguMzI3MUM0LjkxODcxIDE4LjMyNzEgNi44MzY3IDE2LjEwNzYgNy45ODEzMSAxNC4zNTM3QzcuODQyMSAxNC43NDE3IDcuNzY0NzYgMTUuMTI5OCA3Ljc2NDc2IDE1LjUwMjNDNy43NjQ3NiAxNi41MjY3IDguMzUyNTMgMTcuMjU2MiA5LjUxMjYgMTcuMjU2MkMxMS4xMDU4IDE3LjI1NjIgMTIuODA3MiAxNS44NTkzIDEzLjY4ODkgMTQuMzUzN0MxMy42MjcgMTQuNTcxIDEzLjU5NjEgMTQuNzcyOCAxMy41OTYxIDE0Ljk1OUMxMy41OTYxIDE1LjY3MyAxMy45OTgyIDE2LjEyMzEgMTQuODE4IDE2LjEyMzFDMTcuNDAxMSAxNi4xMjMxIDE5Ljk5OTcgMTEuNTQ0NCAxOS45OTk3IDcuNTM5ODdDMTkuOTk5NyA0LjQyMDExIDE4LjQyMiAxLjY3Mjg1IDE0LjQ2MjIgMS42NzI4NUM3LjUwMTgxIDEuNjcyODUgMCAxMC4xNzg1IDAgMTUuNjczQzAgMTcuODMwNSAxLjE2MDA3IDE4LjMyNzEgMi4zNjY1NSAxOC4zMjcxWk0xMi4wNjQ4IDcuMTk4NDFDMTIuMDY0OCA2LjQyMjM1IDEyLjQ5NzkgNS44NzkxIDEzLjEzMiA1Ljg3OTFDMTMuNzUwNyA1Ljg3OTEgMTQuMTgzOCA2LjQyMjM1IDE0LjE4MzggNy4xOTg0MUMxNC4xODM4IDcuOTc0NDcgMTMuNzUwNyA4LjUzMzIzIDEzLjEzMiA4LjUzMzIzQzEyLjQ5NzkgOC41MzMyMyAxMi4wNjQ4IDcuOTc0NDcgMTIuMDY0OCA3LjE5ODQxWk0xNS4zNzQ4IDcuMTk4NDFDMTUuMzc0OCA2LjQyMjM1IDE1LjgwNzkgNS44NzkxIDE2LjQ0MjEgNS44NzkxQzE3LjA2MDggNS44NzkxIDE3LjQ5MzkgNi40MjIzNSAxNy40OTM5IDcuMTk4NDFDMTcuNDkzOSA3Ljk3NDQ3IDE3LjA2MDggOC41MzMyMyAxNi40NDIxIDguNTMzMjNDMTUuODA3OSA4LjUzMzIzIDE1LjM3NDggNy45NzQ0NyAxNS4zNzQ4IDcuMTk4NDFaIiBmaWxsPSJ3aGl0ZSIvPjwvZz48L3N2Zz4=';
|
|
546
|
+
// Show all discovered injected wallets (Phantom, Solflare, Jupiter, etc.)
|
|
547
|
+
if (sdkProviders.includes('injected')) {
|
|
548
|
+
for (const wallet of discoveredWallets) {
|
|
549
|
+
const walletKey = `wallet-${wallet.id}`;
|
|
550
|
+
const iconSrc = wallet.icon || (wallet.id === 'phantom' ? PHANTOM_ICON : null);
|
|
551
|
+
walletButtons.push(React.createElement('button', {
|
|
552
|
+
key: walletKey,
|
|
553
|
+
style: buttonStyle(walletKey),
|
|
554
|
+
onClick: () => handleWalletClick({ walletId: wallet.id }),
|
|
555
|
+
onMouseEnter: () => setHoveredBtn(walletKey),
|
|
556
|
+
onMouseLeave: () => setHoveredBtn(null),
|
|
557
|
+
}, iconSrc
|
|
558
|
+
? React.createElement('img', {
|
|
559
|
+
src: iconSrc,
|
|
560
|
+
alt: wallet.name,
|
|
561
|
+
style: { width: '28px', height: '28px', borderRadius: '6px' },
|
|
562
|
+
})
|
|
563
|
+
: null, wallet.name));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (showDeeplink) {
|
|
567
|
+
walletButtons.push(React.createElement('button', {
|
|
568
|
+
key: 'phantom-deeplink',
|
|
569
|
+
style: buttonStyle('phantom-deeplink'),
|
|
570
|
+
onClick: () => handleWalletClick({ provider: 'deeplink' }),
|
|
571
|
+
onMouseEnter: () => setHoveredBtn('phantom-deeplink'),
|
|
572
|
+
onMouseLeave: () => setHoveredBtn(null),
|
|
573
|
+
}, React.createElement('img', {
|
|
574
|
+
src: PHANTOM_ICON,
|
|
575
|
+
alt: 'Phantom',
|
|
576
|
+
style: { width: '28px', height: '28px', borderRadius: '6px' },
|
|
577
|
+
}), 'Open Phantom app'));
|
|
578
|
+
}
|
|
579
|
+
// Mobile Wallet Adapter button — shown on Android when MWA callback is available
|
|
580
|
+
const isAndroid = index_native.detectAndroid();
|
|
581
|
+
if (isAndroid && that.onSwitchToMWA) {
|
|
582
|
+
walletButtons.push(React.createElement('button', {
|
|
583
|
+
key: 'mobile-wallet',
|
|
584
|
+
style: buttonStyle('mobile-wallet'),
|
|
585
|
+
onClick: handleMobileWalletClick,
|
|
586
|
+
onMouseEnter: () => setHoveredBtn('mobile-wallet'),
|
|
587
|
+
onMouseLeave: () => setHoveredBtn(null),
|
|
588
|
+
},
|
|
589
|
+
// Mobile wallet icon (phone with wallet)
|
|
590
|
+
React.createElement('svg', {
|
|
591
|
+
width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none',
|
|
592
|
+
style: { flexShrink: '0' },
|
|
593
|
+
},
|
|
594
|
+
// Phone outline
|
|
595
|
+
React.createElement('rect', {
|
|
596
|
+
x: '5', y: '2', width: '14', height: '20', rx: '2',
|
|
597
|
+
stroke: textColor, strokeWidth: '2', fill: 'none',
|
|
598
|
+
}),
|
|
599
|
+
// Screen line
|
|
600
|
+
React.createElement('line', {
|
|
601
|
+
x1: '5', y1: '6', x2: '19', y2: '6',
|
|
602
|
+
stroke: textColor, strokeWidth: '1.5',
|
|
603
|
+
}),
|
|
604
|
+
// Wallet/shield checkmark
|
|
605
|
+
React.createElement('path', {
|
|
606
|
+
d: 'M9.5 12.5l2 2 3.5-3.5',
|
|
607
|
+
stroke: textColor, strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', fill: 'none',
|
|
608
|
+
})), 'Connect Mobile Wallet'));
|
|
609
|
+
}
|
|
610
|
+
// Email button — only when Privy fallback is enabled
|
|
611
|
+
if (that.config.enablePrivyFallback) {
|
|
612
|
+
walletButtons.push(React.createElement('button', {
|
|
613
|
+
key: 'email-login',
|
|
614
|
+
style: buttonStyle('email-login'),
|
|
615
|
+
onClick: handleEmailClick,
|
|
616
|
+
onMouseEnter: () => setHoveredBtn('email-login'),
|
|
617
|
+
onMouseLeave: () => setHoveredBtn(null),
|
|
618
|
+
},
|
|
619
|
+
// Email icon
|
|
620
|
+
React.createElement('svg', {
|
|
621
|
+
width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none',
|
|
622
|
+
style: { flexShrink: '0' },
|
|
623
|
+
}, React.createElement('path', {
|
|
624
|
+
d: 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z',
|
|
625
|
+
stroke: textColor, strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', fill: 'none',
|
|
626
|
+
}), React.createElement('path', {
|
|
627
|
+
d: 'M22 6l-10 7L2 6',
|
|
628
|
+
stroke: textColor, strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', fill: 'none',
|
|
629
|
+
})), 'Log in with email'));
|
|
630
|
+
}
|
|
631
|
+
// Render custom modal
|
|
632
|
+
return React.createElement('div', {
|
|
633
|
+
// Overlay
|
|
634
|
+
style: {
|
|
635
|
+
position: 'fixed',
|
|
636
|
+
top: '0',
|
|
637
|
+
left: '0',
|
|
638
|
+
right: '0',
|
|
639
|
+
bottom: '0',
|
|
640
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
641
|
+
display: 'flex',
|
|
642
|
+
alignItems: 'center',
|
|
643
|
+
justifyContent: 'center',
|
|
644
|
+
zIndex: 2147483647,
|
|
645
|
+
pointerEvents: 'auto',
|
|
646
|
+
},
|
|
647
|
+
onClick: (e) => {
|
|
648
|
+
if (e.target === e.currentTarget)
|
|
649
|
+
handleCloseModal();
|
|
650
|
+
},
|
|
651
|
+
onMouseDown: (e) => {
|
|
652
|
+
e.stopPropagation();
|
|
653
|
+
},
|
|
654
|
+
onPointerDown: (e) => {
|
|
655
|
+
e.stopPropagation();
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
// Card
|
|
659
|
+
React.createElement('div', {
|
|
660
|
+
style: {
|
|
661
|
+
backgroundColor: bgColor,
|
|
662
|
+
borderRadius: '24px',
|
|
663
|
+
width: '360px',
|
|
664
|
+
maxWidth: '90vw',
|
|
665
|
+
padding: '24px',
|
|
666
|
+
position: 'relative',
|
|
667
|
+
boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
|
|
668
|
+
pointerEvents: 'auto',
|
|
669
|
+
},
|
|
670
|
+
onMouseDown: (e) => {
|
|
671
|
+
e.stopPropagation();
|
|
672
|
+
},
|
|
673
|
+
onPointerDown: (e) => {
|
|
674
|
+
e.stopPropagation();
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
// Close button (X)
|
|
678
|
+
React.createElement('button', {
|
|
679
|
+
onClick: handleCloseModal,
|
|
680
|
+
style: {
|
|
681
|
+
position: 'absolute',
|
|
682
|
+
top: '16px',
|
|
683
|
+
right: '16px',
|
|
684
|
+
background: 'none',
|
|
685
|
+
border: 'none',
|
|
686
|
+
cursor: 'pointer',
|
|
687
|
+
padding: '4px',
|
|
688
|
+
display: 'flex',
|
|
689
|
+
alignItems: 'center',
|
|
690
|
+
justifyContent: 'center',
|
|
691
|
+
},
|
|
692
|
+
}, React.createElement('svg', {
|
|
693
|
+
width: '14', height: '14', viewBox: '0 0 14 14', fill: 'none',
|
|
694
|
+
}, React.createElement('path', {
|
|
695
|
+
d: 'M13 1L1 13M1 1l12 12',
|
|
696
|
+
stroke: textColor, strokeWidth: '2', strokeLinecap: 'round',
|
|
697
|
+
}))),
|
|
698
|
+
// App icon (if provided)
|
|
699
|
+
that.config.appIcon ? React.createElement('div', {
|
|
700
|
+
style: { display: 'flex', justifyContent: 'center', marginBottom: '16px' },
|
|
701
|
+
}, React.createElement('img', {
|
|
702
|
+
src: that.config.appIcon,
|
|
703
|
+
alt: that.config.appName || 'App',
|
|
704
|
+
style: { width: '48px', height: '48px', borderRadius: '12px' },
|
|
705
|
+
})) : null,
|
|
706
|
+
// Title
|
|
707
|
+
React.createElement('div', {
|
|
708
|
+
style: {
|
|
709
|
+
color: textColor,
|
|
710
|
+
fontFamily,
|
|
711
|
+
fontSize: '18px',
|
|
712
|
+
fontWeight: '600',
|
|
713
|
+
textAlign: 'center',
|
|
714
|
+
marginBottom: '24px',
|
|
715
|
+
},
|
|
716
|
+
}, 'Connect Wallet'),
|
|
717
|
+
// Wallet buttons
|
|
718
|
+
React.createElement('div', {
|
|
719
|
+
style: {
|
|
720
|
+
display: 'flex',
|
|
721
|
+
flexDirection: 'column',
|
|
722
|
+
gap: '8px',
|
|
723
|
+
},
|
|
724
|
+
}, ...walletButtons)));
|
|
725
|
+
};
|
|
726
|
+
// Wrapper component with PhantomProvider
|
|
727
|
+
const PhantomProviderWrapper = () => {
|
|
728
|
+
// Build SDK config
|
|
729
|
+
const sdkConfig = React.useMemo(() => {
|
|
730
|
+
var _a;
|
|
731
|
+
const config = {
|
|
732
|
+
providers: sdkProviders,
|
|
733
|
+
addressTypes: [AddressType.solana],
|
|
734
|
+
autoConnect: (_a = that.config.autoConnect) !== null && _a !== void 0 ? _a : true,
|
|
735
|
+
};
|
|
736
|
+
if (that.config.appId) {
|
|
737
|
+
config.appId = that.config.appId;
|
|
738
|
+
}
|
|
739
|
+
return config;
|
|
740
|
+
}, []);
|
|
741
|
+
const theme = that.config.theme === 'light' ? lightTheme : darkTheme;
|
|
742
|
+
return React.createElement(ReactPhantomProvider, {
|
|
743
|
+
config: sdkConfig,
|
|
744
|
+
theme: theme,
|
|
745
|
+
appName: that.config.appName || 'App',
|
|
746
|
+
appIcon: that.config.appIcon,
|
|
747
|
+
}, React.createElement(PhantomHooksComponent));
|
|
748
|
+
};
|
|
749
|
+
this.root = ReactDOM.createRoot(this.containerElement);
|
|
750
|
+
this.root.render(React.createElement(PhantomProviderWrapper));
|
|
751
|
+
}
|
|
752
|
+
async ensureReady() {
|
|
753
|
+
// Wait for async initialization to complete first
|
|
754
|
+
if (this.initPromise) {
|
|
755
|
+
await this.initPromise;
|
|
756
|
+
}
|
|
757
|
+
// Wait for SDK to be ready
|
|
758
|
+
await new Promise((resolve) => {
|
|
759
|
+
const check = () => {
|
|
760
|
+
var _a;
|
|
761
|
+
if ((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.ready) {
|
|
762
|
+
resolve();
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
setTimeout(check, 100);
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
check();
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
async ensureSolanaReady() {
|
|
772
|
+
var _a;
|
|
773
|
+
await this.ensureReady();
|
|
774
|
+
// If connected at the Phantom level, ensure solana methods are ready
|
|
775
|
+
if ((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected) {
|
|
776
|
+
const timeoutMs = 10000; // 10 second timeout
|
|
777
|
+
const startTime = Date.now();
|
|
778
|
+
// First, wait for solana to be available
|
|
779
|
+
await new Promise((resolve, reject) => {
|
|
780
|
+
const check = () => {
|
|
781
|
+
var _a, _b;
|
|
782
|
+
if ((_b = (_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.solana) === null || _b === void 0 ? void 0 : _b.isAvailable) {
|
|
783
|
+
resolve();
|
|
784
|
+
}
|
|
785
|
+
else if (Date.now() - startTime > timeoutMs) {
|
|
786
|
+
reject(new Error('Timeout waiting for Solana provider to be available'));
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
setTimeout(check, 100);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
check();
|
|
793
|
+
});
|
|
794
|
+
// Now check if the solana provider is connected, if not try to reconnect
|
|
795
|
+
// Note: We check our wrapper's `connected` which may be stale, but this is just
|
|
796
|
+
// an optimization to skip reconnect if we know we're already connected.
|
|
797
|
+
// The actual signing methods use the SDK's bound methods which check live state.
|
|
798
|
+
if (!this.phantomMethods.solana.connected) {
|
|
799
|
+
console.log('Solana provider not connected, attempting to reconnect...');
|
|
800
|
+
try {
|
|
801
|
+
// onlyIfTrusted: true means it will reconnect silently if previously approved
|
|
802
|
+
// If not trusted, it will throw and we'll catch it below
|
|
803
|
+
await this.phantomMethods.solana.connect({ onlyIfTrusted: true });
|
|
804
|
+
console.log('Solana provider reconnected successfully');
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
// If onlyIfTrusted fails, the user may need to re-approve via modal
|
|
808
|
+
console.error('Failed to reconnect solana provider (silent reconnect failed):', error === null || error === void 0 ? void 0 : error.message);
|
|
809
|
+
throw new Error('Failed to reconnect Solana provider. Please try logging in again.');
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// Note: We don't do a final connected check here because our wrapper's `connected`
|
|
813
|
+
// is a snapshot that won't update until React re-renders. If connect() succeeded,
|
|
814
|
+
// the SDK is connected and signing will work.
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Get the list of available (configured) providers for this wallet.
|
|
819
|
+
*/
|
|
820
|
+
getAvailableProviders() {
|
|
821
|
+
return [...this.resolvedProviders];
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Login using the Phantom connect modal.
|
|
825
|
+
* When enablePrivyFallback is true, a custom modal is rendered with discovered
|
|
826
|
+
* wallets and a "Log in with email" button instead of the SDK's built-in modal.
|
|
827
|
+
*/
|
|
828
|
+
async login() {
|
|
829
|
+
var _a, _b;
|
|
830
|
+
await this.ensureReady();
|
|
831
|
+
// Check if already connected with valid session
|
|
832
|
+
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) {
|
|
833
|
+
const solAddress = this.phantomMethods.addresses.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
|
|
834
|
+
if (solAddress) {
|
|
835
|
+
const session = await index_native.WebSessionManager.getSession();
|
|
836
|
+
if (session && session.address === solAddress.address) {
|
|
837
|
+
const user = { provider: this, address: solAddress.address };
|
|
838
|
+
index_native.setCurrentUser(user);
|
|
839
|
+
return user;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
// If already pending, wait for it
|
|
844
|
+
if (this.pendingLogin) {
|
|
845
|
+
return new Promise((resolve, reject) => {
|
|
846
|
+
this.pendingLogin.resolve = resolve;
|
|
847
|
+
this.pendingLogin.reject = reject;
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return new Promise((resolve, reject) => {
|
|
851
|
+
this.pendingLogin = { resolve, reject };
|
|
852
|
+
this.loginInProgress = true;
|
|
853
|
+
// Always use our custom wallet modal
|
|
854
|
+
this.phantomMethods.showCustomModal();
|
|
855
|
+
// Safety timeout
|
|
856
|
+
setTimeout(() => {
|
|
857
|
+
if (this.pendingLogin) {
|
|
858
|
+
this.pendingLogin.reject(new Error('Login timed out'));
|
|
859
|
+
this.pendingLogin = null;
|
|
860
|
+
this.loginInProgress = false;
|
|
861
|
+
}
|
|
862
|
+
}, 180000);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
async restoreSession() {
|
|
866
|
+
await this.ensureReady();
|
|
867
|
+
const session = await index_native.WebSessionManager.getSession();
|
|
868
|
+
if (session) {
|
|
869
|
+
return { provider: this, address: session.address };
|
|
870
|
+
}
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
async address() {
|
|
874
|
+
var _a, _b, _c, _d;
|
|
875
|
+
await this.ensureReady();
|
|
876
|
+
if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected) || !((_b = this.phantomMethods.addresses) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
877
|
+
// Try to connect first
|
|
878
|
+
const user = await this.login();
|
|
879
|
+
if (!user)
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
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);
|
|
883
|
+
return (solAddress === null || solAddress === void 0 ? void 0 : solAddress.address) || null;
|
|
884
|
+
}
|
|
885
|
+
async runTransaction(_evmTransactionData, solTransactionData, options) {
|
|
886
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
887
|
+
if (!solTransactionData) {
|
|
888
|
+
throw new Error("Solana transaction data is required for Phantom wallet");
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
await this.ensureSolanaReady();
|
|
892
|
+
}
|
|
893
|
+
catch (error) {
|
|
894
|
+
console.error('Solana provider reconnection failed, logging out:', error.message);
|
|
895
|
+
await this.logout();
|
|
896
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
897
|
+
}
|
|
898
|
+
if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected)) {
|
|
899
|
+
const user = await this.login();
|
|
900
|
+
if (!user) {
|
|
901
|
+
throw new Error('Failed to connect wallet');
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
await this.ensureSolanaReady();
|
|
905
|
+
}
|
|
906
|
+
catch (error) {
|
|
907
|
+
console.error('Solana provider reconnection failed after login, logging out:', error.message);
|
|
908
|
+
await this.logout();
|
|
909
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (!((_b = this.phantomMethods) === null || _b === void 0 ? void 0 : _b.solana)) {
|
|
913
|
+
throw new Error('Solana signing not available');
|
|
914
|
+
}
|
|
915
|
+
const rpcUrl = this.getRpcUrl(solTransactionData.network);
|
|
916
|
+
const connection = new web3_js.Connection(rpcUrl, 'confirmed');
|
|
917
|
+
const isSurfnet = rpcUrl === index_native.SURFNET_RPC_URL;
|
|
918
|
+
try {
|
|
919
|
+
const remainingAccounts = index_native.convertRemainingAccounts(solTransactionData.txArgs[0].remainingAccounts);
|
|
920
|
+
let app_id = solTransactionData.appId;
|
|
921
|
+
if (typeof window !== 'undefined' && window.CUSTOM_TAROBASE_APP_ID_HEADER) {
|
|
922
|
+
app_id = window.CUSTOM_TAROBASE_APP_ID_HEADER;
|
|
923
|
+
}
|
|
924
|
+
if (!app_id) {
|
|
925
|
+
throw new Error("App ID is required");
|
|
926
|
+
}
|
|
927
|
+
// Get address
|
|
928
|
+
const solAddress = this.phantomMethods.addresses.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
|
|
929
|
+
if (!solAddress) {
|
|
930
|
+
throw new Error('Failed to get Solana address');
|
|
931
|
+
}
|
|
932
|
+
const publicKey = new web3_js.PublicKey(solAddress.address);
|
|
933
|
+
// Create wallet adapter using SDK methods
|
|
934
|
+
const solana = this.phantomMethods.solana;
|
|
935
|
+
const walletAdapter = {
|
|
936
|
+
publicKey,
|
|
937
|
+
signTransaction: async (tx) => {
|
|
938
|
+
return await solana.signTransaction(tx);
|
|
939
|
+
},
|
|
940
|
+
signAllTransactions: async (txs) => {
|
|
941
|
+
return await solana.signAllTransactions(txs);
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
const anchorProvider = new anchor__namespace.AnchorProvider(connection, walletAdapter, anchor__namespace.AnchorProvider.defaultOptions());
|
|
945
|
+
const finalDeduped = [];
|
|
946
|
+
for (const acc of remainingAccounts) {
|
|
947
|
+
const existing = finalDeduped.find((d) => d.pubkey.equals(acc.pubkey));
|
|
948
|
+
if (existing) {
|
|
949
|
+
existing.isSigner = existing.isSigner || acc.isSigner;
|
|
950
|
+
existing.isWritable = existing.isWritable || acc.isWritable;
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
finalDeduped.push(acc);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
// When the server has co-signed a transaction (CPI attestation),
|
|
957
|
+
// deserialize it and just add the wallet signature instead of
|
|
958
|
+
// building from components.
|
|
959
|
+
let tx;
|
|
960
|
+
if (solTransactionData.signedTransaction) {
|
|
961
|
+
tx = web3_js.VersionedTransaction.deserialize(index.bufferExports.Buffer.from(solTransactionData.signedTransaction, 'base64'));
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
const result = await index_native.buildSetDocumentsTransaction(connection, solTransactionData.txArgs[0].idl, anchorProvider, publicKey, {
|
|
965
|
+
app_id,
|
|
966
|
+
documents: solTransactionData.txArgs[0].setDocumentData,
|
|
967
|
+
delete_paths: solTransactionData.txArgs[0].deletePaths,
|
|
968
|
+
txData: solTransactionData.txArgs[0].txData
|
|
969
|
+
}, finalDeduped, solTransactionData.lutKey, solTransactionData.preInstructions, false, solTransactionData.additionalLutAddresses);
|
|
970
|
+
tx = result.tx;
|
|
971
|
+
}
|
|
972
|
+
if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false) {
|
|
973
|
+
const signedTx = await walletAdapter.signTransaction(tx);
|
|
974
|
+
return {
|
|
975
|
+
signedTransaction: signedTx,
|
|
976
|
+
blockNumber: 0,
|
|
977
|
+
gasUsed: "0",
|
|
978
|
+
data: ""
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
// Keep manual submission on surfnet, use Phantom's recommended signAndSend for other networks.
|
|
982
|
+
if (isSurfnet) {
|
|
983
|
+
const signedTx = await walletAdapter.signTransaction(tx);
|
|
984
|
+
const { signature, txInfo } = await this.submitSignedTransaction(signedTx, connection);
|
|
985
|
+
return {
|
|
986
|
+
transactionSignature: signature,
|
|
987
|
+
blockNumber: (txInfo === null || txInfo === void 0 ? void 0 : txInfo.slot) || 0,
|
|
988
|
+
gasUsed: ((_c = txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta) === null || _c === void 0 ? void 0 : _c.fee.toString()) || '0',
|
|
989
|
+
data: txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
const signature = await this.signAndSendViaPhantom(tx);
|
|
993
|
+
const txInfo = await index_native.confirmAndCheckTransaction(connection, signature);
|
|
994
|
+
return {
|
|
995
|
+
transactionSignature: signature,
|
|
996
|
+
blockNumber: (txInfo === null || txInfo === void 0 ? void 0 : txInfo.slot) || 0,
|
|
997
|
+
gasUsed: ((_d = txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta) === null || _d === void 0 ? void 0 : _d.fee.toString()) || '0',
|
|
998
|
+
data: txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
// Check if this is a connection error - if so, log out
|
|
1003
|
+
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'))) {
|
|
1004
|
+
console.error('Solana provider connection lost during transaction, logging out');
|
|
1005
|
+
await this.logout();
|
|
1006
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1007
|
+
}
|
|
1008
|
+
const isUserRejection = (error === null || error === void 0 ? void 0 : error.code) === 4001 ||
|
|
1009
|
+
((_g = error === null || error === void 0 ? void 0 : error.message) === null || _g === void 0 ? void 0 : _g.toLowerCase().includes('user rejected')) ||
|
|
1010
|
+
((_h = error === null || error === void 0 ? void 0 : error.message) === null || _h === void 0 ? void 0 : _h.toLowerCase().includes('user denied')) ||
|
|
1011
|
+
((_j = error === null || error === void 0 ? void 0 : error.message) === null || _j === void 0 ? void 0 : _j.toLowerCase().includes('user cancelled')) ||
|
|
1012
|
+
((_k = error === null || error === void 0 ? void 0 : error.message) === null || _k === void 0 ? void 0 : _k.toLowerCase().includes('user canceled'));
|
|
1013
|
+
if (!isUserRejection) {
|
|
1014
|
+
console.error('Failed to execute transaction', error);
|
|
1015
|
+
}
|
|
1016
|
+
throw new Error(`Failed to execute transaction: ${error.message}`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Signs a Solana transaction without submitting it.
|
|
1021
|
+
*
|
|
1022
|
+
* This method handles blockhash automatically if not set - you do NOT need to
|
|
1023
|
+
* set recentBlockhash on the transaction before calling this method.
|
|
1024
|
+
* The network/RPC URL is derived from the provider's configuration (set during initialization).
|
|
1025
|
+
*
|
|
1026
|
+
* @param transaction - The transaction to sign (Transaction or VersionedTransaction)
|
|
1027
|
+
* @returns The signed transaction
|
|
1028
|
+
*/
|
|
1029
|
+
async signTransaction(transaction) {
|
|
1030
|
+
var _a, _b, _c, _d;
|
|
1031
|
+
try {
|
|
1032
|
+
await this.ensureSolanaReady();
|
|
1033
|
+
}
|
|
1034
|
+
catch (error) {
|
|
1035
|
+
// Reconnection failed - log user out and throw
|
|
1036
|
+
console.error('Solana provider reconnection failed, logging out:', error.message);
|
|
1037
|
+
await this.logout();
|
|
1038
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1039
|
+
}
|
|
1040
|
+
if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected)) {
|
|
1041
|
+
const user = await this.login();
|
|
1042
|
+
if (!user) {
|
|
1043
|
+
throw new Error('Failed to connect wallet');
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
await this.ensureSolanaReady();
|
|
1047
|
+
}
|
|
1048
|
+
catch (error) {
|
|
1049
|
+
console.error('Solana provider reconnection failed after login, logging out:', error.message);
|
|
1050
|
+
await this.logout();
|
|
1051
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (!((_b = this.phantomMethods) === null || _b === void 0 ? void 0 : _b.solana)) {
|
|
1055
|
+
throw new Error('Solana signing not available');
|
|
1056
|
+
}
|
|
1057
|
+
// Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
|
|
1058
|
+
const isLegacyTransaction = 'recentBlockhash' in transaction && !('message' in transaction && 'staticAccountKeys' in transaction.message);
|
|
1059
|
+
// Ensure blockhash is set before signing
|
|
1060
|
+
if (isLegacyTransaction) {
|
|
1061
|
+
const legacyTx = transaction;
|
|
1062
|
+
if (!legacyTx.recentBlockhash) {
|
|
1063
|
+
const rpcUrl = this.getRpcUrl();
|
|
1064
|
+
const connection = new web3_js.Connection(rpcUrl, 'confirmed');
|
|
1065
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
|
1066
|
+
legacyTx.recentBlockhash = blockhash;
|
|
1067
|
+
legacyTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
1068
|
+
}
|
|
1069
|
+
// Set feePayer if not already set
|
|
1070
|
+
if (!legacyTx.feePayer) {
|
|
1071
|
+
const solAddress = this.phantomMethods.addresses.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
|
|
1072
|
+
if (solAddress) {
|
|
1073
|
+
legacyTx.feePayer = new web3_js.PublicKey(solAddress.address);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
// VersionedTransaction
|
|
1079
|
+
const versionedTx = transaction;
|
|
1080
|
+
if (!versionedTx.message.recentBlockhash) {
|
|
1081
|
+
const rpcUrl = this.getRpcUrl();
|
|
1082
|
+
const connection = new web3_js.Connection(rpcUrl, 'confirmed');
|
|
1083
|
+
const { blockhash } = await connection.getLatestBlockhash('confirmed');
|
|
1084
|
+
versionedTx.message.recentBlockhash = blockhash;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
try {
|
|
1088
|
+
return await this.phantomMethods.solana.signTransaction(transaction);
|
|
1089
|
+
}
|
|
1090
|
+
catch (error) {
|
|
1091
|
+
// Check if this is a connection error - if so, log out
|
|
1092
|
+
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'))) {
|
|
1093
|
+
console.error('Solana provider connection lost during signing, logging out');
|
|
1094
|
+
await this.logout();
|
|
1095
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1096
|
+
}
|
|
1097
|
+
throw new Error(`Failed to sign transaction: ${error.message}`);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Signs and submits a Solana transaction to the network.
|
|
1102
|
+
*
|
|
1103
|
+
* This method handles blockhash and transaction confirmation automatically - you do NOT need to
|
|
1104
|
+
* set recentBlockhash or lastValidBlockHeight on the transaction before calling this method.
|
|
1105
|
+
* The network/RPC URL is derived from the provider's configuration (set during initialization).
|
|
1106
|
+
*
|
|
1107
|
+
* @param transaction - The transaction to sign and submit (Transaction or VersionedTransaction)
|
|
1108
|
+
* @param feePayer - Optional fee payer public key. If not provided and the transaction doesn't
|
|
1109
|
+
* already have a feePayer set, the connected wallet address will be used.
|
|
1110
|
+
* Useful for co-signing scenarios where a different account pays the fees.
|
|
1111
|
+
* @returns The transaction signature
|
|
1112
|
+
*/
|
|
1113
|
+
async signAndSubmitTransaction(transaction, feePayer) {
|
|
1114
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1115
|
+
try {
|
|
1116
|
+
await this.ensureSolanaReady();
|
|
1117
|
+
}
|
|
1118
|
+
catch (error) {
|
|
1119
|
+
console.error('Solana provider reconnection failed, logging out:', error.message);
|
|
1120
|
+
await this.logout();
|
|
1121
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1122
|
+
}
|
|
1123
|
+
if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected)) {
|
|
1124
|
+
const user = await this.login();
|
|
1125
|
+
if (!user) {
|
|
1126
|
+
throw new Error('Failed to connect wallet');
|
|
1127
|
+
}
|
|
1128
|
+
try {
|
|
1129
|
+
await this.ensureSolanaReady();
|
|
1130
|
+
}
|
|
1131
|
+
catch (error) {
|
|
1132
|
+
console.error('Solana provider reconnection failed after login, logging out:', error.message);
|
|
1133
|
+
await this.logout();
|
|
1134
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
if (!((_b = this.phantomMethods) === null || _b === void 0 ? void 0 : _b.solana)) {
|
|
1138
|
+
throw new Error('Solana signing not available');
|
|
1139
|
+
}
|
|
1140
|
+
const rpcUrl = this.getRpcUrl();
|
|
1141
|
+
const connection = new web3_js.Connection(rpcUrl, 'confirmed');
|
|
1142
|
+
const isSurfnet = rpcUrl === index_native.SURFNET_RPC_URL;
|
|
1143
|
+
try {
|
|
1144
|
+
// Get fresh blockhash and set it on the transaction before signing
|
|
1145
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
|
1146
|
+
// Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
|
|
1147
|
+
const isLegacyTransaction = 'recentBlockhash' in transaction && !('message' in transaction && 'staticAccountKeys' in transaction.message);
|
|
1148
|
+
if (isLegacyTransaction) {
|
|
1149
|
+
const legacyTx = transaction;
|
|
1150
|
+
legacyTx.recentBlockhash = blockhash;
|
|
1151
|
+
legacyTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
1152
|
+
// Set feePayer if not already set
|
|
1153
|
+
if (!legacyTx.feePayer) {
|
|
1154
|
+
if (feePayer) {
|
|
1155
|
+
legacyTx.feePayer = feePayer;
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
// Use connected wallet address as feePayer
|
|
1159
|
+
const solAddress = this.phantomMethods.addresses.find((addr) => addr.addressType === phantomReactSdk.AddressType.solana);
|
|
1160
|
+
if (solAddress) {
|
|
1161
|
+
legacyTx.feePayer = new web3_js.PublicKey(solAddress.address);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
// VersionedTransaction
|
|
1168
|
+
const versionedTx = transaction;
|
|
1169
|
+
versionedTx.message.recentBlockhash = blockhash;
|
|
1170
|
+
// Note: VersionedTransaction feePayer is set in the message at creation time
|
|
1171
|
+
// and cannot be modified after creation
|
|
1172
|
+
}
|
|
1173
|
+
// Keep manual submission on surfnet, use Phantom's recommended signAndSend for other networks.
|
|
1174
|
+
if (isSurfnet) {
|
|
1175
|
+
const signedTx = await this.phantomMethods.solana.signTransaction(transaction);
|
|
1176
|
+
const { signature } = await this.submitSignedTransactionWithBlockhash(signedTx, connection, blockhash, lastValidBlockHeight);
|
|
1177
|
+
return signature;
|
|
1178
|
+
}
|
|
1179
|
+
const signature = await this.signAndSendViaPhantom(transaction);
|
|
1180
|
+
await index_native.confirmAndCheckTransaction(connection, signature);
|
|
1181
|
+
return signature;
|
|
1182
|
+
}
|
|
1183
|
+
catch (error) {
|
|
1184
|
+
// Check if this is a connection error - if so, log out
|
|
1185
|
+
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'))) {
|
|
1186
|
+
console.error('Solana provider connection lost during transaction, logging out');
|
|
1187
|
+
await this.logout();
|
|
1188
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1189
|
+
}
|
|
1190
|
+
const isUserRejection = (error === null || error === void 0 ? void 0 : error.code) === 4001 ||
|
|
1191
|
+
((_e = error === null || error === void 0 ? void 0 : error.message) === null || _e === void 0 ? void 0 : _e.toLowerCase().includes('user rejected')) ||
|
|
1192
|
+
((_f = error === null || error === void 0 ? void 0 : error.message) === null || _f === void 0 ? void 0 : _f.toLowerCase().includes('user denied')) ||
|
|
1193
|
+
((_g = error === null || error === void 0 ? void 0 : error.message) === null || _g === void 0 ? void 0 : _g.toLowerCase().includes('user cancelled')) ||
|
|
1194
|
+
((_h = error === null || error === void 0 ? void 0 : error.message) === null || _h === void 0 ? void 0 : _h.toLowerCase().includes('user canceled'));
|
|
1195
|
+
if (!isUserRejection) {
|
|
1196
|
+
console.error('Failed to execute transaction', error);
|
|
1197
|
+
}
|
|
1198
|
+
throw new Error(`Failed to execute transaction: ${error.message}`);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
async signMessage(message) {
|
|
1202
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1203
|
+
try {
|
|
1204
|
+
await this.ensureSolanaReady();
|
|
1205
|
+
}
|
|
1206
|
+
catch (error) {
|
|
1207
|
+
// Reconnection failed - log user out and throw
|
|
1208
|
+
console.error('Solana provider reconnection failed, logging out:', error.message);
|
|
1209
|
+
await this.logout();
|
|
1210
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1211
|
+
}
|
|
1212
|
+
if (!((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected)) {
|
|
1213
|
+
const user = await this.login();
|
|
1214
|
+
if (!user) {
|
|
1215
|
+
throw new Error('Failed to connect wallet');
|
|
1216
|
+
}
|
|
1217
|
+
try {
|
|
1218
|
+
await this.ensureSolanaReady();
|
|
1219
|
+
}
|
|
1220
|
+
catch (error) {
|
|
1221
|
+
console.error('Solana provider reconnection failed after login, logging out:', error.message);
|
|
1222
|
+
await this.logout();
|
|
1223
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (!((_b = this.phantomMethods) === null || _b === void 0 ? void 0 : _b.solana)) {
|
|
1227
|
+
throw new Error('Solana signing not available');
|
|
1228
|
+
}
|
|
1229
|
+
try {
|
|
1230
|
+
// signMessage returns { signature: Uint8Array, publicKey: string }
|
|
1231
|
+
const result = await this.phantomMethods.solana.signMessage(message);
|
|
1232
|
+
return index.bufferExports.Buffer.from(result.signature).toString('base64');
|
|
1233
|
+
}
|
|
1234
|
+
catch (error) {
|
|
1235
|
+
// Check if this is a connection error - if so, log out
|
|
1236
|
+
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'))) {
|
|
1237
|
+
console.error('Solana provider connection lost during signing, logging out');
|
|
1238
|
+
await this.logout();
|
|
1239
|
+
throw new Error('Wallet connection lost. Please reconnect.');
|
|
1240
|
+
}
|
|
1241
|
+
const isUserRejection = (error === null || error === void 0 ? void 0 : error.code) === 4001 ||
|
|
1242
|
+
((_e = error === null || error === void 0 ? void 0 : error.message) === null || _e === void 0 ? void 0 : _e.toLowerCase().includes('user rejected')) ||
|
|
1243
|
+
((_f = error === null || error === void 0 ? void 0 : error.message) === null || _f === void 0 ? void 0 : _f.toLowerCase().includes('user denied')) ||
|
|
1244
|
+
((_g = error === null || error === void 0 ? void 0 : error.message) === null || _g === void 0 ? void 0 : _g.toLowerCase().includes('user cancelled')) ||
|
|
1245
|
+
((_h = error === null || error === void 0 ? void 0 : error.message) === null || _h === void 0 ? void 0 : _h.toLowerCase().includes('user canceled'));
|
|
1246
|
+
if (!isUserRejection) {
|
|
1247
|
+
console.error('Failed to sign message', error);
|
|
1248
|
+
}
|
|
1249
|
+
throw new Error('Failed to sign message');
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
async logout() {
|
|
1253
|
+
var _a;
|
|
1254
|
+
await this.ensureReady();
|
|
1255
|
+
try {
|
|
1256
|
+
if ((_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.isConnected) {
|
|
1257
|
+
await this.phantomMethods.disconnect();
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
catch (error) {
|
|
1261
|
+
console.error('Failed to disconnect from Phantom wallet', error);
|
|
1262
|
+
}
|
|
1263
|
+
// Always clear local state
|
|
1264
|
+
index_native.WebSessionManager.clearSession();
|
|
1265
|
+
index_native.setCurrentUser(null);
|
|
1266
|
+
}
|
|
1267
|
+
async getNativeMethods() {
|
|
1268
|
+
await this.ensureReady();
|
|
1269
|
+
return this.phantomMethods;
|
|
1270
|
+
}
|
|
1271
|
+
/* ----------------------------------------------------------- *
|
|
1272
|
+
* Private Helpers
|
|
1273
|
+
* ----------------------------------------------------------- */
|
|
1274
|
+
getRpcUrl(network) {
|
|
1275
|
+
if (this.networkUrl) {
|
|
1276
|
+
return this.networkUrl;
|
|
1277
|
+
}
|
|
1278
|
+
if (network === 'solana_devnet') {
|
|
1279
|
+
return index_native.SOLANA_DEVNET_RPC_URL;
|
|
1280
|
+
}
|
|
1281
|
+
else if (network === 'solana_mainnet') {
|
|
1282
|
+
return index_native.SOLANA_MAINNET_RPC_URL;
|
|
1283
|
+
}
|
|
1284
|
+
else if (network === 'surfnet') {
|
|
1285
|
+
return index_native.SURFNET_RPC_URL;
|
|
1286
|
+
}
|
|
1287
|
+
return index_native.SOLANA_MAINNET_RPC_URL; // default to mainnet
|
|
1288
|
+
}
|
|
1289
|
+
async signAndSendViaPhantom(transaction) {
|
|
1290
|
+
var _a, _b, _c;
|
|
1291
|
+
const result = await ((_b = (_a = this.phantomMethods) === null || _a === void 0 ? void 0 : _a.solana) === null || _b === void 0 ? void 0 : _b.signAndSendTransaction(transaction));
|
|
1292
|
+
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;
|
|
1293
|
+
if (!signature) {
|
|
1294
|
+
throw new Error('Phantom did not return a transaction signature');
|
|
1295
|
+
}
|
|
1296
|
+
return signature;
|
|
1297
|
+
}
|
|
1298
|
+
async submitSignedTransaction(signedTx, connection) {
|
|
1299
|
+
// Get fresh blockhash for confirmation
|
|
1300
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
|
1301
|
+
return this.submitSignedTransactionWithBlockhash(signedTx, connection, blockhash, lastValidBlockHeight);
|
|
1302
|
+
}
|
|
1303
|
+
async submitSignedTransactionWithBlockhash(signedTx, connection, blockhash, lastValidBlockHeight) {
|
|
1304
|
+
// Submit the transaction
|
|
1305
|
+
const signature = await connection.sendRawTransaction(signedTx.serialize(), {
|
|
1306
|
+
preflightCommitment: 'confirmed'
|
|
1307
|
+
});
|
|
1308
|
+
// Confirm using blockhash strategy (same as runTransaction)
|
|
1309
|
+
const confirmation = await connection.confirmTransaction({
|
|
1310
|
+
signature,
|
|
1311
|
+
blockhash,
|
|
1312
|
+
lastValidBlockHeight,
|
|
1313
|
+
}, 'confirmed');
|
|
1314
|
+
if (confirmation.value.err) {
|
|
1315
|
+
throw new Error(`Transaction failed: ${confirmation.value.err.toString()}`);
|
|
1316
|
+
}
|
|
1317
|
+
// Get transaction info
|
|
1318
|
+
const txInfo = await connection.getParsedTransaction(signature, {
|
|
1319
|
+
maxSupportedTransactionVersion: 0,
|
|
1320
|
+
commitment: 'confirmed'
|
|
1321
|
+
});
|
|
1322
|
+
return { signature, txInfo };
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
PhantomWalletProvider.instance = null;
|
|
1326
|
+
|
|
1327
|
+
exports.PhantomWalletProvider = PhantomWalletProvider;
|
|
1328
|
+
//# sourceMappingURL=phantom-wallet-provider-DMxFAUC4.js.map
|