@blazium/ton-connect-mobile 1.0.3 → 1.0.5

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 CHANGED
@@ -223,6 +223,9 @@ new TonConnectMobile(config: TonConnectMobileConfig)
223
223
  - `storageKeyPrefix` (optional): Prefix for storage keys (default: `'tonconnect_'`)
224
224
  - `connectionTimeout` (optional): Connection timeout in ms (default: `300000`)
225
225
  - `transactionTimeout` (optional): Transaction timeout in ms (default: `300000`)
226
+ - `skipCanOpenURLCheck` (optional): Skip canOpenURL check before opening URL (default: `true`)
227
+ - Set to `false` if you want to check if URL can be opened before attempting to open it
228
+ - Note: On Android, `canOpenURL` may return `false` for `tonconnect://` even if wallet is installed
226
229
 
227
230
  #### Methods
228
231
 
@@ -11,7 +11,7 @@ export declare class ExpoAdapter implements PlatformAdapter {
11
11
  private subscription;
12
12
  constructor();
13
13
  private setupURLListener;
14
- openURL(url: string): Promise<boolean>;
14
+ openURL(url: string, skipCanOpenURLCheck?: boolean): Promise<boolean>;
15
15
  getInitialURL(): Promise<string | null>;
16
16
  addURLListener(callback: (url: string) => void): () => void;
17
17
  setItem(key: string, value: string): Promise<void>;
@@ -39,30 +39,38 @@ class ExpoAdapter {
39
39
  this.urlListeners.forEach((listener) => listener(event.url));
40
40
  });
41
41
  }
42
- async openURL(url) {
42
+ async openURL(url, skipCanOpenURLCheck = true) {
43
43
  if (!Linking) {
44
44
  throw new Error('expo-linking is not available');
45
45
  }
46
46
  try {
47
- console.log('[ExpoAdapter] Checking if URL can be opened:', url);
48
- const canOpen = await Linking.canOpenURL(url);
49
- console.log('[ExpoAdapter] canOpenURL result:', canOpen);
50
- if (!canOpen) {
51
- console.error('[ExpoAdapter] Cannot open URL - no app found that handles tonconnect:// protocol');
52
- throw new Error(`Cannot open URL: ${url}. Make sure a wallet app is installed that supports tonconnect:// protocol.`);
47
+ console.log('[ExpoAdapter] Opening URL:', url);
48
+ // Optional canOpenURL check (can be disabled via config)
49
+ if (!skipCanOpenURLCheck) {
50
+ console.log('[ExpoAdapter] Checking if URL can be opened...');
51
+ const canOpen = await Linking.canOpenURL(url);
52
+ console.log('[ExpoAdapter] canOpenURL result:', canOpen);
53
+ if (!canOpen) {
54
+ throw new Error('Cannot open URL. Make sure a wallet app is installed that supports tonconnect:// protocol.');
55
+ }
53
56
  }
54
- console.log('[ExpoAdapter] Opening URL...');
57
+ else {
58
+ console.log('[ExpoAdapter] Skipping canOpenURL check (Android compatibility)');
59
+ }
60
+ // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
61
+ // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
55
62
  await Linking.openURL(url);
56
63
  console.log('[ExpoAdapter] URL opened successfully');
57
64
  return true;
58
65
  }
59
66
  catch (error) {
60
67
  console.error('[ExpoAdapter] Error in openURL:', error);
61
- // Re-throw with more context
62
- if (error.message && error.message.includes('Cannot open URL')) {
63
- throw error;
68
+ // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
69
+ const errorMessage = error?.message || String(error);
70
+ if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
71
+ throw new Error('No TON wallet app found. Please install Tonkeeper or another TON Connect compatible wallet from Google Play Store.');
64
72
  }
65
- throw new Error(`Failed to open URL: ${url}. ${error?.message || String(error)}`);
73
+ throw new Error(`Failed to open wallet app: ${errorMessage}`);
66
74
  }
67
75
  }
68
76
  async getInitialURL() {
@@ -11,7 +11,7 @@ export declare class ReactNativeAdapter implements PlatformAdapter {
11
11
  private subscription;
12
12
  constructor();
13
13
  private setupURLListener;
14
- openURL(url: string): Promise<boolean>;
14
+ openURL(url: string, skipCanOpenURLCheck?: boolean): Promise<boolean>;
15
15
  getInitialURL(): Promise<string | null>;
16
16
  addURLListener(callback: (url: string) => void): () => void;
17
17
  setItem(key: string, value: string): Promise<void>;
@@ -37,30 +37,38 @@ class ReactNativeAdapter {
37
37
  this.urlListeners.forEach((listener) => listener(event.url));
38
38
  });
39
39
  }
40
- async openURL(url) {
40
+ async openURL(url, skipCanOpenURLCheck = true) {
41
41
  if (!Linking) {
42
42
  throw new Error('react-native Linking is not available');
43
43
  }
44
44
  try {
45
- console.log('[ReactNativeAdapter] Checking if URL can be opened:', url);
46
- const canOpen = await Linking.canOpenURL(url);
47
- console.log('[ReactNativeAdapter] canOpenURL result:', canOpen);
48
- if (!canOpen) {
49
- console.error('[ReactNativeAdapter] Cannot open URL - no app found that handles tonconnect:// protocol');
50
- throw new Error(`Cannot open URL: ${url}. Make sure a wallet app is installed that supports tonconnect:// protocol.`);
45
+ console.log('[ReactNativeAdapter] Opening URL:', url);
46
+ // Optional canOpenURL check (can be disabled via config)
47
+ if (!skipCanOpenURLCheck) {
48
+ console.log('[ReactNativeAdapter] Checking if URL can be opened...');
49
+ const canOpen = await Linking.canOpenURL(url);
50
+ console.log('[ReactNativeAdapter] canOpenURL result:', canOpen);
51
+ if (!canOpen) {
52
+ throw new Error('Cannot open URL. Make sure a wallet app is installed that supports tonconnect:// protocol.');
53
+ }
51
54
  }
52
- console.log('[ReactNativeAdapter] Opening URL...');
55
+ else {
56
+ console.log('[ReactNativeAdapter] Skipping canOpenURL check (Android compatibility)');
57
+ }
58
+ // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
59
+ // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
53
60
  await Linking.openURL(url);
54
61
  console.log('[ReactNativeAdapter] URL opened successfully');
55
62
  return true;
56
63
  }
57
64
  catch (error) {
58
65
  console.error('[ReactNativeAdapter] Error in openURL:', error);
59
- // Re-throw with more context
60
- if (error.message && error.message.includes('Cannot open URL')) {
61
- throw error;
66
+ // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
67
+ const errorMessage = error?.message || String(error);
68
+ if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
69
+ throw new Error('No TON wallet app found. Please install Tonkeeper or another TON Connect compatible wallet from Google Play Store.');
62
70
  }
63
- throw new Error(`Failed to open URL: ${url}. ${error?.message || String(error)}`);
71
+ throw new Error(`Failed to open wallet app: ${errorMessage}`);
64
72
  }
65
73
  }
66
74
  async getInitialURL() {
@@ -13,7 +13,7 @@ export declare class WebAdapter implements PlatformAdapter {
13
13
  constructor();
14
14
  private setupURLListener;
15
15
  private handleURLChange;
16
- openURL(url: string): Promise<boolean>;
16
+ openURL(url: string, skipCanOpenURLCheck?: boolean): Promise<boolean>;
17
17
  getInitialURL(): Promise<string | null>;
18
18
  addURLListener(callback: (url: string) => void): () => void;
19
19
  setItem(key: string, value: string): Promise<void>;
@@ -42,7 +42,7 @@ class WebAdapter {
42
42
  }
43
43
  });
44
44
  }
45
- async openURL(url) {
45
+ async openURL(url, skipCanOpenURLCheck) {
46
46
  try {
47
47
  if (typeof window !== 'undefined') {
48
48
  // For web, tonconnect:// deep links don't work
@@ -14,13 +14,15 @@ export declare function decodeBase64URL<T>(encoded: string): T;
14
14
  /**
15
15
  * Build connection request URL
16
16
  * Format: tonconnect://connect?<base64_encoded_payload>
17
+ * Or universal link: https://app.tonkeeper.com/ton-connect?<base64_encoded_payload>
17
18
  */
18
- export declare function buildConnectionRequest(manifestUrl: string, returnScheme: string): string;
19
+ export declare function buildConnectionRequest(manifestUrl: string, returnScheme: string, useUniversalLink?: boolean): string;
19
20
  /**
20
21
  * Build transaction request URL
21
22
  * Format: tonconnect://send-transaction?<base64_encoded_payload>
23
+ * Or universal link: https://app.tonkeeper.com/ton-connect/send-transaction?<base64_encoded_payload>
22
24
  */
23
- export declare function buildTransactionRequest(manifestUrl: string, request: SendTransactionRequest, returnScheme: string): string;
25
+ export declare function buildTransactionRequest(manifestUrl: string, request: SendTransactionRequest, returnScheme: string, useUniversalLink?: boolean): string;
24
26
  /**
25
27
  * Parse callback URL
26
28
  * Format: <scheme>://tonconnect?<base64_encoded_response>
@@ -18,7 +18,9 @@ exports.validateTransactionRequest = validateTransactionRequest;
18
18
  */
19
19
  const PROTOCOL_VERSION = '2';
20
20
  const CONNECT_PREFIX = 'tonconnect://connect';
21
+ const CONNECT_UNIVERSAL_PREFIX = 'https://app.tonkeeper.com/ton-connect';
21
22
  const SEND_TRANSACTION_PREFIX = 'tonconnect://send-transaction';
23
+ const SEND_TRANSACTION_UNIVERSAL_PREFIX = 'https://app.tonkeeper.com/ton-connect/send-transaction';
22
24
  const CALLBACK_PREFIX = 'tonconnect';
23
25
  /**
24
26
  * Get TextEncoder with availability check
@@ -100,21 +102,27 @@ function decodeBase64URL(encoded) {
100
102
  /**
101
103
  * Build connection request URL
102
104
  * Format: tonconnect://connect?<base64_encoded_payload>
105
+ * Or universal link: https://app.tonkeeper.com/ton-connect?<base64_encoded_payload>
103
106
  */
104
- function buildConnectionRequest(manifestUrl, returnScheme) {
107
+ function buildConnectionRequest(manifestUrl, returnScheme, useUniversalLink = true) {
105
108
  const payload = {
106
109
  manifestUrl,
107
110
  items: [{ name: 'ton_addr' }],
108
111
  returnStrategy: 'back',
109
112
  };
110
113
  const encoded = encodeBase64URL(payload);
114
+ // Use universal link for better Android compatibility
115
+ if (useUniversalLink) {
116
+ return `${CONNECT_UNIVERSAL_PREFIX}?${encoded}`;
117
+ }
111
118
  return `${CONNECT_PREFIX}?${encoded}`;
112
119
  }
113
120
  /**
114
121
  * Build transaction request URL
115
122
  * Format: tonconnect://send-transaction?<base64_encoded_payload>
123
+ * Or universal link: https://app.tonkeeper.com/ton-connect/send-transaction?<base64_encoded_payload>
116
124
  */
117
- function buildTransactionRequest(manifestUrl, request, returnScheme) {
125
+ function buildTransactionRequest(manifestUrl, request, returnScheme, useUniversalLink = true) {
118
126
  const payload = {
119
127
  manifestUrl,
120
128
  request: {
@@ -131,6 +139,10 @@ function buildTransactionRequest(manifestUrl, request, returnScheme) {
131
139
  returnStrategy: 'back',
132
140
  };
133
141
  const encoded = encodeBase64URL(payload);
142
+ // Use universal link for better Android compatibility
143
+ if (useUniversalLink) {
144
+ return `${SEND_TRANSACTION_UNIVERSAL_PREFIX}?${encoded}`;
145
+ }
134
146
  return `${SEND_TRANSACTION_PREFIX}?${encoded}`;
135
147
  }
136
148
  /**
package/dist/index.js CHANGED
@@ -91,6 +91,7 @@ class TonConnectMobile {
91
91
  storageKeyPrefix: 'tonconnect_',
92
92
  connectionTimeout: 300000, // 5 minutes
93
93
  transactionTimeout: 300000, // 5 minutes
94
+ skipCanOpenURLCheck: true, // Skip canOpenURL check by default (Android issue)
94
95
  ...config,
95
96
  };
96
97
  console.log('[TON Connect] Initializing SDK with config:', {
@@ -278,9 +279,9 @@ class TonConnectMobile {
278
279
  console.log('[TON Connect] Connection already in progress');
279
280
  throw new ConnectionInProgressError();
280
281
  }
281
- // Build connection request URL
282
+ // Build connection request URL (use universal link for Android compatibility)
282
283
  console.log('[TON Connect] Building connection request URL...');
283
- const url = (0, protocol_1.buildConnectionRequest)(this.config.manifestUrl, this.config.scheme);
284
+ const url = (0, protocol_1.buildConnectionRequest)(this.config.manifestUrl, this.config.scheme, true);
284
285
  // DEBUG: Log the URL being opened
285
286
  console.log('[TON Connect] Opening URL:', url);
286
287
  console.log('[TON Connect] Manifest URL:', this.config.manifestUrl);
@@ -316,7 +317,7 @@ class TonConnectMobile {
316
317
  this.connectionPromise.timeout = timeout;
317
318
  // Open wallet app
318
319
  console.log('[TON Connect] Attempting to open wallet app...');
319
- this.adapter.openURL(url).then((success) => {
320
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
320
321
  console.log('[TON Connect] openURL result:', success);
321
322
  // URL opened successfully, wait for callback
322
323
  // If success is false, it should have thrown an error
@@ -353,8 +354,8 @@ class TonConnectMobile {
353
354
  if (this.transactionPromise) {
354
355
  throw new TransactionInProgressError();
355
356
  }
356
- // Build transaction request URL
357
- const url = (0, protocol_1.buildTransactionRequest)(this.config.manifestUrl, request, this.config.scheme);
357
+ // Build transaction request URL (use universal link for Android compatibility)
358
+ const url = (0, protocol_1.buildTransactionRequest)(this.config.manifestUrl, request, this.config.scheme, true);
358
359
  // Create promise for transaction
359
360
  return new Promise((resolve, reject) => {
360
361
  let timeout = null;
@@ -383,7 +384,7 @@ class TonConnectMobile {
383
384
  }, this.config.transactionTimeout);
384
385
  this.transactionPromise.timeout = timeout;
385
386
  // Open wallet app
386
- this.adapter.openURL(url).then((success) => {
387
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
387
388
  // URL opened successfully, wait for callback
388
389
  // If success is false, it should have thrown an error
389
390
  if (!success && this.transactionPromise) {
@@ -157,7 +157,7 @@ export interface ErrorResponse {
157
157
  */
158
158
  export interface PlatformAdapter {
159
159
  /** Open a deep link URL */
160
- openURL(url: string): Promise<boolean>;
160
+ openURL(url: string, skipCanOpenURLCheck?: boolean): Promise<boolean>;
161
161
  /** Get initial URL when app was opened via deep link */
162
162
  getInitialURL(): Promise<string | null>;
163
163
  /** Add listener for URL changes */
@@ -185,6 +185,11 @@ export interface TonConnectMobileConfig {
185
185
  connectionTimeout?: number;
186
186
  /** Optional transaction timeout in ms (default: 300000 = 5 minutes) */
187
187
  transactionTimeout?: number;
188
+ /** Skip canOpenURL check before opening URL (optional, default: true)
189
+ * Set to false if you want to check if URL can be opened before attempting to open it.
190
+ * Note: On Android, canOpenURL may return false for tonconnect:// even if wallet is installed.
191
+ */
192
+ skipCanOpenURLCheck?: boolean;
188
193
  }
189
194
  /**
190
195
  * Event listener callback type
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazium/ton-connect-mobile",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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",
@@ -48,29 +48,42 @@ export class ExpoAdapter implements PlatformAdapter {
48
48
  });
49
49
  }
50
50
 
51
- async openURL(url: string): Promise<boolean> {
51
+ async openURL(url: string, skipCanOpenURLCheck: boolean = true): Promise<boolean> {
52
52
  if (!Linking) {
53
53
  throw new Error('expo-linking is not available');
54
54
  }
55
55
  try {
56
- console.log('[ExpoAdapter] Checking if URL can be opened:', url);
57
- const canOpen = await Linking.canOpenURL(url);
58
- console.log('[ExpoAdapter] canOpenURL result:', canOpen);
59
- if (!canOpen) {
60
- console.error('[ExpoAdapter] Cannot open URL - no app found that handles tonconnect:// protocol');
61
- throw new Error(`Cannot open URL: ${url}. Make sure a wallet app is installed that supports tonconnect:// protocol.`);
56
+ console.log('[ExpoAdapter] Opening URL:', url);
57
+
58
+ // Optional canOpenURL check (can be disabled via config)
59
+ if (!skipCanOpenURLCheck) {
60
+ console.log('[ExpoAdapter] Checking if URL can be opened...');
61
+ const canOpen = await Linking.canOpenURL(url);
62
+ console.log('[ExpoAdapter] canOpenURL result:', canOpen);
63
+ if (!canOpen) {
64
+ throw new Error(
65
+ 'Cannot open URL. Make sure a wallet app is installed that supports tonconnect:// protocol.'
66
+ );
67
+ }
68
+ } else {
69
+ console.log('[ExpoAdapter] Skipping canOpenURL check (Android compatibility)');
62
70
  }
63
- console.log('[ExpoAdapter] Opening URL...');
71
+
72
+ // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
73
+ // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
64
74
  await Linking.openURL(url);
65
75
  console.log('[ExpoAdapter] URL opened successfully');
66
76
  return true;
67
77
  } catch (error: any) {
68
78
  console.error('[ExpoAdapter] Error in openURL:', error);
69
- // Re-throw with more context
70
- if (error.message && error.message.includes('Cannot open URL')) {
71
- throw error;
79
+ // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
80
+ const errorMessage = error?.message || String(error);
81
+ if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
82
+ throw new Error(
83
+ 'No TON wallet app found. Please install Tonkeeper or another TON Connect compatible wallet from Google Play Store.'
84
+ );
72
85
  }
73
- throw new Error(`Failed to open URL: ${url}. ${error?.message || String(error)}`);
86
+ throw new Error(`Failed to open wallet app: ${errorMessage}`);
74
87
  }
75
88
  }
76
89
 
@@ -46,29 +46,42 @@ export class ReactNativeAdapter implements PlatformAdapter {
46
46
  });
47
47
  }
48
48
 
49
- async openURL(url: string): Promise<boolean> {
49
+ async openURL(url: string, skipCanOpenURLCheck: boolean = true): Promise<boolean> {
50
50
  if (!Linking) {
51
51
  throw new Error('react-native Linking is not available');
52
52
  }
53
53
  try {
54
- console.log('[ReactNativeAdapter] Checking if URL can be opened:', url);
55
- const canOpen = await Linking.canOpenURL(url);
56
- console.log('[ReactNativeAdapter] canOpenURL result:', canOpen);
57
- if (!canOpen) {
58
- console.error('[ReactNativeAdapter] Cannot open URL - no app found that handles tonconnect:// protocol');
59
- throw new Error(`Cannot open URL: ${url}. Make sure a wallet app is installed that supports tonconnect:// protocol.`);
54
+ console.log('[ReactNativeAdapter] Opening URL:', url);
55
+
56
+ // Optional canOpenURL check (can be disabled via config)
57
+ if (!skipCanOpenURLCheck) {
58
+ console.log('[ReactNativeAdapter] Checking if URL can be opened...');
59
+ const canOpen = await Linking.canOpenURL(url);
60
+ console.log('[ReactNativeAdapter] canOpenURL result:', canOpen);
61
+ if (!canOpen) {
62
+ throw new Error(
63
+ 'Cannot open URL. Make sure a wallet app is installed that supports tonconnect:// protocol.'
64
+ );
65
+ }
66
+ } else {
67
+ console.log('[ReactNativeAdapter] Skipping canOpenURL check (Android compatibility)');
60
68
  }
61
- console.log('[ReactNativeAdapter] Opening URL...');
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.
62
72
  await Linking.openURL(url);
63
73
  console.log('[ReactNativeAdapter] URL opened successfully');
64
74
  return true;
65
75
  } catch (error: any) {
66
76
  console.error('[ReactNativeAdapter] Error in openURL:', error);
67
- // Re-throw with more context
68
- if (error.message && error.message.includes('Cannot open URL')) {
69
- throw error;
77
+ // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
78
+ const errorMessage = error?.message || String(error);
79
+ if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
80
+ throw new Error(
81
+ 'No TON wallet app found. Please install Tonkeeper or another TON Connect compatible wallet from Google Play Store.'
82
+ );
70
83
  }
71
- throw new Error(`Failed to open URL: ${url}. ${error?.message || String(error)}`);
84
+ throw new Error(`Failed to open wallet app: ${errorMessage}`);
72
85
  }
73
86
  }
74
87
 
@@ -66,7 +66,7 @@ export class WebAdapter implements PlatformAdapter {
66
66
  });
67
67
  }
68
68
 
69
- async openURL(url: string): Promise<boolean> {
69
+ async openURL(url: string, skipCanOpenURLCheck?: boolean): Promise<boolean> {
70
70
  try {
71
71
  if (typeof window !== 'undefined') {
72
72
  // For web, tonconnect:// deep links don't work
@@ -26,7 +26,9 @@ import {
26
26
  */
27
27
  const PROTOCOL_VERSION = '2';
28
28
  const CONNECT_PREFIX = 'tonconnect://connect';
29
+ const CONNECT_UNIVERSAL_PREFIX = 'https://app.tonkeeper.com/ton-connect';
29
30
  const SEND_TRANSACTION_PREFIX = 'tonconnect://send-transaction';
31
+ const SEND_TRANSACTION_UNIVERSAL_PREFIX = 'https://app.tonkeeper.com/ton-connect/send-transaction';
30
32
  const CALLBACK_PREFIX = 'tonconnect';
31
33
 
32
34
  /**
@@ -122,10 +124,12 @@ export function decodeBase64URL<T>(encoded: string): T {
122
124
  /**
123
125
  * Build connection request URL
124
126
  * Format: tonconnect://connect?<base64_encoded_payload>
127
+ * Or universal link: https://app.tonkeeper.com/ton-connect?<base64_encoded_payload>
125
128
  */
126
129
  export function buildConnectionRequest(
127
130
  manifestUrl: string,
128
- returnScheme: string
131
+ returnScheme: string,
132
+ useUniversalLink: boolean = true
129
133
  ): string {
130
134
  const payload: ConnectionRequestPayload = {
131
135
  manifestUrl,
@@ -134,17 +138,25 @@ export function buildConnectionRequest(
134
138
  };
135
139
 
136
140
  const encoded = encodeBase64URL(payload);
141
+
142
+ // Use universal link for better Android compatibility
143
+ if (useUniversalLink) {
144
+ return `${CONNECT_UNIVERSAL_PREFIX}?${encoded}`;
145
+ }
146
+
137
147
  return `${CONNECT_PREFIX}?${encoded}`;
138
148
  }
139
149
 
140
150
  /**
141
151
  * Build transaction request URL
142
152
  * Format: tonconnect://send-transaction?<base64_encoded_payload>
153
+ * Or universal link: https://app.tonkeeper.com/ton-connect/send-transaction?<base64_encoded_payload>
143
154
  */
144
155
  export function buildTransactionRequest(
145
156
  manifestUrl: string,
146
157
  request: SendTransactionRequest,
147
- returnScheme: string
158
+ returnScheme: string,
159
+ useUniversalLink: boolean = true
148
160
  ): string {
149
161
  const payload: TransactionRequestPayload = {
150
162
  manifestUrl,
@@ -163,6 +175,12 @@ export function buildTransactionRequest(
163
175
  };
164
176
 
165
177
  const encoded = encodeBase64URL(payload);
178
+
179
+ // Use universal link for better Android compatibility
180
+ if (useUniversalLink) {
181
+ return `${SEND_TRANSACTION_UNIVERSAL_PREFIX}?${encoded}`;
182
+ }
183
+
166
184
  return `${SEND_TRANSACTION_PREFIX}?${encoded}`;
167
185
  }
168
186
 
package/src/index.ts CHANGED
@@ -111,6 +111,7 @@ export class TonConnectMobile {
111
111
  storageKeyPrefix: 'tonconnect_',
112
112
  connectionTimeout: 300000, // 5 minutes
113
113
  transactionTimeout: 300000, // 5 minutes
114
+ skipCanOpenURLCheck: true, // Skip canOpenURL check by default (Android issue)
114
115
  ...config,
115
116
  };
116
117
 
@@ -319,9 +320,9 @@ export class TonConnectMobile {
319
320
  throw new ConnectionInProgressError();
320
321
  }
321
322
 
322
- // Build connection request URL
323
+ // Build connection request URL (use universal link for Android compatibility)
323
324
  console.log('[TON Connect] Building connection request URL...');
324
- const url = buildConnectionRequest(this.config.manifestUrl, this.config.scheme);
325
+ const url = buildConnectionRequest(this.config.manifestUrl, this.config.scheme, true);
325
326
 
326
327
  // DEBUG: Log the URL being opened
327
328
  console.log('[TON Connect] Opening URL:', url);
@@ -363,7 +364,7 @@ export class TonConnectMobile {
363
364
 
364
365
  // Open wallet app
365
366
  console.log('[TON Connect] Attempting to open wallet app...');
366
- this.adapter.openURL(url).then((success) => {
367
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
367
368
  console.log('[TON Connect] openURL result:', success);
368
369
  // URL opened successfully, wait for callback
369
370
  // If success is false, it should have thrown an error
@@ -407,8 +408,8 @@ export class TonConnectMobile {
407
408
  throw new TransactionInProgressError();
408
409
  }
409
410
 
410
- // Build transaction request URL
411
- const url = buildTransactionRequest(this.config.manifestUrl, request, this.config.scheme);
411
+ // Build transaction request URL (use universal link for Android compatibility)
412
+ const url = buildTransactionRequest(this.config.manifestUrl, request, this.config.scheme, true);
412
413
 
413
414
  // Create promise for transaction
414
415
  return new Promise<{ boc: string; signature: string }>((resolve, reject) => {
@@ -442,7 +443,7 @@ export class TonConnectMobile {
442
443
  this.transactionPromise.timeout = timeout;
443
444
 
444
445
  // Open wallet app
445
- this.adapter.openURL(url).then((success) => {
446
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
446
447
  // URL opened successfully, wait for callback
447
448
  // If success is false, it should have thrown an error
448
449
  if (!success && this.transactionPromise) {
@@ -168,7 +168,7 @@ export interface ErrorResponse {
168
168
  */
169
169
  export interface PlatformAdapter {
170
170
  /** Open a deep link URL */
171
- openURL(url: string): Promise<boolean>;
171
+ openURL(url: string, skipCanOpenURLCheck?: boolean): Promise<boolean>;
172
172
  /** Get initial URL when app was opened via deep link */
173
173
  getInitialURL(): Promise<string | null>;
174
174
  /** Add listener for URL changes */
@@ -197,6 +197,11 @@ export interface TonConnectMobileConfig {
197
197
  connectionTimeout?: number;
198
198
  /** Optional transaction timeout in ms (default: 300000 = 5 minutes) */
199
199
  transactionTimeout?: number;
200
+ /** Skip canOpenURL check before opening URL (optional, default: true)
201
+ * Set to false if you want to check if URL can be opened before attempting to open it.
202
+ * Note: On Android, canOpenURL may return false for tonconnect:// even if wallet is installed.
203
+ */
204
+ skipCanOpenURLCheck?: boolean;
200
205
  }
201
206
 
202
207
  /**