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