@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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * TON Connect Mobile SDK
4
- * Production-ready implementation for React Native and Expo
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 crypto_1 = require("./core/crypto");
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', 'Please make sure the wallet app is installed and try again. If the issue persists, check your internet connection.');
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. The wallet did not respond in time.', 'TRANSACTION_TIMEOUT', 'Please check the wallet app and try again. Make sure you approve or reject the transaction in the wallet.');
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', 'The user cancelled the operation in the wallet app.');
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', 'Please wait for the current connection attempt to complete before trying again.');
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', 'Please wait for the current transaction to complete before sending another one.');
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
- this.urlUnsubscribe = null;
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.transactionPromise = null;
86
- this.signDataPromise = null;
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, // 5 minutes
106
- transactionTimeout: 300000, // 5 minutes
107
- skipCanOpenURLCheck: true, // Skip canOpenURL check by default (Android issue)
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 which wallet to use
113
+ // Determine wallet
114
114
  if (this.config.preferredWallet) {
115
115
  const wallet = (0, wallets_1.getWalletByName)(this.config.preferredWallet);
116
- if (wallet) {
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 config:', {
129
- manifestUrl: this.config.manifestUrl,
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 based on available modules
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 (error) {
166
- console.log('[TON Connect] ExpoAdapter not available:', error);
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
- * Set up URL listener for wallet callbacks
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
- handleCallback(url) {
198
- console.log('[TON Connect] handleCallback called with URL:', url);
199
- console.log('[TON Connect] Expected scheme:', this.config.scheme);
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
- if (!url.startsWith(`${this.config.scheme}://`)) {
207
- console.log('[TON Connect] Callback URL does not match scheme, ignoring:', url);
208
- console.log('[TON Connect] Expected prefix:', `${this.config.scheme}://`);
209
- return;
210
- }
211
- const parsed = (0, protocol_1.parseCallbackURL)(url, this.config.scheme);
212
- console.log('[TON Connect] Parsed callback:', parsed.type, parsed.data ? 'has data' : 'no data');
213
- // CRITICAL FIX: Check for sign data response first (before other handlers)
214
- // Note: We check if promise exists and hasn't timed out (timeout !== null means not timed out yet)
215
- if (this.signDataPromise && this.signDataPromise.timeout !== null) {
216
- // Sign data request is pending and hasn't timed out
217
- if (parsed.type === 'error' && parsed.data) {
218
- const errorData = parsed.data;
219
- if (errorData?.error) {
220
- const promise = this.signDataPromise;
221
- this.signDataPromise = null;
222
- if (errorData.error.code === 300) {
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
- // Check for sign data response format
232
- // Note: TON Connect protocol may return sign data in different format
233
- // We check for signature field in the response
234
- if (parsed.data && typeof parsed.data === 'object') {
235
- const data = parsed.data;
236
- if (data.signature && typeof data.signature === 'string') {
237
- const promise = this.signDataPromise;
238
- this.signDataPromise = null;
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
- // Handle connection responses
248
- if (parsed.type === 'connect' && parsed.data) {
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
- else if (parsed.type === 'error' && parsed.data) {
255
- const errorData = parsed.data;
256
- if (errorData?.error) {
257
- if (errorData.error.code === 300 || errorData.error.message?.toLowerCase().includes('reject')) {
258
- this.rejectWithError(new UserRejectedError());
259
- }
260
- else {
261
- this.rejectWithError(new TonConnectError(errorData.error.message || 'Unknown error', String(errorData.error.code)));
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 connection response from wallet
227
+ * Handle connect error from wallet
268
228
  */
269
- handleConnectionResponse(response) {
270
- if (!(0, protocol_1.validateConnectionResponse)(response)) {
271
- this.rejectWithError(new TonConnectError('Invalid connection response'));
272
- return;
273
- }
274
- // Verify proof if present
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
- // Log warning if proof is missing (security consideration)
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
- // Save session
293
- this.saveSession(response.session, wallet).catch((error) => {
294
- console.error('TON Connect: Failed to save session:', error);
295
- // Continue anyway - connection is still valid
296
- });
297
- // Update status
298
- this.currentStatus = { connected: true, wallet };
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
- // Emit connect event
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 transaction response from wallet
252
+ * Handle RPC success result
319
253
  */
320
- handleTransactionResponse(response) {
321
- if (!(0, protocol_1.validateTransactionResponse)(response)) {
322
- this.rejectWithError(new TonConnectError('Invalid transaction response'));
323
- return;
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
- const transactionResult = {
326
- boc: response.boc,
327
- signature: response.signature,
328
- };
329
- // Emit transaction event
330
- this.emit('transaction', transactionResult);
331
- // Resolve transaction promise
332
- // CRITICAL: Only resolve if promise still exists and hasn't timed out
333
- if (this.transactionPromise) {
334
- // Clear timeout if it exists
335
- if (this.transactionPromise.timeout !== null) {
336
- clearTimeout(this.transactionPromise.timeout);
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
- // Store reference before clearing
339
- const promise = this.transactionPromise;
340
- // Clear promise first to prevent race conditions
341
- this.transactionPromise = null;
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 current promise with error
283
+ * Reject pending connection promise
348
284
  */
349
- rejectWithError(error) {
350
- // Emit error event
285
+ rejectConnection(error) {
351
286
  this.emit('error', error);
352
287
  if (this.connectionPromise) {
353
- if (this.connectionPromise.timeout !== null) {
288
+ if (this.connectionPromise.timeout) {
354
289
  clearTimeout(this.connectionPromise.timeout);
355
290
  }
356
- this.connectionPromise.reject(error);
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
- // Build connection request URL (use wallet's universal link)
391
- console.log('[TON Connect] Building connection request URL for wallet:', this.currentWallet.name);
392
- console.log('[TON Connect] Using universal link:', this.currentWallet.universalLink);
393
- console.log('[TON Connect] Wallet return strategy:', this.currentWallet.preferredReturnStrategy || 'back');
394
- console.log('[TON Connect] Wallet requires returnScheme:', this.currentWallet.requiresReturnScheme !== false);
395
- const url = (0, protocol_1.buildConnectionRequest)(this.config.manifestUrl, this.config.scheme, this.currentWallet.universalLink, this.currentWallet.preferredReturnStrategy, this.currentWallet.requiresReturnScheme);
396
- // DEBUG: Decode and log the payload for debugging
397
- try {
398
- const urlParts = url.split('?');
399
- if (urlParts.length > 1) {
400
- const payload = urlParts[1];
401
- // CRITICAL FIX: Handle URL encoding - payload might have additional encoding
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
- let timeout = null;
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
- console.log('[TON Connect] Connection timeout after', this.config.connectionTimeout, 'ms');
441
- this.connectionPromise.reject(new ConnectionTimeoutError());
322
+ this.connectionPromise = null;
323
+ this.bridge.close();
324
+ reject(new ConnectionTimeoutError());
442
325
  }
443
326
  }, this.config.connectionTimeout);
444
- this.connectionPromise.timeout = timeout;
445
- // Open wallet app
446
- console.log('[TON Connect] Attempting to open wallet app...');
447
- this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
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
- console.log('[TON Connect] openURL returned false, rejecting promise');
453
- this.connectionPromise.reject(new TonConnectError('Failed to open wallet app. Please make sure a TON wallet is installed.'));
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.reject(new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`));
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
- // CRITICAL FIX: Check if transaction is already in progress
481
- if (this.transactionPromise) {
482
- throw new TransactionInProgressError();
483
- }
484
- // Build transaction request URL (use universal link for Android compatibility)
485
- const url = (0, protocol_1.buildTransactionRequest)(this.config.manifestUrl, request, this.config.scheme, this.currentWallet.universalLink, this.currentWallet.preferredReturnStrategy, this.currentWallet.requiresReturnScheme);
486
- // Create promise for transaction
487
- return new Promise((resolve, reject) => {
488
- let timeout = null;
489
- this.transactionPromise = {
490
- resolve: (response) => {
491
- if (timeout !== null) {
492
- clearTimeout(timeout);
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
- let timeout = null;
611
- let resolved = false;
612
- // CRITICAL FIX: Check if sign data is already in progress
613
- if (this.signDataPromise) {
614
- throw new TonConnectError('Sign data request already in progress');
615
- }
616
- // Create promise for sign data
617
- const signDataPromise = {
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
- if (timeout !== null) {
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: null,
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 for connections
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 wallets: ${wallets_1.SUPPORTED_WALLETS.map(w => w.name).join(', ')}`);
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((callback) => {
479
+ this.statusChangeCallbacks.forEach((cb) => {
756
480
  try {
757
- callback(status);
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 (error) {
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
- if (this.urlUnsubscribe) {
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.transactionPromise = null;
923
- this.signDataPromise = null;
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 either "mainnet" or "testnet"');
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 is required. Either connect a wallet or provide an 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
- // Validate address format
966
- if (!/^[0-9A-Za-z_-]{48}$/.test(targetAddress)) {
967
- throw new TonConnectError('Invalid TON address format');
607
+ const data = await response.json();
608
+ if (data.ok === false) {
609
+ throw new TonConnectError(data.error || 'Failed to fetch transactions');
968
610
  }
969
- try {
970
- const apiEndpoint = this.config.tonApiEndpoint ||
971
- (this.config.network === 'testnet'
972
- ? 'https://testnet.toncenter.com/api/v2'
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
- balance,
994
- balanceTon: balanceTon === '0.' ? '0' : balanceTon,
995
- network: this.config.network,
616
+ status: 'confirmed',
617
+ hash: transaction.transaction_id?.hash || txHash,
618
+ blockNumber: transaction.transaction_id?.lt,
996
619
  };
997
620
  }
998
- catch (error) {
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
- * Get transaction status
1007
- */
1008
- async getTransactionStatus(boc, maxAttempts = 10, intervalMs = 2000) {
1009
- if (!boc || typeof boc !== 'string' || boc.length === 0) {
1010
- throw new TonConnectError('Transaction BOC is required');
1011
- }
1012
- // Extract transaction hash from BOC (simplified - in production, you'd parse the BOC properly)
1013
- // For now, we'll use a polling approach with TON Center API
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 apiEndpoint = this.config.tonApiEndpoint ||
1016
- (this.config.network === 'testnet'
1017
- ? 'https://testnet.toncenter.com/api/v2'
1018
- : 'https://toncenter.com/api/v2');
1019
- // Try to get transaction info
1020
- // Note: This is a simplified implementation. In production, you'd need to:
1021
- // 1. Parse the BOC to extract transaction hash
1022
- // 2. Query the blockchain for transaction status
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
- return {
1052
- status: 'failed',
1053
- error: lastError?.message || 'Failed to check transaction status',
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
- throw new TonConnectError(`Failed to get transaction status: ${error?.message || String(error)}`);
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 apiEndpoint = this.config.tonApiEndpoint ||
1072
- (this.config.network === 'testnet'
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 (error) {
1109
- if (error instanceof TonConnectError) {
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
  }