@openfort/react-native 0.1.19 → 0.1.21
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/components/AuthBoundary.js +4 -1
- package/dist/core/index.js +1 -1
- package/dist/core/provider.js +3 -27
- package/dist/hooks/auth/useEmailAuth.js +90 -3
- package/dist/hooks/core/useOpenfort.js +3 -17
- package/dist/hooks/wallet/index.js +4 -2
- package/dist/hooks/wallet/solanaProvider.js +77 -0
- package/dist/hooks/wallet/useEmbeddedEthereumWallet.js +465 -0
- package/dist/hooks/wallet/useEmbeddedSolanaWallet.js +391 -0
- package/dist/hooks/wallet/utils.js +75 -0
- package/dist/index.js +1 -1
- package/dist/lib/hookConsistency.js +6 -4
- package/dist/native/oauth.js +13 -0
- package/dist/native/storage.js +4 -0
- package/dist/native/webview.js +15 -1
- package/dist/types/components/AuthBoundary.d.ts +1 -0
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/core/provider.d.ts +1 -10
- package/dist/types/hooks/auth/useEmailAuth.d.ts +6 -7
- package/dist/types/hooks/auth/useGuestAuth.d.ts +1 -2
- package/dist/types/hooks/auth/useOAuth.d.ts +1 -2
- package/dist/types/hooks/core/useOpenfort.d.ts +2 -13
- package/dist/types/hooks/wallet/index.d.ts +2 -1
- package/dist/types/hooks/wallet/solanaProvider.d.ts +75 -0
- package/dist/types/hooks/wallet/useEmbeddedEthereumWallet.d.ts +53 -0
- package/dist/types/hooks/wallet/useEmbeddedSolanaWallet.d.ts +47 -0
- package/dist/types/hooks/wallet/utils.d.ts +17 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -2
- package/dist/types/lib/hookConsistency.d.ts +6 -0
- package/dist/types/native/oauth.d.ts +13 -0
- package/dist/types/native/storage.d.ts +4 -0
- package/dist/types/native/webview.d.ts +14 -0
- package/dist/types/types/auth.d.ts +0 -56
- package/dist/types/types/hookOption.d.ts +0 -1
- package/dist/types/types/index.d.ts +3 -30
- package/dist/types/types/oauth.d.ts +0 -38
- package/dist/types/types/wallet.d.ts +120 -216
- package/package.json +1 -1
- package/dist/hooks/auth/useCreateWalletPostAuth.js +0 -34
- package/dist/hooks/wallet/useWallets.js +0 -437
- package/dist/types/config.js +0 -1
- package/dist/types/hooks/auth/useCreateWalletPostAuth.d.ts +0 -1
- package/dist/types/hooks/wallet/useWallets.d.ts +0 -78
- package/dist/types/predicates.js +0 -120
- package/dist/types/types/config.d.ts +0 -59
- package/dist/types/types/predicates.d.ts +0 -118
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { AccountTypeEnum, ChainTypeEnum, EmbeddedState } from '@openfort/openfort-js';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useOpenfortContext } from '../../core/context';
|
|
4
|
+
import { onError, onSuccess } from '../../lib/hookConsistency';
|
|
5
|
+
import { logger } from '../../lib/logger';
|
|
6
|
+
import { OpenfortError, OpenfortErrorType } from '../../types/openfortError';
|
|
7
|
+
import { buildRecoveryParams } from './utils';
|
|
8
|
+
/**
|
|
9
|
+
* Hook for managing embedded Ethereum wallets.
|
|
10
|
+
*
|
|
11
|
+
* This hook provides comprehensive Ethereum wallet management including creation, activation,
|
|
12
|
+
* and recovery. It returns a discriminated union state that enables type-safe wallet interactions.
|
|
13
|
+
*
|
|
14
|
+
* @param options - Configuration with default chainId and callback functions
|
|
15
|
+
* @returns Discriminated union state object. The `status` field determines available properties.
|
|
16
|
+
* Possible states: 'disconnected', 'connecting', 'reconnecting', 'creating', 'needs-recovery',
|
|
17
|
+
* 'connected', 'error'. When connected, includes `provider` and `activeWallet`. All states include
|
|
18
|
+
* `create`, `setActive`, `setRecovery`, `exportPrivateKey`, and `wallets` methods/properties.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import { useEmbeddedEthereumWallet, isConnected, isLoading } from '@openfort/react-native';
|
|
23
|
+
*
|
|
24
|
+
* const ethereum = useEmbeddedEthereumWallet({
|
|
25
|
+
* chainId: 1,
|
|
26
|
+
* onCreateSuccess: (account, provider) => console.log('Wallet created:', account.address),
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* if (isLoading(ethereum)) {
|
|
30
|
+
* return <ActivityIndicator />;
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* if (isConnected(ethereum)) {
|
|
34
|
+
* // TypeScript knows provider and activeWallet are available
|
|
35
|
+
* const tx = await ethereum.provider.request({
|
|
36
|
+
* method: 'eth_sendTransaction',
|
|
37
|
+
* params: [{ from: ethereum.activeWallet.address, to: '0x...', value: '0x0' }]
|
|
38
|
+
* });
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* // Create wallet if none exist
|
|
42
|
+
* if (ethereum.status === 'disconnected' && ethereum.wallets.length === 0) {
|
|
43
|
+
* await ethereum.create({ chainId: 1 });
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function useEmbeddedEthereumWallet(options = {}) {
|
|
48
|
+
const { client, supportedChains, walletConfig, embeddedState } = useOpenfortContext();
|
|
49
|
+
const [embeddedAccounts, setEmbeddedAccounts] = useState([]);
|
|
50
|
+
const [activeWalletId, setActiveWalletId] = useState(null);
|
|
51
|
+
const [activeAccount, setActiveAccount] = useState(null);
|
|
52
|
+
const [provider, setProvider] = useState(null);
|
|
53
|
+
const recoverPromiseRef = useRef(null);
|
|
54
|
+
const [status, setStatus] = useState({
|
|
55
|
+
status: 'idle',
|
|
56
|
+
});
|
|
57
|
+
// Fetch Ethereum embedded accounts
|
|
58
|
+
const fetchEmbeddedAccounts = useCallback(async () => {
|
|
59
|
+
if (!client || embeddedState === EmbeddedState.NONE || embeddedState === EmbeddedState.UNAUTHENTICATED) {
|
|
60
|
+
setEmbeddedAccounts([]);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const accounts = await client.embeddedWallet.list({
|
|
65
|
+
limit: 100,
|
|
66
|
+
chainType: ChainTypeEnum.EVM,
|
|
67
|
+
accountType: walletConfig?.accountType === AccountTypeEnum.EOA ? undefined : AccountTypeEnum.SMART_ACCOUNT,
|
|
68
|
+
});
|
|
69
|
+
// Filter for Ethereum accounts only
|
|
70
|
+
setEmbeddedAccounts(accounts);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
setEmbeddedAccounts([]);
|
|
74
|
+
}
|
|
75
|
+
}, [client, embeddedState, walletConfig]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
fetchEmbeddedAccounts();
|
|
78
|
+
}, [fetchEmbeddedAccounts]);
|
|
79
|
+
// Sync active wallet ID and account with client
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
;
|
|
82
|
+
(async () => {
|
|
83
|
+
try {
|
|
84
|
+
const embeddedAccount = await client.embeddedWallet.get();
|
|
85
|
+
if (embeddedAccount.chainType === ChainTypeEnum.EVM) {
|
|
86
|
+
setActiveWalletId(embeddedAccount.id);
|
|
87
|
+
setActiveAccount(embeddedAccount);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
setActiveWalletId(null);
|
|
91
|
+
setActiveAccount(null);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
setActiveWalletId(null);
|
|
96
|
+
setActiveAccount(null);
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
}, [client]);
|
|
100
|
+
// Get Ethereum provider
|
|
101
|
+
const getEthereumProvider = useCallback(async () => {
|
|
102
|
+
const resolvePolicy = () => {
|
|
103
|
+
const ethereumProviderPolicyId = walletConfig?.ethereumProviderPolicyId;
|
|
104
|
+
if (!ethereumProviderPolicyId)
|
|
105
|
+
return undefined;
|
|
106
|
+
if (typeof ethereumProviderPolicyId === 'string') {
|
|
107
|
+
return ethereumProviderPolicyId;
|
|
108
|
+
}
|
|
109
|
+
if (!options.chainId)
|
|
110
|
+
return undefined;
|
|
111
|
+
const policy = ethereumProviderPolicyId[options.chainId];
|
|
112
|
+
if (!policy) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return policy;
|
|
116
|
+
};
|
|
117
|
+
return await client.embeddedWallet.getEthereumProvider({ announceProvider: false, policy: resolvePolicy() });
|
|
118
|
+
}, [client.embeddedWallet, walletConfig, options.chainId]);
|
|
119
|
+
// Initialize provider when recovering an active wallet on mount
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
// Only initialize if we have an account but no provider
|
|
122
|
+
if (!activeAccount || provider) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// Don't interfere with user-initiated actions
|
|
126
|
+
if (['creating', 'connecting', 'reconnecting', 'loading'].includes(status.status)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// Only initialize if embedded state is ready
|
|
130
|
+
if (embeddedState !== EmbeddedState.READY) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
;
|
|
134
|
+
(async () => {
|
|
135
|
+
try {
|
|
136
|
+
logger.info('Initializing provider for recovered Ethereum wallet session');
|
|
137
|
+
setStatus({ status: 'connecting' });
|
|
138
|
+
const ethProvider = await getEthereumProvider();
|
|
139
|
+
setProvider(ethProvider);
|
|
140
|
+
setStatus({ status: 'success' });
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
const error = e instanceof OpenfortError
|
|
144
|
+
? e
|
|
145
|
+
: new OpenfortError('Failed to initialize provider for active Ethereum wallet', OpenfortErrorType.WALLET_ERROR, { error: e });
|
|
146
|
+
logger.error('Ethereum provider initialization failed', error);
|
|
147
|
+
setStatus({ status: 'error', error });
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
150
|
+
}, [activeAccount, provider, embeddedState, status.status, getEthereumProvider]);
|
|
151
|
+
// Build wallets list with deduplication logic
|
|
152
|
+
const wallets = useMemo(() => {
|
|
153
|
+
// Deduplicate accounts based on account type
|
|
154
|
+
const deduplicatedAccounts = embeddedAccounts.reduce((acc, account) => {
|
|
155
|
+
if (walletConfig?.accountType === AccountTypeEnum.EOA) {
|
|
156
|
+
// For EOAs, deduplicate by address only (EOAs work across all chains)
|
|
157
|
+
if (!acc.some((a) => a.address.toLowerCase() === account.address.toLowerCase())) {
|
|
158
|
+
acc.push(account);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// For Smart Accounts, keep separate entries per chain (they're chain-specific)
|
|
163
|
+
// Only deduplicate exact matches (same address AND same chainId)
|
|
164
|
+
if (!acc.some((a) => a.address.toLowerCase() === account.address.toLowerCase() && a.chainId === account.chainId)) {
|
|
165
|
+
acc.push(account);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return acc;
|
|
169
|
+
}, []);
|
|
170
|
+
return deduplicatedAccounts.map((account, index) => ({
|
|
171
|
+
address: account.address,
|
|
172
|
+
ownerAddress: account.ownerAddress,
|
|
173
|
+
implementationType: account.implementationType,
|
|
174
|
+
chainType: ChainTypeEnum.EVM,
|
|
175
|
+
walletIndex: index,
|
|
176
|
+
getProvider: async () => await getEthereumProvider(),
|
|
177
|
+
}));
|
|
178
|
+
}, [embeddedAccounts, walletConfig?.accountType, getEthereumProvider]);
|
|
179
|
+
// Create wallet action
|
|
180
|
+
const create = useCallback(async (createOptions) => {
|
|
181
|
+
logger.info('Creating Ethereum wallet with options', createOptions);
|
|
182
|
+
try {
|
|
183
|
+
setStatus({ status: 'creating' });
|
|
184
|
+
// Validate chainId
|
|
185
|
+
let chainId;
|
|
186
|
+
if (createOptions?.chainId) {
|
|
187
|
+
if (!supportedChains || !supportedChains.some((chain) => chain.id === createOptions.chainId)) {
|
|
188
|
+
throw new OpenfortError(`Chain ID ${createOptions.chainId} is not supported. Supported chains: ${supportedChains?.map((c) => c.id).join(', ') || 'none'}`, OpenfortErrorType.WALLET_ERROR);
|
|
189
|
+
}
|
|
190
|
+
chainId = createOptions.chainId;
|
|
191
|
+
}
|
|
192
|
+
else if (options.chainId) {
|
|
193
|
+
chainId = options.chainId;
|
|
194
|
+
}
|
|
195
|
+
else if (supportedChains && supportedChains.length > 0) {
|
|
196
|
+
chainId = supportedChains[0].id;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
throw new OpenfortError('No supported chains available for wallet creation', OpenfortErrorType.WALLET_ERROR);
|
|
200
|
+
}
|
|
201
|
+
// Build recovery params
|
|
202
|
+
const recoveryParams = await buildRecoveryParams(createOptions, walletConfig);
|
|
203
|
+
// Create embedded wallet
|
|
204
|
+
const embeddedAccount = await client.embeddedWallet.create({
|
|
205
|
+
chainId,
|
|
206
|
+
accountType: createOptions?.accountType || walletConfig?.accountType || AccountTypeEnum.SMART_ACCOUNT,
|
|
207
|
+
chainType: ChainTypeEnum.EVM,
|
|
208
|
+
recoveryParams,
|
|
209
|
+
});
|
|
210
|
+
logger.info('Embedded Ethereum wallet created');
|
|
211
|
+
// Get provider
|
|
212
|
+
const ethProvider = await getEthereumProvider();
|
|
213
|
+
setProvider(ethProvider);
|
|
214
|
+
// Refresh accounts and set as active
|
|
215
|
+
await fetchEmbeddedAccounts();
|
|
216
|
+
setActiveWalletId(embeddedAccount.id);
|
|
217
|
+
setActiveAccount(embeddedAccount);
|
|
218
|
+
setStatus({ status: 'success' });
|
|
219
|
+
onSuccess({
|
|
220
|
+
options: createOptions,
|
|
221
|
+
data: {
|
|
222
|
+
account: embeddedAccount,
|
|
223
|
+
provider: ethProvider,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
if (createOptions?.onSuccess) {
|
|
227
|
+
createOptions.onSuccess({ account: embeddedAccount, provider: ethProvider });
|
|
228
|
+
}
|
|
229
|
+
if (options.onCreateSuccess) {
|
|
230
|
+
options.onCreateSuccess(embeddedAccount, ethProvider);
|
|
231
|
+
}
|
|
232
|
+
return embeddedAccount;
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
const error = e instanceof OpenfortError
|
|
236
|
+
? e
|
|
237
|
+
: new OpenfortError('Failed to create Ethereum wallet', OpenfortErrorType.WALLET_ERROR, { error: e });
|
|
238
|
+
setStatus({ status: 'error', error });
|
|
239
|
+
onError({
|
|
240
|
+
options: createOptions,
|
|
241
|
+
error,
|
|
242
|
+
});
|
|
243
|
+
if (createOptions?.onError) {
|
|
244
|
+
createOptions.onError(error);
|
|
245
|
+
}
|
|
246
|
+
if (options.onCreateError) {
|
|
247
|
+
options.onCreateError(error);
|
|
248
|
+
}
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
}, [client, supportedChains, walletConfig, options, getEthereumProvider, fetchEmbeddedAccounts]);
|
|
252
|
+
// Set active wallet action
|
|
253
|
+
const setActive = useCallback(async (setActiveOptions) => {
|
|
254
|
+
// Prevent concurrent recoveries
|
|
255
|
+
if (recoverPromiseRef.current) {
|
|
256
|
+
await recoverPromiseRef.current;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (wallets.length === 0) {
|
|
260
|
+
const error = new OpenfortError('No embedded Ethereum wallets available to set as active', OpenfortErrorType.WALLET_ERROR);
|
|
261
|
+
onError({
|
|
262
|
+
options: setActiveOptions,
|
|
263
|
+
error,
|
|
264
|
+
});
|
|
265
|
+
if (setActiveOptions.onError) {
|
|
266
|
+
setActiveOptions.onError(error);
|
|
267
|
+
}
|
|
268
|
+
if (options.onSetActiveError) {
|
|
269
|
+
options.onSetActiveError(error);
|
|
270
|
+
}
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
setStatus({ status: 'connecting' });
|
|
274
|
+
recoverPromiseRef.current = (async () => {
|
|
275
|
+
try {
|
|
276
|
+
// Validate chainId
|
|
277
|
+
let chainId;
|
|
278
|
+
if (setActiveOptions.chainId) {
|
|
279
|
+
if (!supportedChains || !supportedChains.some((chain) => chain.id === setActiveOptions.chainId)) {
|
|
280
|
+
throw new OpenfortError(`Chain ID ${setActiveOptions.chainId} is not supported. Supported chains: ${supportedChains?.map((c) => c.id).join(', ') || 'none'}`, OpenfortErrorType.WALLET_ERROR);
|
|
281
|
+
}
|
|
282
|
+
chainId = setActiveOptions.chainId;
|
|
283
|
+
}
|
|
284
|
+
else if (options.chainId) {
|
|
285
|
+
chainId = options.chainId;
|
|
286
|
+
}
|
|
287
|
+
else if (supportedChains && supportedChains.length > 0) {
|
|
288
|
+
chainId = supportedChains[0].id;
|
|
289
|
+
}
|
|
290
|
+
// Find account to recover
|
|
291
|
+
let embeddedAccountToRecover;
|
|
292
|
+
if (walletConfig?.accountType === AccountTypeEnum.EOA) {
|
|
293
|
+
// For EOAs, match only by address (EOAs work across all chains)
|
|
294
|
+
embeddedAccountToRecover = embeddedAccounts.find((account) => account.address.toLowerCase() === setActiveOptions.address.toLowerCase());
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// For Smart Accounts, match by both address and chainId (Smart Accounts are chain-specific)
|
|
298
|
+
embeddedAccountToRecover = embeddedAccounts.find((account) => account.chainId === chainId && account.address.toLowerCase() === setActiveOptions.address.toLowerCase());
|
|
299
|
+
}
|
|
300
|
+
if (!embeddedAccountToRecover) {
|
|
301
|
+
const errorMsg = walletConfig?.accountType === AccountTypeEnum.EOA
|
|
302
|
+
? `No embedded EOA account found for address ${setActiveOptions.address}`
|
|
303
|
+
: `No embedded smart account found for address ${setActiveOptions.address} on chain ID ${chainId}`;
|
|
304
|
+
throw new OpenfortError(errorMsg, OpenfortErrorType.WALLET_ERROR);
|
|
305
|
+
}
|
|
306
|
+
// Build recovery params
|
|
307
|
+
const recoveryParams = await buildRecoveryParams(setActiveOptions, walletConfig);
|
|
308
|
+
// Recover the embedded wallet
|
|
309
|
+
const embeddedAccount = await client.embeddedWallet.recover({
|
|
310
|
+
account: embeddedAccountToRecover.id,
|
|
311
|
+
recoveryParams,
|
|
312
|
+
});
|
|
313
|
+
// Get provider
|
|
314
|
+
const ethProvider = await getEthereumProvider();
|
|
315
|
+
setProvider(ethProvider);
|
|
316
|
+
// Find the wallet index in the deduplicated accounts list
|
|
317
|
+
const walletIndex = embeddedAccounts.findIndex((acc) => acc.address.toLowerCase() === embeddedAccount.address.toLowerCase() &&
|
|
318
|
+
acc.chainId === embeddedAccount.chainId);
|
|
319
|
+
const wallet = {
|
|
320
|
+
address: embeddedAccount.address,
|
|
321
|
+
ownerAddress: embeddedAccount.ownerAddress,
|
|
322
|
+
implementationType: embeddedAccount.implementationType,
|
|
323
|
+
chainType: ChainTypeEnum.EVM,
|
|
324
|
+
walletIndex: walletIndex >= 0 ? walletIndex : 0,
|
|
325
|
+
getProvider: async () => ethProvider,
|
|
326
|
+
};
|
|
327
|
+
recoverPromiseRef.current = null;
|
|
328
|
+
setStatus({ status: 'success' });
|
|
329
|
+
setActiveWalletId(embeddedAccount.id);
|
|
330
|
+
setActiveAccount(embeddedAccount);
|
|
331
|
+
onSuccess({
|
|
332
|
+
options: setActiveOptions,
|
|
333
|
+
data: {
|
|
334
|
+
wallet,
|
|
335
|
+
provider: ethProvider,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
if (setActiveOptions.onSuccess) {
|
|
339
|
+
setActiveOptions.onSuccess({ wallet, provider: ethProvider });
|
|
340
|
+
}
|
|
341
|
+
if (options.onSetActiveSuccess) {
|
|
342
|
+
options.onSetActiveSuccess(wallet, ethProvider);
|
|
343
|
+
}
|
|
344
|
+
return { wallet, provider: ethProvider };
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
recoverPromiseRef.current = null;
|
|
348
|
+
const error = e instanceof OpenfortError
|
|
349
|
+
? e
|
|
350
|
+
: new OpenfortError('Failed to set active Ethereum wallet', OpenfortErrorType.WALLET_ERROR);
|
|
351
|
+
setStatus({ status: 'error', error });
|
|
352
|
+
onError({
|
|
353
|
+
options: setActiveOptions,
|
|
354
|
+
error,
|
|
355
|
+
});
|
|
356
|
+
if (setActiveOptions.onError) {
|
|
357
|
+
setActiveOptions.onError(error);
|
|
358
|
+
}
|
|
359
|
+
if (options.onSetActiveError) {
|
|
360
|
+
options.onSetActiveError(error);
|
|
361
|
+
}
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
})();
|
|
365
|
+
await recoverPromiseRef.current;
|
|
366
|
+
}, [client, supportedChains, walletConfig, embeddedAccounts, options, wallets.length, getEthereumProvider]);
|
|
367
|
+
// Set recovery method action
|
|
368
|
+
const setRecovery = useCallback(async (params) => {
|
|
369
|
+
try {
|
|
370
|
+
setStatus({ status: 'loading' });
|
|
371
|
+
await client.embeddedWallet.setRecoveryMethod(params.previousRecovery, params.newRecovery);
|
|
372
|
+
setStatus({ status: 'success' });
|
|
373
|
+
onSuccess({
|
|
374
|
+
options: params,
|
|
375
|
+
data: {},
|
|
376
|
+
});
|
|
377
|
+
if (params.onSuccess) {
|
|
378
|
+
params.onSuccess({});
|
|
379
|
+
}
|
|
380
|
+
if (options.onSetRecoverySuccess) {
|
|
381
|
+
options.onSetRecoverySuccess();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (e) {
|
|
385
|
+
const error = new OpenfortError('Failed to set wallet recovery', OpenfortErrorType.WALLET_ERROR, {
|
|
386
|
+
error: e instanceof Error ? e : new Error('Unknown error'),
|
|
387
|
+
});
|
|
388
|
+
setStatus({ status: 'error', error });
|
|
389
|
+
onError({
|
|
390
|
+
options: params,
|
|
391
|
+
error,
|
|
392
|
+
});
|
|
393
|
+
if (params.onError) {
|
|
394
|
+
params.onError(error);
|
|
395
|
+
}
|
|
396
|
+
if (options.onSetRecoveryError) {
|
|
397
|
+
options.onSetRecoveryError(error);
|
|
398
|
+
}
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
}, [client, options]);
|
|
402
|
+
// Build active wallet from embeddedWallet.get()
|
|
403
|
+
const activeWallet = useMemo(() => {
|
|
404
|
+
if (!activeWalletId || !activeAccount)
|
|
405
|
+
return null;
|
|
406
|
+
// Find the wallet index in the accounts list
|
|
407
|
+
const accountIndex = embeddedAccounts.findIndex((acc) => acc.id === activeWalletId);
|
|
408
|
+
return {
|
|
409
|
+
address: activeAccount.address,
|
|
410
|
+
ownerAddress: activeAccount.ownerAddress,
|
|
411
|
+
implementationType: activeAccount.implementationType,
|
|
412
|
+
chainType: ChainTypeEnum.EVM,
|
|
413
|
+
walletIndex: accountIndex >= 0 ? accountIndex : 0,
|
|
414
|
+
getProvider: async () => await getEthereumProvider(),
|
|
415
|
+
};
|
|
416
|
+
}, [activeWalletId, activeAccount, embeddedAccounts, getEthereumProvider]);
|
|
417
|
+
// Build discriminated union state
|
|
418
|
+
const state = useMemo(() => {
|
|
419
|
+
const baseActions = {
|
|
420
|
+
create,
|
|
421
|
+
wallets,
|
|
422
|
+
setActive,
|
|
423
|
+
setRecovery,
|
|
424
|
+
exportPrivateKey: client.embeddedWallet.exportPrivateKey,
|
|
425
|
+
};
|
|
426
|
+
// Priority 1: Explicit action states (user-initiated operations)
|
|
427
|
+
if (status.status === 'creating') {
|
|
428
|
+
return { ...baseActions, status: 'creating', activeWallet: null };
|
|
429
|
+
}
|
|
430
|
+
if (status.status === 'connecting' || status.status === 'reconnecting' || status.status === 'loading') {
|
|
431
|
+
return { ...baseActions, status: 'connecting', activeWallet: activeWallet };
|
|
432
|
+
}
|
|
433
|
+
if (status.status === 'error') {
|
|
434
|
+
return { ...baseActions, status: 'error', activeWallet, error: status.error?.message || 'Unknown error' };
|
|
435
|
+
}
|
|
436
|
+
// Priority 2: Check authentication state from context
|
|
437
|
+
if (embeddedState !== EmbeddedState.READY && embeddedState !== EmbeddedState.CREATING_ACCOUNT) {
|
|
438
|
+
// Not authenticated or no embedded wallet capability
|
|
439
|
+
return { ...baseActions, status: 'disconnected', activeWallet: null };
|
|
440
|
+
}
|
|
441
|
+
// Priority 3: Data-driven connection state
|
|
442
|
+
if (activeWallet && provider) {
|
|
443
|
+
// Fully connected - have both wallet and provider
|
|
444
|
+
return { ...baseActions, status: 'connected', activeWallet, provider };
|
|
445
|
+
}
|
|
446
|
+
if (activeAccount && !provider) {
|
|
447
|
+
// Have wallet but provider not initialized yet (mount recovery in progress)
|
|
448
|
+
return { ...baseActions, status: 'connecting', activeWallet: activeWallet };
|
|
449
|
+
}
|
|
450
|
+
// Default: disconnected (authenticated but no wallet selected)
|
|
451
|
+
return { ...baseActions, status: 'disconnected', activeWallet: null };
|
|
452
|
+
}, [
|
|
453
|
+
status,
|
|
454
|
+
activeWallet,
|
|
455
|
+
activeAccount,
|
|
456
|
+
provider,
|
|
457
|
+
wallets,
|
|
458
|
+
embeddedState,
|
|
459
|
+
create,
|
|
460
|
+
setActive,
|
|
461
|
+
setRecovery,
|
|
462
|
+
client.embeddedWallet,
|
|
463
|
+
]);
|
|
464
|
+
return state;
|
|
465
|
+
}
|