@openfort/react-native 0.1.11 → 0.1.12

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.
@@ -4,6 +4,7 @@ import { OpenfortContext } from './context';
4
4
  import { createOpenfortClient, setDefaultClient } from './client';
5
5
  import { EmbeddedWalletWebView, WebViewUtils } from '../native';
6
6
  import { logger, getEmbeddedStateName } from '../lib/logger';
7
+ import { validateEnvironment } from '../lib/environmentValidation';
7
8
  /**
8
9
  * Starts polling the embedded wallet state and invokes the callback when transitions occur.
9
10
  *
@@ -69,6 +70,11 @@ function startEmbeddedStatePolling(client, onChange, intervalMs = 1000) {
69
70
  * ```
70
71
  */
71
72
  export const OpenfortProvider = ({ children, publishableKey, customAuth, supportedChains, walletConfig, overrides, thirdPartyAuth, verbose = false, }) => {
73
+ // Validate environment variables before anything else
74
+ validateEnvironment({
75
+ publishableKey,
76
+ shieldPublishableKey: walletConfig?.shieldPublishableKey,
77
+ });
72
78
  // Prevent multiple OpenfortProvider instances
73
79
  const existingContext = React.useContext(OpenfortContext);
74
80
  if (existingContext) {
@@ -5,4 +5,3 @@
5
5
  */
6
6
  // Embedded wallet hooks
7
7
  export { useWallets } from './useWallets';
8
- export { useWallet } from './useWallet';
@@ -73,6 +73,39 @@ export function useWallets(hookOptions = {}) {
73
73
  },
74
74
  };
75
75
  }, [activeWalletId, embeddedAccounts, client.embeddedWallet]);
76
+ const resolveEncryptionSession = useCallback(async () => {
77
+ if (!walletConfig) {
78
+ throw new OpenfortError('Encryption session configuration is required', OpenfortErrorType.WALLET_ERROR);
79
+ }
80
+ if (walletConfig.getEncryptionSession) {
81
+ return await walletConfig.getEncryptionSession();
82
+ }
83
+ if (walletConfig.createEncryptedSessionEndpoint) {
84
+ try {
85
+ const response = await fetch(walletConfig.createEncryptedSessionEndpoint, {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json',
89
+ },
90
+ });
91
+ if (!response.ok) {
92
+ throw new OpenfortError('Failed to create encryption session', OpenfortErrorType.WALLET_ERROR, { status: response.status });
93
+ }
94
+ const body = await response.json();
95
+ if (!body?.session || typeof body.session !== 'string') {
96
+ throw new OpenfortError('Encryption session response is missing the `session` property', OpenfortErrorType.WALLET_ERROR);
97
+ }
98
+ return body.session;
99
+ }
100
+ catch (error) {
101
+ if (error instanceof OpenfortError) {
102
+ throw error;
103
+ }
104
+ throw new OpenfortError('Failed to create encryption session', OpenfortErrorType.WALLET_ERROR, { error });
105
+ }
106
+ }
107
+ throw new OpenfortError('Encryption session configuration is required', OpenfortErrorType.WALLET_ERROR);
108
+ }, [walletConfig]);
76
109
  const setActiveWallet = useCallback(async (options) => {
77
110
  // If there's already a recovery in progress, return the existing promise
78
111
  if (recoverPromiseRef.current) {
@@ -105,22 +138,22 @@ export function useWallets(hookOptions = {}) {
105
138
  chainId = supportedChains[0].id;
106
139
  }
107
140
  const address = options?.address || wallets[0]?.address;
108
- const embeddedAccountToRecover = embeddedAccounts.find(account => account.chainId === chainId && account.address === address);
141
+ // Find account to recover based on whether we're using EOA or Smart Account
142
+ let embeddedAccountToRecover;
143
+ if (walletConfig?.accountType === AccountTypeEnum.EOA) {
144
+ // For EOAs, match only by address (EOAs work across all chains)
145
+ embeddedAccountToRecover = embeddedAccounts.find(account => account.address.toLowerCase() === address?.toLowerCase());
146
+ }
147
+ else {
148
+ // For Smart Accounts, match by both address and chainId (Smart Accounts are chain-specific)
149
+ embeddedAccountToRecover = embeddedAccounts.find(account => account.chainId === chainId && account.address.toLowerCase() === address?.toLowerCase());
150
+ }
109
151
  let embeddedAccount;
110
152
  if (!embeddedAccountToRecover) {
111
- // Different chain maybe?
112
- // if (embeddedAccounts.some(account => account.address === address)) {
113
- // create wallet with new chain
114
- // embeddedAccount = await client.embeddedWallet.create({
115
- // chainId: chainId!,
116
- // accountType: AccountTypeEnum.SMART_ACCOUNT,
117
- // chainType: ChainTypeEnum.EVM,
118
- // shieldAuthentication: shieldAuthentication ?? undefined,
119
- // recoveryParams: options?.recoveryPassword ? { password: options.recoveryPassword, recoveryMethod: RecoveryMethod.PASSWORD } : undefined
120
- // });
121
- // } else {
122
- throw new OpenfortError(`No embedded account found for address ${address} on chain ID ${chainId}`, OpenfortErrorType.WALLET_ERROR);
123
- // }
153
+ const errorMsg = walletConfig?.accountType === AccountTypeEnum.EOA
154
+ ? `No embedded EOA account found for address ${address}`
155
+ : `No embedded account found for address ${address} on chain ID ${chainId}`;
156
+ throw new OpenfortError(errorMsg, OpenfortErrorType.WALLET_ERROR);
124
157
  }
125
158
  else {
126
159
  let recoveryParams;
@@ -132,12 +165,9 @@ export function useWallets(hookOptions = {}) {
132
165
  };
133
166
  }
134
167
  else {
135
- if (!walletConfig?.getEncryptionSession) {
136
- throw new OpenfortError('Encryption session (walletConfig.getEncryptionSession) is required for automatic recovery', OpenfortErrorType.WALLET_ERROR);
137
- }
138
168
  recoveryParams = {
139
169
  recoveryMethod: RecoveryMethod.AUTOMATIC,
140
- encryptionSession: await walletConfig.getEncryptionSession()
170
+ encryptionSession: await resolveEncryptionSession()
141
171
  };
142
172
  }
143
173
  // Recover the embedded wallet with shield authentication
@@ -185,7 +215,7 @@ export function useWallets(hookOptions = {}) {
185
215
  }
186
216
  })();
187
217
  return recoverPromiseRef.current;
188
- }, [client, supportedChains, walletConfig, _internal, embeddedAccounts, hookOptions]);
218
+ }, [client, supportedChains, resolveEncryptionSession, _internal, embeddedAccounts, hookOptions]);
189
219
  // Fetch embedded wallets using embeddedWallet.list()
190
220
  const fetchEmbeddedWallets = useCallback(async () => {
191
221
  if (!client || embeddedState === EmbeddedState.NONE || embeddedState === EmbeddedState.UNAUTHENTICATED) {
@@ -193,13 +223,18 @@ export function useWallets(hookOptions = {}) {
193
223
  return;
194
224
  }
195
225
  try {
196
- const accounts = await client.embeddedWallet.list();
226
+ const accounts = await client.embeddedWallet.list({
227
+ limit: 100,
228
+ // If its EOA we want all accounts, otherwise we want only smart accounts
229
+ accountType: walletConfig?.accountType === AccountTypeEnum.EOA ?
230
+ undefined : AccountTypeEnum.SMART_ACCOUNT
231
+ });
197
232
  setEmbeddedAccounts(accounts);
198
233
  }
199
234
  catch {
200
235
  setEmbeddedAccounts([]);
201
236
  }
202
- }, [client, embeddedState, user]);
237
+ }, [client, embeddedState, user, walletConfig]);
203
238
  useEffect(() => {
204
239
  fetchEmbeddedWallets();
205
240
  }, [fetchEmbeddedWallets]);
@@ -233,24 +268,36 @@ export function useWallets(hookOptions = {}) {
233
268
  })();
234
269
  }, [setActiveWalletId, client]);
235
270
  // Extract Ethereum wallets from embedded accounts
236
- const wallets = useMemo(() => (embeddedAccounts
237
- .reduce((acc, account) => {
238
- if (!acc.some(a => a.address === account.address)) {
239
- acc.push(account);
240
- }
241
- return acc;
242
- }, [])
243
- .map((account) => ({
244
- address: account.address,
245
- implementationType: account.implementationType,
246
- ownerAddress: account.ownerAddress,
247
- chainType: account.chainType,
248
- isActive: activeWalletId === account.id,
249
- isConnecting: status.status === "connecting" && status.address === account.address,
250
- getProvider: async () => {
251
- return await getEthereumProvider();
252
- },
253
- }))), [embeddedAccounts, activeWalletId, status.status === "connecting", client.embeddedWallet]);
271
+ const wallets = useMemo(() => {
272
+ // Deduplicate accounts based on account type
273
+ const deduplicatedAccounts = embeddedAccounts.reduce((acc, account) => {
274
+ if (walletConfig?.accountType === AccountTypeEnum.EOA) {
275
+ // For EOAs, deduplicate by address only (EOAs work across all chains)
276
+ if (!acc.some(a => a.address.toLowerCase() === account.address.toLowerCase())) {
277
+ acc.push(account);
278
+ }
279
+ }
280
+ else {
281
+ // For Smart Accounts, keep separate entries per chain (they're chain-specific)
282
+ // Only deduplicate exact matches (same address AND same chainId)
283
+ if (!acc.some(a => a.address.toLowerCase() === account.address.toLowerCase() && a.chainId === account.chainId)) {
284
+ acc.push(account);
285
+ }
286
+ }
287
+ return acc;
288
+ }, []);
289
+ return deduplicatedAccounts.map((account) => ({
290
+ address: account.address,
291
+ implementationType: account.implementationType,
292
+ ownerAddress: account.ownerAddress,
293
+ chainType: account.chainType,
294
+ isActive: activeWalletId === account.id,
295
+ isConnecting: status.status === "connecting" && status.address === account.address,
296
+ getProvider: async () => {
297
+ return await getEthereumProvider();
298
+ },
299
+ }));
300
+ }, [embeddedAccounts, activeWalletId, status.status, walletConfig?.accountType, client.embeddedWallet]);
254
301
  const create = useCallback(async (options) => {
255
302
  logger.info('Creating Ethereum wallet with options', options);
256
303
  try {
@@ -282,12 +329,9 @@ export function useWallets(hookOptions = {}) {
282
329
  };
283
330
  }
284
331
  else {
285
- if (!walletConfig?.getEncryptionSession) {
286
- throw new OpenfortError('Encryption session (walletConfig.getEncryptionSession) is required for automatic recovery', OpenfortErrorType.WALLET_ERROR);
287
- }
288
332
  recoveryParams = {
289
333
  recoveryMethod: RecoveryMethod.AUTOMATIC,
290
- encryptionSession: await walletConfig.getEncryptionSession()
334
+ encryptionSession: await resolveEncryptionSession()
291
335
  };
292
336
  }
293
337
  // Configure embedded wallet with shield authentication
@@ -337,7 +381,7 @@ export function useWallets(hookOptions = {}) {
337
381
  error
338
382
  });
339
383
  }
340
- }, [client, supportedChains, walletConfig, _internal, user]);
384
+ }, [client, supportedChains, walletConfig, resolveEncryptionSession, _internal, user]);
341
385
  const setRecovery = useCallback(async (params) => {
342
386
  try {
343
387
  setStatus({
package/dist/index.js CHANGED
@@ -10,7 +10,9 @@
10
10
  // Re-export commonly used types from @openfort/openfort-js
11
11
  export { OpenfortError, RecoveryMethod, Openfort as OpenfortClient, OpenfortConfiguration, ShieldConfiguration, EmbeddedState, } from '@openfort/openfort-js';
12
12
  // Re-export enums and values from @openfort/openfort-js
13
- export { OAuthProvider, } from '@openfort/openfort-js';
13
+ export { OAuthProvider, AccountTypeEnum, } from '@openfort/openfort-js';
14
+ // Re-export event listener functionality from @openfort/openfort-js
15
+ export { openfortEvents, OpenfortEvents, } from '@openfort/openfort-js';
14
16
  // Re-export all types from the main types module
15
17
  export * from './types';
16
18
  // Re-export all hooks
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Maps config keys to their corresponding .env variable names
3
+ */
4
+ const KEY_TO_ENV_VAR = {
5
+ publishableKey: 'OPENFORT_PUBLISHABLE_KEY',
6
+ shieldPublishableKey: 'OPENFORT_SHIELD_PUBLISHABLE_KEY',
7
+ };
8
+ /**
9
+ * Validates that required environment variables are present.
10
+ *
11
+ * @param config - Dictionary of configuration keys and their values
12
+ * @throws Error if required environment variables are missing
13
+ */
14
+ export function validateEnvironment(config) {
15
+ const missing = [];
16
+ Object.entries(config).forEach(([key, value]) => {
17
+ if (!value) {
18
+ const envVarName = KEY_TO_ENV_VAR[key] || key;
19
+ missing.push(envVarName);
20
+ }
21
+ });
22
+ if (missing.length > 0) {
23
+ throw new Error(`[Openfort SDK] Missing required .env variables: ${missing.join(', ')}`);
24
+ }
25
+ }
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { SDKOverrides, AccountTypeEnum, ThirdPartyAuthConfiguration } from '@openfort/openfort-js';
3
3
  /**
4
- * Shape for configuring custom authentication synchronisation behaviour.
4
+ * Shape for configuring custom authentication synchronization behavior.
5
5
  */
6
6
  interface CustomAuthConfig {
7
7
  enabled: boolean;
@@ -4,4 +4,3 @@
4
4
  * This module re-exports all wallet-related hooks for convenient importing.
5
5
  */
6
6
  export { useWallets } from './useWallets';
7
- export { useWallet } from './useWallet';
@@ -7,8 +7,9 @@
7
7
  * required to integrate Openfort authentication and embedded wallets into React Native and
8
8
  * Expo applications.
9
9
  */
10
- export { AuthPlayerResponse, OpenfortError, RecoveryMethod, Openfort as OpenfortClient, Provider, OpenfortConfiguration, ShieldConfiguration, EmbeddedState, } from '@openfort/openfort-js';
11
- export { OAuthProvider, } from '@openfort/openfort-js';
10
+ export { AuthPlayerResponse, OpenfortError, RecoveryMethod, RecoveryParams, Openfort as OpenfortClient, Provider, OpenfortConfiguration, ShieldConfiguration, EmbeddedState, AuthResponse, EmbeddedAccount, SignedMessagePayload, AuthInitPayload, } from '@openfort/openfort-js';
11
+ export { OAuthProvider, AccountTypeEnum, } from '@openfort/openfort-js';
12
+ export { openfortEvents, OpenfortEventMap, OpenfortEvents, } from '@openfort/openfort-js';
12
13
  export * from './types';
13
14
  export * from './hooks';
14
15
  export * from './components';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Validates that required environment variables are present.
3
+ *
4
+ * @param config - Dictionary of configuration keys and their values
5
+ * @throws Error if required environment variables are missing
6
+ */
7
+ export declare function validateEnvironment(config: Record<string, string | undefined>): void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openfort/react-native",
3
3
  "main": "dist/index.js",
4
- "version": "0.1.11",
4
+ "version": "0.1.12",
5
5
  "license": "MIT",
6
6
  "description": "React Native SDK for Openfort platform integration",
7
7
  "scripts": {
@@ -23,7 +23,7 @@
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "@openfort/openfort-js": "^0.10.24"
26
+ "@openfort/openfort-js": "^0.10.27"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "expo-apple-authentication": "*",
@@ -1,32 +0,0 @@
1
- import { useWallets } from "./useWallets";
2
- /**
3
- * Hook for accessing the currently active embedded wallet
4
- *
5
- * This hook provides access to the currently active embedded wallet from the wallet collection.
6
- * It automatically updates when the active wallet changes through other wallet operations.
7
- *
8
- * @returns The active embedded wallet when available, otherwise `null`
9
- *
10
- * @example
11
- * ```tsx
12
- * const activeWallet = useWallet();
13
- *
14
- * // Check if wallet is available
15
- * if (activeWallet) {
16
- * console.log('Active wallet address:', activeWallet.address);
17
- *
18
- * // Get provider for transactions
19
- * const provider = await activeWallet.getProvider();
20
- *
21
- * // Use wallet for operations
22
- * console.log('Wallet chain type:', activeWallet.chainType);
23
- * console.log('Is connecting:', activeWallet.isConnecting);
24
- * } else {
25
- * console.log('No active wallet available');
26
- * }
27
- * ```
28
- */
29
- export function useWallet() {
30
- const { activeWallet } = useWallets();
31
- return activeWallet;
32
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * Hook for accessing the currently active embedded wallet
3
- *
4
- * This hook provides access to the currently active embedded wallet from the wallet collection.
5
- * It automatically updates when the active wallet changes through other wallet operations.
6
- *
7
- * @returns The active embedded wallet when available, otherwise `null`
8
- *
9
- * @example
10
- * ```tsx
11
- * const activeWallet = useWallet();
12
- *
13
- * // Check if wallet is available
14
- * if (activeWallet) {
15
- * console.log('Active wallet address:', activeWallet.address);
16
- *
17
- * // Get provider for transactions
18
- * const provider = await activeWallet.getProvider();
19
- *
20
- * // Use wallet for operations
21
- * console.log('Wallet chain type:', activeWallet.chainType);
22
- * console.log('Is connecting:', activeWallet.isConnecting);
23
- * } else {
24
- * console.log('No active wallet available');
25
- * }
26
- * ```
27
- */
28
- export declare function useWallet(): import("../..").UserWallet | null;