@blazium/ton-connect-mobile 1.2.0 → 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.
@@ -51,17 +51,37 @@ const TonConnectUIContext = (0, react_1.createContext)(null);
51
51
  * Compatible with @tonconnect/ui-react API
52
52
  */
53
53
  function TonConnectUIProvider({ config, children, sdkInstance, }) {
54
- const [sdk] = (0, react_1.useState)(() => sdkInstance || new index_1.TonConnectMobile(config));
54
+ // CRITICAL: Initialize SDK only once
55
+ const [sdk] = (0, react_1.useState)(() => {
56
+ if (sdkInstance) {
57
+ return sdkInstance;
58
+ }
59
+ try {
60
+ return new index_1.TonConnectMobile(config);
61
+ }
62
+ catch (error) {
63
+ console.error('[TonConnectUIProvider] Failed to initialize SDK:', error);
64
+ throw error;
65
+ }
66
+ });
55
67
  const [walletState, setWalletState] = (0, react_1.useState)(null);
56
68
  const [modalOpen, setModalOpen] = (0, react_1.useState)(false);
57
69
  const [isConnecting, setIsConnecting] = (0, react_1.useState)(false);
70
+ const [customWalletList, setCustomWalletList] = (0, react_1.useState)(null);
71
+ // Get chain ID based on network
72
+ const getChainId = (0, react_1.useCallback)((network) => {
73
+ // TON mainnet chain ID: -239
74
+ // TON testnet chain ID: -3
75
+ return network === 'testnet' ? -3 : -239;
76
+ }, []);
58
77
  // Update wallet state from SDK status
59
78
  const updateWalletState = (0, react_1.useCallback)((status) => {
60
79
  if (status.connected && status.wallet) {
80
+ const network = sdk.getNetwork();
61
81
  setWalletState({
62
82
  account: {
63
83
  address: status.wallet.address,
64
- chain: -239, // TON mainnet chain ID
84
+ chain: getChainId(network),
65
85
  publicKey: status.wallet.publicKey,
66
86
  },
67
87
  wallet: status.wallet,
@@ -75,7 +95,7 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
75
95
  connected: false,
76
96
  });
77
97
  }
78
- }, []);
98
+ }, [sdk, getChainId]);
79
99
  // Subscribe to SDK status changes
80
100
  (0, react_1.useEffect)(() => {
81
101
  // Set initial state
@@ -135,19 +155,94 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
135
155
  }, [sdk]);
136
156
  // Send transaction
137
157
  const sendTransaction = (0, react_1.useCallback)(async (transaction) => {
138
- const response = await sdk.sendTransaction(transaction);
139
- return {
140
- boc: response.boc,
141
- signature: response.signature,
142
- };
158
+ try {
159
+ // Validate transaction before sending
160
+ if (!transaction || !transaction.messages || transaction.messages.length === 0) {
161
+ throw new Error('Invalid transaction: messages array is required and cannot be empty');
162
+ }
163
+ if (!transaction.validUntil || transaction.validUntil <= Date.now()) {
164
+ throw new Error('Invalid transaction: validUntil must be in the future');
165
+ }
166
+ const response = await sdk.sendTransaction(transaction);
167
+ return {
168
+ boc: response.boc,
169
+ signature: response.signature,
170
+ };
171
+ }
172
+ catch (error) {
173
+ console.error('[TonConnectUIProvider] Transaction error:', error);
174
+ throw error;
175
+ }
143
176
  }, [sdk]);
144
177
  // Sign data
145
178
  const signData = (0, react_1.useCallback)(async (request) => {
146
- const response = await sdk.signData(request.data, request.version);
147
- return {
148
- signature: response.signature,
149
- timestamp: response.timestamp,
150
- };
179
+ try {
180
+ // Validate request
181
+ if (!request || (!request.data && request.data !== '')) {
182
+ throw new Error('Invalid sign data request: data is required');
183
+ }
184
+ const response = await sdk.signData(request.data, request.version);
185
+ return {
186
+ signature: response.signature,
187
+ timestamp: response.timestamp,
188
+ };
189
+ }
190
+ catch (error) {
191
+ console.error('[TonConnectUIProvider] Sign data error:', error);
192
+ throw error;
193
+ }
194
+ }, [sdk]);
195
+ // Restore connection from stored session
196
+ const restoreConnection = (0, react_1.useCallback)(async () => {
197
+ try {
198
+ // SDK automatically loads session on initialization
199
+ // This method triggers a re-check of the stored session
200
+ const status = sdk.getStatus();
201
+ if (status.connected && status.wallet) {
202
+ updateWalletState(status);
203
+ }
204
+ }
205
+ catch (error) {
206
+ console.error('[TonConnectUIProvider] Restore connection error:', error);
207
+ throw error;
208
+ }
209
+ }, [sdk, updateWalletState]);
210
+ // Set wallet list (customize available wallets)
211
+ const setWalletList = (0, react_1.useCallback)((wallets) => {
212
+ if (!wallets || !Array.isArray(wallets)) {
213
+ throw new Error('Wallet list must be an array');
214
+ }
215
+ setCustomWalletList(wallets);
216
+ }, []);
217
+ // Get network
218
+ const getNetwork = (0, react_1.useCallback)(() => {
219
+ return sdk.getNetwork();
220
+ }, [sdk]);
221
+ // Set network
222
+ const setNetwork = (0, react_1.useCallback)((network) => {
223
+ sdk.setNetwork(network);
224
+ // Update wallet state to reflect new chain ID
225
+ const status = sdk.getStatus();
226
+ updateWalletState(status);
227
+ }, [sdk, updateWalletState]);
228
+ // Get balance
229
+ const getBalance = (0, react_1.useCallback)(async (address) => {
230
+ return await sdk.getBalance(address);
231
+ }, [sdk]);
232
+ // Get transaction status
233
+ const getTransactionStatus = (0, react_1.useCallback)(async (boc, maxAttempts = 10, intervalMs = 2000) => {
234
+ return await sdk.getTransactionStatus(boc, maxAttempts, intervalMs);
235
+ }, [sdk]);
236
+ // Get transaction status by hash
237
+ const getTransactionStatusByHash = (0, react_1.useCallback)(async (txHash, address) => {
238
+ return await sdk.getTransactionStatusByHash(txHash, address);
239
+ }, [sdk]);
240
+ // Event listeners
241
+ const on = (0, react_1.useCallback)((event, listener) => {
242
+ return sdk.on(event, listener);
243
+ }, [sdk]);
244
+ const off = (0, react_1.useCallback)((event, listener) => {
245
+ sdk.off(event, listener);
151
246
  }, [sdk]);
152
247
  // Create TonConnectUI instance
153
248
  const tonConnectUI = {
@@ -157,6 +252,15 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
157
252
  disconnect,
158
253
  sendTransaction,
159
254
  signData,
255
+ restoreConnection,
256
+ setWalletList,
257
+ getNetwork,
258
+ setNetwork,
259
+ getBalance,
260
+ getTransactionStatus,
261
+ getTransactionStatusByHash,
262
+ on,
263
+ off,
160
264
  wallet: walletState,
161
265
  modalState: {
162
266
  open: modalOpen,
@@ -169,7 +273,7 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
169
273
  };
170
274
  return (react_1.default.createElement(TonConnectUIContext.Provider, { value: contextValue },
171
275
  children,
172
- react_1.default.createElement(WalletSelectionModal_1.WalletSelectionModal, { visible: modalOpen && !walletState?.connected, onClose: closeModal })));
276
+ react_1.default.createElement(WalletSelectionModal_1.WalletSelectionModal, { visible: modalOpen && !walletState?.connected, onClose: closeModal, wallets: customWalletList || undefined })));
173
277
  }
174
278
  /**
175
279
  * Hook to access TonConnectUI instance
@@ -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
  import type { WalletDefinition } from '../index';
6
7
  export interface WalletSelectionModalProps {
@@ -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,25 +21,53 @@ 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
+ // Load wallets and check availability
24
26
  react_1.default.useEffect(() => {
25
- if (customWallets) {
26
- setWallets(customWallets);
27
+ const loadWallets = async () => {
28
+ if (customWallets) {
29
+ setWallets(customWallets);
30
+ }
31
+ else {
32
+ const supportedWallets = sdk.getSupportedWallets();
33
+ // Show all wallets (like @tonconnect/ui-react does)
34
+ // Availability will be checked and displayed
35
+ setWallets(supportedWallets);
36
+ }
37
+ // Check availability for all wallets
38
+ const availability = {};
39
+ const walletsToCheck = customWallets || sdk.getSupportedWallets();
40
+ for (const wallet of walletsToCheck) {
41
+ try {
42
+ const isAvailable = await sdk.isWalletAvailable(wallet.name);
43
+ availability[wallet.name] = isAvailable;
44
+ }
45
+ catch (error) {
46
+ availability[wallet.name] = false;
47
+ }
48
+ }
49
+ setWalletAvailability(availability);
50
+ };
51
+ if (visible) {
52
+ loadWallets();
27
53
  }
28
- else {
29
- const supportedWallets = sdk.getSupportedWallets();
30
- // Filter wallets for current platform
31
- const platform = react_native_1.Platform.OS === 'ios' ? 'ios' : react_native_1.Platform.OS === 'android' ? 'android' : 'web';
32
- const platformWallets = supportedWallets.filter((w) => w.platforms.includes(platform));
33
- setWallets(platformWallets);
34
- }
35
- }, [sdk, customWallets]);
54
+ }, [sdk, customWallets, visible]);
36
55
  // Handle wallet selection
37
56
  const handleSelectWallet = async (wallet) => {
57
+ // Prevent multiple simultaneous connection attempts
58
+ if (connectingWallet) {
59
+ return;
60
+ }
38
61
  try {
39
62
  setConnectingWallet(wallet.name);
40
63
  // Set preferred wallet
41
- sdk.setPreferredWallet(wallet.name);
64
+ try {
65
+ sdk.setPreferredWallet(wallet.name);
66
+ }
67
+ catch (error) {
68
+ console.error('[WalletSelectionModal] Failed to set preferred wallet:', error);
69
+ // Continue anyway - SDK will use default wallet
70
+ }
42
71
  // Close modal
43
72
  onClose();
44
73
  // Small delay to ensure modal closes
@@ -47,38 +76,44 @@ function WalletSelectionModal({ visible, onClose, wallets: customWallets, style,
47
76
  await tonConnectUI.connectWallet();
48
77
  }
49
78
  catch (error) {
50
- console.error('Wallet connection error:', error);
79
+ console.error('[WalletSelectionModal] Wallet connection error:', error);
51
80
  setConnectingWallet(null);
52
- // Re-open modal on error
53
- onClose();
54
- setTimeout(() => {
55
- // Modal will be re-opened by parent if needed
56
- }, 500);
81
+ // Error is handled by SDK/UI, just reset connecting state
57
82
  }
58
83
  };
59
84
  return (react_1.default.createElement(react_native_1.Modal, { visible: visible, animationType: "slide", transparent: true, onRequestClose: onClose },
60
85
  react_1.default.createElement(react_native_1.View, { style: [styles.overlay, style] },
61
86
  react_1.default.createElement(react_native_1.View, { style: styles.modalContainer },
62
87
  react_1.default.createElement(react_native_1.View, { style: styles.header },
63
- react_1.default.createElement(react_native_1.Text, { style: styles.title }, "Connect your TON wallet"),
64
- react_1.default.createElement(react_native_1.Text, { style: styles.subtitle }, "Choose a wallet to connect to this app"),
88
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.backButton, onPress: onClose },
89
+ react_1.default.createElement(react_native_1.Text, { style: styles.backButtonText }, "\u2190")),
90
+ react_1.default.createElement(react_native_1.Text, { style: styles.title }, "Wallets"),
65
91
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.closeButton, onPress: onClose },
66
92
  react_1.default.createElement(react_native_1.Text, { style: styles.closeButtonText }, "\u2715"))),
67
- 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 },
93
+ 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 },
68
94
  react_1.default.createElement(react_native_1.Text, { style: styles.emptyStateText }, "No wallets available"),
69
95
  react_1.default.createElement(react_native_1.Text, { style: styles.emptyStateSubtext }, "Please install a TON wallet app to continue"))) : (wallets.map((wallet) => {
70
96
  const isConnecting = connectingWallet === wallet.name;
71
- return (react_1.default.createElement(react_native_1.TouchableOpacity, { key: wallet.name, style: [styles.walletItem, isConnecting && styles.walletItemConnecting], onPress: () => handleSelectWallet(wallet), disabled: isConnecting },
72
- react_1.default.createElement(react_native_1.View, { style: styles.walletIconContainer }, wallet.iconUrl ? (react_1.default.createElement(react_native_1.Image, { source: { uri: wallet.iconUrl }, style: styles.walletIcon })) : (react_1.default.createElement(react_native_1.View, { style: styles.walletIconPlaceholder },
97
+ const isAvailable = walletAvailability[wallet.name] !== false;
98
+ return (react_1.default.createElement(react_native_1.TouchableOpacity, { key: wallet.name, style: [
99
+ styles.walletCard,
100
+ !isAvailable && styles.walletCardUnavailable,
101
+ isConnecting && styles.walletCardConnecting,
102
+ ], onPress: () => isAvailable && !isConnecting && handleSelectWallet(wallet), disabled: !isAvailable || isConnecting },
103
+ react_1.default.createElement(react_native_1.View, { style: styles.walletIconContainer }, wallet.iconUrl && react_native_1.Platform.OS !== 'web' ? (react_1.default.createElement(react_native_1.Image, { source: { uri: wallet.iconUrl }, style: styles.walletIcon, onError: () => {
104
+ // Fallback to placeholder on error
105
+ }, resizeMode: "cover" })) : (react_1.default.createElement(react_native_1.View, { style: styles.walletIconPlaceholder },
73
106
  react_1.default.createElement(react_native_1.Text, { style: styles.walletIconText }, wallet.name.charAt(0).toUpperCase())))),
74
- react_1.default.createElement(react_native_1.View, { style: styles.walletInfo },
75
- react_1.default.createElement(react_native_1.Text, { style: styles.walletName }, wallet.name),
76
- react_1.default.createElement(react_native_1.Text, { style: styles.walletAppName }, wallet.appName)),
77
- isConnecting && (react_1.default.createElement(react_native_1.View, { style: styles.connectingIndicator },
78
- react_1.default.createElement(react_native_1.Text, { style: styles.connectingText }, "Connecting...")))));
107
+ react_1.default.createElement(react_native_1.Text, { style: styles.walletName, numberOfLines: 1 }, wallet.name),
108
+ isConnecting && (react_1.default.createElement(react_native_1.ActivityIndicator, { size: "small", color: "#0088cc", style: styles.connectingSpinner }))));
79
109
  }))),
80
110
  react_1.default.createElement(react_native_1.View, { style: styles.footer },
81
- 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"))))));
111
+ react_1.default.createElement(react_native_1.View, { style: styles.footerContent },
112
+ react_1.default.createElement(react_native_1.View, { style: styles.tonConnectLogo },
113
+ react_1.default.createElement(react_native_1.Text, { style: styles.tonConnectLogoText }, "TON")),
114
+ react_1.default.createElement(react_native_1.Text, { style: styles.footerText }, "TON Connect")),
115
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.helpButton },
116
+ react_1.default.createElement(react_native_1.Text, { style: styles.helpButtonText }, "?")))))));
82
117
  }
83
118
  const styles = react_native_1.StyleSheet.create({
84
119
  overlay: {
@@ -90,30 +125,36 @@ const styles = react_native_1.StyleSheet.create({
90
125
  backgroundColor: '#1a1a1a',
91
126
  borderTopLeftRadius: 24,
92
127
  borderTopRightRadius: 24,
93
- maxHeight: '85%',
128
+ maxHeight: '90%',
94
129
  paddingBottom: react_native_1.Platform.OS === 'ios' ? 34 : 20,
95
130
  },
96
131
  header: {
97
- padding: 24,
132
+ flexDirection: 'row',
133
+ alignItems: 'center',
134
+ justifyContent: 'space-between',
135
+ padding: 16,
98
136
  borderBottomWidth: 1,
99
137
  borderBottomColor: '#2a2a2a',
100
- position: 'relative',
138
+ },
139
+ backButton: {
140
+ width: 32,
141
+ height: 32,
142
+ justifyContent: 'center',
143
+ alignItems: 'center',
144
+ },
145
+ backButtonText: {
146
+ color: '#ffffff',
147
+ fontSize: 20,
148
+ fontWeight: '600',
101
149
  },
102
150
  title: {
103
- fontSize: 28,
151
+ fontSize: 20,
104
152
  fontWeight: 'bold',
105
153
  color: '#ffffff',
106
- marginBottom: 8,
107
- },
108
- subtitle: {
109
- fontSize: 15,
110
- color: '#999999',
111
- lineHeight: 20,
154
+ flex: 1,
155
+ textAlign: 'center',
112
156
  },
113
157
  closeButton: {
114
- position: 'absolute',
115
- top: 24,
116
- right: 24,
117
158
  width: 32,
118
159
  height: 32,
119
160
  borderRadius: 16,
@@ -127,66 +168,65 @@ const styles = react_native_1.StyleSheet.create({
127
168
  fontWeight: '600',
128
169
  },
129
170
  walletList: {
130
- padding: 16,
131
- maxHeight: 400,
171
+ flex: 1,
132
172
  },
133
- walletItem: {
173
+ walletGrid: {
134
174
  flexDirection: 'row',
135
- alignItems: 'center',
175
+ flexWrap: 'wrap',
136
176
  padding: 16,
137
- backgroundColor: '#2a2a2a',
138
- borderRadius: 16,
139
- marginBottom: 12,
140
- minHeight: 72,
177
+ justifyContent: 'flex-start',
178
+ },
179
+ walletCard: {
180
+ width: '25%',
181
+ aspectRatio: 1,
182
+ alignItems: 'center',
183
+ justifyContent: 'center',
184
+ padding: 8,
185
+ marginBottom: 16,
186
+ },
187
+ walletCardUnavailable: {
188
+ opacity: 0.5,
141
189
  },
142
- walletItemConnecting: {
190
+ walletCardConnecting: {
143
191
  opacity: 0.7,
144
192
  },
145
193
  walletIconContainer: {
146
- marginRight: 16,
194
+ width: 64,
195
+ height: 64,
196
+ marginBottom: 8,
147
197
  },
148
198
  walletIcon: {
149
- width: 48,
150
- height: 48,
151
- borderRadius: 12,
199
+ width: 64,
200
+ height: 64,
201
+ borderRadius: 16,
152
202
  },
153
203
  walletIconPlaceholder: {
154
- width: 48,
155
- height: 48,
156
- borderRadius: 12,
157
- backgroundColor: '#3a3a3a',
204
+ width: 64,
205
+ height: 64,
206
+ borderRadius: 16,
207
+ backgroundColor: '#2a2a2a',
158
208
  justifyContent: 'center',
159
209
  alignItems: 'center',
160
210
  },
161
211
  walletIconText: {
162
212
  color: '#ffffff',
163
- fontSize: 20,
213
+ fontSize: 24,
164
214
  fontWeight: 'bold',
165
215
  },
166
- walletInfo: {
167
- flex: 1,
168
- },
169
216
  walletName: {
170
- fontSize: 17,
171
- fontWeight: '600',
217
+ fontSize: 12,
218
+ fontWeight: '500',
172
219
  color: '#ffffff',
173
- marginBottom: 4,
174
- },
175
- walletAppName: {
176
- fontSize: 14,
177
- color: '#999999',
220
+ textAlign: 'center',
221
+ maxWidth: '100%',
178
222
  },
179
- connectingIndicator: {
180
- marginLeft: 12,
181
- },
182
- connectingText: {
183
- fontSize: 14,
184
- color: '#0088cc',
185
- fontWeight: '500',
223
+ connectingSpinner: {
224
+ marginTop: 4,
186
225
  },
187
226
  emptyState: {
188
227
  padding: 40,
189
228
  alignItems: 'center',
229
+ width: '100%',
190
230
  },
191
231
  emptyStateText: {
192
232
  fontSize: 18,
@@ -200,14 +240,47 @@ const styles = react_native_1.StyleSheet.create({
200
240
  textAlign: 'center',
201
241
  },
202
242
  footer: {
243
+ flexDirection: 'row',
244
+ alignItems: 'center',
245
+ justifyContent: 'space-between',
203
246
  padding: 16,
204
247
  borderTopWidth: 1,
205
248
  borderTopColor: '#2a2a2a',
206
249
  },
250
+ footerContent: {
251
+ flexDirection: 'row',
252
+ alignItems: 'center',
253
+ },
254
+ tonConnectLogo: {
255
+ width: 20,
256
+ height: 20,
257
+ borderRadius: 4,
258
+ backgroundColor: '#0088cc',
259
+ justifyContent: 'center',
260
+ alignItems: 'center',
261
+ marginRight: 8,
262
+ },
263
+ tonConnectLogoText: {
264
+ color: '#ffffff',
265
+ fontSize: 10,
266
+ fontWeight: 'bold',
267
+ },
207
268
  footerText: {
208
269
  fontSize: 12,
209
- color: '#666666',
210
- textAlign: 'center',
211
- lineHeight: 16,
270
+ color: '#ffffff',
271
+ fontWeight: '500',
272
+ },
273
+ helpButton: {
274
+ width: 24,
275
+ height: 24,
276
+ borderRadius: 12,
277
+ backgroundColor: '#2a2a2a',
278
+ justifyContent: 'center',
279
+ alignItems: 'center',
280
+ },
281
+ helpButtonText: {
282
+ color: '#999999',
283
+ fontSize: 14,
284
+ fontWeight: '600',
212
285
  },
213
286
  });
@@ -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
+ }
@@ -42,12 +42,29 @@ function nanoToTon(nanotons) {
42
42
  * @returns Transaction request
43
43
  */
44
44
  function buildTransferTransaction(to, amount, validUntil) {
45
+ // Validate address
46
+ if (!to || typeof to !== 'string') {
47
+ throw new Error('Recipient address is required');
48
+ }
49
+ if (!isValidTonAddress(to)) {
50
+ throw new Error(`Invalid TON address format: ${to}`);
51
+ }
52
+ // Validate amount
53
+ const nanoAmount = tonToNano(amount);
54
+ if (BigInt(nanoAmount) <= 0n) {
55
+ throw new Error('Transaction amount must be greater than 0');
56
+ }
57
+ // Validate validUntil
58
+ const expiration = validUntil || Date.now() + 5 * 60 * 1000; // 5 minutes default
59
+ if (expiration <= Date.now()) {
60
+ throw new Error('Transaction expiration must be in the future');
61
+ }
45
62
  return {
46
- validUntil: validUntil || Date.now() + 5 * 60 * 1000, // 5 minutes default
63
+ validUntil: expiration,
47
64
  messages: [
48
65
  {
49
66
  address: to,
50
- amount: tonToNano(amount),
67
+ amount: nanoAmount,
51
68
  },
52
69
  ],
53
70
  };
@@ -59,12 +76,38 @@ function buildTransferTransaction(to, amount, validUntil) {
59
76
  * @returns Transaction request
60
77
  */
61
78
  function buildMultiTransferTransaction(transfers, validUntil) {
62
- return {
63
- validUntil: validUntil || Date.now() + 5 * 60 * 1000,
64
- messages: transfers.map((transfer) => ({
79
+ // Validate transfers array
80
+ if (!transfers || !Array.isArray(transfers) || transfers.length === 0) {
81
+ throw new Error('Transfers array is required and cannot be empty');
82
+ }
83
+ if (transfers.length > 255) {
84
+ throw new Error('Maximum 255 transfers allowed per transaction');
85
+ }
86
+ // Validate each transfer
87
+ const messages = transfers.map((transfer, index) => {
88
+ if (!transfer.to || typeof transfer.to !== 'string') {
89
+ throw new Error(`Transfer ${index + 1}: Recipient address is required`);
90
+ }
91
+ if (!isValidTonAddress(transfer.to)) {
92
+ throw new Error(`Transfer ${index + 1}: Invalid TON address format: ${transfer.to}`);
93
+ }
94
+ const nanoAmount = tonToNano(transfer.amount);
95
+ if (BigInt(nanoAmount) <= 0n) {
96
+ throw new Error(`Transfer ${index + 1}: Amount must be greater than 0`);
97
+ }
98
+ return {
65
99
  address: transfer.to,
66
- amount: tonToNano(transfer.amount),
67
- })),
100
+ amount: nanoAmount,
101
+ };
102
+ });
103
+ // Validate validUntil
104
+ const expiration = validUntil || Date.now() + 5 * 60 * 1000;
105
+ if (expiration <= Date.now()) {
106
+ throw new Error('Transaction expiration must be in the future');
107
+ }
108
+ return {
109
+ validUntil: expiration,
110
+ messages,
68
111
  };
69
112
  }
70
113
  /**