@blazium/ton-connect-mobile 1.2.4 → 1.2.6
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 +7 -20
- package/dist/adapters/react-native.js +8 -1
- package/dist/core/bridge.d.ts +61 -0
- package/dist/core/bridge.js +237 -0
- package/dist/core/crypto.d.ts +8 -19
- package/dist/core/crypto.js +15 -141
- package/dist/core/index.d.ts +5 -3
- package/dist/core/index.js +20 -17
- package/dist/core/protocol.d.ts +35 -32
- package/dist/core/protocol.js +109 -285
- package/dist/core/session.d.ts +65 -0
- package/dist/core/session.js +235 -0
- package/dist/core/wallets.d.ts +6 -6
- package/dist/core/wallets.js +17 -18
- package/dist/index.d.ts +33 -72
- package/dist/index.js +322 -769
- package/dist/react/TonConnectUIProvider.d.ts +4 -52
- package/dist/react/TonConnectUIProvider.js +18 -122
- package/dist/react/index.d.ts +1 -2
- package/dist/react/index.js +0 -1
- package/dist/types/index.d.ts +84 -139
- package/dist/types/index.js +1 -1
- package/package.json +2 -3
- package/src/adapters/react-native.ts +7 -1
- package/src/core/bridge.ts +307 -0
- package/src/core/crypto.ts +62 -238
- package/src/core/index.ts +17 -7
- package/src/core/protocol.ts +217 -441
- package/src/core/session.ts +247 -0
- package/src/core/wallets.ts +90 -93
- package/src/index.ts +811 -1338
- package/src/react/TonConnectUIProvider.tsx +272 -441
- package/src/react/index.ts +23 -27
- package/src/types/index.ts +217 -272
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* TON Connect Mobile SDK
|
|
4
|
-
* Production-ready implementation
|
|
4
|
+
* Production-ready implementation using TON Connect v2 bridge protocol
|
|
5
5
|
*/
|
|
6
6
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
7
|
if (k2 === undefined) k2 = k;
|
|
@@ -20,7 +20,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.getWalletsForPlatform = exports.getDefaultWallet = exports.getWalletByName = exports.SUPPORTED_WALLETS = exports.TonConnectMobile = exports.TransactionInProgressError = exports.ConnectionInProgressError = exports.UserRejectedError = exports.TransactionTimeoutError = exports.ConnectionTimeoutError = exports.TonConnectError = void 0;
|
|
22
22
|
const protocol_1 = require("./core/protocol");
|
|
23
|
-
const
|
|
23
|
+
const session_1 = require("./core/session");
|
|
24
|
+
const bridge_1 = require("./core/bridge");
|
|
24
25
|
const expo_1 = require("./adapters/expo");
|
|
25
26
|
const react_native_1 = require("./adapters/react-native");
|
|
26
27
|
const web_1 = require("./adapters/web");
|
|
@@ -39,623 +40,353 @@ class TonConnectError extends Error {
|
|
|
39
40
|
exports.TonConnectError = TonConnectError;
|
|
40
41
|
class ConnectionTimeoutError extends TonConnectError {
|
|
41
42
|
constructor() {
|
|
42
|
-
super('Connection request timed out. The wallet did not respond in time.', 'CONNECTION_TIMEOUT', '
|
|
43
|
+
super('Connection request timed out. The wallet did not respond in time.', 'CONNECTION_TIMEOUT', 'Make sure the wallet app is installed and try again.');
|
|
43
44
|
this.name = 'ConnectionTimeoutError';
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
exports.ConnectionTimeoutError = ConnectionTimeoutError;
|
|
47
48
|
class TransactionTimeoutError extends TonConnectError {
|
|
48
49
|
constructor() {
|
|
49
|
-
super('Transaction request timed out.
|
|
50
|
+
super('Transaction request timed out.', 'TRANSACTION_TIMEOUT', 'Check the wallet app and try again.');
|
|
50
51
|
this.name = 'TransactionTimeoutError';
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
exports.TransactionTimeoutError = TransactionTimeoutError;
|
|
54
55
|
class UserRejectedError extends TonConnectError {
|
|
55
56
|
constructor(message) {
|
|
56
|
-
super(message || 'User rejected the request', 'USER_REJECTED'
|
|
57
|
+
super(message || 'User rejected the request', 'USER_REJECTED');
|
|
57
58
|
this.name = 'UserRejectedError';
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
exports.UserRejectedError = UserRejectedError;
|
|
61
62
|
class ConnectionInProgressError extends TonConnectError {
|
|
62
63
|
constructor() {
|
|
63
|
-
super('Connection request already in progress', 'CONNECTION_IN_PROGRESS'
|
|
64
|
+
super('Connection request already in progress', 'CONNECTION_IN_PROGRESS');
|
|
64
65
|
this.name = 'ConnectionInProgressError';
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
exports.ConnectionInProgressError = ConnectionInProgressError;
|
|
68
69
|
class TransactionInProgressError extends TonConnectError {
|
|
69
70
|
constructor() {
|
|
70
|
-
super('Transaction request already in progress', 'TRANSACTION_IN_PROGRESS'
|
|
71
|
+
super('Transaction request already in progress', 'TRANSACTION_IN_PROGRESS');
|
|
71
72
|
this.name = 'TransactionInProgressError';
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
exports.TransactionInProgressError = TransactionInProgressError;
|
|
75
76
|
/**
|
|
76
77
|
* Main TON Connect Mobile SDK class
|
|
78
|
+
* Implements the real TON Connect v2 bridge protocol
|
|
77
79
|
*/
|
|
78
80
|
class TonConnectMobile {
|
|
79
81
|
constructor(config) {
|
|
80
82
|
this.statusChangeCallbacks = new Set();
|
|
81
83
|
this.eventListeners = new Map();
|
|
82
84
|
this.currentStatus = { connected: false, wallet: null };
|
|
83
|
-
|
|
85
|
+
// TON Connect v2 protocol state
|
|
86
|
+
this.session = null;
|
|
87
|
+
this.bridge = new bridge_1.BridgeGateway();
|
|
88
|
+
this.walletBridgePublicKey = null; // hex wallet public key from bridge
|
|
89
|
+
// Pending promises
|
|
84
90
|
this.connectionPromise = null;
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
// Validate config
|
|
91
|
+
this.pendingRpcRequests = new Map();
|
|
92
|
+
this.rpcIdCounter = 1;
|
|
88
93
|
if (!config.manifestUrl) {
|
|
89
94
|
throw new TonConnectError('manifestUrl is required');
|
|
90
95
|
}
|
|
91
96
|
if (!config.scheme) {
|
|
92
97
|
throw new TonConnectError('scheme is required');
|
|
93
98
|
}
|
|
94
|
-
// Validate network
|
|
95
99
|
const network = config.network || 'mainnet';
|
|
96
|
-
if (network !== 'mainnet' && network !== 'testnet') {
|
|
97
|
-
throw new TonConnectError('Network must be either "mainnet" or "testnet"');
|
|
98
|
-
}
|
|
99
|
-
// Set default TON API endpoint based on network
|
|
100
100
|
const defaultTonApiEndpoint = network === 'testnet'
|
|
101
101
|
? 'https://testnet.toncenter.com/api/v2'
|
|
102
102
|
: 'https://toncenter.com/api/v2';
|
|
103
103
|
this.config = {
|
|
104
104
|
storageKeyPrefix: 'tonconnect_',
|
|
105
|
-
connectionTimeout: 300000,
|
|
106
|
-
transactionTimeout: 300000,
|
|
107
|
-
skipCanOpenURLCheck: true,
|
|
105
|
+
connectionTimeout: 300000,
|
|
106
|
+
transactionTimeout: 300000,
|
|
107
|
+
skipCanOpenURLCheck: true,
|
|
108
108
|
preferredWallet: config.preferredWallet,
|
|
109
109
|
network,
|
|
110
110
|
tonApiEndpoint: config.tonApiEndpoint || defaultTonApiEndpoint,
|
|
111
111
|
...config,
|
|
112
112
|
};
|
|
113
|
-
// Determine
|
|
113
|
+
// Determine wallet
|
|
114
114
|
if (this.config.preferredWallet) {
|
|
115
115
|
const wallet = (0, wallets_1.getWalletByName)(this.config.preferredWallet);
|
|
116
|
-
|
|
117
|
-
this.currentWallet = wallet;
|
|
118
|
-
console.log('[TON Connect] Using preferred wallet:', wallet.name);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
console.warn('[TON Connect] Preferred wallet not found, using default');
|
|
122
|
-
this.currentWallet = (0, wallets_1.getDefaultWallet)();
|
|
123
|
-
}
|
|
116
|
+
this.currentWallet = wallet || (0, wallets_1.getDefaultWallet)();
|
|
124
117
|
}
|
|
125
118
|
else {
|
|
126
119
|
this.currentWallet = (0, wallets_1.getDefaultWallet)();
|
|
127
120
|
}
|
|
128
|
-
console.log('[TON Connect] Initializing SDK with
|
|
129
|
-
|
|
130
|
-
scheme: this.config.scheme,
|
|
131
|
-
network: this.config.network,
|
|
132
|
-
wallet: this.currentWallet.name,
|
|
133
|
-
universalLink: this.currentWallet.universalLink,
|
|
134
|
-
});
|
|
121
|
+
console.log('[TON Connect] Initializing SDK v2 with wallet:', this.currentWallet.name);
|
|
122
|
+
console.log('[TON Connect] Bridge URL:', this.currentWallet.bridgeUrl);
|
|
135
123
|
// Initialize platform adapter
|
|
136
124
|
this.adapter = this.createAdapter();
|
|
137
|
-
console.log('[TON Connect] Adapter initialized:', this.adapter.constructor.name);
|
|
138
|
-
// Set up URL listener
|
|
139
|
-
this.setupURLListener();
|
|
140
125
|
// Load persisted session
|
|
141
126
|
this.loadSession();
|
|
142
127
|
}
|
|
143
128
|
/**
|
|
144
|
-
* Create platform adapter
|
|
129
|
+
* Create platform adapter
|
|
145
130
|
*/
|
|
146
131
|
createAdapter() {
|
|
147
|
-
// Check if we're in a web environment
|
|
148
132
|
// eslint-disable-next-line no-undef
|
|
149
133
|
if (typeof globalThis !== 'undefined' && globalThis.window && globalThis.document) {
|
|
150
|
-
// Web platform
|
|
151
|
-
console.log('[TON Connect] Using WebAdapter');
|
|
152
134
|
return new web_1.WebAdapter();
|
|
153
135
|
}
|
|
154
|
-
// Try to detect Expo environment
|
|
155
136
|
try {
|
|
156
|
-
// Check if expo-linking is available
|
|
157
137
|
if (typeof require !== 'undefined') {
|
|
158
138
|
const expoLinking = require('expo-linking');
|
|
159
139
|
if (expoLinking) {
|
|
160
|
-
console.log('[TON Connect] Using ExpoAdapter');
|
|
161
140
|
return new expo_1.ExpoAdapter();
|
|
162
141
|
}
|
|
163
142
|
}
|
|
164
143
|
}
|
|
165
|
-
catch
|
|
166
|
-
|
|
167
|
-
// expo-linking not available, continue to React Native adapter
|
|
144
|
+
catch {
|
|
145
|
+
// Not Expo
|
|
168
146
|
}
|
|
169
|
-
// Fall back to React Native adapter
|
|
170
|
-
// This will work for both React Native CLI and Expo (since Expo also has react-native)
|
|
171
|
-
console.log('[TON Connect] Using ReactNativeAdapter');
|
|
172
147
|
return new react_native_1.ReactNativeAdapter();
|
|
173
148
|
}
|
|
174
149
|
/**
|
|
175
|
-
*
|
|
176
|
-
*/
|
|
177
|
-
setupURLListener() {
|
|
178
|
-
console.log('[TON Connect] Setting up URL listener...');
|
|
179
|
-
this.urlUnsubscribe = this.adapter.addURLListener((url) => {
|
|
180
|
-
console.log('[TON Connect] URL callback received:', url);
|
|
181
|
-
this.handleCallback(url);
|
|
182
|
-
});
|
|
183
|
-
// Also check initial URL (when app was opened via deep link)
|
|
184
|
-
this.adapter.getInitialURL().then((url) => {
|
|
185
|
-
if (url) {
|
|
186
|
-
console.log('[TON Connect] Initial URL found:', url);
|
|
187
|
-
this.handleCallback(url);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
console.log('[TON Connect] No initial URL');
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Handle callback from wallet
|
|
150
|
+
* Handle incoming bridge message from wallet
|
|
196
151
|
*/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
console.log('[TON Connect] URL starts with scheme?', url?.startsWith(`${this.config.scheme}://`));
|
|
201
|
-
// CRITICAL FIX: Check if URL matches our scheme
|
|
202
|
-
if (!url || typeof url !== 'string') {
|
|
203
|
-
console.log('[TON Connect] Invalid URL, ignoring:', url);
|
|
152
|
+
handleBridgeMessage(msg) {
|
|
153
|
+
if (!this.session) {
|
|
154
|
+
console.error('[TON Connect] Received bridge message but no session');
|
|
204
155
|
return;
|
|
205
156
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
promise.reject(new UserRejectedError());
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
promise.reject(new TonConnectError(errorData.error.message || 'Sign data failed'));
|
|
227
|
-
}
|
|
228
|
-
return;
|
|
157
|
+
try {
|
|
158
|
+
// Decode base64 encrypted message
|
|
159
|
+
const encryptedBytes = (0, session_1.base64ToBytes)(msg.message);
|
|
160
|
+
const senderPublicKey = (0, session_1.hexToBytes)(msg.from);
|
|
161
|
+
// Decrypt
|
|
162
|
+
const decrypted = this.session.decrypt(encryptedBytes, senderPublicKey);
|
|
163
|
+
console.log('[TON Connect] Decrypted bridge message');
|
|
164
|
+
// Store wallet's bridge public key for future communication
|
|
165
|
+
this.walletBridgePublicKey = msg.from;
|
|
166
|
+
// Try to parse as connect response first
|
|
167
|
+
const connectResult = (0, protocol_1.parseConnectResponse)(decrypted);
|
|
168
|
+
if (connectResult) {
|
|
169
|
+
if (connectResult.type === 'connect') {
|
|
170
|
+
this.handleConnectSuccess(connectResult.data);
|
|
171
|
+
}
|
|
172
|
+
else if (connectResult.type === 'error') {
|
|
173
|
+
this.handleConnectError(connectResult.data);
|
|
229
174
|
}
|
|
175
|
+
return;
|
|
230
176
|
}
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this.
|
|
239
|
-
promise.resolve({
|
|
240
|
-
signature: data.signature,
|
|
241
|
-
timestamp: data.timestamp || Date.now(),
|
|
242
|
-
});
|
|
243
|
-
return;
|
|
177
|
+
// Try to parse as RPC response
|
|
178
|
+
const rpcResult = (0, protocol_1.parseRpcResponse)(decrypted);
|
|
179
|
+
if (rpcResult) {
|
|
180
|
+
if (rpcResult.type === 'event' && rpcResult.event === 'disconnect') {
|
|
181
|
+
this.handleRemoteDisconnect();
|
|
182
|
+
}
|
|
183
|
+
else if (rpcResult.type === 'result') {
|
|
184
|
+
this.handleRpcResult(rpcResult.data);
|
|
244
185
|
}
|
|
186
|
+
else if (rpcResult.type === 'error') {
|
|
187
|
+
this.handleRpcError(rpcResult.data);
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
245
190
|
}
|
|
191
|
+
console.warn('[TON Connect] Unknown bridge message format');
|
|
246
192
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this.handleConnectionResponse(parsed.data);
|
|
250
|
-
}
|
|
251
|
-
else if (parsed.type === 'transaction' && parsed.data) {
|
|
252
|
-
this.handleTransactionResponse(parsed.data);
|
|
193
|
+
catch (error) {
|
|
194
|
+
console.error('[TON Connect] Error handling bridge message:', error);
|
|
253
195
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Handle successful wallet connection
|
|
199
|
+
*/
|
|
200
|
+
handleConnectSuccess(event) {
|
|
201
|
+
try {
|
|
202
|
+
const wallet = (0, protocol_1.extractWalletInfoFromEvent)(event);
|
|
203
|
+
// Save session for persistence
|
|
204
|
+
this.saveSession(wallet).catch((err) => {
|
|
205
|
+
console.error('[TON Connect] Failed to save session:', err);
|
|
206
|
+
});
|
|
207
|
+
// Update status
|
|
208
|
+
this.currentStatus = { connected: true, wallet };
|
|
209
|
+
this.notifyStatusChange();
|
|
210
|
+
this.emit('connect', wallet);
|
|
211
|
+
// Resolve pending connection promise
|
|
212
|
+
if (this.connectionPromise) {
|
|
213
|
+
if (this.connectionPromise.timeout) {
|
|
214
|
+
clearTimeout(this.connectionPromise.timeout);
|
|
262
215
|
}
|
|
216
|
+
const promise = this.connectionPromise;
|
|
217
|
+
this.connectionPromise = null;
|
|
218
|
+
promise.resolve(wallet);
|
|
263
219
|
}
|
|
264
220
|
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.error('[TON Connect] Error processing connect response:', error);
|
|
223
|
+
this.rejectConnection(new TonConnectError(error?.message || 'Invalid connect response'));
|
|
224
|
+
}
|
|
265
225
|
}
|
|
266
226
|
/**
|
|
267
|
-
* Handle
|
|
227
|
+
* Handle connect error from wallet
|
|
268
228
|
*/
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (response.proof) {
|
|
276
|
-
const isValid = (0, crypto_1.verifyConnectionProof)(response, this.config.manifestUrl);
|
|
277
|
-
if (!isValid) {
|
|
278
|
-
this.rejectWithError(new TonConnectError('Connection proof verification failed'));
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
229
|
+
handleConnectError(event) {
|
|
230
|
+
const code = event.payload?.code;
|
|
231
|
+
const message = event.payload?.message || 'Connection rejected';
|
|
232
|
+
let error;
|
|
233
|
+
if (code === 300) {
|
|
234
|
+
error = new UserRejectedError(message);
|
|
281
235
|
}
|
|
282
236
|
else {
|
|
283
|
-
|
|
284
|
-
console.warn('TON Connect: Connection proof missing - wallet may not support proof verification');
|
|
285
|
-
}
|
|
286
|
-
const wallet = (0, protocol_1.extractWalletInfo)(response);
|
|
287
|
-
// Validate session ID before saving
|
|
288
|
-
if (!this.validateSessionId(response.session)) {
|
|
289
|
-
this.rejectWithError(new TonConnectError('Invalid session ID format'));
|
|
290
|
-
return;
|
|
237
|
+
error = new TonConnectError(message, String(code));
|
|
291
238
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
239
|
+
this.rejectConnection(error);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Handle remote disconnect from wallet
|
|
243
|
+
*/
|
|
244
|
+
handleRemoteDisconnect() {
|
|
245
|
+
console.log('[TON Connect] Wallet disconnected remotely');
|
|
246
|
+
this.currentStatus = { connected: false, wallet: null };
|
|
247
|
+
this.clearSession().catch(() => { });
|
|
299
248
|
this.notifyStatusChange();
|
|
300
|
-
|
|
301
|
-
this.emit('connect', wallet);
|
|
302
|
-
// Resolve connection promise
|
|
303
|
-
// CRITICAL: Only resolve if promise still exists and hasn't timed out
|
|
304
|
-
if (this.connectionPromise) {
|
|
305
|
-
// Clear timeout if it exists
|
|
306
|
-
if (this.connectionPromise.timeout !== null) {
|
|
307
|
-
clearTimeout(this.connectionPromise.timeout);
|
|
308
|
-
}
|
|
309
|
-
// Store reference before clearing to prevent race conditions
|
|
310
|
-
const promise = this.connectionPromise;
|
|
311
|
-
// Clear promise first
|
|
312
|
-
this.connectionPromise = null;
|
|
313
|
-
// Then resolve
|
|
314
|
-
promise.resolve(wallet);
|
|
315
|
-
}
|
|
249
|
+
this.emit('disconnect', null);
|
|
316
250
|
}
|
|
317
251
|
/**
|
|
318
|
-
* Handle
|
|
252
|
+
* Handle RPC success result
|
|
319
253
|
*/
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
254
|
+
handleRpcResult(response) {
|
|
255
|
+
const pending = this.pendingRpcRequests.get(response.id);
|
|
256
|
+
if (pending) {
|
|
257
|
+
if (pending.timeout)
|
|
258
|
+
clearTimeout(pending.timeout);
|
|
259
|
+
this.pendingRpcRequests.delete(response.id);
|
|
260
|
+
pending.resolve(response.result);
|
|
324
261
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
this.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Handle RPC error result
|
|
265
|
+
*/
|
|
266
|
+
handleRpcError(response) {
|
|
267
|
+
const pending = this.pendingRpcRequests.get(response.id);
|
|
268
|
+
if (pending) {
|
|
269
|
+
if (pending.timeout)
|
|
270
|
+
clearTimeout(pending.timeout);
|
|
271
|
+
this.pendingRpcRequests.delete(response.id);
|
|
272
|
+
let error;
|
|
273
|
+
if (response.error.code === 300) {
|
|
274
|
+
error = new UserRejectedError(response.error.message);
|
|
337
275
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
// Then resolve
|
|
343
|
-
promise.resolve(transactionResult);
|
|
276
|
+
else {
|
|
277
|
+
error = new TonConnectError(response.error.message, String(response.error.code));
|
|
278
|
+
}
|
|
279
|
+
pending.reject(error);
|
|
344
280
|
}
|
|
345
281
|
}
|
|
346
282
|
/**
|
|
347
|
-
* Reject
|
|
283
|
+
* Reject pending connection promise
|
|
348
284
|
*/
|
|
349
|
-
|
|
350
|
-
// Emit error event
|
|
285
|
+
rejectConnection(error) {
|
|
351
286
|
this.emit('error', error);
|
|
352
287
|
if (this.connectionPromise) {
|
|
353
|
-
if (this.connectionPromise.timeout
|
|
288
|
+
if (this.connectionPromise.timeout) {
|
|
354
289
|
clearTimeout(this.connectionPromise.timeout);
|
|
355
290
|
}
|
|
356
|
-
this.connectionPromise
|
|
291
|
+
const promise = this.connectionPromise;
|
|
357
292
|
this.connectionPromise = null;
|
|
358
|
-
|
|
359
|
-
if (this.transactionPromise) {
|
|
360
|
-
if (this.transactionPromise.timeout !== null) {
|
|
361
|
-
clearTimeout(this.transactionPromise.timeout);
|
|
362
|
-
}
|
|
363
|
-
this.transactionPromise.reject(error);
|
|
364
|
-
this.transactionPromise = null;
|
|
365
|
-
}
|
|
366
|
-
// CRITICAL FIX: Also clear signDataPromise to prevent memory leaks
|
|
367
|
-
if (this.signDataPromise) {
|
|
368
|
-
if (this.signDataPromise.timeout !== null) {
|
|
369
|
-
clearTimeout(this.signDataPromise.timeout);
|
|
370
|
-
}
|
|
371
|
-
this.signDataPromise.reject(error);
|
|
372
|
-
this.signDataPromise = null;
|
|
293
|
+
promise.reject(error);
|
|
373
294
|
}
|
|
374
295
|
}
|
|
375
296
|
/**
|
|
376
|
-
* Connect to wallet
|
|
297
|
+
* Connect to wallet using TON Connect v2 bridge protocol
|
|
377
298
|
*/
|
|
378
299
|
async connect() {
|
|
379
|
-
console.log('[TON Connect] connect() called');
|
|
380
300
|
// If already connected, return current wallet
|
|
381
301
|
if (this.currentStatus.connected && this.currentStatus.wallet) {
|
|
382
|
-
console.log('[TON Connect] Already connected, returning existing wallet');
|
|
383
302
|
return this.currentStatus.wallet;
|
|
384
303
|
}
|
|
385
|
-
// CRITICAL FIX: Check if connection is already in progress
|
|
386
304
|
if (this.connectionPromise) {
|
|
387
|
-
console.log('[TON Connect] Connection already in progress');
|
|
388
305
|
throw new ConnectionInProgressError();
|
|
389
306
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
console.log('[TON Connect]
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const cleanPayload = decodeURIComponent(payload);
|
|
403
|
-
const decoded = (0, protocol_1.decodeBase64URL)(cleanPayload);
|
|
404
|
-
console.log('[TON Connect] Connection request payload:', JSON.stringify(decoded, null, 2));
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
catch (e) {
|
|
408
|
-
// Log decode errors for debugging but don't fail
|
|
409
|
-
console.log('[TON Connect] Could not decode payload for logging:', e?.message || e);
|
|
410
|
-
// This is just for logging, the actual URL is correct
|
|
411
|
-
}
|
|
412
|
-
console.log('[TON Connect] Built URL:', url.substring(0, 100) + '...');
|
|
413
|
-
console.log('[TON Connect] Full URL:', url);
|
|
414
|
-
console.log('[TON Connect] Manifest URL:', this.config.manifestUrl);
|
|
415
|
-
console.log('[TON Connect] Return scheme:', this.config.scheme);
|
|
416
|
-
console.log('[TON Connect] Adapter type:', this.adapter.constructor.name);
|
|
417
|
-
// Create promise for connection
|
|
307
|
+
console.log('[TON Connect] Starting connection to', this.currentWallet.name);
|
|
308
|
+
// 1. Create new session (X25519 keypair)
|
|
309
|
+
this.session = new session_1.SessionCrypto();
|
|
310
|
+
console.log('[TON Connect] Session ID:', this.session.sessionId.substring(0, 16) + '...');
|
|
311
|
+
// 2. Connect to bridge SSE
|
|
312
|
+
this.bridge.close(); // Close any existing connection
|
|
313
|
+
this.bridge.connect(this.currentWallet.bridgeUrl, this.session.sessionId, (msg) => this.handleBridgeMessage(msg), (error) => console.error('[TON Connect] Bridge error:', error));
|
|
314
|
+
console.log('[TON Connect] Bridge SSE connection initiated');
|
|
315
|
+
// 3. Build universal link
|
|
316
|
+
const universalLink = (0, protocol_1.buildConnectUniversalLink)(this.currentWallet.universalLink, this.session.sessionId, this.config.manifestUrl, 'back');
|
|
317
|
+
console.log('[TON Connect] Universal link built');
|
|
318
|
+
// 4. Return promise that resolves when wallet responds via bridge
|
|
418
319
|
return new Promise((resolve, reject) => {
|
|
419
|
-
|
|
420
|
-
this.connectionPromise = {
|
|
421
|
-
resolve: (wallet) => {
|
|
422
|
-
if (timeout !== null) {
|
|
423
|
-
clearTimeout(timeout);
|
|
424
|
-
}
|
|
425
|
-
this.connectionPromise = null;
|
|
426
|
-
resolve(wallet);
|
|
427
|
-
},
|
|
428
|
-
reject: (error) => {
|
|
429
|
-
if (timeout !== null) {
|
|
430
|
-
clearTimeout(timeout);
|
|
431
|
-
}
|
|
432
|
-
this.connectionPromise = null;
|
|
433
|
-
reject(error);
|
|
434
|
-
},
|
|
435
|
-
timeout: null,
|
|
436
|
-
};
|
|
437
|
-
// Set timeout
|
|
438
|
-
timeout = setTimeout(() => {
|
|
320
|
+
const timeout = setTimeout(() => {
|
|
439
321
|
if (this.connectionPromise) {
|
|
440
|
-
|
|
441
|
-
this.
|
|
322
|
+
this.connectionPromise = null;
|
|
323
|
+
this.bridge.close();
|
|
324
|
+
reject(new ConnectionTimeoutError());
|
|
442
325
|
}
|
|
443
326
|
}, this.config.connectionTimeout);
|
|
444
|
-
this.connectionPromise
|
|
445
|
-
// Open wallet app
|
|
446
|
-
console.log('[TON Connect]
|
|
447
|
-
this.adapter.openURL(
|
|
448
|
-
console.log('[TON Connect] openURL result:', success);
|
|
449
|
-
// URL opened successfully, wait for callback
|
|
450
|
-
// If success is false, it should have thrown an error
|
|
327
|
+
this.connectionPromise = { resolve, reject, timeout };
|
|
328
|
+
// 5. Open wallet app
|
|
329
|
+
console.log('[TON Connect] Opening wallet app...');
|
|
330
|
+
this.adapter.openURL(universalLink, this.config.skipCanOpenURLCheck).then((success) => {
|
|
451
331
|
if (!success && this.connectionPromise) {
|
|
452
|
-
|
|
453
|
-
this.
|
|
454
|
-
|
|
455
|
-
else {
|
|
456
|
-
console.log('[TON Connect] URL opened successfully, waiting for wallet callback...');
|
|
332
|
+
this.connectionPromise = null;
|
|
333
|
+
this.bridge.close();
|
|
334
|
+
reject(new TonConnectError('Failed to open wallet app'));
|
|
457
335
|
}
|
|
336
|
+
console.log('[TON Connect] Wallet app opened, waiting for bridge response...');
|
|
458
337
|
}).catch((error) => {
|
|
459
|
-
// Error opening URL - reject the promise
|
|
460
|
-
console.error('[TON Connect] Error opening URL:', error);
|
|
461
338
|
if (this.connectionPromise) {
|
|
462
|
-
this.connectionPromise.
|
|
339
|
+
if (this.connectionPromise.timeout)
|
|
340
|
+
clearTimeout(this.connectionPromise.timeout);
|
|
341
|
+
this.connectionPromise = null;
|
|
342
|
+
this.bridge.close();
|
|
343
|
+
reject(new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`));
|
|
463
344
|
}
|
|
464
345
|
});
|
|
465
346
|
});
|
|
466
347
|
}
|
|
467
348
|
/**
|
|
468
|
-
* Send transaction
|
|
349
|
+
* Send transaction via TON Connect v2 bridge protocol
|
|
469
350
|
*/
|
|
470
351
|
async sendTransaction(request) {
|
|
471
|
-
// Validate request
|
|
472
352
|
const validation = (0, protocol_1.validateTransactionRequest)(request);
|
|
473
353
|
if (!validation.valid) {
|
|
474
354
|
throw new TonConnectError(validation.error || 'Invalid transaction request');
|
|
475
355
|
}
|
|
476
|
-
// Check if connected
|
|
477
356
|
if (!this.currentStatus.connected || !this.currentStatus.wallet) {
|
|
478
357
|
throw new TonConnectError('Not connected to wallet. Call connect() first.');
|
|
479
358
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
this.transactionPromise = null;
|
|
495
|
-
resolve(response);
|
|
496
|
-
},
|
|
497
|
-
reject: (error) => {
|
|
498
|
-
if (timeout !== null) {
|
|
499
|
-
clearTimeout(timeout);
|
|
500
|
-
}
|
|
501
|
-
this.transactionPromise = null;
|
|
502
|
-
reject(error);
|
|
503
|
-
},
|
|
504
|
-
timeout: null,
|
|
505
|
-
};
|
|
506
|
-
// Set timeout
|
|
507
|
-
timeout = setTimeout(() => {
|
|
508
|
-
if (this.transactionPromise) {
|
|
509
|
-
this.transactionPromise.reject(new TransactionTimeoutError());
|
|
510
|
-
}
|
|
511
|
-
}, this.config.transactionTimeout);
|
|
512
|
-
this.transactionPromise.timeout = timeout;
|
|
513
|
-
// Open wallet app
|
|
514
|
-
this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
|
|
515
|
-
// URL opened successfully, wait for callback
|
|
516
|
-
// If success is false, it should have thrown an error
|
|
517
|
-
if (!success && this.transactionPromise) {
|
|
518
|
-
this.transactionPromise.reject(new TonConnectError('Failed to open wallet app. Please make sure a TON wallet is installed.'));
|
|
519
|
-
}
|
|
520
|
-
}).catch((error) => {
|
|
521
|
-
// Error opening URL - reject the promise
|
|
522
|
-
if (this.transactionPromise) {
|
|
523
|
-
this.transactionPromise.reject(new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`));
|
|
524
|
-
}
|
|
525
|
-
});
|
|
359
|
+
if (!this.session || !this.walletBridgePublicKey) {
|
|
360
|
+
throw new TonConnectError('Session not established. Please reconnect.');
|
|
361
|
+
}
|
|
362
|
+
// Build JSON-RPC request
|
|
363
|
+
const rpcId = this.rpcIdCounter++;
|
|
364
|
+
const rpcRequest = (0, protocol_1.buildSendTransactionRpcRequest)(request, rpcId);
|
|
365
|
+
// Encrypt and send via bridge
|
|
366
|
+
const walletPubKeyBytes = (0, session_1.hexToBytes)(this.walletBridgePublicKey);
|
|
367
|
+
const encrypted = this.session.encrypt(rpcRequest, walletPubKeyBytes);
|
|
368
|
+
await this.bridge.send(this.currentWallet.bridgeUrl, this.session.sessionId, this.walletBridgePublicKey, encrypted);
|
|
369
|
+
// Open wallet to foreground
|
|
370
|
+
const returnLink = (0, protocol_1.buildReturnUniversalLink)(this.currentWallet.universalLink, 'back');
|
|
371
|
+
this.adapter.openURL(returnLink, this.config.skipCanOpenURLCheck).catch(() => {
|
|
372
|
+
// Non-critical — wallet may already be in foreground
|
|
526
373
|
});
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Sign data (for authentication, etc.)
|
|
530
|
-
* Note: Not all wallets support signData. This is a TON Connect extension.
|
|
531
|
-
*/
|
|
532
|
-
async signData(data, version = '1.0') {
|
|
533
|
-
// Check if connected
|
|
534
|
-
if (!this.currentStatus.connected || !this.currentStatus.wallet) {
|
|
535
|
-
throw new TonConnectError('Not connected to wallet. Call connect() first.');
|
|
536
|
-
}
|
|
537
|
-
// Helper function to encode bytes to base64
|
|
538
|
-
const base64EncodeBytes = (bytes) => {
|
|
539
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
540
|
-
let result = '';
|
|
541
|
-
let i = 0;
|
|
542
|
-
while (i < bytes.length) {
|
|
543
|
-
const a = bytes[i++];
|
|
544
|
-
const b = i < bytes.length ? bytes[i++] : 0;
|
|
545
|
-
const c = i < bytes.length ? bytes[i++] : 0;
|
|
546
|
-
const bitmap = (a << 16) | (b << 8) | c;
|
|
547
|
-
result += chars.charAt((bitmap >> 18) & 63);
|
|
548
|
-
result += chars.charAt((bitmap >> 12) & 63);
|
|
549
|
-
result += i - 2 < bytes.length ? chars.charAt((bitmap >> 6) & 63) : '=';
|
|
550
|
-
result += i - 1 < bytes.length ? chars.charAt(bitmap & 63) : '=';
|
|
551
|
-
}
|
|
552
|
-
return result;
|
|
553
|
-
};
|
|
554
|
-
// Helper function to get TextEncoder
|
|
555
|
-
const getTextEncoder = () => {
|
|
556
|
-
// eslint-disable-next-line no-undef
|
|
557
|
-
if (typeof globalThis !== 'undefined' && globalThis.TextEncoder) {
|
|
558
|
-
// eslint-disable-next-line no-undef
|
|
559
|
-
return new globalThis.TextEncoder();
|
|
560
|
-
}
|
|
561
|
-
// Fallback: manual encoding
|
|
562
|
-
return {
|
|
563
|
-
encode(input) {
|
|
564
|
-
const bytes = new Uint8Array(input.length);
|
|
565
|
-
for (let i = 0; i < input.length; i++) {
|
|
566
|
-
bytes[i] = input.charCodeAt(i);
|
|
567
|
-
}
|
|
568
|
-
return bytes;
|
|
569
|
-
},
|
|
570
|
-
};
|
|
571
|
-
};
|
|
572
|
-
// Convert data to base64
|
|
573
|
-
let dataBase64;
|
|
574
|
-
if (typeof data === 'string') {
|
|
575
|
-
// Check if it's already base64
|
|
576
|
-
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
577
|
-
if (base64Regex.test(data) && data.length % 4 === 0) {
|
|
578
|
-
// Likely base64, use as-is
|
|
579
|
-
dataBase64 = data;
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
// Not base64, encode it
|
|
583
|
-
const encoder = getTextEncoder();
|
|
584
|
-
const bytes = encoder.encode(data);
|
|
585
|
-
dataBase64 = base64EncodeBytes(bytes);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
else {
|
|
589
|
-
// Uint8Array - convert to base64
|
|
590
|
-
dataBase64 = base64EncodeBytes(data);
|
|
591
|
-
}
|
|
592
|
-
// Build sign data request
|
|
593
|
-
const payload = {
|
|
594
|
-
manifestUrl: this.config.manifestUrl,
|
|
595
|
-
data: dataBase64,
|
|
596
|
-
version,
|
|
597
|
-
returnStrategy: this.currentWallet.preferredReturnStrategy || 'back',
|
|
598
|
-
returnScheme: this.currentWallet.requiresReturnScheme !== false ? this.config.scheme : undefined,
|
|
599
|
-
};
|
|
600
|
-
// Encode payload
|
|
601
|
-
const { encodeBase64URL } = require('./core/protocol');
|
|
602
|
-
const encoded = encodeBase64URL(payload);
|
|
603
|
-
// Build URL
|
|
604
|
-
const baseUrl = this.currentWallet.universalLink.endsWith('/ton-connect')
|
|
605
|
-
? this.currentWallet.universalLink
|
|
606
|
-
: `${this.currentWallet.universalLink}/ton-connect`;
|
|
607
|
-
const url = `${baseUrl}/sign-data?${encoded}`;
|
|
608
|
-
// Open wallet app and wait for response
|
|
374
|
+
// Wait for response via bridge
|
|
609
375
|
return new Promise((resolve, reject) => {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
resolve: (response) => {
|
|
619
|
-
if (timeout !== null) {
|
|
620
|
-
clearTimeout(timeout);
|
|
621
|
-
}
|
|
622
|
-
resolved = true;
|
|
623
|
-
if (this.signDataPromise === signDataPromise) {
|
|
624
|
-
this.signDataPromise = null;
|
|
625
|
-
}
|
|
626
|
-
resolve(response);
|
|
376
|
+
const timeout = setTimeout(() => {
|
|
377
|
+
this.pendingRpcRequests.delete(rpcId);
|
|
378
|
+
reject(new TransactionTimeoutError());
|
|
379
|
+
}, this.config.transactionTimeout);
|
|
380
|
+
this.pendingRpcRequests.set(rpcId, {
|
|
381
|
+
resolve: (result) => {
|
|
382
|
+
this.emit('transaction', { boc: result });
|
|
383
|
+
resolve({ boc: result });
|
|
627
384
|
},
|
|
628
385
|
reject: (error) => {
|
|
629
|
-
|
|
630
|
-
clearTimeout(timeout);
|
|
631
|
-
}
|
|
632
|
-
resolved = true;
|
|
633
|
-
if (this.signDataPromise === signDataPromise) {
|
|
634
|
-
this.signDataPromise = null;
|
|
635
|
-
}
|
|
386
|
+
this.emit('error', error);
|
|
636
387
|
reject(error);
|
|
637
388
|
},
|
|
638
|
-
timeout
|
|
639
|
-
};
|
|
640
|
-
// Set timeout
|
|
641
|
-
timeout = setTimeout(() => {
|
|
642
|
-
if (!resolved && this.signDataPromise === signDataPromise) {
|
|
643
|
-
this.signDataPromise = null;
|
|
644
|
-
signDataPromise.reject(new TonConnectError('Sign data request timed out'));
|
|
645
|
-
}
|
|
646
|
-
}, this.config.transactionTimeout);
|
|
647
|
-
signDataPromise.timeout = timeout;
|
|
648
|
-
// Store promise for callback handling
|
|
649
|
-
// CRITICAL FIX: Don't mutate handleCallback method - use a separate tracking mechanism
|
|
650
|
-
this.signDataPromise = signDataPromise;
|
|
651
|
-
// Open URL
|
|
652
|
-
this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then(() => {
|
|
653
|
-
// URL opened, wait for callback
|
|
654
|
-
// Callback will be handled by handleCallback method checking signDataPromise
|
|
655
|
-
}).catch((error) => {
|
|
656
|
-
// Clear promise on error
|
|
657
|
-
this.signDataPromise = null;
|
|
658
|
-
signDataPromise.reject(new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`));
|
|
389
|
+
timeout,
|
|
659
390
|
});
|
|
660
391
|
});
|
|
661
392
|
}
|
|
@@ -663,12 +394,28 @@ class TonConnectMobile {
|
|
|
663
394
|
* Disconnect from wallet
|
|
664
395
|
*/
|
|
665
396
|
async disconnect() {
|
|
397
|
+
// Send disconnect event via bridge if connected
|
|
398
|
+
if (this.session && this.walletBridgePublicKey) {
|
|
399
|
+
try {
|
|
400
|
+
const rpcId = this.rpcIdCounter++;
|
|
401
|
+
const disconnectRequest = (0, protocol_1.buildDisconnectRpcRequest)(rpcId);
|
|
402
|
+
const walletPubKeyBytes = (0, session_1.hexToBytes)(this.walletBridgePublicKey);
|
|
403
|
+
const encrypted = this.session.encrypt(disconnectRequest, walletPubKeyBytes);
|
|
404
|
+
await this.bridge.send(this.currentWallet.bridgeUrl, this.session.sessionId, this.walletBridgePublicKey, encrypted);
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
console.warn('[TON Connect] Failed to send disconnect to wallet:', error);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Close bridge
|
|
411
|
+
this.bridge.close();
|
|
666
412
|
// Clear session
|
|
413
|
+
this.session = null;
|
|
414
|
+
this.walletBridgePublicKey = null;
|
|
667
415
|
await this.clearSession();
|
|
668
416
|
// Update status
|
|
669
417
|
this.currentStatus = { connected: false, wallet: null };
|
|
670
418
|
this.notifyStatusChange();
|
|
671
|
-
// Emit disconnect event
|
|
672
419
|
this.emit('disconnect', null);
|
|
673
420
|
}
|
|
674
421
|
/**
|
|
@@ -691,25 +438,15 @@ class TonConnectMobile {
|
|
|
691
438
|
}
|
|
692
439
|
/**
|
|
693
440
|
* Check if a wallet is available on the current platform
|
|
694
|
-
* Note: This is a best-effort check and may not be 100% accurate
|
|
695
|
-
* CRITICAL FIX: On web, if wallet has universalLink, it's considered available
|
|
696
|
-
* because universal links can open in new tabs/windows
|
|
697
441
|
*/
|
|
698
442
|
async isWalletAvailable(walletName) {
|
|
699
443
|
const wallet = walletName ? (0, wallets_1.getWalletByName)(walletName) : this.currentWallet;
|
|
700
|
-
if (!wallet)
|
|
444
|
+
if (!wallet)
|
|
701
445
|
return false;
|
|
702
|
-
}
|
|
703
|
-
// CRITICAL FIX: Check adapter type to reliably detect web platform
|
|
704
|
-
// WebAdapter is only used on web, so this is the most reliable check
|
|
705
446
|
const isWeb = this.adapter.constructor.name === 'WebAdapter';
|
|
706
447
|
if (isWeb) {
|
|
707
|
-
// On web, if wallet has universalLink or supports web platform, it's available
|
|
708
|
-
// Universal links can open in a new tab on web
|
|
709
448
|
return wallet.platforms.includes('web') || !!wallet.universalLink;
|
|
710
449
|
}
|
|
711
|
-
// On mobile, we can't reliably check if wallet is installed
|
|
712
|
-
// Return true if wallet supports the current platform
|
|
713
450
|
// eslint-disable-next-line no-undef
|
|
714
451
|
const platform = typeof globalThis !== 'undefined' && globalThis.Platform
|
|
715
452
|
? globalThis.Platform.OS === 'ios' ? 'ios' : 'android'
|
|
@@ -717,20 +454,12 @@ class TonConnectMobile {
|
|
|
717
454
|
return wallet.platforms.includes(platform);
|
|
718
455
|
}
|
|
719
456
|
/**
|
|
720
|
-
* Set preferred wallet
|
|
457
|
+
* Set preferred wallet
|
|
721
458
|
*/
|
|
722
459
|
setPreferredWallet(walletName) {
|
|
723
460
|
const wallet = (0, wallets_1.getWalletByName)(walletName);
|
|
724
461
|
if (!wallet) {
|
|
725
|
-
throw new TonConnectError(`Wallet "${walletName}" not found. Available
|
|
726
|
-
}
|
|
727
|
-
// CRITICAL FIX: Clear any pending connection when wallet changes
|
|
728
|
-
if (this.connectionPromise) {
|
|
729
|
-
console.log('[TON Connect] Clearing pending connection due to wallet change');
|
|
730
|
-
if (this.connectionPromise.timeout !== null) {
|
|
731
|
-
clearTimeout(this.connectionPromise.timeout);
|
|
732
|
-
}
|
|
733
|
-
this.connectionPromise = null;
|
|
462
|
+
throw new TonConnectError(`Wallet "${walletName}" not found. Available: ${wallets_1.SUPPORTED_WALLETS.map((w) => w.name).join(', ')}`);
|
|
734
463
|
}
|
|
735
464
|
this.currentWallet = wallet;
|
|
736
465
|
console.log('[TON Connect] Preferred wallet changed to:', wallet.name);
|
|
@@ -740,32 +469,21 @@ class TonConnectMobile {
|
|
|
740
469
|
*/
|
|
741
470
|
onStatusChange(callback) {
|
|
742
471
|
this.statusChangeCallbacks.add(callback);
|
|
743
|
-
// Immediately call with current status
|
|
744
472
|
callback(this.getStatus());
|
|
745
|
-
// Return unsubscribe function
|
|
746
473
|
return () => {
|
|
747
474
|
this.statusChangeCallbacks.delete(callback);
|
|
748
475
|
};
|
|
749
476
|
}
|
|
750
|
-
/**
|
|
751
|
-
* Notify all status change callbacks
|
|
752
|
-
*/
|
|
753
477
|
notifyStatusChange() {
|
|
754
478
|
const status = this.getStatus();
|
|
755
|
-
this.statusChangeCallbacks.forEach((
|
|
479
|
+
this.statusChangeCallbacks.forEach((cb) => {
|
|
756
480
|
try {
|
|
757
|
-
|
|
758
|
-
}
|
|
759
|
-
catch (error) {
|
|
760
|
-
// Ignore errors in callbacks
|
|
481
|
+
cb(status);
|
|
761
482
|
}
|
|
483
|
+
catch { /* ignore */ }
|
|
762
484
|
});
|
|
763
|
-
// Emit statusChange event
|
|
764
485
|
this.emit('statusChange', status);
|
|
765
486
|
}
|
|
766
|
-
/**
|
|
767
|
-
* Emit event to all listeners
|
|
768
|
-
*/
|
|
769
487
|
emit(event, data) {
|
|
770
488
|
const listeners = this.eventListeners.get(event);
|
|
771
489
|
if (listeners) {
|
|
@@ -773,40 +491,26 @@ class TonConnectMobile {
|
|
|
773
491
|
try {
|
|
774
492
|
listener(data);
|
|
775
493
|
}
|
|
776
|
-
catch
|
|
777
|
-
console.error(`[TON Connect] Error in event listener for ${event}:`, error);
|
|
778
|
-
}
|
|
494
|
+
catch { /* ignore */ }
|
|
779
495
|
});
|
|
780
496
|
}
|
|
781
497
|
}
|
|
782
|
-
/**
|
|
783
|
-
* Add event listener
|
|
784
|
-
*/
|
|
785
498
|
on(event, listener) {
|
|
786
499
|
if (!this.eventListeners.has(event)) {
|
|
787
500
|
this.eventListeners.set(event, new Set());
|
|
788
501
|
}
|
|
789
502
|
this.eventListeners.get(event).add(listener);
|
|
790
|
-
// Return unsubscribe function
|
|
791
503
|
return () => {
|
|
792
504
|
const listeners = this.eventListeners.get(event);
|
|
793
|
-
if (listeners)
|
|
505
|
+
if (listeners)
|
|
794
506
|
listeners.delete(listener);
|
|
795
|
-
}
|
|
796
507
|
};
|
|
797
508
|
}
|
|
798
|
-
/**
|
|
799
|
-
* Remove event listener
|
|
800
|
-
*/
|
|
801
509
|
off(event, listener) {
|
|
802
510
|
const listeners = this.eventListeners.get(event);
|
|
803
|
-
if (listeners)
|
|
511
|
+
if (listeners)
|
|
804
512
|
listeners.delete(listener);
|
|
805
|
-
}
|
|
806
513
|
}
|
|
807
|
-
/**
|
|
808
|
-
* Remove all listeners for an event
|
|
809
|
-
*/
|
|
810
514
|
removeAllListeners(event) {
|
|
811
515
|
if (event) {
|
|
812
516
|
this.eventListeners.delete(event);
|
|
@@ -815,143 +519,35 @@ class TonConnectMobile {
|
|
|
815
519
|
this.eventListeners.clear();
|
|
816
520
|
}
|
|
817
521
|
}
|
|
818
|
-
/**
|
|
819
|
-
* Validate session ID format
|
|
820
|
-
*/
|
|
821
|
-
validateSessionId(sessionId) {
|
|
822
|
-
if (!sessionId || typeof sessionId !== 'string') {
|
|
823
|
-
return false;
|
|
824
|
-
}
|
|
825
|
-
// Session ID should be reasonable length (1-200 characters)
|
|
826
|
-
if (sessionId.length === 0 || sessionId.length > 200) {
|
|
827
|
-
return false;
|
|
828
|
-
}
|
|
829
|
-
// Basic validation: should not contain control characters
|
|
830
|
-
if (/[\x00-\x1F\x7F]/.test(sessionId)) {
|
|
831
|
-
return false;
|
|
832
|
-
}
|
|
833
|
-
return true;
|
|
834
|
-
}
|
|
835
|
-
/**
|
|
836
|
-
* Save session to storage
|
|
837
|
-
*/
|
|
838
|
-
async saveSession(sessionId, wallet) {
|
|
839
|
-
// Validate inputs
|
|
840
|
-
if (!this.validateSessionId(sessionId)) {
|
|
841
|
-
throw new TonConnectError('Invalid session ID format');
|
|
842
|
-
}
|
|
843
|
-
if (!wallet || !wallet.address || !wallet.publicKey) {
|
|
844
|
-
throw new TonConnectError('Invalid wallet data');
|
|
845
|
-
}
|
|
846
|
-
try {
|
|
847
|
-
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
848
|
-
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
849
|
-
await this.adapter.setItem(sessionKey, sessionId);
|
|
850
|
-
await this.adapter.setItem(walletKey, JSON.stringify(wallet));
|
|
851
|
-
}
|
|
852
|
-
catch (error) {
|
|
853
|
-
// Log error but don't throw - connection is still valid
|
|
854
|
-
console.error('TON Connect: Failed to save session to storage:', error);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Load session from storage
|
|
859
|
-
*/
|
|
860
|
-
async loadSession() {
|
|
861
|
-
try {
|
|
862
|
-
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
863
|
-
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
864
|
-
const sessionId = await this.adapter.getItem(sessionKey);
|
|
865
|
-
const walletJson = await this.adapter.getItem(walletKey);
|
|
866
|
-
if (sessionId && walletJson) {
|
|
867
|
-
try {
|
|
868
|
-
// Validate session ID
|
|
869
|
-
if (!this.validateSessionId(sessionId)) {
|
|
870
|
-
await this.clearSession();
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
const wallet = JSON.parse(walletJson);
|
|
874
|
-
// Validate wallet data
|
|
875
|
-
if (!wallet || !wallet.address || !wallet.publicKey) {
|
|
876
|
-
await this.clearSession();
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
this.currentStatus = { connected: true, wallet };
|
|
880
|
-
this.notifyStatusChange();
|
|
881
|
-
}
|
|
882
|
-
catch (error) {
|
|
883
|
-
// Invalid wallet data, clear it
|
|
884
|
-
console.error('TON Connect: Invalid session data, clearing:', error);
|
|
885
|
-
await this.clearSession();
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
catch (error) {
|
|
890
|
-
// Log storage errors for debugging
|
|
891
|
-
console.error('TON Connect: Failed to load session from storage:', error);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* Clear session from storage
|
|
896
|
-
*/
|
|
897
|
-
async clearSession() {
|
|
898
|
-
try {
|
|
899
|
-
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
900
|
-
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
901
|
-
await this.adapter.removeItem(sessionKey);
|
|
902
|
-
await this.adapter.removeItem(walletKey);
|
|
903
|
-
}
|
|
904
|
-
catch (error) {
|
|
905
|
-
// Ignore storage errors
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
522
|
/**
|
|
909
523
|
* Cleanup resources
|
|
910
524
|
*/
|
|
911
525
|
destroy() {
|
|
912
|
-
|
|
913
|
-
this.urlUnsubscribe();
|
|
914
|
-
this.urlUnsubscribe = null;
|
|
915
|
-
}
|
|
916
|
-
if ('destroy' in this.adapter && typeof this.adapter.destroy === 'function') {
|
|
917
|
-
this.adapter.destroy();
|
|
918
|
-
}
|
|
526
|
+
this.bridge.close();
|
|
919
527
|
this.statusChangeCallbacks.clear();
|
|
920
528
|
this.eventListeners.clear();
|
|
921
529
|
this.connectionPromise = null;
|
|
922
|
-
this.
|
|
923
|
-
|
|
530
|
+
this.pendingRpcRequests.forEach((req) => {
|
|
531
|
+
if (req.timeout)
|
|
532
|
+
clearTimeout(req.timeout);
|
|
533
|
+
});
|
|
534
|
+
this.pendingRpcRequests.clear();
|
|
924
535
|
}
|
|
925
|
-
/**
|
|
926
|
-
* Get current network
|
|
927
|
-
*/
|
|
928
536
|
getNetwork() {
|
|
929
537
|
return this.config.network;
|
|
930
538
|
}
|
|
931
|
-
/**
|
|
932
|
-
* Set network (mainnet/testnet)
|
|
933
|
-
*/
|
|
934
539
|
setNetwork(network) {
|
|
935
540
|
if (network !== 'mainnet' && network !== 'testnet') {
|
|
936
|
-
throw new TonConnectError('Network must be
|
|
541
|
+
throw new TonConnectError('Network must be "mainnet" or "testnet"');
|
|
937
542
|
}
|
|
938
543
|
const oldNetwork = this.config.network;
|
|
939
|
-
// Warn if switching network while connected (wallet connection is network-specific)
|
|
940
|
-
if (this.currentStatus.connected && oldNetwork !== network) {
|
|
941
|
-
console.warn('[TON Connect] Network changed while wallet is connected. ' +
|
|
942
|
-
'The wallet connection may be invalid for the new network. ' +
|
|
943
|
-
'Consider disconnecting and reconnecting after network change.');
|
|
944
|
-
}
|
|
945
544
|
this.config.network = network;
|
|
946
|
-
// Update TON API endpoint if not explicitly set
|
|
947
545
|
if (!this.config.tonApiEndpoint || this.config.tonApiEndpoint.includes(oldNetwork)) {
|
|
948
546
|
this.config.tonApiEndpoint =
|
|
949
547
|
network === 'testnet'
|
|
950
548
|
? 'https://testnet.toncenter.com/api/v2'
|
|
951
549
|
: 'https://toncenter.com/api/v2';
|
|
952
550
|
}
|
|
953
|
-
console.log('[TON Connect] Network changed to:', network);
|
|
954
|
-
// Notify status change to update chain ID in React components
|
|
955
551
|
this.notifyStatusChange();
|
|
956
552
|
}
|
|
957
553
|
/**
|
|
@@ -960,159 +556,116 @@ class TonConnectMobile {
|
|
|
960
556
|
async getBalance(address) {
|
|
961
557
|
const targetAddress = address || this.currentStatus.wallet?.address;
|
|
962
558
|
if (!targetAddress) {
|
|
963
|
-
throw new TonConnectError('Address
|
|
559
|
+
throw new TonConnectError('Address required. Connect a wallet or provide an address.');
|
|
560
|
+
}
|
|
561
|
+
const apiEndpoint = this.config.tonApiEndpoint ||
|
|
562
|
+
(this.config.network === 'testnet'
|
|
563
|
+
? 'https://testnet.toncenter.com/api/v2'
|
|
564
|
+
: 'https://toncenter.com/api/v2');
|
|
565
|
+
const url = `${apiEndpoint}/getAddressInformation?address=${encodeURIComponent(targetAddress)}`;
|
|
566
|
+
const response = await fetch(url, {
|
|
567
|
+
method: 'GET',
|
|
568
|
+
headers: { 'Accept': 'application/json' },
|
|
569
|
+
});
|
|
570
|
+
if (!response.ok) {
|
|
571
|
+
throw new TonConnectError(`Failed to fetch balance: ${response.status}`);
|
|
572
|
+
}
|
|
573
|
+
const data = await response.json();
|
|
574
|
+
if (data.ok === false) {
|
|
575
|
+
throw new TonConnectError(data.error || 'Failed to fetch balance');
|
|
576
|
+
}
|
|
577
|
+
const balance = data.result?.balance || '0';
|
|
578
|
+
const balanceTon = (BigInt(balance) / BigInt(1000000000)).toString() +
|
|
579
|
+
'.' +
|
|
580
|
+
(BigInt(balance) % BigInt(1000000000)).toString().padStart(9, '0').replace(/0+$/, '');
|
|
581
|
+
return {
|
|
582
|
+
balance,
|
|
583
|
+
balanceTon: balanceTon === '0.' ? '0' : balanceTon,
|
|
584
|
+
network: this.config.network,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get transaction status by hash
|
|
589
|
+
*/
|
|
590
|
+
async getTransactionStatusByHash(txHash, address) {
|
|
591
|
+
if (!txHash)
|
|
592
|
+
throw new TonConnectError('Transaction hash is required');
|
|
593
|
+
if (!address)
|
|
594
|
+
throw new TonConnectError('Address is required');
|
|
595
|
+
const apiEndpoint = this.config.tonApiEndpoint ||
|
|
596
|
+
(this.config.network === 'testnet'
|
|
597
|
+
? 'https://testnet.toncenter.com/api/v2'
|
|
598
|
+
: 'https://toncenter.com/api/v2');
|
|
599
|
+
const url = `${apiEndpoint}/getTransactions?address=${encodeURIComponent(address)}&limit=100`;
|
|
600
|
+
const response = await fetch(url, {
|
|
601
|
+
method: 'GET',
|
|
602
|
+
headers: { 'Accept': 'application/json' },
|
|
603
|
+
});
|
|
604
|
+
if (!response.ok) {
|
|
605
|
+
throw new TonConnectError(`Failed to fetch transactions: ${response.status}`);
|
|
964
606
|
}
|
|
965
|
-
|
|
966
|
-
if (
|
|
967
|
-
throw new TonConnectError('
|
|
607
|
+
const data = await response.json();
|
|
608
|
+
if (data.ok === false) {
|
|
609
|
+
throw new TonConnectError(data.error || 'Failed to fetch transactions');
|
|
968
610
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
: 'https://toncenter.com/api/v2');
|
|
974
|
-
const url = `${apiEndpoint}/getAddressInformation?address=${encodeURIComponent(targetAddress)}`;
|
|
975
|
-
const response = await fetch(url, {
|
|
976
|
-
method: 'GET',
|
|
977
|
-
headers: {
|
|
978
|
-
'Accept': 'application/json',
|
|
979
|
-
},
|
|
980
|
-
});
|
|
981
|
-
if (!response.ok) {
|
|
982
|
-
throw new TonConnectError(`Failed to fetch balance: ${response.status} ${response.statusText}`);
|
|
983
|
-
}
|
|
984
|
-
const data = await response.json();
|
|
985
|
-
if (data.ok === false) {
|
|
986
|
-
throw new TonConnectError(data.error || 'Failed to fetch balance');
|
|
987
|
-
}
|
|
988
|
-
// TON Center API returns balance in nanotons
|
|
989
|
-
const balance = data.result?.balance || '0';
|
|
990
|
-
const balanceTon = (BigInt(balance) / BigInt(1000000000)).toString() + '.' +
|
|
991
|
-
(BigInt(balance) % BigInt(1000000000)).toString().padStart(9, '0').replace(/0+$/, '');
|
|
611
|
+
const transactions = data.result || [];
|
|
612
|
+
const transaction = transactions.find((tx) => tx.transaction_id?.hash === txHash ||
|
|
613
|
+
tx.transaction_id?.lt === txHash);
|
|
614
|
+
if (transaction) {
|
|
992
615
|
return {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
616
|
+
status: 'confirmed',
|
|
617
|
+
hash: transaction.transaction_id?.hash || txHash,
|
|
618
|
+
blockNumber: transaction.transaction_id?.lt,
|
|
996
619
|
};
|
|
997
620
|
}
|
|
998
|
-
|
|
999
|
-
if (error instanceof TonConnectError) {
|
|
1000
|
-
throw error;
|
|
1001
|
-
}
|
|
1002
|
-
throw new TonConnectError(`Failed to get balance: ${error?.message || String(error)}`);
|
|
1003
|
-
}
|
|
621
|
+
return { status: 'pending', hash: txHash };
|
|
1004
622
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
623
|
+
// ─── Session Persistence ───
|
|
624
|
+
async saveSession(wallet) {
|
|
625
|
+
if (!this.session || !this.walletBridgePublicKey)
|
|
626
|
+
return;
|
|
627
|
+
const sessionData = {
|
|
628
|
+
sessionSecretKey: (0, session_1.bytesToHex)(this.session.secretKey),
|
|
629
|
+
walletPublicKey: this.walletBridgePublicKey,
|
|
630
|
+
bridgeUrl: this.currentWallet.bridgeUrl,
|
|
631
|
+
wallet,
|
|
632
|
+
};
|
|
633
|
+
const key = `${this.config.storageKeyPrefix}session_v2`;
|
|
634
|
+
await this.adapter.setItem(key, JSON.stringify(sessionData));
|
|
635
|
+
}
|
|
636
|
+
async loadSession() {
|
|
1014
637
|
try {
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
// 3. Handle different confirmation states
|
|
1024
|
-
// For now, we'll return a basic status
|
|
1025
|
-
// In a real implementation, you'd query the blockchain API
|
|
1026
|
-
let attempts = 0;
|
|
1027
|
-
let lastError = null;
|
|
1028
|
-
while (attempts < maxAttempts) {
|
|
1029
|
-
try {
|
|
1030
|
-
// This is a placeholder - you'd need to implement actual transaction lookup
|
|
1031
|
-
// For now, we'll simulate checking
|
|
1032
|
-
await new Promise((resolve) => setTimeout(() => resolve(), intervalMs));
|
|
1033
|
-
// In production, you would:
|
|
1034
|
-
// 1. Parse BOC to get transaction hash
|
|
1035
|
-
// 2. Query TON API: GET /getTransactions?address=...&limit=1
|
|
1036
|
-
// 3. Check if transaction exists and is confirmed
|
|
1037
|
-
// For now, return unknown status (as we can't parse BOC without additional libraries)
|
|
1038
|
-
return {
|
|
1039
|
-
status: 'unknown',
|
|
1040
|
-
error: 'Transaction status checking requires BOC parsing. Please use a TON library to parse the BOC and extract the transaction hash.',
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
catch (error) {
|
|
1044
|
-
lastError = error;
|
|
1045
|
-
attempts++;
|
|
1046
|
-
if (attempts < maxAttempts) {
|
|
1047
|
-
await new Promise((resolve) => setTimeout(() => resolve(), intervalMs));
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
638
|
+
const key = `${this.config.storageKeyPrefix}session_v2`;
|
|
639
|
+
const json = await this.adapter.getItem(key);
|
|
640
|
+
if (!json)
|
|
641
|
+
return;
|
|
642
|
+
const data = JSON.parse(json);
|
|
643
|
+
if (!data.sessionSecretKey || !data.walletPublicKey || !data.wallet) {
|
|
644
|
+
await this.clearSession();
|
|
645
|
+
return;
|
|
1050
646
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
647
|
+
// Restore session
|
|
648
|
+
this.session = session_1.SessionCrypto.fromState({ secretKey: data.sessionSecretKey });
|
|
649
|
+
this.walletBridgePublicKey = data.walletPublicKey;
|
|
650
|
+
// Reconnect to bridge
|
|
651
|
+
this.bridge.connect(data.bridgeUrl, this.session.sessionId, (msg) => this.handleBridgeMessage(msg), (error) => console.error('[TON Connect] Bridge error:', error));
|
|
652
|
+
// Restore status
|
|
653
|
+
this.currentStatus = { connected: true, wallet: data.wallet };
|
|
654
|
+
this.notifyStatusChange();
|
|
655
|
+
console.log('[TON Connect] Session restored for wallet:', data.wallet.name);
|
|
1055
656
|
}
|
|
1056
657
|
catch (error) {
|
|
1057
|
-
|
|
658
|
+
console.error('[TON Connect] Failed to load session:', error);
|
|
659
|
+
await this.clearSession();
|
|
1058
660
|
}
|
|
1059
661
|
}
|
|
1060
|
-
|
|
1061
|
-
* Get transaction status by hash (more reliable than BOC)
|
|
1062
|
-
*/
|
|
1063
|
-
async getTransactionStatusByHash(txHash, address) {
|
|
1064
|
-
if (!txHash || typeof txHash !== 'string' || txHash.length === 0) {
|
|
1065
|
-
throw new TonConnectError('Transaction hash is required');
|
|
1066
|
-
}
|
|
1067
|
-
if (!address || typeof address !== 'string' || address.length === 0) {
|
|
1068
|
-
throw new TonConnectError('Address is required');
|
|
1069
|
-
}
|
|
662
|
+
async clearSession() {
|
|
1070
663
|
try {
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
? 'https://testnet.toncenter.com/api/v2'
|
|
1074
|
-
: 'https://toncenter.com/api/v2');
|
|
1075
|
-
// Query transactions for the address
|
|
1076
|
-
const url = `${apiEndpoint}/getTransactions?address=${encodeURIComponent(address)}&limit=100`;
|
|
1077
|
-
const response = await fetch(url, {
|
|
1078
|
-
method: 'GET',
|
|
1079
|
-
headers: {
|
|
1080
|
-
'Accept': 'application/json',
|
|
1081
|
-
},
|
|
1082
|
-
});
|
|
1083
|
-
if (!response.ok) {
|
|
1084
|
-
throw new TonConnectError(`Failed to fetch transactions: ${response.status} ${response.statusText}`);
|
|
1085
|
-
}
|
|
1086
|
-
const data = await response.json();
|
|
1087
|
-
if (data.ok === false) {
|
|
1088
|
-
throw new TonConnectError(data.error || 'Failed to fetch transactions');
|
|
1089
|
-
}
|
|
1090
|
-
// Search for transaction with matching hash
|
|
1091
|
-
const transactions = data.result || [];
|
|
1092
|
-
const transaction = transactions.find((tx) => tx.transaction_id?.hash === txHash ||
|
|
1093
|
-
tx.transaction_id?.lt === txHash ||
|
|
1094
|
-
JSON.stringify(tx.transaction_id).includes(txHash));
|
|
1095
|
-
if (transaction) {
|
|
1096
|
-
return {
|
|
1097
|
-
status: 'confirmed',
|
|
1098
|
-
hash: transaction.transaction_id?.hash || txHash,
|
|
1099
|
-
blockNumber: transaction.transaction_id?.lt,
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
// Transaction not found - could be pending or failed
|
|
1103
|
-
return {
|
|
1104
|
-
status: 'pending',
|
|
1105
|
-
hash: txHash,
|
|
1106
|
-
};
|
|
664
|
+
const key = `${this.config.storageKeyPrefix}session_v2`;
|
|
665
|
+
await this.adapter.removeItem(key);
|
|
1107
666
|
}
|
|
1108
|
-
catch
|
|
1109
|
-
|
|
1110
|
-
throw error;
|
|
1111
|
-
}
|
|
1112
|
-
return {
|
|
1113
|
-
status: 'failed',
|
|
1114
|
-
error: error?.message || 'Failed to check transaction status',
|
|
1115
|
-
};
|
|
667
|
+
catch {
|
|
668
|
+
// Ignore
|
|
1116
669
|
}
|
|
1117
670
|
}
|
|
1118
671
|
}
|