@blazium/ton-connect-mobile 1.2.1 → 1.2.4

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.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * WalletSelectionModal component
3
3
  * Provides a beautiful wallet selection UI compatible with @tonconnect/ui-react
4
+ * Matches the exact UI/UX of @tonconnect/ui-react wallet selection modal
4
5
  */
5
6
 
6
7
  import React from 'react';
@@ -12,6 +13,8 @@ import {
12
13
  ScrollView,
13
14
  StyleSheet,
14
15
  Platform,
16
+ Image,
17
+ ActivityIndicator,
15
18
  } from 'react-native';
16
19
  import { useTonConnectUI, useTonConnectSDK } from './index';
17
20
  import type { WalletDefinition } from '../index';
@@ -41,26 +44,39 @@ export function WalletSelectionModal({
41
44
  const sdk = useTonConnectSDK();
42
45
  const [wallets, setWallets] = React.useState<WalletDefinition[]>([]);
43
46
  const [connectingWallet, setConnectingWallet] = React.useState<string | null>(null);
47
+ const [walletAvailability, setWalletAvailability] = React.useState<Record<string, boolean>>({});
48
+ const [imageErrors, setImageErrors] = React.useState<Record<string, boolean>>({});
44
49
 
45
- // Load wallets
50
+ // Load wallets and check availability
46
51
  React.useEffect(() => {
47
- if (customWallets) {
48
- setWallets(customWallets);
49
- } else {
50
- const supportedWallets = sdk.getSupportedWallets();
51
- // CRITICAL FIX: On web, show all wallets with universalLink (they can open in new tab)
52
- // On mobile, filter by platform
53
- const platform = Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'web';
54
- let platformWallets: WalletDefinition[];
55
- if (platform === 'web') {
56
- // On web, show all wallets with universalLink (they can open in new tab)
57
- platformWallets = supportedWallets.filter((w) => w.platforms.includes('web') || !!w.universalLink);
52
+ const loadWallets = async () => {
53
+ if (customWallets) {
54
+ setWallets(customWallets);
58
55
  } else {
59
- platformWallets = supportedWallets.filter((w) => w.platforms.includes(platform));
56
+ const supportedWallets = sdk.getSupportedWallets();
57
+ // Show all wallets (like @tonconnect/ui-react does)
58
+ // Availability will be checked and displayed
59
+ setWallets(supportedWallets);
60
60
  }
61
- setWallets(platformWallets);
61
+
62
+ // Check availability for all wallets
63
+ const availability: Record<string, boolean> = {};
64
+ const walletsToCheck = customWallets || sdk.getSupportedWallets();
65
+ for (const wallet of walletsToCheck) {
66
+ try {
67
+ const isAvailable = await sdk.isWalletAvailable(wallet.name);
68
+ availability[wallet.name] = isAvailable;
69
+ } catch (error) {
70
+ availability[wallet.name] = false;
71
+ }
72
+ }
73
+ setWalletAvailability(availability);
74
+ };
75
+
76
+ if (visible) {
77
+ loadWallets();
62
78
  }
63
- }, [sdk, customWallets]);
79
+ }, [sdk, customWallets, visible]);
64
80
 
65
81
  // Handle wallet selection
66
82
  const handleSelectWallet = async (wallet: WalletDefinition) => {
@@ -104,17 +120,23 @@ export function WalletSelectionModal({
104
120
  >
105
121
  <View style={[styles.overlay, style]}>
106
122
  <View style={styles.modalContainer}>
107
- {/* Header */}
123
+ {/* Header - matches @tonconnect/ui-react style */}
108
124
  <View style={styles.header}>
109
- <Text style={styles.title}>Connect your TON wallet</Text>
110
- <Text style={styles.subtitle}>Choose a wallet to connect to this app</Text>
125
+ <TouchableOpacity style={styles.backButton} onPress={onClose}>
126
+ <Text style={styles.backButtonText}>←</Text>
127
+ </TouchableOpacity>
128
+ <Text style={styles.title}>Wallets</Text>
111
129
  <TouchableOpacity style={styles.closeButton} onPress={onClose}>
112
130
  <Text style={styles.closeButtonText}>✕</Text>
113
131
  </TouchableOpacity>
114
132
  </View>
115
133
 
116
- {/* Wallet List */}
117
- <ScrollView style={styles.walletList} showsVerticalScrollIndicator={false}>
134
+ {/* Wallet Grid - matches @tonconnect/ui-react grid layout */}
135
+ <ScrollView
136
+ style={styles.walletList}
137
+ showsVerticalScrollIndicator={false}
138
+ contentContainerStyle={styles.walletGrid}
139
+ >
118
140
  {wallets.length === 0 ? (
119
141
  <View style={styles.emptyState}>
120
142
  <Text style={styles.emptyStateText}>No wallets available</Text>
@@ -125,29 +147,50 @@ export function WalletSelectionModal({
125
147
  ) : (
126
148
  wallets.map((wallet) => {
127
149
  const isConnecting = connectingWallet === wallet.name;
150
+ const isAvailable = walletAvailability[wallet.name] !== false;
151
+ const imageError = imageErrors[wallet.name] || false;
128
152
  return (
129
153
  <TouchableOpacity
130
154
  key={wallet.name}
131
- style={[styles.walletItem, isConnecting && styles.walletItemConnecting]}
132
- onPress={() => handleSelectWallet(wallet)}
133
- disabled={isConnecting}
155
+ style={[
156
+ styles.walletCard,
157
+ !isAvailable && styles.walletCardUnavailable,
158
+ isConnecting && styles.walletCardConnecting,
159
+ ]}
160
+ onPress={() => isAvailable && !isConnecting && handleSelectWallet(wallet)}
161
+ disabled={!isAvailable || isConnecting}
134
162
  >
135
163
  <View style={styles.walletIconContainer}>
136
- {/* Always use placeholder to avoid web image loading issues */}
137
- <View style={styles.walletIconPlaceholder}>
138
- <Text style={styles.walletIconText}>
139
- {wallet.name.charAt(0).toUpperCase()}
140
- </Text>
141
- </View>
142
- </View>
143
- <View style={styles.walletInfo}>
144
- <Text style={styles.walletName}>{wallet.name}</Text>
145
- <Text style={styles.walletAppName}>{wallet.appName}</Text>
164
+ {wallet.iconUrl && !imageError ? (
165
+ <Image
166
+ source={{ uri: wallet.iconUrl }}
167
+ style={styles.walletIcon}
168
+ onError={(error) => {
169
+ console.log(`[WalletSelectionModal] Failed to load image for ${wallet.name}:`, wallet.iconUrl, error);
170
+ setImageErrors((prev) => ({ ...prev, [wallet.name]: true }));
171
+ }}
172
+ onLoad={() => {
173
+ console.log(`[WalletSelectionModal] Successfully loaded image for ${wallet.name}:`, wallet.iconUrl);
174
+ }}
175
+ resizeMode="cover"
176
+ />
177
+ ) : (
178
+ <View style={styles.walletIconPlaceholder}>
179
+ <Text style={styles.walletIconText}>
180
+ {wallet.name.charAt(0).toUpperCase()}
181
+ </Text>
182
+ </View>
183
+ )}
146
184
  </View>
185
+ <Text style={styles.walletName} numberOfLines={1}>
186
+ {wallet.name}
187
+ </Text>
147
188
  {isConnecting && (
148
- <View style={styles.connectingIndicator}>
149
- <Text style={styles.connectingText}>Connecting...</Text>
150
- </View>
189
+ <ActivityIndicator
190
+ size="small"
191
+ color="#0088cc"
192
+ style={styles.connectingSpinner}
193
+ />
151
194
  )}
152
195
  </TouchableOpacity>
153
196
  );
@@ -155,11 +198,17 @@ export function WalletSelectionModal({
155
198
  )}
156
199
  </ScrollView>
157
200
 
158
- {/* Footer */}
201
+ {/* Footer - matches @tonconnect/ui-react footer */}
159
202
  <View style={styles.footer}>
160
- <Text style={styles.footerText}>
161
- By connecting, you agree to the app's Terms of Service and Privacy Policy
162
- </Text>
203
+ <View style={styles.footerContent}>
204
+ <View style={styles.tonConnectLogo}>
205
+ <Text style={styles.tonConnectLogoText}>TON</Text>
206
+ </View>
207
+ <Text style={styles.footerText}>TON Connect</Text>
208
+ </View>
209
+ <TouchableOpacity style={styles.helpButton}>
210
+ <Text style={styles.helpButtonText}>?</Text>
211
+ </TouchableOpacity>
163
212
  </View>
164
213
  </View>
165
214
  </View>
@@ -177,30 +226,36 @@ const styles = StyleSheet.create({
177
226
  backgroundColor: '#1a1a1a',
178
227
  borderTopLeftRadius: 24,
179
228
  borderTopRightRadius: 24,
180
- maxHeight: '85%',
229
+ maxHeight: '90%',
181
230
  paddingBottom: Platform.OS === 'ios' ? 34 : 20,
182
231
  },
183
232
  header: {
184
- padding: 24,
233
+ flexDirection: 'row',
234
+ alignItems: 'center',
235
+ justifyContent: 'space-between',
236
+ padding: 16,
185
237
  borderBottomWidth: 1,
186
238
  borderBottomColor: '#2a2a2a',
187
- position: 'relative',
239
+ },
240
+ backButton: {
241
+ width: 32,
242
+ height: 32,
243
+ justifyContent: 'center',
244
+ alignItems: 'center',
245
+ },
246
+ backButtonText: {
247
+ color: '#ffffff',
248
+ fontSize: 20,
249
+ fontWeight: '600',
188
250
  },
189
251
  title: {
190
- fontSize: 28,
252
+ fontSize: 20,
191
253
  fontWeight: 'bold',
192
254
  color: '#ffffff',
193
- marginBottom: 8,
194
- },
195
- subtitle: {
196
- fontSize: 15,
197
- color: '#999999',
198
- lineHeight: 20,
255
+ flex: 1,
256
+ textAlign: 'center',
199
257
  },
200
258
  closeButton: {
201
- position: 'absolute',
202
- top: 24,
203
- right: 24,
204
259
  width: 32,
205
260
  height: 32,
206
261
  borderRadius: 16,
@@ -214,66 +269,65 @@ const styles = StyleSheet.create({
214
269
  fontWeight: '600',
215
270
  },
216
271
  walletList: {
217
- padding: 16,
218
- maxHeight: 400,
272
+ flex: 1,
219
273
  },
220
- walletItem: {
274
+ walletGrid: {
221
275
  flexDirection: 'row',
222
- alignItems: 'center',
276
+ flexWrap: 'wrap',
223
277
  padding: 16,
224
- backgroundColor: '#2a2a2a',
225
- borderRadius: 16,
226
- marginBottom: 12,
227
- minHeight: 72,
278
+ justifyContent: 'flex-start',
279
+ },
280
+ walletCard: {
281
+ width: '25%',
282
+ aspectRatio: 1,
283
+ alignItems: 'center',
284
+ justifyContent: 'center',
285
+ padding: 8,
286
+ marginBottom: 16,
287
+ },
288
+ walletCardUnavailable: {
289
+ opacity: 0.5,
228
290
  },
229
- walletItemConnecting: {
291
+ walletCardConnecting: {
230
292
  opacity: 0.7,
231
293
  },
232
294
  walletIconContainer: {
233
- marginRight: 16,
295
+ width: 64,
296
+ height: 64,
297
+ marginBottom: 8,
234
298
  },
235
299
  walletIcon: {
236
- width: 48,
237
- height: 48,
238
- borderRadius: 12,
300
+ width: 64,
301
+ height: 64,
302
+ borderRadius: 16,
239
303
  },
240
304
  walletIconPlaceholder: {
241
- width: 48,
242
- height: 48,
243
- borderRadius: 12,
244
- backgroundColor: '#3a3a3a',
305
+ width: 64,
306
+ height: 64,
307
+ borderRadius: 16,
308
+ backgroundColor: '#2a2a2a',
245
309
  justifyContent: 'center',
246
310
  alignItems: 'center',
247
311
  },
248
312
  walletIconText: {
249
313
  color: '#ffffff',
250
- fontSize: 20,
314
+ fontSize: 24,
251
315
  fontWeight: 'bold',
252
316
  },
253
- walletInfo: {
254
- flex: 1,
255
- },
256
317
  walletName: {
257
- fontSize: 17,
258
- fontWeight: '600',
318
+ fontSize: 12,
319
+ fontWeight: '500',
259
320
  color: '#ffffff',
260
- marginBottom: 4,
261
- },
262
- walletAppName: {
263
- fontSize: 14,
264
- color: '#999999',
265
- },
266
- connectingIndicator: {
267
- marginLeft: 12,
321
+ textAlign: 'center',
322
+ maxWidth: '100%',
268
323
  },
269
- connectingText: {
270
- fontSize: 14,
271
- color: '#0088cc',
272
- fontWeight: '500',
324
+ connectingSpinner: {
325
+ marginTop: 4,
273
326
  },
274
327
  emptyState: {
275
328
  padding: 40,
276
329
  alignItems: 'center',
330
+ width: '100%',
277
331
  },
278
332
  emptyStateText: {
279
333
  fontSize: 18,
@@ -287,15 +341,48 @@ const styles = StyleSheet.create({
287
341
  textAlign: 'center',
288
342
  },
289
343
  footer: {
344
+ flexDirection: 'row',
345
+ alignItems: 'center',
346
+ justifyContent: 'space-between',
290
347
  padding: 16,
291
348
  borderTopWidth: 1,
292
349
  borderTopColor: '#2a2a2a',
293
350
  },
351
+ footerContent: {
352
+ flexDirection: 'row',
353
+ alignItems: 'center',
354
+ },
355
+ tonConnectLogo: {
356
+ width: 20,
357
+ height: 20,
358
+ borderRadius: 4,
359
+ backgroundColor: '#0088cc',
360
+ justifyContent: 'center',
361
+ alignItems: 'center',
362
+ marginRight: 8,
363
+ },
364
+ tonConnectLogoText: {
365
+ color: '#ffffff',
366
+ fontSize: 10,
367
+ fontWeight: 'bold',
368
+ },
294
369
  footerText: {
295
370
  fontSize: 12,
296
- color: '#666666',
297
- textAlign: 'center',
298
- lineHeight: 16,
371
+ color: '#ffffff',
372
+ fontWeight: '500',
373
+ },
374
+ helpButton: {
375
+ width: 24,
376
+ height: 24,
377
+ borderRadius: 12,
378
+ backgroundColor: '#2a2a2a',
379
+ justifyContent: 'center',
380
+ alignItems: 'center',
381
+ },
382
+ helpButtonText: {
383
+ color: '#999999',
384
+ fontSize: 14,
385
+ fontWeight: '600',
299
386
  },
300
387
  });
301
388
 
@@ -19,6 +19,7 @@ export type {
19
19
  SignDataRequest,
20
20
  SignDataResponse,
21
21
  } from './TonConnectUIProvider';
22
+ export type { Network, BalanceResponse, TransactionStatusResponse, TransactionStatus } from '../types';
22
23
  export { TonConnectButton } from './TonConnectButton';
23
24
  export type { TonConnectButtonProps } from './TonConnectButton';
24
25
  export { WalletSelectionModal } from './WalletSelectionModal';
@@ -187,6 +187,11 @@ export interface PlatformAdapter {
187
187
  randomBytes(length: number): Promise<Uint8Array>;
188
188
  }
189
189
 
190
+ /**
191
+ * Network type
192
+ */
193
+ export type Network = 'mainnet' | 'testnet';
194
+
190
195
  /**
191
196
  * SDK configuration
192
197
  */
@@ -211,6 +216,12 @@ export interface TonConnectMobileConfig {
211
216
  * Available: 'Tonkeeper', 'MyTonWallet', 'Wallet in Telegram', 'Tonhub'
212
217
  */
213
218
  preferredWallet?: string;
219
+ /** Network (mainnet/testnet) - default: 'mainnet' */
220
+ network?: Network;
221
+ /** TON API endpoint for balance checking (optional)
222
+ * Default: 'https://toncenter.com/api/v2' for mainnet, 'https://testnet.toncenter.com/api/v2' for testnet
223
+ */
224
+ tonApiEndpoint?: string;
214
225
  }
215
226
 
216
227
  /**
@@ -218,3 +229,44 @@ export interface TonConnectMobileConfig {
218
229
  */
219
230
  export type StatusChangeCallback = (status: ConnectionStatus) => void;
220
231
 
232
+ /**
233
+ * Event types for TonConnectMobile
234
+ */
235
+ export type TonConnectEventType = 'connect' | 'disconnect' | 'transaction' | 'error' | 'statusChange';
236
+
237
+ /**
238
+ * Event listener callback
239
+ */
240
+ export type TonConnectEventListener<T = any> = (data: T) => void;
241
+
242
+ /**
243
+ * Transaction status
244
+ */
245
+ export type TransactionStatus = 'pending' | 'confirmed' | 'failed' | 'unknown';
246
+
247
+ /**
248
+ * Transaction status response
249
+ */
250
+ export interface TransactionStatusResponse {
251
+ /** Transaction status */
252
+ status: TransactionStatus;
253
+ /** Transaction hash (if available) */
254
+ hash?: string;
255
+ /** Block number (if confirmed) */
256
+ blockNumber?: number;
257
+ /** Error message (if failed) */
258
+ error?: string;
259
+ }
260
+
261
+ /**
262
+ * Balance response
263
+ */
264
+ export interface BalanceResponse {
265
+ /** Balance in nanotons */
266
+ balance: string;
267
+ /** Balance in TON (formatted) */
268
+ balanceTon: string;
269
+ /** Network */
270
+ network: Network;
271
+ }
272
+