@reown/appkit-react-native 0.0.0-chore-added-import-20251002170458 → 0.0.0-chore-qr-borders-20251104183806

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.
Files changed (114) hide show
  1. package/lib/commonjs/AppKit.js +32 -17
  2. package/lib/commonjs/AppKit.js.map +1 -1
  3. package/lib/commonjs/hooks/useAccount.js +65 -2
  4. package/lib/commonjs/hooks/useAccount.js.map +1 -1
  5. package/lib/commonjs/hooks/useProvider.js +20 -8
  6. package/lib/commonjs/hooks/useProvider.js.map +1 -1
  7. package/lib/commonjs/modal/w3m-modal/styles.js +2 -1
  8. package/lib/commonjs/modal/w3m-modal/styles.js.map +1 -1
  9. package/lib/commonjs/partials/w3m-all-wallets-list/components/WalletList.js +62 -25
  10. package/lib/commonjs/partials/w3m-all-wallets-list/components/WalletList.js.map +1 -1
  11. package/lib/commonjs/partials/w3m-all-wallets-list/index.js +1 -1
  12. package/lib/commonjs/partials/w3m-all-wallets-list/index.js.map +1 -1
  13. package/lib/commonjs/partials/w3m-all-wallets-search/index.js +1 -0
  14. package/lib/commonjs/partials/w3m-all-wallets-search/index.js.map +1 -1
  15. package/lib/commonjs/partials/w3m-connecting-qrcode/index.js +5 -1
  16. package/lib/commonjs/partials/w3m-connecting-qrcode/index.js.map +1 -1
  17. package/lib/commonjs/partials/w3m-send-input-token/index.js +6 -3
  18. package/lib/commonjs/partials/w3m-send-input-token/index.js.map +1 -1
  19. package/lib/commonjs/partials/w3m-swap-input/index.js +6 -3
  20. package/lib/commonjs/partials/w3m-swap-input/index.js.map +1 -1
  21. package/lib/commonjs/views/w3m-account-default-view/index.js +2 -1
  22. package/lib/commonjs/views/w3m-account-default-view/index.js.map +1 -1
  23. package/lib/commonjs/views/w3m-connect-view/components/all-wallet-list.js +28 -3
  24. package/lib/commonjs/views/w3m-connect-view/components/all-wallet-list.js.map +1 -1
  25. package/lib/commonjs/views/w3m-onramp-settings-view/index.js +2 -0
  26. package/lib/commonjs/views/w3m-onramp-settings-view/index.js.map +1 -1
  27. package/lib/commonjs/views/w3m-onramp-view/index.js +3 -2
  28. package/lib/commonjs/views/w3m-onramp-view/index.js.map +1 -1
  29. package/lib/commonjs/views/w3m-swap-view/components/select-token-modal/index.js +2 -1
  30. package/lib/commonjs/views/w3m-swap-view/components/select-token-modal/index.js.map +1 -1
  31. package/lib/commonjs/views/w3m-swap-view/index.js +4 -2
  32. package/lib/commonjs/views/w3m-swap-view/index.js.map +1 -1
  33. package/lib/commonjs/views/w3m-wallet-send-view/index.js +2 -1
  34. package/lib/commonjs/views/w3m-wallet-send-view/index.js.map +1 -1
  35. package/lib/module/AppKit.js +32 -17
  36. package/lib/module/AppKit.js.map +1 -1
  37. package/lib/module/hooks/useAccount.js +66 -2
  38. package/lib/module/hooks/useAccount.js.map +1 -1
  39. package/lib/module/hooks/useProvider.js +21 -9
  40. package/lib/module/hooks/useProvider.js.map +1 -1
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/modal/w3m-modal/styles.js +2 -1
  43. package/lib/module/modal/w3m-modal/styles.js.map +1 -1
  44. package/lib/module/partials/w3m-all-wallets-list/components/WalletList.js +63 -26
  45. package/lib/module/partials/w3m-all-wallets-list/components/WalletList.js.map +1 -1
  46. package/lib/module/partials/w3m-all-wallets-list/index.js +1 -1
  47. package/lib/module/partials/w3m-all-wallets-list/index.js.map +1 -1
  48. package/lib/module/partials/w3m-all-wallets-search/index.js +1 -0
  49. package/lib/module/partials/w3m-all-wallets-search/index.js.map +1 -1
  50. package/lib/module/partials/w3m-connecting-qrcode/index.js +5 -1
  51. package/lib/module/partials/w3m-connecting-qrcode/index.js.map +1 -1
  52. package/lib/module/partials/w3m-send-input-token/index.js +6 -3
  53. package/lib/module/partials/w3m-send-input-token/index.js.map +1 -1
  54. package/lib/module/partials/w3m-swap-input/index.js +6 -3
  55. package/lib/module/partials/w3m-swap-input/index.js.map +1 -1
  56. package/lib/module/views/w3m-account-default-view/index.js +2 -1
  57. package/lib/module/views/w3m-account-default-view/index.js.map +1 -1
  58. package/lib/module/views/w3m-connect-view/components/all-wallet-list.js +29 -4
  59. package/lib/module/views/w3m-connect-view/components/all-wallet-list.js.map +1 -1
  60. package/lib/module/views/w3m-onramp-settings-view/index.js +2 -0
  61. package/lib/module/views/w3m-onramp-settings-view/index.js.map +1 -1
  62. package/lib/module/views/w3m-onramp-view/index.js +3 -2
  63. package/lib/module/views/w3m-onramp-view/index.js.map +1 -1
  64. package/lib/module/views/w3m-swap-view/components/select-token-modal/index.js +2 -1
  65. package/lib/module/views/w3m-swap-view/components/select-token-modal/index.js.map +1 -1
  66. package/lib/module/views/w3m-swap-view/index.js +4 -2
  67. package/lib/module/views/w3m-swap-view/index.js.map +1 -1
  68. package/lib/module/views/w3m-wallet-send-view/index.js +2 -1
  69. package/lib/module/views/w3m-wallet-send-view/index.js.map +1 -1
  70. package/lib/typescript/AppKit.d.ts +10 -3
  71. package/lib/typescript/AppKit.d.ts.map +1 -1
  72. package/lib/typescript/AppKitContext.d.ts +1 -1
  73. package/lib/typescript/hooks/useAccount.d.ts +57 -33
  74. package/lib/typescript/hooks/useAccount.d.ts.map +1 -1
  75. package/lib/typescript/hooks/useAppKitEvents.d.ts +13 -0
  76. package/lib/typescript/hooks/useAppKitEvents.d.ts.map +1 -1
  77. package/lib/typescript/hooks/useProvider.d.ts.map +1 -1
  78. package/lib/typescript/index.d.ts +1 -1
  79. package/lib/typescript/index.d.ts.map +1 -1
  80. package/lib/typescript/modal/w3m-modal/styles.d.ts +1 -0
  81. package/lib/typescript/modal/w3m-modal/styles.d.ts.map +1 -1
  82. package/lib/typescript/partials/w3m-all-wallets-list/components/WalletList.d.ts +2 -1
  83. package/lib/typescript/partials/w3m-all-wallets-list/components/WalletList.d.ts.map +1 -1
  84. package/lib/typescript/partials/w3m-all-wallets-search/index.d.ts.map +1 -1
  85. package/lib/typescript/partials/w3m-connecting-qrcode/index.d.ts.map +1 -1
  86. package/lib/typescript/partials/w3m-send-input-token/index.d.ts +2 -1
  87. package/lib/typescript/partials/w3m-send-input-token/index.d.ts.map +1 -1
  88. package/lib/typescript/partials/w3m-swap-input/index.d.ts +2 -1
  89. package/lib/typescript/partials/w3m-swap-input/index.d.ts.map +1 -1
  90. package/lib/typescript/views/w3m-account-default-view/index.d.ts.map +1 -1
  91. package/lib/typescript/views/w3m-connect-view/components/all-wallet-list.d.ts.map +1 -1
  92. package/lib/typescript/views/w3m-onramp-settings-view/index.d.ts.map +1 -1
  93. package/lib/typescript/views/w3m-onramp-view/index.d.ts.map +1 -1
  94. package/lib/typescript/views/w3m-swap-view/index.d.ts.map +1 -1
  95. package/lib/typescript/views/w3m-wallet-send-view/index.d.ts.map +1 -1
  96. package/package.json +4 -4
  97. package/src/AppKit.ts +40 -18
  98. package/src/hooks/useAccount.ts +89 -3
  99. package/src/hooks/useProvider.ts +15 -6
  100. package/src/index.ts +1 -1
  101. package/src/modal/w3m-modal/styles.ts +2 -1
  102. package/src/partials/w3m-all-wallets-list/components/WalletList.tsx +73 -25
  103. package/src/partials/w3m-all-wallets-list/index.tsx +1 -1
  104. package/src/partials/w3m-all-wallets-search/index.tsx +8 -1
  105. package/src/partials/w3m-connecting-qrcode/index.tsx +10 -1
  106. package/src/partials/w3m-send-input-token/index.tsx +5 -1
  107. package/src/partials/w3m-swap-input/index.tsx +5 -1
  108. package/src/views/w3m-account-default-view/index.tsx +6 -1
  109. package/src/views/w3m-connect-view/components/all-wallet-list.tsx +40 -11
  110. package/src/views/w3m-onramp-settings-view/index.tsx +7 -1
  111. package/src/views/w3m-onramp-view/index.tsx +3 -2
  112. package/src/views/w3m-swap-view/components/select-token-modal/index.tsx +1 -1
  113. package/src/views/w3m-swap-view/index.tsx +2 -0
  114. package/src/views/w3m-wallet-send-view/index.tsx +1 -0
package/src/AppKit.ts CHANGED
@@ -214,6 +214,7 @@ export class AppKit {
214
214
  SendController.resetState();
215
215
  OnRampController.resetState();
216
216
  WcController.resetState();
217
+ EventsController.resetState();
217
218
 
218
219
  if (ConnectionsController.state.activeNamespace === undefined) {
219
220
  ConnectionsController.setActiveNamespace(
@@ -244,7 +245,7 @@ export class AppKit {
244
245
  /**
245
246
  * Returns the provider for a given namespace.
246
247
  * @param namespace - The namespace to get the provider for.
247
- * @returns The provider for the given namespace.
248
+ * @returns The provider for the given namespace, or null if not available or not yet initialized.
248
249
  */
249
250
  getProvider<T extends Provider>(namespace?: string): T | null {
250
251
  const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace;
@@ -255,36 +256,64 @@ export class AppKit {
255
256
  );
256
257
  if (!connection || !connection.adapter || !connection.adapter.connector) return null;
257
258
 
258
- return connection.adapter.connector.getProvider() as T;
259
+ try {
260
+ return connection.adapter.connector.getProvider() as T | null;
261
+ } catch (error) {
262
+ // Provider not initialized yet during session restoration
263
+ // This can happen on app restart when restoring a previous connection
264
+ LogController.sendError(error, 'AppKit.ts', 'getProvider');
265
+
266
+ return null;
267
+ }
259
268
  }
260
269
 
261
270
  getNetworks() {
262
271
  return this.networks;
263
272
  }
264
273
 
265
- async switchNetwork(network: AppKitNetwork): Promise<void> {
274
+ /**
275
+ * Switches to a different network.
276
+ * @param network - Either an AppKitNetwork object or a CAIP network ID string (e.g., 'eip155:1')
277
+ * @throws {Error} When the network is not found in configured networks
278
+ * @throws {Error} When no active adapter is available (only when connected)
279
+ * @returns Promise that resolves when the network switch is complete
280
+ */
281
+ async switchNetwork(network: AppKitNetwork | CaipNetworkId): Promise<void> {
266
282
  const { isConnected } = ConnectionsController.state;
267
283
 
284
+ const appKitNetwork =
285
+ typeof network === 'string' ? this.networks.find(n => n.caipNetworkId === network) : network;
286
+
287
+ if (!appKitNetwork) {
288
+ const error = new Error(`Network not found: ${network}`);
289
+ LogController.sendError(`Network not found: ${network}`, 'AppKit.ts', 'switchNetwork');
290
+
291
+ throw error;
292
+ }
293
+
268
294
  if (!isConnected) {
269
- OptionsController.setDefaultNetwork(network);
295
+ OptionsController.setDefaultNetwork(appKitNetwork);
270
296
 
271
- return Promise.resolve();
297
+ return;
272
298
  }
273
299
 
274
- const adapter = this.getAdapterByNamespace(network.chainNamespace);
300
+ const adapter = this.getAdapterByNamespace(appKitNetwork.chainNamespace);
275
301
  if (!adapter) throw new Error('No active adapter');
276
302
 
277
- await adapter.switchNetwork(network);
303
+ await adapter.switchNetwork(appKitNetwork);
278
304
 
279
305
  EventsController.sendEvent({
280
306
  type: 'track',
281
307
  event: 'SWITCH_NETWORK',
282
308
  properties: {
283
- network: network.caipNetworkId
309
+ network: appKitNetwork.caipNetworkId
284
310
  }
285
311
  });
286
312
 
287
- ConnectionsController.setActiveNetwork(network.chainNamespace, network.caipNetworkId);
313
+ ConnectionsController.setActiveNetwork(
314
+ appKitNetwork.chainNamespace,
315
+ appKitNetwork.caipNetworkId
316
+ );
288
317
  }
289
318
 
290
319
  open(options?: AppKitOpenOptions) {
@@ -308,6 +337,7 @@ export class AppKit {
308
337
 
309
338
  RouterUtil.checkOnRampBack();
310
339
  RouterUtil.checkSocialLoginBack();
340
+ EventsController.sendWalletImpressions();
311
341
  }
312
342
 
313
343
  back() {
@@ -634,12 +664,6 @@ export class AppKit {
634
664
  const namespace = adapter.getSupportedNamespace();
635
665
  const chain = `${namespace}:${chainId}` as CaipNetworkId;
636
666
 
637
- const activeNetwork = ConnectionsController.getActiveNetworkId(namespace);
638
- if (activeNetwork === chain) {
639
- // No need to update the active network
640
- return;
641
- }
642
-
643
667
  ConnectionsController.setActiveNetwork(namespace, chain);
644
668
 
645
669
  const connection = ConnectionsController.state.connections.get(namespace);
@@ -692,10 +716,8 @@ export class AppKit {
692
716
  this.setCustomWallets(options);
693
717
  OptionsController.setFeaturedWalletIds(options.featuredWalletIds);
694
718
  OptionsController.setEnableAnalytics(options.enableAnalytics);
695
- OptionsController.setDebug(options.debug && __DEV__);
719
+ OptionsController.setDebug(options.debug);
696
720
 
697
- // Initialize LogController after debug option is set
698
- LogController.initialize();
699
721
  LogController.sendInfo('AppKit initialization started', 'AppKit.ts', 'initControllers', {
700
722
  projectId: options.projectId,
701
723
  adapters: this.adapters.map(a => a.constructor.name),
@@ -1,9 +1,68 @@
1
1
  /* eslint-disable valtio/state-snapshot-rule */
2
+ import {
3
+ ConnectionsController,
4
+ CoreHelperUtil,
5
+ LogController
6
+ } from '@reown/appkit-core-react-native';
2
7
  import { useMemo } from 'react';
3
8
  import { useSnapshot } from 'valtio';
4
- import { ConnectionsController, CoreHelperUtil } from '@reown/appkit-core-react-native';
5
9
  import { useAppKit } from './useAppKit';
10
+ import type { AccountType, AppKitNetwork } from '@reown/appkit-common-react-native';
6
11
 
12
+ /**
13
+ * Represents a blockchain account with its associated metadata
14
+ */
15
+ export interface Account {
16
+ /** The blockchain address of the account */
17
+ address: string;
18
+ /** The blockchain namespace (e.g., 'eip155' for Ethereum, 'solana' for Solana) */
19
+ namespace: string;
20
+ /** The chain ID where this account is active */
21
+ chainId: string;
22
+ /** Optional account type (e.g. 'eoa' or 'smartAccount') */
23
+ type?: AccountType;
24
+ }
25
+
26
+ /**
27
+ * Hook to access the current account state and connection information
28
+ *
29
+ * @remarks
30
+ * This hook provides access to all connected accounts, the currently active account,
31
+ * connection status, and active chain information. It automatically subscribes to
32
+ * connection state changes via valtio.
33
+ *
34
+ * The hook parses account data from CAIP-10 format (namespace:chainId:address)
35
+ * and provides a normalized structure.
36
+ *
37
+ * @returns An object containing:
38
+ * - `allAccounts` - Array of all connected accounts across all connections
39
+ * - `address` - The plain address of the currently active account (without namespace or chain prefix)
40
+ * - `isConnected` - Boolean indicating if a wallet is currently connected
41
+ * - `chainId` - The ID of the currently active chain
42
+ * - `chain` - The full chain/network object of the currently active chain
43
+ * - `namespace` - The namespace of the currently active account (e.g. 'eip155', 'solana' or 'bip122')
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * function MyComponent() {
48
+ * const { address, isConnected, chainId, allAccounts } = useAccount();
49
+ *
50
+ * if (!isConnected) {
51
+ * return <Text>Not connected</Text>;
52
+ * }
53
+ *
54
+ * return (
55
+ * <View>
56
+ * <Text>Connected: {address}</Text>
57
+ * <Text>Chain: {chainId}</Text>
58
+ * <Text>Total accounts: {allAccounts.length}</Text>
59
+ * </View>
60
+ * );
61
+ * }
62
+ * ```
63
+ *
64
+ * @throws Will log errors via LogController if account parsing fails
65
+ */
7
66
  export function useAccount() {
8
67
  useAppKit(); // Use the hook for checks
9
68
 
@@ -11,9 +70,35 @@ export function useAccount() {
11
70
  activeAddress: address,
12
71
  activeNamespace,
13
72
  connection,
73
+ connections,
14
74
  networks
15
75
  } = useSnapshot(ConnectionsController.state);
16
76
 
77
+ const allAccounts: Account[] = useMemo(() => {
78
+ return Array.from(connections.values()).flatMap(
79
+ _connection =>
80
+ _connection.accounts
81
+ .map(account => {
82
+ const [namespace, chainId, plainAddress] = account.split(':');
83
+ if (!plainAddress || !namespace || !chainId) {
84
+ LogController.sendError('Invalid account', 'useAccount.ts', 'useAccount', {
85
+ account
86
+ });
87
+
88
+ return undefined;
89
+ }
90
+
91
+ return {
92
+ address: plainAddress,
93
+ namespace,
94
+ chainId,
95
+ type: _connection.type
96
+ };
97
+ })
98
+ .filter(account => account !== undefined) as Account[]
99
+ );
100
+ }, [connections]);
101
+
17
102
  const activeChain = useMemo(
18
103
  () =>
19
104
  connection?.caipNetwork
@@ -23,10 +108,11 @@ export function useAccount() {
23
108
  );
24
109
 
25
110
  return {
111
+ allAccounts,
26
112
  address: CoreHelperUtil.getPlainAddress(address),
27
113
  isConnected: !!address,
28
- chainId: activeChain?.id,
29
- chain: activeChain,
114
+ chainId: activeChain?.id !== undefined ? String(activeChain.id) : undefined,
115
+ chain: activeChain as AppKitNetwork | undefined,
30
116
  namespace: activeNamespace
31
117
  };
32
118
  }
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable valtio/state-snapshot-rule */
2
2
  import { useMemo } from 'react';
3
3
  import { useSnapshot } from 'valtio';
4
- import { ConnectionsController } from '@reown/appkit-core-react-native';
4
+ import { ConnectionsController, LogController } from '@reown/appkit-core-react-native';
5
5
  import type { Provider, ChainNamespace } from '@reown/appkit-common-react-native';
6
6
 
7
7
  /**
@@ -40,12 +40,21 @@ export function useProvider(): ProviderResult {
40
40
  const { connection } = useSnapshot(ConnectionsController.state);
41
41
 
42
42
  const returnValue = useMemo(() => {
43
- if (!connection) return { provider: undefined, providerType: undefined };
43
+ if (!connection || !connection.adapter) {
44
+ return { provider: undefined, providerType: undefined };
45
+ }
44
46
 
45
- return {
46
- provider: connection.adapter.getProvider(),
47
- providerType: connection.adapter.getSupportedNamespace()
48
- };
47
+ try {
48
+ return {
49
+ provider: connection.adapter.getProvider(),
50
+ providerType: connection.adapter.getSupportedNamespace()
51
+ };
52
+ } catch (error) {
53
+ LogController.sendError(error, 'useProvider', 'useProvider');
54
+
55
+ // Provider not initialized yet during session restoration
56
+ return { provider: undefined, providerType: undefined };
57
+ }
49
58
  }, [connection]);
50
59
 
51
60
  return returnValue;
package/src/index.ts CHANGED
@@ -31,7 +31,7 @@ export type { AppKitConfig } from './types';
31
31
  /****** Hooks *******/
32
32
  export { useAppKit } from './hooks/useAppKit';
33
33
  export { useProvider } from './hooks/useProvider';
34
- export { useAccount } from './hooks/useAccount';
34
+ export { useAccount, type Account as UseAccountReturn } from './hooks/useAccount';
35
35
  export { useWalletInfo } from './hooks/useWalletInfo';
36
36
  export { useAppKitEvents, useAppKitEventSubscription } from './hooks/useAppKitEvents';
37
37
  export { useAppKitState } from './hooks/useAppKitState';
@@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native';
3
3
  export default StyleSheet.create({
4
4
  card: {
5
5
  borderBottomLeftRadius: 0,
6
- borderBottomRightRadius: 0
6
+ borderBottomRightRadius: 0,
7
+ borderWidth: 0
7
8
  }
8
9
  });
@@ -1,4 +1,4 @@
1
- import { FlatList, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
1
+ import { FlatList, StyleSheet, type StyleProp, type ViewStyle, type ViewToken } from 'react-native';
2
2
  import { WalletItem } from './WalletItem';
3
3
  import {
4
4
  CardSelectHeight,
@@ -7,8 +7,9 @@ import {
7
7
  CardSelectLoader,
8
8
  CardSelectWidth
9
9
  } from '@reown/appkit-ui-react-native';
10
- import { ApiController } from '@reown/appkit-core-react-native';
10
+ import { ApiController, EventsController } from '@reown/appkit-core-react-native';
11
11
  import type { WcWallet } from '@reown/appkit-common-react-native';
12
+ import { useCallback, useRef } from 'react';
12
13
 
13
14
  const imageHeaders = ApiController._getApiHeaders();
14
15
 
@@ -25,6 +26,7 @@ interface Props {
25
26
  loadingItems?: number;
26
27
  style?: StyleProp<ViewStyle>;
27
28
  testIDKey?: string;
29
+ searchQuery?: string;
28
30
  }
29
31
 
30
32
  export function WalletList({
@@ -35,15 +37,77 @@ export function WalletList({
35
37
  isLoading = false,
36
38
  loadingItems = 20,
37
39
  testIDKey,
38
- style
40
+ style,
41
+ searchQuery
39
42
  }: Props) {
40
43
  const { padding, maxHeight } = useCustomDimensions();
44
+ const viewedWalletsRef = useRef<Set<string>>(new Set());
41
45
 
42
46
  // Create loading data if isLoading is true
43
47
  const displayData = isLoading
44
48
  ? Array.from({ length: loadingItems }, (_, index) => ({ id: `loading-${index}` }) as WcWallet)
45
49
  : data;
46
50
 
51
+ const keyExtractor = useCallback(
52
+ (item: WcWallet, index: number) => item?.id ?? `item-${index}`,
53
+ []
54
+ );
55
+
56
+ const getItemLayout = useCallback((_: any, index: number) => {
57
+ return {
58
+ length: ITEM_HEIGHT_WITH_GAP,
59
+ offset: ITEM_HEIGHT_WITH_GAP * index,
60
+ index
61
+ };
62
+ }, []);
63
+
64
+ const renderItem = useCallback(
65
+ ({ item, index }: { item: WcWallet; index: number }) => {
66
+ if (isLoading) {
67
+ return <CardSelectLoader style={styles.itemContainer} />;
68
+ }
69
+
70
+ return (
71
+ <WalletItem
72
+ item={item}
73
+ imageHeaders={imageHeaders}
74
+ displayIndex={index}
75
+ onItemPress={onItemPress}
76
+ style={styles.itemContainer}
77
+ testID={testIDKey ? `${testIDKey}-${item?.id}` : undefined}
78
+ />
79
+ );
80
+ },
81
+ [isLoading, onItemPress, testIDKey]
82
+ );
83
+
84
+ const onViewableItemsChanged = useCallback(
85
+ ({ viewableItems }: { viewableItems: ViewToken[] }) => {
86
+ if (isLoading) return;
87
+
88
+ viewableItems.forEach(({ item }, index) => {
89
+ const wallet = item as WcWallet;
90
+ if (wallet?.id && !viewedWalletsRef.current.has(wallet.id)) {
91
+ viewedWalletsRef.current.add(wallet.id);
92
+ const isInstalled = !!ApiController.state.installed.find(w => w?.id === item?.id);
93
+ EventsController.trackWalletImpression({
94
+ wallet,
95
+ view: 'AllWallets',
96
+ displayIndex: index,
97
+ query: searchQuery,
98
+ installed: isInstalled
99
+ });
100
+ }
101
+ });
102
+ },
103
+ [isLoading, searchQuery]
104
+ );
105
+
106
+ const viewabilityConfig = useRef({
107
+ itemVisiblePercentThreshold: 50, // Item is considered visible when 50% is visible
108
+ minimumViewTime: 100 // Must be visible for at least 100ms
109
+ }).current;
110
+
47
111
  return (
48
112
  <FlatList
49
113
  fadingEdgeLength={20}
@@ -52,34 +116,18 @@ export function WalletList({
52
116
  data={displayData}
53
117
  style={[styles.list, { height: maxHeight }, style]}
54
118
  columnWrapperStyle={styles.columnWrapperStyle}
55
- renderItem={({ item, index }) => {
56
- if (isLoading) {
57
- return <CardSelectLoader style={styles.itemContainer} />;
58
- }
59
-
60
- return (
61
- <WalletItem
62
- item={item}
63
- imageHeaders={imageHeaders}
64
- displayIndex={index}
65
- onItemPress={onItemPress}
66
- style={styles.itemContainer}
67
- testID={testIDKey ? `${testIDKey}-${item?.id}` : undefined}
68
- />
69
- );
70
- }}
119
+ renderItem={renderItem}
71
120
  contentContainerStyle={[styles.contentContainer, { paddingHorizontal: padding }]}
72
121
  initialNumToRender={32}
73
122
  maxToRenderPerBatch={12}
74
123
  windowSize={10}
75
124
  onEndReached={onEndReached}
76
125
  onEndReachedThreshold={onEndReachedThreshold}
77
- keyExtractor={(item, index) => item?.id ?? `item-${index}`}
78
- getItemLayout={(_, index) => ({
79
- length: ITEM_HEIGHT_WITH_GAP,
80
- offset: ITEM_HEIGHT_WITH_GAP * index,
81
- index
82
- })}
126
+ keyExtractor={keyExtractor}
127
+ removeClippedSubviews={true}
128
+ getItemLayout={getItemLayout}
129
+ onViewableItemsChanged={onViewableItemsChanged}
130
+ viewabilityConfig={viewabilityConfig}
83
131
  />
84
132
  );
85
133
  }
@@ -116,7 +116,7 @@ export function AllWalletsList({ onItemPress }: AllWalletsListProps) {
116
116
  <WalletList
117
117
  data={walletList}
118
118
  onEndReached={fetchNextPage}
119
- onEndReachedThreshold={2}
119
+ onEndReachedThreshold={0.5}
120
120
  onItemPress={onItemPress}
121
121
  />
122
122
  );
@@ -84,5 +84,12 @@ export function AllWalletsSearch({ searchQuery, onItemPress }: AllWalletsSearchP
84
84
  );
85
85
  }
86
86
 
87
- return <WalletList onItemPress={onItemPress} data={results} testIDKey="wallet-search-item" />;
87
+ return (
88
+ <WalletList
89
+ onItemPress={onItemPress}
90
+ searchQuery={searchQuery}
91
+ data={results}
92
+ testIDKey="wallet-search-item"
93
+ />
94
+ );
88
95
  }
@@ -20,6 +20,9 @@ import styles from './styles';
20
20
  import { ReownButton } from './components/ReownButton';
21
21
  import { useWindowDimensions } from 'react-native';
22
22
 
23
+ const LOGO_SIZE = 60;
24
+ const LOGO_BORDER_RADIUS = 20;
25
+
23
26
  export function ConnectingQrCode() {
24
27
  const { height, width } = useWindowDimensions();
25
28
  const windowSize = Math.min(height, width);
@@ -65,7 +68,13 @@ export function ConnectingQrCode() {
65
68
  flexDirection={isPortrait ? 'column' : 'row'}
66
69
  padding={['xl', 'xl', 'xs', 'xl']}
67
70
  >
68
- <QrCode size={qrSize} uri={wcUri} testID="qr-code" />
71
+ <QrCode
72
+ size={qrSize}
73
+ uri={wcUri}
74
+ testID="qr-code"
75
+ logoBorderRadius={LOGO_BORDER_RADIUS}
76
+ logoSize={LOGO_SIZE}
77
+ />
69
78
  <FlexView alignItems="center" margin="m">
70
79
  <Text variant="paragraph-500">Scan this QR code with your phone</Text>
71
80
  {showCopy ? (
@@ -20,6 +20,7 @@ export interface SendInputTokenProps {
20
20
  style?: StyleProp<ViewStyle>;
21
21
  onTokenPress?: () => void;
22
22
  loading?: boolean;
23
+ testID?: string;
23
24
  }
24
25
 
25
26
  export function SendInputToken({
@@ -27,7 +28,8 @@ export function SendInputToken({
27
28
  sendTokenAmount,
28
29
  style,
29
30
  onTokenPress,
30
- loading
31
+ loading,
32
+ testID
31
33
  }: SendInputTokenProps) {
32
34
  const Theme = useTheme();
33
35
  const valueInputRef = useRef<TextInput | null>(null);
@@ -86,12 +88,14 @@ export function SendInputToken({
86
88
  selectTextOnFocus={false}
87
89
  numberOfLines={1}
88
90
  autoFocus={!!token}
91
+ testID={testID}
89
92
  />
90
93
  <TokenButton
91
94
  imageUrl={token?.iconUrl}
92
95
  text={token?.symbol}
93
96
  onPress={onTokenPress}
94
97
  chevron
98
+ testID={testID ? `${testID}-button` : undefined}
95
99
  />
96
100
  </FlexView>
97
101
  {token ? (
@@ -28,6 +28,7 @@ export interface SwapInputProps {
28
28
  marketValue?: number;
29
29
  editable?: boolean;
30
30
  autoFocus?: boolean;
31
+ testID?: string;
31
32
  }
32
33
 
33
34
  const MINIMUM_USD_VALUE_TO_CONVERT = 0.00005;
@@ -43,7 +44,8 @@ export function SwapInput({
43
44
  onChange,
44
45
  marketValue,
45
46
  editable,
46
- autoFocus
47
+ autoFocus,
48
+ testID
47
49
  }: SwapInputProps) {
48
50
  const Theme = useTheme();
49
51
  const valueInputRef = useRef<TextInput | null>(null);
@@ -118,6 +120,7 @@ export function SwapInput({
118
120
  numberOfLines={1}
119
121
  editable={editable}
120
122
  autoFocus={autoFocus}
123
+ testID={testID}
121
124
  />
122
125
  )}
123
126
  <TokenButton
@@ -125,6 +128,7 @@ export function SwapInput({
125
128
  imageUrl={token?.logoUri}
126
129
  onPress={onTokenPress}
127
130
  chevron
131
+ testID={testID ? `${testID}-button` : undefined}
128
132
  />
129
133
  </FlexView>
130
134
  {loadingValues ? (
@@ -186,7 +186,12 @@ export function AccountDefaultView() {
186
186
  return (
187
187
  <>
188
188
  {showBack ? (
189
- <IconLink icon="chevronLeft" style={styles.backIcon} onPress={RouterController.goBack} />
189
+ <IconLink
190
+ icon="chevronLeft"
191
+ style={styles.backIcon}
192
+ onPress={RouterController.goBack}
193
+ testID="header-back"
194
+ />
190
195
  ) : null}
191
196
  <IconLink
192
197
  icon="close"
@@ -4,6 +4,7 @@ import {
4
4
  ApiController,
5
5
  AssetController,
6
6
  AssetUtil,
7
+ EventsController,
7
8
  OptionsController,
8
9
  WcController,
9
10
  type WcControllerState
@@ -11,6 +12,7 @@ import {
11
12
  import { type WcWallet } from '@reown/appkit-common-react-native';
12
13
  import { ListItemLoader, ListWallet } from '@reown/appkit-ui-react-native';
13
14
  import { UiUtil } from '../../../utils/UiUtil';
15
+ import { useEffect, useMemo, useRef } from 'react';
14
16
 
15
17
  interface Props {
16
18
  itemStyle: StyleProp<ViewStyle>;
@@ -24,18 +26,45 @@ export function AllWalletList({ itemStyle, onWalletPress }: Props) {
24
26
  const { walletImages } = useSnapshot(AssetController.state);
25
27
  const imageHeaders = ApiController._getApiHeaders();
26
28
 
27
- const combinedWallets = [
28
- ...(recentWallets?.slice(0, 1) ?? []),
29
- ...installed,
30
- ...featured,
31
- ...recommended,
32
- ...(customWallets ?? [])
33
- ];
29
+ // Track which wallets have been tracked to prevent duplicates
30
+ const trackedWalletsRef = useRef<Set<string>>(new Set());
34
31
 
35
- // Deduplicate by wallet ID
36
- const list = Array.from(
37
- new Map(combinedWallets.map(wallet => [wallet.id, wallet])).values()
38
- ).slice(0, UiUtil.TOTAL_VISIBLE_WALLETS);
32
+ const list = useMemo(() => {
33
+ const combinedWallets = [
34
+ ...(recentWallets?.slice(0, 1) ?? []),
35
+ ...installed,
36
+ ...featured,
37
+ ...recommended,
38
+ ...(customWallets ?? [])
39
+ ];
40
+
41
+ // Deduplicate by wallet ID
42
+ return Array.from(new Map(combinedWallets.map(wallet => [wallet.id, wallet])).values()).slice(
43
+ 0,
44
+ UiUtil.TOTAL_VISIBLE_WALLETS
45
+ );
46
+ }, [recentWallets, installed, featured, recommended, customWallets]);
47
+
48
+ // Track impressions once when the list stabilizes
49
+ useEffect(() => {
50
+ if (!prefetchLoading && list.length > 0) {
51
+ list.forEach((wallet, index) => {
52
+ if (!trackedWalletsRef.current.has(wallet.id)) {
53
+ trackedWalletsRef.current.add(wallet.id);
54
+ const isInstalled = !!ApiController.state.installed.find(
55
+ installedWallet => installedWallet.id === wallet.id
56
+ );
57
+ EventsController.trackWalletImpression({
58
+ wallet,
59
+ view: 'Connect',
60
+ displayIndex: index,
61
+ // eslint-disable-next-line valtio/state-snapshot-rule
62
+ installed: isInstalled
63
+ });
64
+ }
65
+ });
66
+ }
67
+ }, [prefetchLoading, list]);
39
68
 
40
69
  if (!list?.length) {
41
70
  return null;
@@ -90,6 +90,7 @@ export function OnRampSettingsView() {
90
90
  chevron
91
91
  style={styles.firstItem}
92
92
  contentStyle={styles.listItem}
93
+ testID="button-select-country"
93
94
  >
94
95
  <FlexView
95
96
  alignItems="center"
@@ -111,7 +112,12 @@ export function OnRampSettingsView() {
111
112
  ) : null}
112
113
  </FlexView>
113
114
  </ListItem>
114
- <ListItem onPress={onPaymentCurrencyPress} chevron contentStyle={styles.listItem}>
115
+ <ListItem
116
+ onPress={onPaymentCurrencyPress}
117
+ chevron
118
+ contentStyle={styles.listItem}
119
+ testID="button-select-currency"
120
+ >
115
121
  <FlexView
116
122
  alignItems="center"
117
123
  justifyContent="center"