@blazium/ton-connect-mobile 1.2.0 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazium/ton-connect-mobile",
3
- "version": "1.2.0",
3
+ "version": "1.2.3",
4
4
  "description": "Production-ready TON Connect Mobile SDK for React Native and Expo. Implements the real TonConnect protocol for mobile applications using deep links and callbacks.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -78,14 +78,14 @@ export class ExpoAdapter implements PlatformAdapter {
78
78
  console.log('[ExpoAdapter] Skipping canOpenURL check (Android compatibility)');
79
79
  }
80
80
 
81
- // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
82
- // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
81
+ // CRITICAL FIX: On Android, canOpenURL() may not recognize tonconnect:// protocol
82
+ // So we call openURL() directly. If it fails, it will throw an error.
83
83
  await Linking.openURL(url);
84
84
  console.log('[ExpoAdapter] URL opened successfully');
85
85
  return true;
86
86
  } catch (error: any) {
87
87
  console.error('[ExpoAdapter] Error in openURL:', error);
88
- // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
88
+ // On Android, if tonconnect:// protocol is not recognized or wallet is not installed, it will throw an error
89
89
  const errorMessage = error?.message || String(error);
90
90
  if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
91
91
  throw new Error(
@@ -67,14 +67,14 @@ export class ReactNativeAdapter implements PlatformAdapter {
67
67
  console.log('[ReactNativeAdapter] Skipping canOpenURL check (Android compatibility)');
68
68
  }
69
69
 
70
- // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
71
- // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
70
+ // CRITICAL FIX: On Android, canOpenURL() may not recognize tonconnect:// protocol
71
+ // So we call openURL() directly. If it fails, it will throw an error.
72
72
  await Linking.openURL(url);
73
73
  console.log('[ReactNativeAdapter] URL opened successfully');
74
74
  return true;
75
75
  } catch (error: any) {
76
76
  console.error('[ReactNativeAdapter] Error in openURL:', error);
77
- // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
77
+ // On Android, if tonconnect:// protocol is not recognized or wallet is not installed, it will throw an error
78
78
  const errorMessage = error?.message || String(error);
79
79
  if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
80
80
  throw new Error(
@@ -248,14 +248,23 @@ export function parseCallbackURL(url: string, scheme: string): {
248
248
  }
249
249
 
250
250
  // Extract encoded payload
251
- const encoded = url.substring(expectedPrefix.length);
251
+ let encoded = url.substring(expectedPrefix.length);
252
+
253
+ // CRITICAL FIX: Decode URL encoding first (wallet may URL-encode the payload)
254
+ try {
255
+ encoded = decodeURIComponent(encoded);
256
+ } catch (error) {
257
+ // If decodeURIComponent fails, try using the original encoded string
258
+ // Some wallets may not URL-encode the payload
259
+ console.log('[TON Connect] Payload not URL-encoded, using as-is');
260
+ }
252
261
 
253
262
  // CRITICAL FIX: Validate base64 payload size (prevent DoS)
254
263
  if (encoded.length === 0 || encoded.length > 5000) {
255
264
  return { type: 'unknown', data: null };
256
265
  }
257
266
 
258
- // CRITICAL FIX: Validate base64 characters only
267
+ // CRITICAL FIX: Validate base64 characters only (after URL decoding)
259
268
  if (!/^[A-Za-z0-9_-]+$/.test(encoded)) {
260
269
  return { type: 'unknown', data: null };
261
270
  }
@@ -306,14 +315,20 @@ export function parseCallbackURL(url: string, scheme: string): {
306
315
 
307
316
  /**
308
317
  * Extract wallet info from connection response
318
+ * CRITICAL: This function assumes response has been validated by validateConnectionResponse
309
319
  */
310
320
  export function extractWalletInfo(
311
321
  response: ConnectionResponsePayload
312
322
  ): WalletInfo {
323
+ // CRITICAL FIX: Add null checks to prevent runtime errors
324
+ if (!response || !response.name || !response.address || !response.publicKey) {
325
+ throw new Error('Invalid connection response: missing required fields');
326
+ }
327
+
313
328
  return {
314
329
  name: response.name,
315
- appName: response.appName,
316
- version: response.version,
330
+ appName: response.appName || response.name,
331
+ version: response.version || 'unknown',
317
332
  platform: response.platform || 'unknown',
318
333
  address: response.address,
319
334
  publicKey: response.publicKey,
@@ -360,13 +375,65 @@ export function validateTransactionRequest(
360
375
  return { valid: false, error: 'Transaction must have at least one message' };
361
376
  }
362
377
 
363
- for (const msg of request.messages) {
364
- if (!msg.address) {
365
- return { valid: false, error: 'Message address is required' };
378
+ // CRITICAL: Validate each message
379
+ for (let i = 0; i < request.messages.length; i++) {
380
+ const msg = request.messages[i];
381
+
382
+ // Validate address
383
+ if (!msg.address || typeof msg.address !== 'string') {
384
+ return { valid: false, error: `Message ${i + 1}: Address is required and must be a string` };
385
+ }
386
+
387
+ // CRITICAL: Validate TON address format (EQ... or 0Q...)
388
+ if (!/^(EQ|0Q)[A-Za-z0-9_-]{46}$/.test(msg.address)) {
389
+ return { valid: false, error: `Message ${i + 1}: Invalid TON address format. Address must start with EQ or 0Q and be 48 characters long.` };
366
390
  }
367
- if (!msg.amount || isNaN(Number(msg.amount))) {
368
- return { valid: false, error: 'Message amount must be a valid number' };
391
+
392
+ // Validate amount
393
+ if (!msg.amount || typeof msg.amount !== 'string') {
394
+ return { valid: false, error: `Message ${i + 1}: Amount is required and must be a string (nanotons)` };
395
+ }
396
+
397
+ // CRITICAL: Validate amount is a valid positive number (nanotons)
398
+ try {
399
+ const amount = BigInt(msg.amount);
400
+ if (amount <= 0n) {
401
+ return { valid: false, error: `Message ${i + 1}: Amount must be greater than 0` };
402
+ }
403
+ // Check for reasonable maximum (prevent overflow)
404
+ if (amount > BigInt('1000000000000000000')) { // 1 billion TON
405
+ return { valid: false, error: `Message ${i + 1}: Amount exceeds maximum allowed (1 billion TON)` };
406
+ }
407
+ } catch (error) {
408
+ return { valid: false, error: `Message ${i + 1}: Amount must be a valid number string (nanotons)` };
369
409
  }
410
+
411
+ // Validate payload if provided (must be base64)
412
+ if (msg.payload !== undefined && msg.payload !== null) {
413
+ if (typeof msg.payload !== 'string') {
414
+ return { valid: false, error: `Message ${i + 1}: Payload must be a base64 string` };
415
+ }
416
+ // Basic base64 validation
417
+ if (msg.payload.length > 0 && !/^[A-Za-z0-9+/=]+$/.test(msg.payload)) {
418
+ return { valid: false, error: `Message ${i + 1}: Payload must be valid base64 encoded` };
419
+ }
420
+ }
421
+
422
+ // Validate stateInit if provided (must be base64)
423
+ if (msg.stateInit !== undefined && msg.stateInit !== null) {
424
+ if (typeof msg.stateInit !== 'string') {
425
+ return { valid: false, error: `Message ${i + 1}: StateInit must be a base64 string` };
426
+ }
427
+ // Basic base64 validation
428
+ if (msg.stateInit.length > 0 && !/^[A-Za-z0-9+/=]+$/.test(msg.stateInit)) {
429
+ return { valid: false, error: `Message ${i + 1}: StateInit must be valid base64 encoded` };
430
+ }
431
+ }
432
+ }
433
+
434
+ // CRITICAL: Limit maximum number of messages (prevent DoS)
435
+ if (request.messages.length > 255) {
436
+ return { valid: false, error: 'Transaction cannot have more than 255 messages' };
370
437
  }
371
438
 
372
439
  return { valid: true };
@@ -31,7 +31,8 @@ export const SUPPORTED_WALLETS: WalletDefinition[] = [
31
31
  appName: 'Tonkeeper',
32
32
  universalLink: 'https://app.tonkeeper.com/ton-connect',
33
33
  deepLink: 'tonkeeper://',
34
- platforms: ['ios', 'android'],
34
+ iconUrl: 'https://tonkeeper.com/assets/tonconnect-icon.png',
35
+ platforms: ['ios', 'android', 'web'], // CRITICAL FIX: Tonkeeper Web is supported
35
36
  preferredReturnStrategy: 'post_redirect', // CRITICAL FIX: 'back' strategy may not send callback properly, use 'post_redirect'
36
37
  requiresReturnScheme: true, // CRITICAL FIX: Mobile apps need returnScheme for proper callback handling
37
38
  },
@@ -40,6 +41,7 @@ export const SUPPORTED_WALLETS: WalletDefinition[] = [
40
41
  appName: 'MyTonWallet',
41
42
  universalLink: 'https://connect.mytonwallet.org',
42
43
  deepLink: 'mytonwallet://',
44
+ iconUrl: 'https://static.mytonwallet.io/icon-256.png',
43
45
  platforms: ['ios', 'android', 'web'],
44
46
  preferredReturnStrategy: 'post_redirect',
45
47
  requiresReturnScheme: true, // MyTonWallet requires explicit returnScheme
@@ -49,6 +51,7 @@ export const SUPPORTED_WALLETS: WalletDefinition[] = [
49
51
  appName: 'Wallet',
50
52
  universalLink: 'https://wallet.tonapi.io/ton-connect',
51
53
  deepLink: 'tg://',
54
+ iconUrl: 'https://wallet.tonapi.io/icon.png',
52
55
  platforms: ['ios', 'android'],
53
56
  preferredReturnStrategy: 'post_redirect',
54
57
  requiresReturnScheme: true, // Telegram Wallet requires explicit returnScheme
@@ -58,6 +61,7 @@ export const SUPPORTED_WALLETS: WalletDefinition[] = [
58
61
  appName: 'Tonhub',
59
62
  universalLink: 'https://tonhub.com/ton-connect',
60
63
  deepLink: 'tonhub://',
64
+ iconUrl: 'https://tonhub.com/tonconnect_logo.png',
61
65
  platforms: ['ios', 'android'],
62
66
  preferredReturnStrategy: 'post_redirect',
63
67
  requiresReturnScheme: true, // Tonhub requires explicit returnScheme for proper callback