@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,455 @@
|
|
|
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 { OpenfortSolanaProvider } from './solanaProvider';
|
|
8
|
+
import { buildRecoveryParams } from './utils';
|
|
9
|
+
/**
|
|
10
|
+
* Hook for managing embedded Solana wallets.
|
|
11
|
+
*
|
|
12
|
+
* This hook provides comprehensive management of embedded Solana (SVM) wallets including
|
|
13
|
+
* creation, recovery, activation, and transaction signing. Returns a discriminated union
|
|
14
|
+
* state that enables type-safe wallet interactions based on connection status.
|
|
15
|
+
*
|
|
16
|
+
* **Note:** Solana wallets are always EOA (Externally Owned Accounts) and work across
|
|
17
|
+
* all Solana networks (mainnet, devnet, testnet).
|
|
18
|
+
*
|
|
19
|
+
* **Recovery Methods:**
|
|
20
|
+
* - Automatic recovery (via encryption session)
|
|
21
|
+
* - Password-based recovery
|
|
22
|
+
*
|
|
23
|
+
* @param options - Configuration options including:
|
|
24
|
+
* - `onCreateSuccess` - Callback when wallet is created
|
|
25
|
+
* - `onCreateError` - Callback when wallet creation fails
|
|
26
|
+
* - `onSetActiveSuccess` - Callback when wallet is activated/recovered
|
|
27
|
+
* - `onSetActiveError` - Callback when wallet activation fails
|
|
28
|
+
*
|
|
29
|
+
* @returns Discriminated union state based on `status` field:
|
|
30
|
+
* - **'disconnected'**: No active wallet. Properties: `create`, `setActive`, `wallets`
|
|
31
|
+
* - **'connecting'**: Activating wallet. Properties: same as disconnected
|
|
32
|
+
* - **'reconnecting'**: Reconnecting to wallet. Properties: same as disconnected + `activeWallet`
|
|
33
|
+
* - **'creating'**: Creating new wallet. Properties: same as disconnected
|
|
34
|
+
* - **'needs-recovery'**: Recovery required. Properties: same as reconnecting
|
|
35
|
+
* - **'connected'**: Wallet ready. Properties: all + `provider` (Solana wallet adapter)
|
|
36
|
+
* - **'error'**: Operation failed. Properties: all + `error` message + optional `activeWallet`
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* import { useEmbeddedSolanaWallet } from '@openfort/react-native';
|
|
41
|
+
* import { Transaction } from '@solana/web3.js';
|
|
42
|
+
* import { ActivityIndicator } from 'react-native';
|
|
43
|
+
*
|
|
44
|
+
* function SolanaWalletComponent() {
|
|
45
|
+
* const solana = useEmbeddedSolanaWallet({
|
|
46
|
+
* onCreateSuccess: (account, provider) => {
|
|
47
|
+
* console.log('Solana wallet created:', account.address);
|
|
48
|
+
* console.log('Public key:', provider.publicKey);
|
|
49
|
+
* },
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* // Handle loading states
|
|
53
|
+
* if (solana.status === 'creating' || solana.status === 'connecting') {
|
|
54
|
+
* return <ActivityIndicator />;
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* // Create first wallet
|
|
58
|
+
* if (solana.status === 'disconnected' && solana.wallets.length === 0) {
|
|
59
|
+
* return (
|
|
60
|
+
* <Button
|
|
61
|
+
* onPress={() => solana.create({ recoveryPassword: 'optional' })}
|
|
62
|
+
* title="Create Solana Wallet"
|
|
63
|
+
* />
|
|
64
|
+
* );
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* // Activate existing wallet
|
|
68
|
+
* if (solana.status === 'disconnected' && solana.wallets.length > 0) {
|
|
69
|
+
* return (
|
|
70
|
+
* <Button
|
|
71
|
+
* onPress={() => solana.setActive({
|
|
72
|
+
* address: solana.wallets[0].address,
|
|
73
|
+
* recoveryPassword: 'optional'
|
|
74
|
+
* })}
|
|
75
|
+
* title="Connect Solana Wallet"
|
|
76
|
+
* />
|
|
77
|
+
* );
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* // Use connected wallet
|
|
81
|
+
* if (solana.status === 'connected') {
|
|
82
|
+
* const signTransaction = async () => {
|
|
83
|
+
* const transaction = new Transaction();
|
|
84
|
+
* // ... add instructions to transaction
|
|
85
|
+
*
|
|
86
|
+
* const signed = await solana.provider.signTransaction(transaction);
|
|
87
|
+
* console.log('Signed transaction:', signed);
|
|
88
|
+
* };
|
|
89
|
+
*
|
|
90
|
+
* const signMessage = async () => {
|
|
91
|
+
* const message = 'Hello Solana!';
|
|
92
|
+
* const signature = await solana.provider.signMessage(message);
|
|
93
|
+
* console.log('Message signature:', signature);
|
|
94
|
+
* };
|
|
95
|
+
*
|
|
96
|
+
* return (
|
|
97
|
+
* <View>
|
|
98
|
+
* <Text>Connected: {solana.activeWallet.address}</Text>
|
|
99
|
+
* <Button onPress={signTransaction} title="Sign Transaction" />
|
|
100
|
+
* <Button onPress={signMessage} title="Sign Message" />
|
|
101
|
+
* </View>
|
|
102
|
+
* );
|
|
103
|
+
* }
|
|
104
|
+
*
|
|
105
|
+
* return null;
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function useEmbeddedSolanaWallet(options = {}) {
|
|
110
|
+
const { client, walletConfig, embeddedState } = useOpenfortContext();
|
|
111
|
+
const [embeddedAccounts, setEmbeddedAccounts] = useState([]);
|
|
112
|
+
const [activeWalletId, setActiveWalletId] = useState(null);
|
|
113
|
+
const [activeAccount, setActiveAccount] = useState(null);
|
|
114
|
+
const [provider, setProvider] = useState(null);
|
|
115
|
+
const recoverPromiseRef = useRef(null);
|
|
116
|
+
const [status, setStatus] = useState({
|
|
117
|
+
status: 'idle',
|
|
118
|
+
});
|
|
119
|
+
// Fetch Solana embedded accounts
|
|
120
|
+
const fetchEmbeddedAccounts = useCallback(async () => {
|
|
121
|
+
if (!client || embeddedState === EmbeddedState.NONE || embeddedState === EmbeddedState.UNAUTHENTICATED) {
|
|
122
|
+
setEmbeddedAccounts([]);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const accounts = await client.embeddedWallet.list({
|
|
127
|
+
chainType: ChainTypeEnum.SVM,
|
|
128
|
+
accountType: AccountTypeEnum.EOA,
|
|
129
|
+
limit: 100,
|
|
130
|
+
});
|
|
131
|
+
setEmbeddedAccounts(accounts);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
setEmbeddedAccounts([]);
|
|
135
|
+
}
|
|
136
|
+
}, [client, embeddedState]);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
fetchEmbeddedAccounts();
|
|
139
|
+
}, [fetchEmbeddedAccounts]);
|
|
140
|
+
// Sync active wallet ID and account with client
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
;
|
|
143
|
+
(async () => {
|
|
144
|
+
try {
|
|
145
|
+
const embeddedAccount = await client.embeddedWallet.get();
|
|
146
|
+
// here we check in case the current account is not SVM
|
|
147
|
+
if (embeddedAccount.chainType === ChainTypeEnum.SVM) {
|
|
148
|
+
setActiveWalletId(embeddedAccount.id);
|
|
149
|
+
setActiveAccount(embeddedAccount);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
setActiveWalletId(null);
|
|
153
|
+
setActiveAccount(null);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
setActiveWalletId(null);
|
|
158
|
+
setActiveAccount(null);
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
}, [client]);
|
|
162
|
+
// Get Solana provider
|
|
163
|
+
const getSolanaProvider = useCallback(async (account) => {
|
|
164
|
+
// Helper function to sign a single transaction
|
|
165
|
+
const signSingleTransaction = async (transaction) => {
|
|
166
|
+
// Extract the message bytes from the transaction
|
|
167
|
+
// For @solana/kit compiledTransaction, the messageBytes property contains what needs to be signed
|
|
168
|
+
let messageBytes;
|
|
169
|
+
if (transaction.messageBytes) {
|
|
170
|
+
// @solana/kit compiled transaction
|
|
171
|
+
messageBytes = transaction.messageBytes;
|
|
172
|
+
}
|
|
173
|
+
else if (transaction.serializeMessage) {
|
|
174
|
+
// @solana/web3.js Transaction
|
|
175
|
+
messageBytes = transaction.serializeMessage();
|
|
176
|
+
}
|
|
177
|
+
else if (transaction instanceof Uint8Array) {
|
|
178
|
+
// Raw bytes
|
|
179
|
+
messageBytes = transaction;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
throw new OpenfortError('Unsupported transaction format. Expected @solana/kit compiled transaction, @solana/web3.js Transaction, or Uint8Array', OpenfortErrorType.WALLET_ERROR);
|
|
183
|
+
}
|
|
184
|
+
// Convert Uint8Array to Buffer JSON format for React Native WebView serialization
|
|
185
|
+
// This is necessary because React Native's postMessage only accepts strings,
|
|
186
|
+
// and Uint8Array serializes incorrectly as {0:1, 1:2, ...} instead of {type:"Buffer", data:[...]}
|
|
187
|
+
const bufferFormatMessage = {
|
|
188
|
+
type: 'Buffer',
|
|
189
|
+
data: Array.from(messageBytes),
|
|
190
|
+
};
|
|
191
|
+
// Sign the message bytes (hashMessage: false for Solana - Ed25519 signs raw bytes)
|
|
192
|
+
// Note: We cast to any because the iframe will deserialize the Buffer format correctly,
|
|
193
|
+
// but TypeScript doesn't know about this serialization detail
|
|
194
|
+
const signatureBase58 = await client.embeddedWallet.signMessage(bufferFormatMessage, {
|
|
195
|
+
hashMessage: false,
|
|
196
|
+
});
|
|
197
|
+
// Return the signature in the expected format
|
|
198
|
+
// Different libraries expect different return formats, so we return a flexible object
|
|
199
|
+
return {
|
|
200
|
+
signature: signatureBase58,
|
|
201
|
+
publicKey: account.address,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
const provider = new OpenfortSolanaProvider({
|
|
205
|
+
account,
|
|
206
|
+
signTransaction: signSingleTransaction,
|
|
207
|
+
signAllTransactions: async (transactions) => {
|
|
208
|
+
// Sign each transaction sequentially
|
|
209
|
+
const signedTransactions = [];
|
|
210
|
+
for (const transaction of transactions) {
|
|
211
|
+
const signed = await signSingleTransaction(transaction);
|
|
212
|
+
signedTransactions.push(signed);
|
|
213
|
+
}
|
|
214
|
+
return signedTransactions;
|
|
215
|
+
},
|
|
216
|
+
signMessage: async (message) => {
|
|
217
|
+
// Sign message using openfort-js (with hashMessage: false for Solana)
|
|
218
|
+
const result = await client.embeddedWallet.signMessage(message, { hashMessage: false });
|
|
219
|
+
return result;
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
return provider;
|
|
223
|
+
}, [client.embeddedWallet]);
|
|
224
|
+
// Initialize provider when recovering an active wallet on mount
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
// Only initialize if we have an account but no provider
|
|
227
|
+
if (!activeAccount || provider) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// Don't interfere with user-initiated actions
|
|
231
|
+
if (['creating', 'connecting', 'reconnecting', 'loading'].includes(status.status)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Only initialize if embedded state is ready
|
|
235
|
+
if (embeddedState !== EmbeddedState.READY) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
;
|
|
239
|
+
(async () => {
|
|
240
|
+
try {
|
|
241
|
+
logger.info('Initializing provider for recovered Solana wallet session');
|
|
242
|
+
setStatus({ status: 'connecting' });
|
|
243
|
+
const solProvider = await getSolanaProvider(activeAccount);
|
|
244
|
+
setProvider(solProvider);
|
|
245
|
+
setStatus({ status: 'success' });
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
const error = e instanceof OpenfortError
|
|
249
|
+
? e
|
|
250
|
+
: new OpenfortError('Failed to initialize provider for active Solana wallet', OpenfortErrorType.WALLET_ERROR, { error: e });
|
|
251
|
+
logger.error('Solana provider initialization failed', error);
|
|
252
|
+
setStatus({ status: 'error', error });
|
|
253
|
+
}
|
|
254
|
+
})();
|
|
255
|
+
}, [activeAccount, provider, embeddedState, status.status, getSolanaProvider]);
|
|
256
|
+
// Build wallets list (simple deduplication by address)
|
|
257
|
+
const wallets = useMemo(() => {
|
|
258
|
+
return embeddedAccounts.map((account, index) => ({
|
|
259
|
+
address: account.address,
|
|
260
|
+
chainType: ChainTypeEnum.SVM,
|
|
261
|
+
walletIndex: index,
|
|
262
|
+
getProvider: async () => await getSolanaProvider(account),
|
|
263
|
+
}));
|
|
264
|
+
}, [embeddedAccounts, getSolanaProvider]);
|
|
265
|
+
// Create wallet action
|
|
266
|
+
const create = useCallback(async (createOptions) => {
|
|
267
|
+
logger.info('Creating Solana wallet with options', createOptions);
|
|
268
|
+
try {
|
|
269
|
+
setStatus({ status: 'creating' });
|
|
270
|
+
// Build recovery params (only use recoveryPassword, ignore createAdditional)
|
|
271
|
+
const recoveryParams = await buildRecoveryParams(createOptions?.recoveryPassword ? { recoveryPassword: createOptions.recoveryPassword } : undefined, walletConfig);
|
|
272
|
+
// Create embedded wallet
|
|
273
|
+
const embeddedAccount = await client.embeddedWallet.create({
|
|
274
|
+
chainType: ChainTypeEnum.SVM,
|
|
275
|
+
recoveryParams,
|
|
276
|
+
accountType: AccountTypeEnum.EOA, // Solana wallets are EOA
|
|
277
|
+
});
|
|
278
|
+
logger.info('Embedded Solana wallet created');
|
|
279
|
+
// Get provider
|
|
280
|
+
const solProvider = await getSolanaProvider(embeddedAccount);
|
|
281
|
+
setProvider(solProvider);
|
|
282
|
+
// Refresh accounts and set as active
|
|
283
|
+
await fetchEmbeddedAccounts();
|
|
284
|
+
setActiveWalletId(embeddedAccount.id);
|
|
285
|
+
setActiveAccount(embeddedAccount);
|
|
286
|
+
setStatus({ status: 'success' });
|
|
287
|
+
onSuccess({
|
|
288
|
+
options: createOptions,
|
|
289
|
+
data: {
|
|
290
|
+
account: embeddedAccount,
|
|
291
|
+
provider: solProvider,
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
if (createOptions?.onSuccess) {
|
|
295
|
+
createOptions.onSuccess({ account: embeddedAccount, provider: solProvider });
|
|
296
|
+
}
|
|
297
|
+
if (options.onCreateSuccess) {
|
|
298
|
+
options.onCreateSuccess(embeddedAccount, solProvider);
|
|
299
|
+
}
|
|
300
|
+
return embeddedAccount;
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
const error = e instanceof OpenfortError
|
|
304
|
+
? e
|
|
305
|
+
: new OpenfortError('Failed to create Solana wallet', OpenfortErrorType.WALLET_ERROR, { error: e });
|
|
306
|
+
setStatus({ status: 'error', error });
|
|
307
|
+
onError({
|
|
308
|
+
options: createOptions,
|
|
309
|
+
error,
|
|
310
|
+
});
|
|
311
|
+
if (createOptions?.onError) {
|
|
312
|
+
createOptions.onError(error);
|
|
313
|
+
}
|
|
314
|
+
if (options.onCreateError) {
|
|
315
|
+
options.onCreateError(error);
|
|
316
|
+
}
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}, [client, walletConfig, options, getSolanaProvider, fetchEmbeddedAccounts]);
|
|
320
|
+
// Set active wallet action
|
|
321
|
+
const setActive = useCallback(async (setActiveOptions) => {
|
|
322
|
+
// Prevent concurrent recoveries
|
|
323
|
+
if (recoverPromiseRef.current) {
|
|
324
|
+
await recoverPromiseRef.current;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (wallets.length === 0) {
|
|
328
|
+
const error = new OpenfortError('No embedded Solana wallets available to set as active', OpenfortErrorType.WALLET_ERROR);
|
|
329
|
+
onError({
|
|
330
|
+
options: setActiveOptions,
|
|
331
|
+
error,
|
|
332
|
+
});
|
|
333
|
+
if (setActiveOptions.onError) {
|
|
334
|
+
setActiveOptions.onError(error);
|
|
335
|
+
}
|
|
336
|
+
if (options.onSetActiveError) {
|
|
337
|
+
options.onSetActiveError(error);
|
|
338
|
+
}
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
setStatus({ status: 'connecting' });
|
|
342
|
+
recoverPromiseRef.current = (async () => {
|
|
343
|
+
try {
|
|
344
|
+
// Find account to recover by address only
|
|
345
|
+
const embeddedAccountToRecover = embeddedAccounts.find((account) => account.address.toLowerCase() === setActiveOptions.address.toLowerCase());
|
|
346
|
+
if (!embeddedAccountToRecover) {
|
|
347
|
+
throw new OpenfortError(`No embedded Solana account found for address ${setActiveOptions.address}`, OpenfortErrorType.WALLET_ERROR);
|
|
348
|
+
}
|
|
349
|
+
// Build recovery params
|
|
350
|
+
const recoveryParams = await buildRecoveryParams(setActiveOptions, walletConfig);
|
|
351
|
+
// Recover the embedded wallet
|
|
352
|
+
const embeddedAccount = await client.embeddedWallet.recover({
|
|
353
|
+
account: embeddedAccountToRecover.id,
|
|
354
|
+
recoveryParams,
|
|
355
|
+
});
|
|
356
|
+
// Get provider
|
|
357
|
+
const solProvider = await getSolanaProvider(embeddedAccount);
|
|
358
|
+
setProvider(solProvider);
|
|
359
|
+
// Find the wallet index in the accounts list
|
|
360
|
+
const walletIndex = embeddedAccounts.findIndex((acc) => acc.address.toLowerCase() === embeddedAccount.address.toLowerCase());
|
|
361
|
+
const wallet = {
|
|
362
|
+
address: embeddedAccount.address,
|
|
363
|
+
chainType: ChainTypeEnum.SVM,
|
|
364
|
+
walletIndex: walletIndex >= 0 ? walletIndex : 0,
|
|
365
|
+
getProvider: async () => solProvider,
|
|
366
|
+
};
|
|
367
|
+
recoverPromiseRef.current = null;
|
|
368
|
+
setStatus({ status: 'success' });
|
|
369
|
+
setActiveWalletId(embeddedAccount.id);
|
|
370
|
+
setActiveAccount(embeddedAccount);
|
|
371
|
+
onSuccess({
|
|
372
|
+
options: setActiveOptions,
|
|
373
|
+
data: {
|
|
374
|
+
wallet,
|
|
375
|
+
provider: solProvider,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
if (setActiveOptions.onSuccess) {
|
|
379
|
+
setActiveOptions.onSuccess({ wallet, provider: solProvider });
|
|
380
|
+
}
|
|
381
|
+
if (options.onSetActiveSuccess) {
|
|
382
|
+
options.onSetActiveSuccess(wallet, solProvider);
|
|
383
|
+
}
|
|
384
|
+
return { wallet, provider: solProvider };
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
recoverPromiseRef.current = null;
|
|
388
|
+
const error = e instanceof OpenfortError
|
|
389
|
+
? e
|
|
390
|
+
: new OpenfortError('Failed to set active Solana wallet', OpenfortErrorType.WALLET_ERROR);
|
|
391
|
+
setStatus({ status: 'error', error });
|
|
392
|
+
onError({
|
|
393
|
+
options: setActiveOptions,
|
|
394
|
+
error,
|
|
395
|
+
});
|
|
396
|
+
if (setActiveOptions.onError) {
|
|
397
|
+
setActiveOptions.onError(error);
|
|
398
|
+
}
|
|
399
|
+
if (options.onSetActiveError) {
|
|
400
|
+
options.onSetActiveError(error);
|
|
401
|
+
}
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
})();
|
|
405
|
+
await recoverPromiseRef.current;
|
|
406
|
+
}, [client, walletConfig, embeddedAccounts, options, wallets.length, getSolanaProvider]);
|
|
407
|
+
// Build active wallet from embeddedWallet.get()
|
|
408
|
+
const activeWallet = useMemo(() => {
|
|
409
|
+
if (!activeWalletId || !activeAccount)
|
|
410
|
+
return null;
|
|
411
|
+
// Find the wallet index in the accounts list
|
|
412
|
+
const accountIndex = embeddedAccounts.findIndex((acc) => acc.id === activeWalletId);
|
|
413
|
+
return {
|
|
414
|
+
address: activeAccount.address,
|
|
415
|
+
chainType: ChainTypeEnum.SVM,
|
|
416
|
+
walletIndex: accountIndex >= 0 ? accountIndex : 0,
|
|
417
|
+
getProvider: async () => await getSolanaProvider(activeAccount),
|
|
418
|
+
};
|
|
419
|
+
}, [activeWalletId, activeAccount, embeddedAccounts, getSolanaProvider]);
|
|
420
|
+
// Build discriminated union state
|
|
421
|
+
const state = useMemo(() => {
|
|
422
|
+
const baseActions = {
|
|
423
|
+
create,
|
|
424
|
+
wallets,
|
|
425
|
+
setActive,
|
|
426
|
+
};
|
|
427
|
+
// Priority 1: Explicit action states (user-initiated operations)
|
|
428
|
+
if (status.status === 'creating') {
|
|
429
|
+
return { ...baseActions, status: 'creating', activeWallet: null };
|
|
430
|
+
}
|
|
431
|
+
if (status.status === 'connecting' || status.status === 'reconnecting' || status.status === 'loading') {
|
|
432
|
+
return { ...baseActions, status: 'connecting' };
|
|
433
|
+
}
|
|
434
|
+
if (status.status === 'error') {
|
|
435
|
+
return { ...baseActions, status: 'error', activeWallet, error: status.error?.message || 'Unknown error' };
|
|
436
|
+
}
|
|
437
|
+
// Priority 2: Check authentication state from context
|
|
438
|
+
if (embeddedState !== EmbeddedState.READY && embeddedState !== EmbeddedState.CREATING_ACCOUNT) {
|
|
439
|
+
// Not authenticated or no embedded wallet capability
|
|
440
|
+
return { ...baseActions, status: 'disconnected', activeWallet: null };
|
|
441
|
+
}
|
|
442
|
+
// Priority 3: Data-driven connection state
|
|
443
|
+
if (activeWallet && provider) {
|
|
444
|
+
// Fully connected - have both wallet and provider
|
|
445
|
+
return { ...baseActions, status: 'connected', activeWallet, provider };
|
|
446
|
+
}
|
|
447
|
+
if (activeAccount && !provider) {
|
|
448
|
+
// Have wallet but provider not initialized yet (mount recovery in progress)
|
|
449
|
+
return { ...baseActions, status: 'connecting' };
|
|
450
|
+
}
|
|
451
|
+
// Default: disconnected (authenticated but no wallet selected)
|
|
452
|
+
return { ...baseActions, status: 'disconnected', activeWallet: null };
|
|
453
|
+
}, [status, activeWallet, activeAccount, provider, wallets, embeddedState, create, setActive]);
|
|
454
|
+
return state;
|
|
455
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { RecoveryMethod } from '@openfort/openfort-js';
|
|
2
|
+
import { OpenfortError, OpenfortErrorType } from '../../types/openfortError';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves an encryption session from wallet configuration.
|
|
5
|
+
*
|
|
6
|
+
* This utility handles encryption session resolution for automatic wallet recovery.
|
|
7
|
+
* It supports both callback-based session retrieval and endpoint-based session creation.
|
|
8
|
+
*
|
|
9
|
+
* @param walletConfig - The embedded wallet configuration from the provider
|
|
10
|
+
* @returns A promise that resolves to the encryption session string
|
|
11
|
+
* @throws {OpenfortError} When wallet config is missing or session cannot be retrieved
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
async function resolveEncryptionSession(walletConfig) {
|
|
16
|
+
if (!walletConfig) {
|
|
17
|
+
throw new OpenfortError('Encryption session configuration is required', OpenfortErrorType.WALLET_ERROR);
|
|
18
|
+
}
|
|
19
|
+
// Try callback-based session retrieval first
|
|
20
|
+
if (walletConfig.getEncryptionSession) {
|
|
21
|
+
return await walletConfig.getEncryptionSession();
|
|
22
|
+
}
|
|
23
|
+
// Try endpoint-based session creation
|
|
24
|
+
if (walletConfig.createEncryptedSessionEndpoint) {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(walletConfig.createEncryptedSessionEndpoint, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new OpenfortError('Failed to create encryption session', OpenfortErrorType.WALLET_ERROR, {
|
|
34
|
+
status: response.status,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const body = (await response.json());
|
|
38
|
+
if (!body?.session || typeof body.session !== 'string') {
|
|
39
|
+
throw new OpenfortError('Encryption session response is missing the `session` property', OpenfortErrorType.WALLET_ERROR);
|
|
40
|
+
}
|
|
41
|
+
return body.session;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof OpenfortError) {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
throw new OpenfortError('Failed to create encryption session', OpenfortErrorType.WALLET_ERROR, { error });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new OpenfortError('Encryption session configuration is required', OpenfortErrorType.WALLET_ERROR);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Builds recovery parameters from options and wallet configuration.
|
|
54
|
+
*
|
|
55
|
+
* This utility constructs the appropriate RecoveryParams object based on whether
|
|
56
|
+
* a recovery password is provided or automatic recovery should be used.
|
|
57
|
+
*
|
|
58
|
+
* @param options - Options containing optional recovery password
|
|
59
|
+
* @param walletConfig - The embedded wallet configuration from the provider
|
|
60
|
+
* @returns A promise that resolves to RecoveryParams for the SDK
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export async function buildRecoveryParams(options, walletConfig) {
|
|
65
|
+
if (options?.recoveryPassword) {
|
|
66
|
+
return {
|
|
67
|
+
recoveryMethod: RecoveryMethod.PASSWORD,
|
|
68
|
+
password: options.recoveryPassword,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
recoveryMethod: RecoveryMethod.AUTOMATIC,
|
|
73
|
+
encryptionSession: await resolveEncryptionSession(walletConfig),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* ensuring consistent callback execution across all hooks in the SDK.
|
|
6
6
|
*
|
|
7
7
|
* @param params - Object containing hook options and success data
|
|
8
|
+
* @param params.hookOptions - Primary hook options (from the hook itself)
|
|
9
|
+
* @param params.options - Secondary hook options (from the action call)
|
|
10
|
+
* @param params.data - The success data to pass to callbacks
|
|
8
11
|
* @returns The success data that was passed in
|
|
9
12
|
*
|
|
10
13
|
* @example
|
|
@@ -29,6 +32,9 @@ export const onSuccess = ({ hookOptions, options, data, }) => {
|
|
|
29
32
|
* and optionally throws the error if throwOnError is configured.
|
|
30
33
|
*
|
|
31
34
|
* @param params - Object containing hook options and error information
|
|
35
|
+
* @param params.hookOptions - Primary hook options (from the hook itself)
|
|
36
|
+
* @param params.options - Secondary hook options (from the action call)
|
|
37
|
+
* @param params.error - The error that occurred during the operation
|
|
32
38
|
* @returns Object containing the error, or throws if throwOnError is enabled
|
|
33
39
|
*
|
|
34
40
|
* @example
|
package/dist/native/oauth.js
CHANGED
|
@@ -6,6 +6,11 @@ import { Platform } from 'react-native';
|
|
|
6
6
|
import { logger } from '../lib/logger';
|
|
7
7
|
/**
|
|
8
8
|
* Opens an OAuth authentication session
|
|
9
|
+
*
|
|
10
|
+
* @param config - OAuth session configuration
|
|
11
|
+
* @param config.url - OAuth provider URL to open
|
|
12
|
+
* @param config.redirectUri - Redirect URI for OAuth flow callback
|
|
13
|
+
* @returns Promise resolving to OAuth result indicating success, cancellation, or error
|
|
9
14
|
*/
|
|
10
15
|
export async function openOAuthSession(config) {
|
|
11
16
|
try {
|
|
@@ -38,6 +43,12 @@ export async function openOAuthSession(config) {
|
|
|
38
43
|
}
|
|
39
44
|
/**
|
|
40
45
|
* Handles Apple Sign-In authentication for iOS
|
|
46
|
+
*
|
|
47
|
+
* @param options - Options for Apple authentication
|
|
48
|
+
* @param options.state - State parameter for the OAuth flow
|
|
49
|
+
* @param options.isLogin - Whether this is a login or link operation (affects error codes)
|
|
50
|
+
* @returns Promise resolving to Apple authentication result with authorization code and user info
|
|
51
|
+
* @throws {Error} When not running on iOS platform or authentication fails
|
|
41
52
|
*/
|
|
42
53
|
export async function authenticateWithApple(options) {
|
|
43
54
|
if (Platform.OS !== 'ios') {
|
|
@@ -77,6 +88,8 @@ export async function authenticateWithApple(options) {
|
|
|
77
88
|
}
|
|
78
89
|
/**
|
|
79
90
|
* Checks if Apple Sign-In is available on the current device
|
|
91
|
+
*
|
|
92
|
+
* @returns Promise resolving to true if Apple Sign-In is available, false otherwise
|
|
80
93
|
*/
|
|
81
94
|
export async function isAppleSignInAvailable() {
|
|
82
95
|
if (Platform.OS !== 'ios') {
|
package/dist/native/storage.js
CHANGED
|
@@ -160,12 +160,16 @@ function normalizeKey(key) {
|
|
|
160
160
|
export const NativeStorageUtils = {
|
|
161
161
|
/**
|
|
162
162
|
* Checks if secure storage is available on the current platform.
|
|
163
|
+
*
|
|
164
|
+
* @returns True if the platform is iOS or Android, false otherwise
|
|
163
165
|
*/
|
|
164
166
|
isAvailable() {
|
|
165
167
|
return Platform.OS === 'ios' || Platform.OS === 'android';
|
|
166
168
|
},
|
|
167
169
|
/**
|
|
168
170
|
* Gets the platform-specific storage options.
|
|
171
|
+
*
|
|
172
|
+
* @returns Secure store options with keychain accessibility configuration
|
|
169
173
|
*/
|
|
170
174
|
getStorageOptions() {
|
|
171
175
|
return {
|
package/dist/native/webview.js
CHANGED
|
@@ -9,6 +9,8 @@ import { handleSecureStorageMessage, isSecureStorageMessage } from './storage';
|
|
|
9
9
|
* WebView component for embedded wallet integration
|
|
10
10
|
* Handles secure communication between React Native and the embedded wallet WebView
|
|
11
11
|
* This component is hidden and only used for wallet communication
|
|
12
|
+
*
|
|
13
|
+
* @param props - Component props, see {@link EmbeddedWalletWebViewProps}
|
|
12
14
|
*/
|
|
13
15
|
export const EmbeddedWalletWebView = ({ client, isClientReady, onProxyStatusChange, }) => {
|
|
14
16
|
const webViewRef = useRef(null);
|
|
@@ -42,7 +44,7 @@ export const EmbeddedWalletWebView = ({ client, isClientReady, onProxyStatusChan
|
|
|
42
44
|
// Set up WebView reference with client immediately when both are available
|
|
43
45
|
useEffect(() => {
|
|
44
46
|
if (webViewRef.current) {
|
|
45
|
-
//
|
|
47
|
+
// Message poster with Uint8Array preprocessing for React Native
|
|
46
48
|
const messagePoster = {
|
|
47
49
|
postMessage: (message) => {
|
|
48
50
|
webViewRef.current?.postMessage(message);
|
|
@@ -97,12 +99,16 @@ export const EmbeddedWalletWebView = ({ client, isClientReady, onProxyStatusChan
|
|
|
97
99
|
export const WebViewUtils = {
|
|
98
100
|
/**
|
|
99
101
|
* Checks if WebView is supported on the current platform
|
|
102
|
+
*
|
|
103
|
+
* @returns True if the platform is iOS or Android, false otherwise
|
|
100
104
|
*/
|
|
101
105
|
isSupported() {
|
|
102
106
|
return Platform.OS === 'ios' || Platform.OS === 'android';
|
|
103
107
|
},
|
|
104
108
|
/**
|
|
105
109
|
* Gets platform-specific WebView configuration
|
|
110
|
+
*
|
|
111
|
+
* @returns Platform-specific WebView configuration object
|
|
106
112
|
*/
|
|
107
113
|
getPlatformConfig() {
|
|
108
114
|
if (Platform.OS === 'ios') {
|
|
@@ -123,6 +129,9 @@ export const WebViewUtils = {
|
|
|
123
129
|
},
|
|
124
130
|
/**
|
|
125
131
|
* Creates a secure message for WebView communication
|
|
132
|
+
*
|
|
133
|
+
* @param data - Data to include in the message
|
|
134
|
+
* @returns JSON-stringified message with timestamp and platform information
|
|
126
135
|
*/
|
|
127
136
|
createSecureMessage(data) {
|
|
128
137
|
return JSON.stringify({
|
|
@@ -133,6 +142,9 @@ export const WebViewUtils = {
|
|
|
133
142
|
},
|
|
134
143
|
/**
|
|
135
144
|
* Validates a message received from WebView
|
|
145
|
+
*
|
|
146
|
+
* @param message - JSON string message to validate
|
|
147
|
+
* @returns Validation result with parsed data or error information
|
|
136
148
|
*/
|
|
137
149
|
validateMessage(message) {
|
|
138
150
|
try {
|
|
@@ -152,6 +164,8 @@ export const WebViewUtils = {
|
|
|
152
164
|
},
|
|
153
165
|
/**
|
|
154
166
|
* Gets WebView user agent for the current platform
|
|
167
|
+
*
|
|
168
|
+
* @returns User agent string including platform and version information
|
|
155
169
|
*/
|
|
156
170
|
getUserAgent() {
|
|
157
171
|
const baseAgent = 'OpenfortEmbeddedWallet/1.0';
|
|
@@ -31,6 +31,7 @@ export interface AuthBoundaryProps {
|
|
|
31
31
|
* 3. **Unauthenticated** – the user is not logged in.
|
|
32
32
|
* 4. **Authenticated** – the user is logged in and the SDK is ready.
|
|
33
33
|
*
|
|
34
|
+
* @param props - Component props, see {@link AuthBoundaryProps}
|
|
34
35
|
* @example
|
|
35
36
|
* ```tsx
|
|
36
37
|
* import { AuthBoundary } from '@openfort/react-native';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { SDKOverrides } from '@openfort/openfort-js';
|
|
2
2
|
export { RecoveryMethod } from '@openfort/openfort-js';
|
|
3
|
-
export { createOpenfortClient
|
|
3
|
+
export { createOpenfortClient } from './client';
|
|
4
4
|
export type { OpenfortContextValue } from './context';
|
|
5
5
|
export { isOpenfortContextValue, OpenfortContext, useOpenfortContext, useOpenfortContextSafe } from './context';
|
|
6
6
|
export type { CommonEmbeddedWalletConfiguration, EmbeddedWalletConfiguration, EncryptionSession, OpenfortProviderProps, } from './provider';
|