@blazium/ton-connect-mobile 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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,12 +39,24 @@ 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
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
+ }
56
+ }
57
+ else {
58
+ console.log('[ExpoAdapter] Skipping canOpenURL check (Android compatibility)');
59
+ }
48
60
  // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
49
61
  // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
50
62
  await Linking.openURL(url);
@@ -55,7 +67,7 @@ class ExpoAdapter {
55
67
  console.error('[ExpoAdapter] Error in openURL:', error);
56
68
  // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
57
69
  const errorMessage = error?.message || String(error);
58
- if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found')) {
70
+ if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
59
71
  throw new Error('No TON wallet app found. Please install Tonkeeper or another TON Connect compatible wallet from Google Play Store.');
60
72
  }
61
73
  throw new Error(`Failed to open wallet app: ${errorMessage}`);
@@ -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,12 +37,24 @@ 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
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
+ }
54
+ }
55
+ else {
56
+ console.log('[ReactNativeAdapter] Skipping canOpenURL check (Android compatibility)');
57
+ }
46
58
  // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
47
59
  // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
48
60
  await Linking.openURL(url);
@@ -53,7 +65,7 @@ class ReactNativeAdapter {
53
65
  console.error('[ReactNativeAdapter] Error in openURL:', error);
54
66
  // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
55
67
  const errorMessage = error?.message || String(error);
56
- if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found')) {
68
+ if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
57
69
  throw new Error('No TON wallet app found. Please install Tonkeeper or another TON Connect compatible wallet from Google Play Store.');
58
70
  }
59
71
  throw new Error(`Failed to open wallet app: ${errorMessage}`);
@@ -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,17 @@ 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>
18
+ * Or custom wallet universal link
17
19
  */
18
- export declare function buildConnectionRequest(manifestUrl: string, returnScheme: string): string;
20
+ export declare function buildConnectionRequest(manifestUrl: string, returnScheme: string, walletUniversalLink?: string): string;
19
21
  /**
20
22
  * Build transaction request URL
21
23
  * Format: tonconnect://send-transaction?<base64_encoded_payload>
24
+ * Or universal link: https://app.tonkeeper.com/ton-connect/send-transaction?<base64_encoded_payload>
25
+ * Or custom wallet universal link
22
26
  */
23
- export declare function buildTransactionRequest(manifestUrl: string, request: SendTransactionRequest, returnScheme: string): string;
27
+ export declare function buildTransactionRequest(manifestUrl: string, request: SendTransactionRequest, returnScheme: string, walletUniversalLink?: string): string;
24
28
  /**
25
29
  * Parse callback URL
26
30
  * 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,30 @@ 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>
106
+ * Or custom wallet universal link
103
107
  */
104
- function buildConnectionRequest(manifestUrl, returnScheme) {
108
+ function buildConnectionRequest(manifestUrl, returnScheme, walletUniversalLink) {
105
109
  const payload = {
106
110
  manifestUrl,
107
111
  items: [{ name: 'ton_addr' }],
108
112
  returnStrategy: 'back',
109
113
  };
110
114
  const encoded = encodeBase64URL(payload);
111
- return `${CONNECT_PREFIX}?${encoded}`;
115
+ // Use custom wallet universal link if provided
116
+ if (walletUniversalLink) {
117
+ return `${walletUniversalLink}?${encoded}`;
118
+ }
119
+ // Default to Tonkeeper universal link for Android compatibility
120
+ return `${CONNECT_UNIVERSAL_PREFIX}?${encoded}`;
112
121
  }
113
122
  /**
114
123
  * Build transaction request URL
115
124
  * Format: tonconnect://send-transaction?<base64_encoded_payload>
125
+ * Or universal link: https://app.tonkeeper.com/ton-connect/send-transaction?<base64_encoded_payload>
126
+ * Or custom wallet universal link
116
127
  */
117
- function buildTransactionRequest(manifestUrl, request, returnScheme) {
128
+ function buildTransactionRequest(manifestUrl, request, returnScheme, walletUniversalLink) {
118
129
  const payload = {
119
130
  manifestUrl,
120
131
  request: {
@@ -131,7 +142,16 @@ function buildTransactionRequest(manifestUrl, request, returnScheme) {
131
142
  returnStrategy: 'back',
132
143
  };
133
144
  const encoded = encodeBase64URL(payload);
134
- return `${SEND_TRANSACTION_PREFIX}?${encoded}`;
145
+ // Use custom wallet universal link if provided
146
+ if (walletUniversalLink) {
147
+ // For transaction, append /send-transaction to the base universal link
148
+ const baseUrl = walletUniversalLink.endsWith('/ton-connect')
149
+ ? walletUniversalLink
150
+ : `${walletUniversalLink}/ton-connect`;
151
+ return `${baseUrl}/send-transaction?${encoded}`;
152
+ }
153
+ // Default to Tonkeeper universal link for Android compatibility
154
+ return `${SEND_TRANSACTION_UNIVERSAL_PREFIX}?${encoded}`;
135
155
  }
136
156
  /**
137
157
  * Parse callback URL
@@ -0,0 +1,34 @@
1
+ /**
2
+ * TON Connect compatible wallet definitions
3
+ * Each wallet has its own universal link format
4
+ */
5
+ export interface WalletDefinition {
6
+ /** Wallet name for display */
7
+ name: string;
8
+ /** Wallet app name */
9
+ appName: string;
10
+ /** Universal link base URL for this wallet */
11
+ universalLink: string;
12
+ /** Deep link scheme (if supported) */
13
+ deepLink?: string;
14
+ /** Wallet icon URL (optional) */
15
+ iconUrl?: string;
16
+ /** Platform support */
17
+ platforms: ('ios' | 'android' | 'web')[];
18
+ }
19
+ /**
20
+ * List of supported TON Connect wallets
21
+ */
22
+ export declare const SUPPORTED_WALLETS: WalletDefinition[];
23
+ /**
24
+ * Get wallet definition by name
25
+ */
26
+ export declare function getWalletByName(name: string): WalletDefinition | undefined;
27
+ /**
28
+ * Get default wallet (Tonkeeper)
29
+ */
30
+ export declare function getDefaultWallet(): WalletDefinition;
31
+ /**
32
+ * Get all wallets for a specific platform
33
+ */
34
+ export declare function getWalletsForPlatform(platform: 'ios' | 'android' | 'web'): WalletDefinition[];
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /**
3
+ * TON Connect compatible wallet definitions
4
+ * Each wallet has its own universal link format
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SUPPORTED_WALLETS = void 0;
8
+ exports.getWalletByName = getWalletByName;
9
+ exports.getDefaultWallet = getDefaultWallet;
10
+ exports.getWalletsForPlatform = getWalletsForPlatform;
11
+ /**
12
+ * List of supported TON Connect wallets
13
+ */
14
+ exports.SUPPORTED_WALLETS = [
15
+ {
16
+ name: 'Tonkeeper',
17
+ appName: 'Tonkeeper',
18
+ universalLink: 'https://app.tonkeeper.com/ton-connect',
19
+ deepLink: 'tonkeeper://',
20
+ platforms: ['ios', 'android'],
21
+ },
22
+ {
23
+ name: 'MyTonWallet',
24
+ appName: 'MyTonWallet',
25
+ universalLink: 'https://connect.mytonwallet.org',
26
+ deepLink: 'mytonwallet://',
27
+ platforms: ['ios', 'android', 'web'],
28
+ },
29
+ {
30
+ name: 'Wallet in Telegram',
31
+ appName: 'Wallet',
32
+ universalLink: 'https://wallet.tonapi.io/ton-connect',
33
+ deepLink: 'tg://',
34
+ platforms: ['ios', 'android'],
35
+ },
36
+ {
37
+ name: 'Tonhub',
38
+ appName: 'Tonhub',
39
+ universalLink: 'https://tonhub.com/ton-connect',
40
+ deepLink: 'tonhub://',
41
+ platforms: ['ios', 'android'],
42
+ },
43
+ ];
44
+ /**
45
+ * Get wallet definition by name
46
+ */
47
+ function getWalletByName(name) {
48
+ return exports.SUPPORTED_WALLETS.find((wallet) => wallet.name.toLowerCase() === name.toLowerCase() || wallet.appName.toLowerCase() === name.toLowerCase());
49
+ }
50
+ /**
51
+ * Get default wallet (Tonkeeper)
52
+ */
53
+ function getDefaultWallet() {
54
+ return exports.SUPPORTED_WALLETS[0]; // Tonkeeper
55
+ }
56
+ /**
57
+ * Get all wallets for a specific platform
58
+ */
59
+ function getWalletsForPlatform(platform) {
60
+ return exports.SUPPORTED_WALLETS.filter((wallet) => wallet.platforms.includes(platform));
61
+ }
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Production-ready implementation for React Native and Expo
4
4
  */
5
5
  import { TonConnectMobileConfig, ConnectionStatus, WalletInfo, SendTransactionRequest, StatusChangeCallback } from './types';
6
+ import { type WalletDefinition } from './core/wallets';
6
7
  /**
7
8
  * Custom error classes
8
9
  */
@@ -34,6 +35,7 @@ export declare class TonConnectMobile {
34
35
  private statusChangeCallbacks;
35
36
  private currentStatus;
36
37
  private urlUnsubscribe;
38
+ private currentWallet;
37
39
  private connectionPromise;
38
40
  private transactionPromise;
39
41
  constructor(config: TonConnectMobileConfig);
@@ -80,6 +82,18 @@ export declare class TonConnectMobile {
80
82
  * Get current connection status
81
83
  */
82
84
  getStatus(): ConnectionStatus;
85
+ /**
86
+ * Get list of supported wallets
87
+ */
88
+ getSupportedWallets(): WalletDefinition[];
89
+ /**
90
+ * Get current wallet being used
91
+ */
92
+ getCurrentWallet(): WalletDefinition;
93
+ /**
94
+ * Set preferred wallet for connections
95
+ */
96
+ setPreferredWallet(walletName: string): void;
83
97
  /**
84
98
  * Subscribe to status changes
85
99
  */
@@ -110,3 +124,5 @@ export declare class TonConnectMobile {
110
124
  destroy(): void;
111
125
  }
112
126
  export * from './types';
127
+ export type { WalletDefinition } from './core/wallets';
128
+ export { SUPPORTED_WALLETS, getWalletByName, getDefaultWallet, getWalletsForPlatform } from './core/wallets';
package/dist/index.js CHANGED
@@ -18,12 +18,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
18
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.TonConnectMobile = exports.TransactionInProgressError = exports.ConnectionInProgressError = exports.UserRejectedError = exports.TransactionTimeoutError = exports.ConnectionTimeoutError = exports.TonConnectError = void 0;
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
23
  const crypto_1 = require("./core/crypto");
24
24
  const expo_1 = require("./adapters/expo");
25
25
  const react_native_1 = require("./adapters/react-native");
26
26
  const web_1 = require("./adapters/web");
27
+ const wallets_1 = require("./core/wallets");
27
28
  /**
28
29
  * Custom error classes
29
30
  */
@@ -91,11 +92,30 @@ class TonConnectMobile {
91
92
  storageKeyPrefix: 'tonconnect_',
92
93
  connectionTimeout: 300000, // 5 minutes
93
94
  transactionTimeout: 300000, // 5 minutes
95
+ skipCanOpenURLCheck: true, // Skip canOpenURL check by default (Android issue)
96
+ preferredWallet: config.preferredWallet,
94
97
  ...config,
95
98
  };
99
+ // Determine which wallet to use
100
+ if (this.config.preferredWallet) {
101
+ const wallet = (0, wallets_1.getWalletByName)(this.config.preferredWallet);
102
+ if (wallet) {
103
+ this.currentWallet = wallet;
104
+ console.log('[TON Connect] Using preferred wallet:', wallet.name);
105
+ }
106
+ else {
107
+ console.warn('[TON Connect] Preferred wallet not found, using default');
108
+ this.currentWallet = (0, wallets_1.getDefaultWallet)();
109
+ }
110
+ }
111
+ else {
112
+ this.currentWallet = (0, wallets_1.getDefaultWallet)();
113
+ }
96
114
  console.log('[TON Connect] Initializing SDK with config:', {
97
115
  manifestUrl: this.config.manifestUrl,
98
116
  scheme: this.config.scheme,
117
+ wallet: this.currentWallet.name,
118
+ universalLink: this.currentWallet.universalLink,
99
119
  });
100
120
  // Initialize platform adapter
101
121
  this.adapter = this.createAdapter();
@@ -278,9 +298,11 @@ class TonConnectMobile {
278
298
  console.log('[TON Connect] Connection already in progress');
279
299
  throw new ConnectionInProgressError();
280
300
  }
281
- // Build connection request URL
282
- console.log('[TON Connect] Building connection request URL...');
283
- const url = (0, protocol_1.buildConnectionRequest)(this.config.manifestUrl, this.config.scheme);
301
+ // Build connection request URL (use wallet's universal link)
302
+ console.log('[TON Connect] Building connection request URL for wallet:', this.currentWallet.name);
303
+ console.log('[TON Connect] Using universal link:', this.currentWallet.universalLink);
304
+ const url = (0, protocol_1.buildConnectionRequest)(this.config.manifestUrl, this.config.scheme, this.currentWallet.universalLink);
305
+ console.log('[TON Connect] Built URL:', url.substring(0, 100) + '...');
284
306
  // DEBUG: Log the URL being opened
285
307
  console.log('[TON Connect] Opening URL:', url);
286
308
  console.log('[TON Connect] Manifest URL:', this.config.manifestUrl);
@@ -316,7 +338,7 @@ class TonConnectMobile {
316
338
  this.connectionPromise.timeout = timeout;
317
339
  // Open wallet app
318
340
  console.log('[TON Connect] Attempting to open wallet app...');
319
- this.adapter.openURL(url).then((success) => {
341
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
320
342
  console.log('[TON Connect] openURL result:', success);
321
343
  // URL opened successfully, wait for callback
322
344
  // If success is false, it should have thrown an error
@@ -353,8 +375,8 @@ class TonConnectMobile {
353
375
  if (this.transactionPromise) {
354
376
  throw new TransactionInProgressError();
355
377
  }
356
- // Build transaction request URL
357
- const url = (0, protocol_1.buildTransactionRequest)(this.config.manifestUrl, request, this.config.scheme);
378
+ // Build transaction request URL (use universal link for Android compatibility)
379
+ const url = (0, protocol_1.buildTransactionRequest)(this.config.manifestUrl, request, this.config.scheme, this.currentWallet.universalLink);
358
380
  // Create promise for transaction
359
381
  return new Promise((resolve, reject) => {
360
382
  let timeout = null;
@@ -383,7 +405,7 @@ class TonConnectMobile {
383
405
  }, this.config.transactionTimeout);
384
406
  this.transactionPromise.timeout = timeout;
385
407
  // Open wallet app
386
- this.adapter.openURL(url).then((success) => {
408
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
387
409
  // URL opened successfully, wait for callback
388
410
  // If success is false, it should have thrown an error
389
411
  if (!success && this.transactionPromise) {
@@ -413,6 +435,29 @@ class TonConnectMobile {
413
435
  getStatus() {
414
436
  return { ...this.currentStatus };
415
437
  }
438
+ /**
439
+ * Get list of supported wallets
440
+ */
441
+ getSupportedWallets() {
442
+ return wallets_1.SUPPORTED_WALLETS;
443
+ }
444
+ /**
445
+ * Get current wallet being used
446
+ */
447
+ getCurrentWallet() {
448
+ return this.currentWallet;
449
+ }
450
+ /**
451
+ * Set preferred wallet for connections
452
+ */
453
+ setPreferredWallet(walletName) {
454
+ const wallet = (0, wallets_1.getWalletByName)(walletName);
455
+ if (!wallet) {
456
+ throw new TonConnectError(`Wallet "${walletName}" not found. Available wallets: ${wallets_1.SUPPORTED_WALLETS.map(w => w.name).join(', ')}`);
457
+ }
458
+ this.currentWallet = wallet;
459
+ console.log('[TON Connect] Preferred wallet changed to:', wallet.name);
460
+ }
416
461
  /**
417
462
  * Subscribe to status changes
418
463
  */
@@ -548,3 +593,8 @@ class TonConnectMobile {
548
593
  exports.TonConnectMobile = TonConnectMobile;
549
594
  // Export types
550
595
  __exportStar(require("./types"), exports);
596
+ var wallets_2 = require("./core/wallets");
597
+ Object.defineProperty(exports, "SUPPORTED_WALLETS", { enumerable: true, get: function () { return wallets_2.SUPPORTED_WALLETS; } });
598
+ Object.defineProperty(exports, "getWalletByName", { enumerable: true, get: function () { return wallets_2.getWalletByName; } });
599
+ Object.defineProperty(exports, "getDefaultWallet", { enumerable: true, get: function () { return wallets_2.getDefaultWallet; } });
600
+ Object.defineProperty(exports, "getWalletsForPlatform", { enumerable: true, get: function () { return wallets_2.getWalletsForPlatform; } });
@@ -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,16 @@ 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;
193
+ /** Preferred wallet name (optional)
194
+ * If not specified, will use default wallet (Tonkeeper)
195
+ * Available: 'Tonkeeper', 'MyTonWallet', 'Wallet in Telegram', 'Tonhub'
196
+ */
197
+ preferredWallet?: string;
188
198
  }
189
199
  /**
190
200
  * 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.4",
3
+ "version": "1.0.6",
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,12 +48,27 @@ 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
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)');
70
+ }
71
+
57
72
  // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
58
73
  // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
59
74
  await Linking.openURL(url);
@@ -63,7 +78,7 @@ export class ExpoAdapter implements PlatformAdapter {
63
78
  console.error('[ExpoAdapter] Error in openURL:', error);
64
79
  // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
65
80
  const errorMessage = error?.message || String(error);
66
- if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found')) {
81
+ if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
67
82
  throw new Error(
68
83
  'No TON wallet app found. Please install Tonkeeper or another TON Connect compatible wallet from Google Play Store.'
69
84
  );
@@ -46,12 +46,27 @@ 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
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)');
68
+ }
69
+
55
70
  // CRITICAL FIX: Android'de canOpenURL() tonconnect:// protokolünü tanımayabilir
56
71
  // Bu yüzden direkt openURL() çağırıyoruz. Eğer açılamazsa hata fırlatır.
57
72
  await Linking.openURL(url);
@@ -61,7 +76,7 @@ export class ReactNativeAdapter implements PlatformAdapter {
61
76
  console.error('[ReactNativeAdapter] Error in openURL:', error);
62
77
  // Android'de tonconnect:// protokolü tanınmıyorsa veya cüzdan yüklü değilse hata verir
63
78
  const errorMessage = error?.message || String(error);
64
- if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found')) {
79
+ if (errorMessage.includes('No Activity found') || errorMessage.includes('No app found') || errorMessage.includes('Cannot open URL')) {
65
80
  throw new Error(
66
81
  'No TON wallet app found. Please install Tonkeeper or another TON Connect compatible wallet from Google Play Store.'
67
82
  );
@@ -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,13 @@ 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>
128
+ * Or custom wallet universal link
125
129
  */
126
130
  export function buildConnectionRequest(
127
131
  manifestUrl: string,
128
- returnScheme: string
132
+ returnScheme: string,
133
+ walletUniversalLink?: string
129
134
  ): string {
130
135
  const payload: ConnectionRequestPayload = {
131
136
  manifestUrl,
@@ -134,17 +139,27 @@ export function buildConnectionRequest(
134
139
  };
135
140
 
136
141
  const encoded = encodeBase64URL(payload);
137
- return `${CONNECT_PREFIX}?${encoded}`;
142
+
143
+ // Use custom wallet universal link if provided
144
+ if (walletUniversalLink) {
145
+ return `${walletUniversalLink}?${encoded}`;
146
+ }
147
+
148
+ // Default to Tonkeeper universal link for Android compatibility
149
+ return `${CONNECT_UNIVERSAL_PREFIX}?${encoded}`;
138
150
  }
139
151
 
140
152
  /**
141
153
  * Build transaction request URL
142
154
  * Format: tonconnect://send-transaction?<base64_encoded_payload>
155
+ * Or universal link: https://app.tonkeeper.com/ton-connect/send-transaction?<base64_encoded_payload>
156
+ * Or custom wallet universal link
143
157
  */
144
158
  export function buildTransactionRequest(
145
159
  manifestUrl: string,
146
160
  request: SendTransactionRequest,
147
- returnScheme: string
161
+ returnScheme: string,
162
+ walletUniversalLink?: string
148
163
  ): string {
149
164
  const payload: TransactionRequestPayload = {
150
165
  manifestUrl,
@@ -163,7 +178,18 @@ export function buildTransactionRequest(
163
178
  };
164
179
 
165
180
  const encoded = encodeBase64URL(payload);
166
- return `${SEND_TRANSACTION_PREFIX}?${encoded}`;
181
+
182
+ // Use custom wallet universal link if provided
183
+ if (walletUniversalLink) {
184
+ // For transaction, append /send-transaction to the base universal link
185
+ const baseUrl = walletUniversalLink.endsWith('/ton-connect')
186
+ ? walletUniversalLink
187
+ : `${walletUniversalLink}/ton-connect`;
188
+ return `${baseUrl}/send-transaction?${encoded}`;
189
+ }
190
+
191
+ // Default to Tonkeeper universal link for Android compatibility
192
+ return `${SEND_TRANSACTION_UNIVERSAL_PREFIX}?${encoded}`;
167
193
  }
168
194
 
169
195
  /**
@@ -0,0 +1,77 @@
1
+ /**
2
+ * TON Connect compatible wallet definitions
3
+ * Each wallet has its own universal link format
4
+ */
5
+
6
+ export interface WalletDefinition {
7
+ /** Wallet name for display */
8
+ name: string;
9
+ /** Wallet app name */
10
+ appName: string;
11
+ /** Universal link base URL for this wallet */
12
+ universalLink: string;
13
+ /** Deep link scheme (if supported) */
14
+ deepLink?: string;
15
+ /** Wallet icon URL (optional) */
16
+ iconUrl?: string;
17
+ /** Platform support */
18
+ platforms: ('ios' | 'android' | 'web')[];
19
+ }
20
+
21
+ /**
22
+ * List of supported TON Connect wallets
23
+ */
24
+ export const SUPPORTED_WALLETS: WalletDefinition[] = [
25
+ {
26
+ name: 'Tonkeeper',
27
+ appName: 'Tonkeeper',
28
+ universalLink: 'https://app.tonkeeper.com/ton-connect',
29
+ deepLink: 'tonkeeper://',
30
+ platforms: ['ios', 'android'],
31
+ },
32
+ {
33
+ name: 'MyTonWallet',
34
+ appName: 'MyTonWallet',
35
+ universalLink: 'https://connect.mytonwallet.org',
36
+ deepLink: 'mytonwallet://',
37
+ platforms: ['ios', 'android', 'web'],
38
+ },
39
+ {
40
+ name: 'Wallet in Telegram',
41
+ appName: 'Wallet',
42
+ universalLink: 'https://wallet.tonapi.io/ton-connect',
43
+ deepLink: 'tg://',
44
+ platforms: ['ios', 'android'],
45
+ },
46
+ {
47
+ name: 'Tonhub',
48
+ appName: 'Tonhub',
49
+ universalLink: 'https://tonhub.com/ton-connect',
50
+ deepLink: 'tonhub://',
51
+ platforms: ['ios', 'android'],
52
+ },
53
+ ];
54
+
55
+ /**
56
+ * Get wallet definition by name
57
+ */
58
+ export function getWalletByName(name: string): WalletDefinition | undefined {
59
+ return SUPPORTED_WALLETS.find(
60
+ (wallet) => wallet.name.toLowerCase() === name.toLowerCase() || wallet.appName.toLowerCase() === name.toLowerCase()
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Get default wallet (Tonkeeper)
66
+ */
67
+ export function getDefaultWallet(): WalletDefinition {
68
+ return SUPPORTED_WALLETS[0]; // Tonkeeper
69
+ }
70
+
71
+ /**
72
+ * Get all wallets for a specific platform
73
+ */
74
+ export function getWalletsForPlatform(platform: 'ios' | 'android' | 'web'): WalletDefinition[] {
75
+ return SUPPORTED_WALLETS.filter((wallet) => wallet.platforms.includes(platform));
76
+ }
77
+
package/src/index.ts CHANGED
@@ -32,6 +32,7 @@ import { verifyConnectionProof, generateSessionId } from './core/crypto';
32
32
  import { ExpoAdapter } from './adapters/expo';
33
33
  import { ReactNativeAdapter } from './adapters/react-native';
34
34
  import { WebAdapter } from './adapters/web';
35
+ import { getWalletByName, getDefaultWallet, SUPPORTED_WALLETS, type WalletDefinition } from './core/wallets';
35
36
 
36
37
  /**
37
38
  * Custom error classes
@@ -83,10 +84,11 @@ export class TransactionInProgressError extends TonConnectError {
83
84
  */
84
85
  export class TonConnectMobile {
85
86
  private adapter: PlatformAdapter;
86
- private config: Required<TonConnectMobileConfig>;
87
+ private config: Required<Omit<TonConnectMobileConfig, 'preferredWallet'>> & { preferredWallet?: string };
87
88
  private statusChangeCallbacks: Set<StatusChangeCallback> = new Set();
88
89
  private currentStatus: ConnectionStatus = { connected: false, wallet: null };
89
90
  private urlUnsubscribe: (() => void) | null = null;
91
+ private currentWallet!: WalletDefinition;
90
92
  private connectionPromise: {
91
93
  resolve: (wallet: WalletInfo) => void;
92
94
  reject: (error: Error) => void;
@@ -111,12 +113,30 @@ export class TonConnectMobile {
111
113
  storageKeyPrefix: 'tonconnect_',
112
114
  connectionTimeout: 300000, // 5 minutes
113
115
  transactionTimeout: 300000, // 5 minutes
116
+ skipCanOpenURLCheck: true, // Skip canOpenURL check by default (Android issue)
117
+ preferredWallet: config.preferredWallet,
114
118
  ...config,
115
- };
119
+ } as Required<Omit<TonConnectMobileConfig, 'preferredWallet'>> & { preferredWallet?: string };
120
+
121
+ // Determine which wallet to use
122
+ if (this.config.preferredWallet) {
123
+ const wallet = getWalletByName(this.config.preferredWallet);
124
+ if (wallet) {
125
+ this.currentWallet = wallet;
126
+ console.log('[TON Connect] Using preferred wallet:', wallet.name);
127
+ } else {
128
+ console.warn('[TON Connect] Preferred wallet not found, using default');
129
+ this.currentWallet = getDefaultWallet();
130
+ }
131
+ } else {
132
+ this.currentWallet = getDefaultWallet();
133
+ }
116
134
 
117
135
  console.log('[TON Connect] Initializing SDK with config:', {
118
136
  manifestUrl: this.config.manifestUrl,
119
137
  scheme: this.config.scheme,
138
+ wallet: this.currentWallet.name,
139
+ universalLink: this.currentWallet.universalLink,
120
140
  });
121
141
 
122
142
  // Initialize platform adapter
@@ -319,9 +339,11 @@ export class TonConnectMobile {
319
339
  throw new ConnectionInProgressError();
320
340
  }
321
341
 
322
- // Build connection request URL
323
- console.log('[TON Connect] Building connection request URL...');
324
- const url = buildConnectionRequest(this.config.manifestUrl, this.config.scheme);
342
+ // Build connection request URL (use wallet's universal link)
343
+ console.log('[TON Connect] Building connection request URL for wallet:', this.currentWallet.name);
344
+ console.log('[TON Connect] Using universal link:', this.currentWallet.universalLink);
345
+ const url = buildConnectionRequest(this.config.manifestUrl, this.config.scheme, this.currentWallet.universalLink);
346
+ console.log('[TON Connect] Built URL:', url.substring(0, 100) + '...');
325
347
 
326
348
  // DEBUG: Log the URL being opened
327
349
  console.log('[TON Connect] Opening URL:', url);
@@ -363,7 +385,7 @@ export class TonConnectMobile {
363
385
 
364
386
  // Open wallet app
365
387
  console.log('[TON Connect] Attempting to open wallet app...');
366
- this.adapter.openURL(url).then((success) => {
388
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
367
389
  console.log('[TON Connect] openURL result:', success);
368
390
  // URL opened successfully, wait for callback
369
391
  // If success is false, it should have thrown an error
@@ -407,8 +429,8 @@ export class TonConnectMobile {
407
429
  throw new TransactionInProgressError();
408
430
  }
409
431
 
410
- // Build transaction request URL
411
- const url = buildTransactionRequest(this.config.manifestUrl, request, this.config.scheme);
432
+ // Build transaction request URL (use universal link for Android compatibility)
433
+ const url = buildTransactionRequest(this.config.manifestUrl, request, this.config.scheme, this.currentWallet.universalLink);
412
434
 
413
435
  // Create promise for transaction
414
436
  return new Promise<{ boc: string; signature: string }>((resolve, reject) => {
@@ -442,7 +464,7 @@ export class TonConnectMobile {
442
464
  this.transactionPromise.timeout = timeout;
443
465
 
444
466
  // Open wallet app
445
- this.adapter.openURL(url).then((success) => {
467
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then((success) => {
446
468
  // URL opened successfully, wait for callback
447
469
  // If success is false, it should have thrown an error
448
470
  if (!success && this.transactionPromise) {
@@ -480,6 +502,32 @@ export class TonConnectMobile {
480
502
  return { ...this.currentStatus };
481
503
  }
482
504
 
505
+ /**
506
+ * Get list of supported wallets
507
+ */
508
+ getSupportedWallets(): WalletDefinition[] {
509
+ return SUPPORTED_WALLETS;
510
+ }
511
+
512
+ /**
513
+ * Get current wallet being used
514
+ */
515
+ getCurrentWallet(): WalletDefinition {
516
+ return this.currentWallet;
517
+ }
518
+
519
+ /**
520
+ * Set preferred wallet for connections
521
+ */
522
+ setPreferredWallet(walletName: string): void {
523
+ const wallet = getWalletByName(walletName);
524
+ if (!wallet) {
525
+ throw new TonConnectError(`Wallet "${walletName}" not found. Available wallets: ${SUPPORTED_WALLETS.map(w => w.name).join(', ')}`);
526
+ }
527
+ this.currentWallet = wallet;
528
+ console.log('[TON Connect] Preferred wallet changed to:', wallet.name);
529
+ }
530
+
483
531
  /**
484
532
  * Subscribe to status changes
485
533
  */
@@ -628,4 +676,6 @@ export class TonConnectMobile {
628
676
 
629
677
  // Export types
630
678
  export * from './types';
679
+ export type { WalletDefinition } from './core/wallets';
680
+ export { SUPPORTED_WALLETS, getWalletByName, getDefaultWallet, getWalletsForPlatform } from './core/wallets';
631
681
 
@@ -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,16 @@ 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;
205
+ /** Preferred wallet name (optional)
206
+ * If not specified, will use default wallet (Tonkeeper)
207
+ * Available: 'Tonkeeper', 'MyTonWallet', 'Wallet in Telegram', 'Tonhub'
208
+ */
209
+ preferredWallet?: string;
200
210
  }
201
211
 
202
212
  /**