@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.
@@ -2,6 +2,7 @@
2
2
  /**
3
3
  * WalletSelectionModal component
4
4
  * Provides a beautiful wallet selection UI compatible with @tonconnect/ui-react
5
+ * Matches the exact UI/UX of @tonconnect/ui-react wallet selection modal
5
6
  */
6
7
  var __importDefault = (this && this.__importDefault) || function (mod) {
7
8
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -20,27 +21,38 @@ function WalletSelectionModal({ visible, onClose, wallets: customWallets, style,
20
21
  const sdk = (0, index_1.useTonConnectSDK)();
21
22
  const [wallets, setWallets] = react_1.default.useState([]);
22
23
  const [connectingWallet, setConnectingWallet] = react_1.default.useState(null);
23
- // Load wallets
24
+ const [walletAvailability, setWalletAvailability] = react_1.default.useState({});
25
+ const [imageErrors, setImageErrors] = react_1.default.useState({});
26
+ // Load wallets and check availability
24
27
  react_1.default.useEffect(() => {
25
- if (customWallets) {
26
- setWallets(customWallets);
27
- }
28
- else {
29
- const supportedWallets = sdk.getSupportedWallets();
30
- // CRITICAL FIX: On web, show all wallets with universalLink (they can open in new tab)
31
- // On mobile, filter by platform
32
- const platform = react_native_1.Platform.OS === 'ios' ? 'ios' : react_native_1.Platform.OS === 'android' ? 'android' : 'web';
33
- let platformWallets;
34
- if (platform === 'web') {
35
- // On web, show all wallets with universalLink (they can open in new tab)
36
- platformWallets = supportedWallets.filter((w) => w.platforms.includes('web') || !!w.universalLink);
28
+ const loadWallets = async () => {
29
+ if (customWallets) {
30
+ setWallets(customWallets);
37
31
  }
38
32
  else {
39
- platformWallets = supportedWallets.filter((w) => w.platforms.includes(platform));
33
+ const supportedWallets = sdk.getSupportedWallets();
34
+ // Show all wallets (like @tonconnect/ui-react does)
35
+ // Availability will be checked and displayed
36
+ setWallets(supportedWallets);
37
+ }
38
+ // Check availability for all wallets
39
+ const availability = {};
40
+ const walletsToCheck = customWallets || sdk.getSupportedWallets();
41
+ for (const wallet of walletsToCheck) {
42
+ try {
43
+ const isAvailable = await sdk.isWalletAvailable(wallet.name);
44
+ availability[wallet.name] = isAvailable;
45
+ }
46
+ catch (error) {
47
+ availability[wallet.name] = false;
48
+ }
40
49
  }
41
- setWallets(platformWallets);
50
+ setWalletAvailability(availability);
51
+ };
52
+ if (visible) {
53
+ loadWallets();
42
54
  }
43
- }, [sdk, customWallets]);
55
+ }, [sdk, customWallets, visible]);
44
56
  // Handle wallet selection
45
57
  const handleSelectWallet = async (wallet) => {
46
58
  // Prevent multiple simultaneous connection attempts
@@ -74,26 +86,39 @@ function WalletSelectionModal({ visible, onClose, wallets: customWallets, style,
74
86
  react_1.default.createElement(react_native_1.View, { style: [styles.overlay, style] },
75
87
  react_1.default.createElement(react_native_1.View, { style: styles.modalContainer },
76
88
  react_1.default.createElement(react_native_1.View, { style: styles.header },
77
- react_1.default.createElement(react_native_1.Text, { style: styles.title }, "Connect your TON wallet"),
78
- react_1.default.createElement(react_native_1.Text, { style: styles.subtitle }, "Choose a wallet to connect to this app"),
89
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.backButton, onPress: onClose },
90
+ react_1.default.createElement(react_native_1.Text, { style: styles.backButtonText }, "\u2190")),
91
+ react_1.default.createElement(react_native_1.Text, { style: styles.title }, "Wallets"),
79
92
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.closeButton, onPress: onClose },
80
93
  react_1.default.createElement(react_native_1.Text, { style: styles.closeButtonText }, "\u2715"))),
81
- react_1.default.createElement(react_native_1.ScrollView, { style: styles.walletList, showsVerticalScrollIndicator: false }, wallets.length === 0 ? (react_1.default.createElement(react_native_1.View, { style: styles.emptyState },
94
+ react_1.default.createElement(react_native_1.ScrollView, { style: styles.walletList, showsVerticalScrollIndicator: false, contentContainerStyle: styles.walletGrid }, wallets.length === 0 ? (react_1.default.createElement(react_native_1.View, { style: styles.emptyState },
82
95
  react_1.default.createElement(react_native_1.Text, { style: styles.emptyStateText }, "No wallets available"),
83
96
  react_1.default.createElement(react_native_1.Text, { style: styles.emptyStateSubtext }, "Please install a TON wallet app to continue"))) : (wallets.map((wallet) => {
84
97
  const isConnecting = connectingWallet === wallet.name;
85
- return (react_1.default.createElement(react_native_1.TouchableOpacity, { key: wallet.name, style: [styles.walletItem, isConnecting && styles.walletItemConnecting], onPress: () => handleSelectWallet(wallet), disabled: isConnecting },
86
- react_1.default.createElement(react_native_1.View, { style: styles.walletIconContainer },
87
- react_1.default.createElement(react_native_1.View, { style: styles.walletIconPlaceholder },
88
- react_1.default.createElement(react_native_1.Text, { style: styles.walletIconText }, wallet.name.charAt(0).toUpperCase()))),
89
- react_1.default.createElement(react_native_1.View, { style: styles.walletInfo },
90
- react_1.default.createElement(react_native_1.Text, { style: styles.walletName }, wallet.name),
91
- react_1.default.createElement(react_native_1.Text, { style: styles.walletAppName }, wallet.appName)),
92
- isConnecting && (react_1.default.createElement(react_native_1.View, { style: styles.connectingIndicator },
93
- react_1.default.createElement(react_native_1.Text, { style: styles.connectingText }, "Connecting...")))));
98
+ const isAvailable = walletAvailability[wallet.name] !== false;
99
+ const imageError = imageErrors[wallet.name] || false;
100
+ return (react_1.default.createElement(react_native_1.TouchableOpacity, { key: wallet.name, style: [
101
+ styles.walletCard,
102
+ !isAvailable && styles.walletCardUnavailable,
103
+ isConnecting && styles.walletCardConnecting,
104
+ ], onPress: () => isAvailable && !isConnecting && handleSelectWallet(wallet), disabled: !isAvailable || isConnecting },
105
+ react_1.default.createElement(react_native_1.View, { style: styles.walletIconContainer }, wallet.iconUrl && !imageError ? (react_1.default.createElement(react_native_1.Image, { source: { uri: wallet.iconUrl }, style: styles.walletIcon, onError: (error) => {
106
+ console.log(`[WalletSelectionModal] Failed to load image for ${wallet.name}:`, wallet.iconUrl, error);
107
+ setImageErrors((prev) => ({ ...prev, [wallet.name]: true }));
108
+ }, onLoad: () => {
109
+ console.log(`[WalletSelectionModal] Successfully loaded image for ${wallet.name}:`, wallet.iconUrl);
110
+ }, resizeMode: "cover" })) : (react_1.default.createElement(react_native_1.View, { style: styles.walletIconPlaceholder },
111
+ react_1.default.createElement(react_native_1.Text, { style: styles.walletIconText }, wallet.name.charAt(0).toUpperCase())))),
112
+ react_1.default.createElement(react_native_1.Text, { style: styles.walletName, numberOfLines: 1 }, wallet.name),
113
+ isConnecting && (react_1.default.createElement(react_native_1.ActivityIndicator, { size: "small", color: "#0088cc", style: styles.connectingSpinner }))));
94
114
  }))),
95
115
  react_1.default.createElement(react_native_1.View, { style: styles.footer },
96
- react_1.default.createElement(react_native_1.Text, { style: styles.footerText }, "By connecting, you agree to the app's Terms of Service and Privacy Policy"))))));
116
+ react_1.default.createElement(react_native_1.View, { style: styles.footerContent },
117
+ react_1.default.createElement(react_native_1.View, { style: styles.tonConnectLogo },
118
+ react_1.default.createElement(react_native_1.Text, { style: styles.tonConnectLogoText }, "TON")),
119
+ react_1.default.createElement(react_native_1.Text, { style: styles.footerText }, "TON Connect")),
120
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.helpButton },
121
+ react_1.default.createElement(react_native_1.Text, { style: styles.helpButtonText }, "?")))))));
97
122
  }
98
123
  const styles = react_native_1.StyleSheet.create({
99
124
  overlay: {
@@ -105,30 +130,36 @@ const styles = react_native_1.StyleSheet.create({
105
130
  backgroundColor: '#1a1a1a',
106
131
  borderTopLeftRadius: 24,
107
132
  borderTopRightRadius: 24,
108
- maxHeight: '85%',
133
+ maxHeight: '90%',
109
134
  paddingBottom: react_native_1.Platform.OS === 'ios' ? 34 : 20,
110
135
  },
111
136
  header: {
112
- padding: 24,
137
+ flexDirection: 'row',
138
+ alignItems: 'center',
139
+ justifyContent: 'space-between',
140
+ padding: 16,
113
141
  borderBottomWidth: 1,
114
142
  borderBottomColor: '#2a2a2a',
115
- position: 'relative',
143
+ },
144
+ backButton: {
145
+ width: 32,
146
+ height: 32,
147
+ justifyContent: 'center',
148
+ alignItems: 'center',
149
+ },
150
+ backButtonText: {
151
+ color: '#ffffff',
152
+ fontSize: 20,
153
+ fontWeight: '600',
116
154
  },
117
155
  title: {
118
- fontSize: 28,
156
+ fontSize: 20,
119
157
  fontWeight: 'bold',
120
158
  color: '#ffffff',
121
- marginBottom: 8,
122
- },
123
- subtitle: {
124
- fontSize: 15,
125
- color: '#999999',
126
- lineHeight: 20,
159
+ flex: 1,
160
+ textAlign: 'center',
127
161
  },
128
162
  closeButton: {
129
- position: 'absolute',
130
- top: 24,
131
- right: 24,
132
163
  width: 32,
133
164
  height: 32,
134
165
  borderRadius: 16,
@@ -142,66 +173,65 @@ const styles = react_native_1.StyleSheet.create({
142
173
  fontWeight: '600',
143
174
  },
144
175
  walletList: {
145
- padding: 16,
146
- maxHeight: 400,
176
+ flex: 1,
147
177
  },
148
- walletItem: {
178
+ walletGrid: {
149
179
  flexDirection: 'row',
150
- alignItems: 'center',
180
+ flexWrap: 'wrap',
151
181
  padding: 16,
152
- backgroundColor: '#2a2a2a',
153
- borderRadius: 16,
154
- marginBottom: 12,
155
- minHeight: 72,
182
+ justifyContent: 'flex-start',
183
+ },
184
+ walletCard: {
185
+ width: '25%',
186
+ aspectRatio: 1,
187
+ alignItems: 'center',
188
+ justifyContent: 'center',
189
+ padding: 8,
190
+ marginBottom: 16,
191
+ },
192
+ walletCardUnavailable: {
193
+ opacity: 0.5,
156
194
  },
157
- walletItemConnecting: {
195
+ walletCardConnecting: {
158
196
  opacity: 0.7,
159
197
  },
160
198
  walletIconContainer: {
161
- marginRight: 16,
199
+ width: 64,
200
+ height: 64,
201
+ marginBottom: 8,
162
202
  },
163
203
  walletIcon: {
164
- width: 48,
165
- height: 48,
166
- borderRadius: 12,
204
+ width: 64,
205
+ height: 64,
206
+ borderRadius: 16,
167
207
  },
168
208
  walletIconPlaceholder: {
169
- width: 48,
170
- height: 48,
171
- borderRadius: 12,
172
- backgroundColor: '#3a3a3a',
209
+ width: 64,
210
+ height: 64,
211
+ borderRadius: 16,
212
+ backgroundColor: '#2a2a2a',
173
213
  justifyContent: 'center',
174
214
  alignItems: 'center',
175
215
  },
176
216
  walletIconText: {
177
217
  color: '#ffffff',
178
- fontSize: 20,
218
+ fontSize: 24,
179
219
  fontWeight: 'bold',
180
220
  },
181
- walletInfo: {
182
- flex: 1,
183
- },
184
221
  walletName: {
185
- fontSize: 17,
186
- fontWeight: '600',
222
+ fontSize: 12,
223
+ fontWeight: '500',
187
224
  color: '#ffffff',
188
- marginBottom: 4,
189
- },
190
- walletAppName: {
191
- fontSize: 14,
192
- color: '#999999',
193
- },
194
- connectingIndicator: {
195
- marginLeft: 12,
225
+ textAlign: 'center',
226
+ maxWidth: '100%',
196
227
  },
197
- connectingText: {
198
- fontSize: 14,
199
- color: '#0088cc',
200
- fontWeight: '500',
228
+ connectingSpinner: {
229
+ marginTop: 4,
201
230
  },
202
231
  emptyState: {
203
232
  padding: 40,
204
233
  alignItems: 'center',
234
+ width: '100%',
205
235
  },
206
236
  emptyStateText: {
207
237
  fontSize: 18,
@@ -215,14 +245,47 @@ const styles = react_native_1.StyleSheet.create({
215
245
  textAlign: 'center',
216
246
  },
217
247
  footer: {
248
+ flexDirection: 'row',
249
+ alignItems: 'center',
250
+ justifyContent: 'space-between',
218
251
  padding: 16,
219
252
  borderTopWidth: 1,
220
253
  borderTopColor: '#2a2a2a',
221
254
  },
255
+ footerContent: {
256
+ flexDirection: 'row',
257
+ alignItems: 'center',
258
+ },
259
+ tonConnectLogo: {
260
+ width: 20,
261
+ height: 20,
262
+ borderRadius: 4,
263
+ backgroundColor: '#0088cc',
264
+ justifyContent: 'center',
265
+ alignItems: 'center',
266
+ marginRight: 8,
267
+ },
268
+ tonConnectLogoText: {
269
+ color: '#ffffff',
270
+ fontSize: 10,
271
+ fontWeight: 'bold',
272
+ },
222
273
  footerText: {
223
274
  fontSize: 12,
224
- color: '#666666',
225
- textAlign: 'center',
226
- lineHeight: 16,
275
+ color: '#ffffff',
276
+ fontWeight: '500',
277
+ },
278
+ helpButton: {
279
+ width: 24,
280
+ height: 24,
281
+ borderRadius: 12,
282
+ backgroundColor: '#2a2a2a',
283
+ justifyContent: 'center',
284
+ alignItems: 'center',
285
+ },
286
+ helpButtonText: {
287
+ color: '#999999',
288
+ fontSize: 14,
289
+ fontWeight: '600',
227
290
  },
228
291
  });
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export { TonConnectUIProvider, useTonConnectUI, useTonWallet, useTonConnectModal, useTonConnectSDK, } from './TonConnectUIProvider';
6
6
  export type { TonConnectUIProviderProps, TonConnectUI, WalletState, Account, TransactionResponse, SignDataRequest, SignDataResponse, } from './TonConnectUIProvider';
7
+ export type { Network, BalanceResponse, TransactionStatusResponse, TransactionStatus } from '../types';
7
8
  export { TonConnectButton } from './TonConnectButton';
8
9
  export type { TonConnectButtonProps } from './TonConnectButton';
9
10
  export { WalletSelectionModal } from './WalletSelectionModal';
@@ -175,6 +175,10 @@ export interface PlatformAdapter {
175
175
  /** Generate random bytes */
176
176
  randomBytes(length: number): Promise<Uint8Array>;
177
177
  }
178
+ /**
179
+ * Network type
180
+ */
181
+ export type Network = 'mainnet' | 'testnet';
178
182
  /**
179
183
  * SDK configuration
180
184
  */
@@ -199,8 +203,50 @@ export interface TonConnectMobileConfig {
199
203
  * Available: 'Tonkeeper', 'MyTonWallet', 'Wallet in Telegram', 'Tonhub'
200
204
  */
201
205
  preferredWallet?: string;
206
+ /** Network (mainnet/testnet) - default: 'mainnet' */
207
+ network?: Network;
208
+ /** TON API endpoint for balance checking (optional)
209
+ * Default: 'https://toncenter.com/api/v2' for mainnet, 'https://testnet.toncenter.com/api/v2' for testnet
210
+ */
211
+ tonApiEndpoint?: string;
202
212
  }
203
213
  /**
204
214
  * Event listener callback type
205
215
  */
206
216
  export type StatusChangeCallback = (status: ConnectionStatus) => void;
217
+ /**
218
+ * Event types for TonConnectMobile
219
+ */
220
+ export type TonConnectEventType = 'connect' | 'disconnect' | 'transaction' | 'error' | 'statusChange';
221
+ /**
222
+ * Event listener callback
223
+ */
224
+ export type TonConnectEventListener<T = any> = (data: T) => void;
225
+ /**
226
+ * Transaction status
227
+ */
228
+ export type TransactionStatus = 'pending' | 'confirmed' | 'failed' | 'unknown';
229
+ /**
230
+ * Transaction status response
231
+ */
232
+ export interface TransactionStatusResponse {
233
+ /** Transaction status */
234
+ status: TransactionStatus;
235
+ /** Transaction hash (if available) */
236
+ hash?: string;
237
+ /** Block number (if confirmed) */
238
+ blockNumber?: number;
239
+ /** Error message (if failed) */
240
+ error?: string;
241
+ }
242
+ /**
243
+ * Balance response
244
+ */
245
+ export interface BalanceResponse {
246
+ /** Balance in nanotons */
247
+ balance: string;
248
+ /** Balance in TON (formatted) */
249
+ balanceTon: string;
250
+ /** Network */
251
+ network: Network;
252
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazium/ton-connect-mobile",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "description": "Production-ready TON Connect Mobile SDK for React Native and Expo. Implements the real TonConnect protocol for mobile applications using deep links and callbacks.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -31,6 +31,7 @@ export const SUPPORTED_WALLETS: WalletDefinition[] = [
31
31
  appName: 'Tonkeeper',
32
32
  universalLink: 'https://app.tonkeeper.com/ton-connect',
33
33
  deepLink: 'tonkeeper://',
34
+ iconUrl: 'https://tonkeeper.com/assets/tonconnect-icon.png',
34
35
  platforms: ['ios', 'android', 'web'], // CRITICAL FIX: Tonkeeper Web is supported
35
36
  preferredReturnStrategy: 'post_redirect', // CRITICAL FIX: 'back' strategy may not send callback properly, use 'post_redirect'
36
37
  requiresReturnScheme: true, // CRITICAL FIX: Mobile apps need returnScheme for proper callback handling
@@ -40,6 +41,7 @@ export const SUPPORTED_WALLETS: WalletDefinition[] = [
40
41
  appName: 'MyTonWallet',
41
42
  universalLink: 'https://connect.mytonwallet.org',
42
43
  deepLink: 'mytonwallet://',
44
+ iconUrl: 'https://static.mytonwallet.io/icon-256.png',
43
45
  platforms: ['ios', 'android', 'web'],
44
46
  preferredReturnStrategy: 'post_redirect',
45
47
  requiresReturnScheme: true, // MyTonWallet requires explicit returnScheme
@@ -47,8 +49,9 @@ export const SUPPORTED_WALLETS: WalletDefinition[] = [
47
49
  {
48
50
  name: 'Wallet in Telegram',
49
51
  appName: 'Wallet',
50
- universalLink: 'https://wallet.tonapi.io/ton-connect',
52
+ universalLink: 'https://wallet.tg/ton-connect',
51
53
  deepLink: 'tg://',
54
+ iconUrl: 'https://wallet.tg/images/logo-288.png',
52
55
  platforms: ['ios', 'android'],
53
56
  preferredReturnStrategy: 'post_redirect',
54
57
  requiresReturnScheme: true, // Telegram Wallet requires explicit returnScheme
@@ -58,6 +61,7 @@ export const SUPPORTED_WALLETS: WalletDefinition[] = [
58
61
  appName: 'Tonhub',
59
62
  universalLink: 'https://tonhub.com/ton-connect',
60
63
  deepLink: 'tonhub://',
64
+ iconUrl: 'https://tonhub.com/tonconnect_logo.png',
61
65
  platforms: ['ios', 'android'],
62
66
  preferredReturnStrategy: 'post_redirect',
63
67
  requiresReturnScheme: true, // Tonhub requires explicit returnScheme for proper callback