@blazium/ton-connect-mobile 1.2.1 → 1.2.3

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,38 @@ 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>>({});
44
48
 
45
- // Load wallets
49
+ // Load wallets and check availability
46
50
  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);
51
+ const loadWallets = async () => {
52
+ if (customWallets) {
53
+ setWallets(customWallets);
58
54
  } else {
59
- platformWallets = supportedWallets.filter((w) => w.platforms.includes(platform));
55
+ const supportedWallets = sdk.getSupportedWallets();
56
+ // Show all wallets (like @tonconnect/ui-react does)
57
+ // Availability will be checked and displayed
58
+ setWallets(supportedWallets);
60
59
  }
61
- setWallets(platformWallets);
60
+
61
+ // Check availability for all wallets
62
+ const availability: Record<string, boolean> = {};
63
+ const walletsToCheck = customWallets || sdk.getSupportedWallets();
64
+ for (const wallet of walletsToCheck) {
65
+ try {
66
+ const isAvailable = await sdk.isWalletAvailable(wallet.name);
67
+ availability[wallet.name] = isAvailable;
68
+ } catch (error) {
69
+ availability[wallet.name] = false;
70
+ }
71
+ }
72
+ setWalletAvailability(availability);
73
+ };
74
+
75
+ if (visible) {
76
+ loadWallets();
62
77
  }
63
- }, [sdk, customWallets]);
78
+ }, [sdk, customWallets, visible]);
64
79
 
65
80
  // Handle wallet selection
66
81
  const handleSelectWallet = async (wallet: WalletDefinition) => {
@@ -104,17 +119,23 @@ export function WalletSelectionModal({
104
119
  >
105
120
  <View style={[styles.overlay, style]}>
106
121
  <View style={styles.modalContainer}>
107
- {/* Header */}
122
+ {/* Header - matches @tonconnect/ui-react style */}
108
123
  <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>
124
+ <TouchableOpacity style={styles.backButton} onPress={onClose}>
125
+ <Text style={styles.backButtonText}>←</Text>
126
+ </TouchableOpacity>
127
+ <Text style={styles.title}>Wallets</Text>
111
128
  <TouchableOpacity style={styles.closeButton} onPress={onClose}>
112
129
  <Text style={styles.closeButtonText}>✕</Text>
113
130
  </TouchableOpacity>
114
131
  </View>
115
132
 
116
- {/* Wallet List */}
117
- <ScrollView style={styles.walletList} showsVerticalScrollIndicator={false}>
133
+ {/* Wallet Grid - matches @tonconnect/ui-react grid layout */}
134
+ <ScrollView
135
+ style={styles.walletList}
136
+ showsVerticalScrollIndicator={false}
137
+ contentContainerStyle={styles.walletGrid}
138
+ >
118
139
  {wallets.length === 0 ? (
119
140
  <View style={styles.emptyState}>
120
141
  <Text style={styles.emptyStateText}>No wallets available</Text>
@@ -125,29 +146,45 @@ export function WalletSelectionModal({
125
146
  ) : (
126
147
  wallets.map((wallet) => {
127
148
  const isConnecting = connectingWallet === wallet.name;
149
+ const isAvailable = walletAvailability[wallet.name] !== false;
128
150
  return (
129
151
  <TouchableOpacity
130
152
  key={wallet.name}
131
- style={[styles.walletItem, isConnecting && styles.walletItemConnecting]}
132
- onPress={() => handleSelectWallet(wallet)}
133
- disabled={isConnecting}
153
+ style={[
154
+ styles.walletCard,
155
+ !isAvailable && styles.walletCardUnavailable,
156
+ isConnecting && styles.walletCardConnecting,
157
+ ]}
158
+ onPress={() => isAvailable && !isConnecting && handleSelectWallet(wallet)}
159
+ disabled={!isAvailable || isConnecting}
134
160
  >
135
161
  <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>
162
+ {wallet.iconUrl && Platform.OS !== 'web' ? (
163
+ <Image
164
+ source={{ uri: wallet.iconUrl }}
165
+ style={styles.walletIcon}
166
+ onError={() => {
167
+ // Fallback to placeholder on error
168
+ }}
169
+ resizeMode="cover"
170
+ />
171
+ ) : (
172
+ <View style={styles.walletIconPlaceholder}>
173
+ <Text style={styles.walletIconText}>
174
+ {wallet.name.charAt(0).toUpperCase()}
175
+ </Text>
176
+ </View>
177
+ )}
146
178
  </View>
179
+ <Text style={styles.walletName} numberOfLines={1}>
180
+ {wallet.name}
181
+ </Text>
147
182
  {isConnecting && (
148
- <View style={styles.connectingIndicator}>
149
- <Text style={styles.connectingText}>Connecting...</Text>
150
- </View>
183
+ <ActivityIndicator
184
+ size="small"
185
+ color="#0088cc"
186
+ style={styles.connectingSpinner}
187
+ />
151
188
  )}
152
189
  </TouchableOpacity>
153
190
  );
@@ -155,11 +192,17 @@ export function WalletSelectionModal({
155
192
  )}
156
193
  </ScrollView>
157
194
 
158
- {/* Footer */}
195
+ {/* Footer - matches @tonconnect/ui-react footer */}
159
196
  <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>
197
+ <View style={styles.footerContent}>
198
+ <View style={styles.tonConnectLogo}>
199
+ <Text style={styles.tonConnectLogoText}>TON</Text>
200
+ </View>
201
+ <Text style={styles.footerText}>TON Connect</Text>
202
+ </View>
203
+ <TouchableOpacity style={styles.helpButton}>
204
+ <Text style={styles.helpButtonText}>?</Text>
205
+ </TouchableOpacity>
163
206
  </View>
164
207
  </View>
165
208
  </View>
@@ -177,30 +220,36 @@ const styles = StyleSheet.create({
177
220
  backgroundColor: '#1a1a1a',
178
221
  borderTopLeftRadius: 24,
179
222
  borderTopRightRadius: 24,
180
- maxHeight: '85%',
223
+ maxHeight: '90%',
181
224
  paddingBottom: Platform.OS === 'ios' ? 34 : 20,
182
225
  },
183
226
  header: {
184
- padding: 24,
227
+ flexDirection: 'row',
228
+ alignItems: 'center',
229
+ justifyContent: 'space-between',
230
+ padding: 16,
185
231
  borderBottomWidth: 1,
186
232
  borderBottomColor: '#2a2a2a',
187
- position: 'relative',
233
+ },
234
+ backButton: {
235
+ width: 32,
236
+ height: 32,
237
+ justifyContent: 'center',
238
+ alignItems: 'center',
239
+ },
240
+ backButtonText: {
241
+ color: '#ffffff',
242
+ fontSize: 20,
243
+ fontWeight: '600',
188
244
  },
189
245
  title: {
190
- fontSize: 28,
246
+ fontSize: 20,
191
247
  fontWeight: 'bold',
192
248
  color: '#ffffff',
193
- marginBottom: 8,
194
- },
195
- subtitle: {
196
- fontSize: 15,
197
- color: '#999999',
198
- lineHeight: 20,
249
+ flex: 1,
250
+ textAlign: 'center',
199
251
  },
200
252
  closeButton: {
201
- position: 'absolute',
202
- top: 24,
203
- right: 24,
204
253
  width: 32,
205
254
  height: 32,
206
255
  borderRadius: 16,
@@ -214,66 +263,65 @@ const styles = StyleSheet.create({
214
263
  fontWeight: '600',
215
264
  },
216
265
  walletList: {
217
- padding: 16,
218
- maxHeight: 400,
266
+ flex: 1,
219
267
  },
220
- walletItem: {
268
+ walletGrid: {
221
269
  flexDirection: 'row',
222
- alignItems: 'center',
270
+ flexWrap: 'wrap',
223
271
  padding: 16,
224
- backgroundColor: '#2a2a2a',
225
- borderRadius: 16,
226
- marginBottom: 12,
227
- minHeight: 72,
272
+ justifyContent: 'flex-start',
273
+ },
274
+ walletCard: {
275
+ width: '25%',
276
+ aspectRatio: 1,
277
+ alignItems: 'center',
278
+ justifyContent: 'center',
279
+ padding: 8,
280
+ marginBottom: 16,
281
+ },
282
+ walletCardUnavailable: {
283
+ opacity: 0.5,
228
284
  },
229
- walletItemConnecting: {
285
+ walletCardConnecting: {
230
286
  opacity: 0.7,
231
287
  },
232
288
  walletIconContainer: {
233
- marginRight: 16,
289
+ width: 64,
290
+ height: 64,
291
+ marginBottom: 8,
234
292
  },
235
293
  walletIcon: {
236
- width: 48,
237
- height: 48,
238
- borderRadius: 12,
294
+ width: 64,
295
+ height: 64,
296
+ borderRadius: 16,
239
297
  },
240
298
  walletIconPlaceholder: {
241
- width: 48,
242
- height: 48,
243
- borderRadius: 12,
244
- backgroundColor: '#3a3a3a',
299
+ width: 64,
300
+ height: 64,
301
+ borderRadius: 16,
302
+ backgroundColor: '#2a2a2a',
245
303
  justifyContent: 'center',
246
304
  alignItems: 'center',
247
305
  },
248
306
  walletIconText: {
249
307
  color: '#ffffff',
250
- fontSize: 20,
308
+ fontSize: 24,
251
309
  fontWeight: 'bold',
252
310
  },
253
- walletInfo: {
254
- flex: 1,
255
- },
256
311
  walletName: {
257
- fontSize: 17,
258
- fontWeight: '600',
312
+ fontSize: 12,
313
+ fontWeight: '500',
259
314
  color: '#ffffff',
260
- marginBottom: 4,
261
- },
262
- walletAppName: {
263
- fontSize: 14,
264
- color: '#999999',
265
- },
266
- connectingIndicator: {
267
- marginLeft: 12,
315
+ textAlign: 'center',
316
+ maxWidth: '100%',
268
317
  },
269
- connectingText: {
270
- fontSize: 14,
271
- color: '#0088cc',
272
- fontWeight: '500',
318
+ connectingSpinner: {
319
+ marginTop: 4,
273
320
  },
274
321
  emptyState: {
275
322
  padding: 40,
276
323
  alignItems: 'center',
324
+ width: '100%',
277
325
  },
278
326
  emptyStateText: {
279
327
  fontSize: 18,
@@ -287,15 +335,48 @@ const styles = StyleSheet.create({
287
335
  textAlign: 'center',
288
336
  },
289
337
  footer: {
338
+ flexDirection: 'row',
339
+ alignItems: 'center',
340
+ justifyContent: 'space-between',
290
341
  padding: 16,
291
342
  borderTopWidth: 1,
292
343
  borderTopColor: '#2a2a2a',
293
344
  },
345
+ footerContent: {
346
+ flexDirection: 'row',
347
+ alignItems: 'center',
348
+ },
349
+ tonConnectLogo: {
350
+ width: 20,
351
+ height: 20,
352
+ borderRadius: 4,
353
+ backgroundColor: '#0088cc',
354
+ justifyContent: 'center',
355
+ alignItems: 'center',
356
+ marginRight: 8,
357
+ },
358
+ tonConnectLogoText: {
359
+ color: '#ffffff',
360
+ fontSize: 10,
361
+ fontWeight: 'bold',
362
+ },
294
363
  footerText: {
295
364
  fontSize: 12,
296
- color: '#666666',
297
- textAlign: 'center',
298
- lineHeight: 16,
365
+ color: '#ffffff',
366
+ fontWeight: '500',
367
+ },
368
+ helpButton: {
369
+ width: 24,
370
+ height: 24,
371
+ borderRadius: 12,
372
+ backgroundColor: '#2a2a2a',
373
+ justifyContent: 'center',
374
+ alignItems: 'center',
375
+ },
376
+ helpButtonText: {
377
+ color: '#999999',
378
+ fontSize: 14,
379
+ fontWeight: '600',
299
380
  },
300
381
  });
301
382
 
@@ -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
+