@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.
- package/README.md +329 -14
- package/dist/core/wallets.js +5 -1
- package/dist/index.d.ts +38 -1
- package/dist/index.js +274 -4
- package/dist/react/TonConnectUIProvider.d.ts +21 -2
- package/dist/react/TonConnectUIProvider.js +72 -3
- package/dist/react/WalletSelectionModal.d.ts +1 -0
- package/dist/react/WalletSelectionModal.js +143 -80
- package/dist/react/index.d.ts +1 -0
- package/dist/types/index.d.ts +46 -0
- package/package.json +1 -1
- package/src/core/wallets.ts +5 -1
- package/src/index.ts +337 -6
- package/src/react/TonConnectUIProvider.tsx +109 -4
- package/src/react/WalletSelectionModal.tsx +178 -91
- package/src/react/index.ts +1 -0
- package/src/types/index.ts +52 -0
|
@@ -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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
110
|
-
|
|
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
|
|
117
|
-
<ScrollView
|
|
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={[
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
{
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
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: '
|
|
229
|
+
maxHeight: '90%',
|
|
181
230
|
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
|
|
182
231
|
},
|
|
183
232
|
header: {
|
|
184
|
-
|
|
233
|
+
flexDirection: 'row',
|
|
234
|
+
alignItems: 'center',
|
|
235
|
+
justifyContent: 'space-between',
|
|
236
|
+
padding: 16,
|
|
185
237
|
borderBottomWidth: 1,
|
|
186
238
|
borderBottomColor: '#2a2a2a',
|
|
187
|
-
|
|
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:
|
|
252
|
+
fontSize: 20,
|
|
191
253
|
fontWeight: 'bold',
|
|
192
254
|
color: '#ffffff',
|
|
193
|
-
|
|
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
|
-
|
|
218
|
-
maxHeight: 400,
|
|
272
|
+
flex: 1,
|
|
219
273
|
},
|
|
220
|
-
|
|
274
|
+
walletGrid: {
|
|
221
275
|
flexDirection: 'row',
|
|
222
|
-
|
|
276
|
+
flexWrap: 'wrap',
|
|
223
277
|
padding: 16,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
291
|
+
walletCardConnecting: {
|
|
230
292
|
opacity: 0.7,
|
|
231
293
|
},
|
|
232
294
|
walletIconContainer: {
|
|
233
|
-
|
|
295
|
+
width: 64,
|
|
296
|
+
height: 64,
|
|
297
|
+
marginBottom: 8,
|
|
234
298
|
},
|
|
235
299
|
walletIcon: {
|
|
236
|
-
width:
|
|
237
|
-
height:
|
|
238
|
-
borderRadius:
|
|
300
|
+
width: 64,
|
|
301
|
+
height: 64,
|
|
302
|
+
borderRadius: 16,
|
|
239
303
|
},
|
|
240
304
|
walletIconPlaceholder: {
|
|
241
|
-
width:
|
|
242
|
-
height:
|
|
243
|
-
borderRadius:
|
|
244
|
-
backgroundColor: '#
|
|
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:
|
|
314
|
+
fontSize: 24,
|
|
251
315
|
fontWeight: 'bold',
|
|
252
316
|
},
|
|
253
|
-
walletInfo: {
|
|
254
|
-
flex: 1,
|
|
255
|
-
},
|
|
256
317
|
walletName: {
|
|
257
|
-
fontSize:
|
|
258
|
-
fontWeight: '
|
|
318
|
+
fontSize: 12,
|
|
319
|
+
fontWeight: '500',
|
|
259
320
|
color: '#ffffff',
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
walletAppName: {
|
|
263
|
-
fontSize: 14,
|
|
264
|
-
color: '#999999',
|
|
265
|
-
},
|
|
266
|
-
connectingIndicator: {
|
|
267
|
-
marginLeft: 12,
|
|
321
|
+
textAlign: 'center',
|
|
322
|
+
maxWidth: '100%',
|
|
268
323
|
},
|
|
269
|
-
|
|
270
|
-
|
|
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: '#
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
package/src/react/index.ts
CHANGED
|
@@ -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';
|
package/src/types/index.ts
CHANGED
|
@@ -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
|
+
|