@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.
- package/README.md +316 -18
- package/dist/adapters/expo.js +3 -3
- package/dist/adapters/react-native.js +3 -3
- package/dist/core/protocol.d.ts +1 -0
- package/dist/core/protocol.js +69 -9
- package/dist/core/wallets.js +5 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +309 -13
- package/dist/react/TonConnectUIProvider.d.ts +21 -2
- package/dist/react/TonConnectUIProvider.js +118 -14
- package/dist/react/WalletSelectionModal.d.ts +1 -0
- package/dist/react/WalletSelectionModal.js +153 -80
- package/dist/react/index.d.ts +1 -0
- package/dist/types/index.d.ts +46 -0
- package/dist/utils/transactionBuilder.js +50 -7
- package/package.json +1 -1
- package/src/adapters/expo.ts +3 -3
- package/src/adapters/react-native.ts +3 -3
- package/src/core/protocol.ts +76 -9
- package/src/core/wallets.ts +5 -1
- package/src/index.ts +381 -15
- package/src/react/TonConnectUIProvider.tsx +154 -15
- package/src/react/WalletSelectionModal.tsx +180 -90
- package/src/react/index.ts +1 -0
- package/src/types/index.ts +52 -0
- package/src/utils/transactionBuilder.ts +56 -7
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
24
|
+
const [walletAvailability, setWalletAvailability] = react_1.default.useState({});
|
|
25
|
+
// Load wallets and check availability
|
|
24
26
|
react_1.default.useEffect(() => {
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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.
|
|
64
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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.
|
|
75
|
-
|
|
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.
|
|
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: '
|
|
128
|
+
maxHeight: '90%',
|
|
94
129
|
paddingBottom: react_native_1.Platform.OS === 'ios' ? 34 : 20,
|
|
95
130
|
},
|
|
96
131
|
header: {
|
|
97
|
-
|
|
132
|
+
flexDirection: 'row',
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
justifyContent: 'space-between',
|
|
135
|
+
padding: 16,
|
|
98
136
|
borderBottomWidth: 1,
|
|
99
137
|
borderBottomColor: '#2a2a2a',
|
|
100
|
-
|
|
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:
|
|
151
|
+
fontSize: 20,
|
|
104
152
|
fontWeight: 'bold',
|
|
105
153
|
color: '#ffffff',
|
|
106
|
-
|
|
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
|
-
|
|
131
|
-
maxHeight: 400,
|
|
171
|
+
flex: 1,
|
|
132
172
|
},
|
|
133
|
-
|
|
173
|
+
walletGrid: {
|
|
134
174
|
flexDirection: 'row',
|
|
135
|
-
|
|
175
|
+
flexWrap: 'wrap',
|
|
136
176
|
padding: 16,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
190
|
+
walletCardConnecting: {
|
|
143
191
|
opacity: 0.7,
|
|
144
192
|
},
|
|
145
193
|
walletIconContainer: {
|
|
146
|
-
|
|
194
|
+
width: 64,
|
|
195
|
+
height: 64,
|
|
196
|
+
marginBottom: 8,
|
|
147
197
|
},
|
|
148
198
|
walletIcon: {
|
|
149
|
-
width:
|
|
150
|
-
height:
|
|
151
|
-
borderRadius:
|
|
199
|
+
width: 64,
|
|
200
|
+
height: 64,
|
|
201
|
+
borderRadius: 16,
|
|
152
202
|
},
|
|
153
203
|
walletIconPlaceholder: {
|
|
154
|
-
width:
|
|
155
|
-
height:
|
|
156
|
-
borderRadius:
|
|
157
|
-
backgroundColor: '#
|
|
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:
|
|
213
|
+
fontSize: 24,
|
|
164
214
|
fontWeight: 'bold',
|
|
165
215
|
},
|
|
166
|
-
walletInfo: {
|
|
167
|
-
flex: 1,
|
|
168
|
-
},
|
|
169
216
|
walletName: {
|
|
170
|
-
fontSize:
|
|
171
|
-
fontWeight: '
|
|
217
|
+
fontSize: 12,
|
|
218
|
+
fontWeight: '500',
|
|
172
219
|
color: '#ffffff',
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
walletAppName: {
|
|
176
|
-
fontSize: 14,
|
|
177
|
-
color: '#999999',
|
|
220
|
+
textAlign: 'center',
|
|
221
|
+
maxWidth: '100%',
|
|
178
222
|
},
|
|
179
|
-
|
|
180
|
-
|
|
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: '#
|
|
210
|
-
|
|
211
|
-
|
|
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
|
});
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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:
|
|
63
|
+
validUntil: expiration,
|
|
47
64
|
messages: [
|
|
48
65
|
{
|
|
49
66
|
address: to,
|
|
50
|
-
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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:
|
|
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
|
/**
|