@blazium/ton-connect-mobile 1.2.5 → 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.
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ /**
3
+ * Session crypto for TON Connect v2 protocol
4
+ * Uses X25519 (NaCl box) for key exchange and message encryption
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SessionCrypto = void 0;
8
+ exports.bytesToHex = bytesToHex;
9
+ exports.hexToBytes = hexToBytes;
10
+ exports.bytesToBase64 = bytesToBase64;
11
+ exports.base64ToBytes = base64ToBytes;
12
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
13
+ const nacl = require('tweetnacl');
14
+ /**
15
+ * Convert Uint8Array to hex string
16
+ */
17
+ function bytesToHex(bytes) {
18
+ return Array.from(bytes)
19
+ .map((b) => b.toString(16).padStart(2, '0'))
20
+ .join('');
21
+ }
22
+ /**
23
+ * Convert hex string to Uint8Array
24
+ */
25
+ function hexToBytes(hex) {
26
+ const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
27
+ const padded = clean.length % 2 === 0 ? clean : '0' + clean;
28
+ const bytes = new Uint8Array(padded.length / 2);
29
+ for (let i = 0; i < padded.length; i += 2) {
30
+ bytes[i / 2] = parseInt(padded.substring(i, i + 2), 16);
31
+ }
32
+ return bytes;
33
+ }
34
+ /**
35
+ * Convert Uint8Array to base64
36
+ */
37
+ function bytesToBase64(bytes) {
38
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
39
+ let result = '';
40
+ const len = bytes.length;
41
+ for (let i = 0; i < len; i += 3) {
42
+ const a = bytes[i];
43
+ const b = i + 1 < len ? bytes[i + 1] : 0;
44
+ const c = i + 2 < len ? bytes[i + 2] : 0;
45
+ const bitmap = (a << 16) | (b << 8) | c;
46
+ result += chars.charAt((bitmap >> 18) & 63);
47
+ result += chars.charAt((bitmap >> 12) & 63);
48
+ result += i + 1 < len ? chars.charAt((bitmap >> 6) & 63) : '=';
49
+ result += i + 2 < len ? chars.charAt(bitmap & 63) : '=';
50
+ }
51
+ return result;
52
+ }
53
+ /**
54
+ * Convert base64 to Uint8Array
55
+ */
56
+ function base64ToBytes(base64) {
57
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
58
+ const bytes = [];
59
+ let buffer = 0;
60
+ let bitsCollected = 0;
61
+ for (let i = 0; i < base64.length; i++) {
62
+ const ch = base64[i];
63
+ if (ch === '=')
64
+ break;
65
+ // Also handle URL-safe base64
66
+ let index;
67
+ if (ch === '-') {
68
+ index = 62;
69
+ }
70
+ else if (ch === '_') {
71
+ index = 63;
72
+ }
73
+ else {
74
+ index = chars.indexOf(ch);
75
+ }
76
+ if (index === -1)
77
+ continue;
78
+ buffer = (buffer << 6) | index;
79
+ bitsCollected += 6;
80
+ if (bitsCollected >= 8) {
81
+ bitsCollected -= 8;
82
+ bytes.push((buffer >> bitsCollected) & 0xff);
83
+ buffer &= (1 << bitsCollected) - 1;
84
+ }
85
+ }
86
+ return new Uint8Array(bytes);
87
+ }
88
+ /**
89
+ * Session crypto for TON Connect v2
90
+ * Handles X25519 key exchange and NaCl box encryption/decryption
91
+ */
92
+ class SessionCrypto {
93
+ constructor(existingSecretKey) {
94
+ if (existingSecretKey) {
95
+ this.keypair = nacl.box.keyPair.fromSecretKey(existingSecretKey);
96
+ }
97
+ else {
98
+ this.keypair = nacl.box.keyPair();
99
+ }
100
+ }
101
+ /**
102
+ * Session ID = hex-encoded public key (used as client_id in bridge)
103
+ */
104
+ get sessionId() {
105
+ return bytesToHex(this.keypair.publicKey);
106
+ }
107
+ /**
108
+ * Public key bytes
109
+ */
110
+ get publicKey() {
111
+ return this.keypair.publicKey;
112
+ }
113
+ /**
114
+ * Secret key bytes (for persistence)
115
+ */
116
+ get secretKey() {
117
+ return this.keypair.secretKey;
118
+ }
119
+ /**
120
+ * Encrypt a message for a recipient
121
+ * Format: nonce (24 bytes) + ciphertext
122
+ */
123
+ encrypt(message, receiverPublicKey) {
124
+ const msgBytes = encodeUTF8(message);
125
+ const nonce = nacl.randomBytes(nacl.box.nonceLength); // 24 bytes
126
+ const encrypted = nacl.box(msgBytes, nonce, receiverPublicKey, this.keypair.secretKey);
127
+ if (!encrypted) {
128
+ throw new Error('Encryption failed');
129
+ }
130
+ // Prepend nonce to ciphertext
131
+ const result = new Uint8Array(nonce.length + encrypted.length);
132
+ result.set(nonce);
133
+ result.set(encrypted, nonce.length);
134
+ return result;
135
+ }
136
+ /**
137
+ * Decrypt a message from a sender
138
+ * Input format: nonce (24 bytes) + ciphertext
139
+ */
140
+ decrypt(encryptedMessage, senderPublicKey) {
141
+ if (encryptedMessage.length < nacl.box.nonceLength) {
142
+ throw new Error('Encrypted message too short');
143
+ }
144
+ const nonce = encryptedMessage.slice(0, nacl.box.nonceLength);
145
+ const ciphertext = encryptedMessage.slice(nacl.box.nonceLength);
146
+ const decrypted = nacl.box.open(ciphertext, nonce, senderPublicKey, this.keypair.secretKey);
147
+ if (!decrypted) {
148
+ throw new Error('Decryption failed — invalid key or corrupted message');
149
+ }
150
+ return decodeUTF8(decrypted);
151
+ }
152
+ /**
153
+ * Serialize session for persistence
154
+ */
155
+ serialize() {
156
+ return {
157
+ secretKey: bytesToHex(this.keypair.secretKey),
158
+ };
159
+ }
160
+ /**
161
+ * Restore session from persisted state
162
+ */
163
+ static fromState(state) {
164
+ return new SessionCrypto(hexToBytes(state.secretKey));
165
+ }
166
+ }
167
+ exports.SessionCrypto = SessionCrypto;
168
+ /**
169
+ * Encode string to UTF-8 Uint8Array
170
+ */
171
+ function encodeUTF8(str) {
172
+ // eslint-disable-next-line no-undef
173
+ if (typeof globalThis !== 'undefined' && globalThis.TextEncoder) {
174
+ // eslint-disable-next-line no-undef
175
+ return new globalThis.TextEncoder().encode(str);
176
+ }
177
+ // Fallback for older environments
178
+ const bytes = [];
179
+ for (let i = 0; i < str.length; i++) {
180
+ let charCode = str.charCodeAt(i);
181
+ if (charCode < 0x80) {
182
+ bytes.push(charCode);
183
+ }
184
+ else if (charCode < 0x800) {
185
+ bytes.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
186
+ }
187
+ else if (charCode >= 0xd800 && charCode < 0xdc00) {
188
+ // Surrogate pair
189
+ i++;
190
+ const low = str.charCodeAt(i);
191
+ charCode = ((charCode - 0xd800) << 10) + (low - 0xdc00) + 0x10000;
192
+ bytes.push(0xf0 | (charCode >> 18), 0x80 | ((charCode >> 12) & 0x3f), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
193
+ }
194
+ else {
195
+ bytes.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
196
+ }
197
+ }
198
+ return new Uint8Array(bytes);
199
+ }
200
+ /**
201
+ * Decode UTF-8 Uint8Array to string
202
+ */
203
+ function decodeUTF8(bytes) {
204
+ // eslint-disable-next-line no-undef
205
+ if (typeof globalThis !== 'undefined' && globalThis.TextDecoder) {
206
+ // eslint-disable-next-line no-undef
207
+ return new globalThis.TextDecoder().decode(bytes);
208
+ }
209
+ // Fallback
210
+ let result = '';
211
+ let i = 0;
212
+ while (i < bytes.length) {
213
+ const byte = bytes[i];
214
+ if (byte < 0x80) {
215
+ result += String.fromCharCode(byte);
216
+ i++;
217
+ }
218
+ else if ((byte & 0xe0) === 0xc0) {
219
+ result += String.fromCharCode(((byte & 0x1f) << 6) | (bytes[i + 1] & 0x3f));
220
+ i += 2;
221
+ }
222
+ else if ((byte & 0xf0) === 0xe0) {
223
+ result += String.fromCharCode(((byte & 0x0f) << 12) | ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f));
224
+ i += 3;
225
+ }
226
+ else {
227
+ const codePoint = ((byte & 0x07) << 18) | ((bytes[i + 1] & 0x3f) << 12) | ((bytes[i + 2] & 0x3f) << 6) | (bytes[i + 3] & 0x3f);
228
+ // Convert to surrogate pair
229
+ const offset = codePoint - 0x10000;
230
+ result += String.fromCharCode(0xd800 + (offset >> 10), 0xdc00 + (offset & 0x3ff));
231
+ i += 4;
232
+ }
233
+ }
234
+ return result;
235
+ }
@@ -1,27 +1,27 @@
1
1
  /**
2
2
  * TON Connect compatible wallet definitions
3
- * Each wallet has its own universal link format
3
+ * Each wallet has its own universal link and bridge URL
4
+ * Data sourced from official https://github.com/ton-connect/wallets-list
4
5
  */
5
6
  export interface WalletDefinition {
6
7
  /** Wallet name for display */
7
8
  name: string;
8
- /** Wallet app name */
9
+ /** Wallet app name (identifier) */
9
10
  appName: string;
10
11
  /** Universal link base URL for this wallet */
11
12
  universalLink: string;
13
+ /** HTTP Bridge URL for SSE communication */
14
+ bridgeUrl: string;
12
15
  /** Deep link scheme (if supported) */
13
16
  deepLink?: string;
14
17
  /** Wallet icon URL (optional) */
15
18
  iconUrl?: string;
16
19
  /** Platform support */
17
20
  platforms: ('ios' | 'android' | 'web')[];
18
- /** Preferred return strategy for this wallet */
19
- preferredReturnStrategy?: 'back' | 'post_redirect' | 'none';
20
- /** Whether this wallet requires returnScheme in payload */
21
- requiresReturnScheme?: boolean;
22
21
  }
23
22
  /**
24
23
  * List of supported TON Connect wallets
24
+ * Bridge URLs from official wallets-v2.json
25
25
  */
26
26
  export declare const SUPPORTED_WALLETS: WalletDefinition[];
27
27
  /**
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  /**
3
3
  * TON Connect compatible wallet definitions
4
- * Each wallet has its own universal link format
4
+ * Each wallet has its own universal link and bridge URL
5
+ * Data sourced from official https://github.com/ton-connect/wallets-list
5
6
  */
6
7
  Object.defineProperty(exports, "__esModule", { value: true });
7
8
  exports.SUPPORTED_WALLETS = void 0;
@@ -10,54 +11,52 @@ exports.getDefaultWallet = getDefaultWallet;
10
11
  exports.getWalletsForPlatform = getWalletsForPlatform;
11
12
  /**
12
13
  * List of supported TON Connect wallets
14
+ * Bridge URLs from official wallets-v2.json
13
15
  */
14
16
  exports.SUPPORTED_WALLETS = [
15
17
  {
16
18
  name: 'Tonkeeper',
17
- appName: 'Tonkeeper',
19
+ appName: 'tonkeeper',
18
20
  universalLink: 'https://app.tonkeeper.com/ton-connect',
19
- deepLink: 'tonkeeper://',
21
+ bridgeUrl: 'https://bridge.tonapi.io/bridge',
22
+ deepLink: 'tonkeeper-tc://',
20
23
  iconUrl: 'https://tonkeeper.com/assets/tonconnect-icon.png',
21
- platforms: ['ios', 'android', 'web'], // CRITICAL FIX: Tonkeeper Web is supported
22
- preferredReturnStrategy: 'post_redirect', // CRITICAL FIX: 'back' strategy may not send callback properly, use 'post_redirect'
23
- requiresReturnScheme: true, // CRITICAL FIX: Mobile apps need returnScheme for proper callback handling
24
+ platforms: ['ios', 'android', 'web'],
24
25
  },
25
26
  {
26
27
  name: 'MyTonWallet',
27
- appName: 'MyTonWallet',
28
+ appName: 'mytonwallet',
28
29
  universalLink: 'https://connect.mytonwallet.org',
29
- deepLink: 'mytonwallet://',
30
+ bridgeUrl: 'https://tonconnectbridge.mytonwallet.org/bridge/',
31
+ deepLink: 'mytonwallet-tc://',
30
32
  iconUrl: 'https://static.mytonwallet.io/icon-256.png',
31
33
  platforms: ['ios', 'android', 'web'],
32
- preferredReturnStrategy: 'post_redirect',
33
- requiresReturnScheme: true, // MyTonWallet requires explicit returnScheme
34
34
  },
35
35
  {
36
36
  name: 'Wallet in Telegram',
37
- appName: 'Wallet',
38
- universalLink: 'https://wallet.tg/ton-connect',
37
+ appName: 'telegram-wallet',
38
+ universalLink: 'https://t.me/wallet?attach=wallet',
39
+ bridgeUrl: 'https://walletbot.me/tonconnect-bridge/bridge',
39
40
  deepLink: 'tg://',
40
41
  iconUrl: 'https://wallet.tg/images/logo-288.png',
41
42
  platforms: ['ios', 'android'],
42
- preferredReturnStrategy: 'post_redirect',
43
- requiresReturnScheme: true, // Telegram Wallet requires explicit returnScheme
44
43
  },
45
44
  {
46
45
  name: 'Tonhub',
47
- appName: 'Tonhub',
46
+ appName: 'tonhub',
48
47
  universalLink: 'https://tonhub.com/ton-connect',
48
+ bridgeUrl: 'https://connect.tonhubapi.com/tonconnect',
49
49
  deepLink: 'tonhub://',
50
50
  iconUrl: 'https://tonhub.com/tonconnect_logo.png',
51
51
  platforms: ['ios', 'android'],
52
- preferredReturnStrategy: 'post_redirect',
53
- requiresReturnScheme: true, // Tonhub requires explicit returnScheme for proper callback
54
52
  },
55
53
  ];
56
54
  /**
57
55
  * Get wallet definition by name
58
56
  */
59
57
  function getWalletByName(name) {
60
- return exports.SUPPORTED_WALLETS.find((wallet) => wallet.name.toLowerCase() === name.toLowerCase() || wallet.appName.toLowerCase() === name.toLowerCase());
58
+ return exports.SUPPORTED_WALLETS.find((wallet) => wallet.name.toLowerCase() === name.toLowerCase() ||
59
+ wallet.appName.toLowerCase() === name.toLowerCase());
61
60
  }
62
61
  /**
63
62
  * Get default wallet (Tonkeeper)
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * TON Connect Mobile SDK
3
- * Production-ready implementation for React Native and Expo
3
+ * Production-ready implementation using TON Connect v2 bridge protocol
4
4
  */
5
5
  import { TonConnectMobileConfig, ConnectionStatus, WalletInfo, SendTransactionRequest, StatusChangeCallback, Network, TonConnectEventType, TonConnectEventListener, TransactionStatusResponse, BalanceResponse } from './types';
6
6
  import { type WalletDefinition } from './core/wallets';
@@ -29,6 +29,7 @@ export declare class TransactionInProgressError extends TonConnectError {
29
29
  }
30
30
  /**
31
31
  * Main TON Connect Mobile SDK class
32
+ * Implements the real TON Connect v2 bridge protocol
32
33
  */
33
34
  export declare class TonConnectMobile {
34
35
  private adapter;
@@ -36,54 +37,55 @@ export declare class TonConnectMobile {
36
37
  private statusChangeCallbacks;
37
38
  private eventListeners;
38
39
  private currentStatus;
39
- private urlUnsubscribe;
40
40
  private currentWallet;
41
+ private session;
42
+ private bridge;
43
+ private walletBridgePublicKey;
41
44
  private connectionPromise;
42
- private transactionPromise;
43
- private signDataPromise;
45
+ private pendingRpcRequests;
46
+ private rpcIdCounter;
44
47
  constructor(config: TonConnectMobileConfig);
45
48
  /**
46
- * Create platform adapter based on available modules
49
+ * Create platform adapter
47
50
  */
48
51
  private createAdapter;
49
52
  /**
50
- * Set up URL listener for wallet callbacks
53
+ * Handle incoming bridge message from wallet
51
54
  */
52
- private setupURLListener;
55
+ private handleBridgeMessage;
53
56
  /**
54
- * Handle callback from wallet
57
+ * Handle successful wallet connection
55
58
  */
56
- private handleCallback;
59
+ private handleConnectSuccess;
57
60
  /**
58
- * Handle connection response from wallet
61
+ * Handle connect error from wallet
59
62
  */
60
- private handleConnectionResponse;
63
+ private handleConnectError;
61
64
  /**
62
- * Handle transaction response from wallet
65
+ * Handle remote disconnect from wallet
63
66
  */
64
- private handleTransactionResponse;
67
+ private handleRemoteDisconnect;
65
68
  /**
66
- * Reject current promise with error
69
+ * Handle RPC success result
67
70
  */
68
- private rejectWithError;
71
+ private handleRpcResult;
69
72
  /**
70
- * Connect to wallet
73
+ * Handle RPC error result
74
+ */
75
+ private handleRpcError;
76
+ /**
77
+ * Reject pending connection promise
78
+ */
79
+ private rejectConnection;
80
+ /**
81
+ * Connect to wallet using TON Connect v2 bridge protocol
71
82
  */
72
83
  connect(): Promise<WalletInfo>;
73
84
  /**
74
- * Send transaction
85
+ * Send transaction via TON Connect v2 bridge protocol
75
86
  */
76
87
  sendTransaction(request: SendTransactionRequest): Promise<{
77
88
  boc: string;
78
- signature: string;
79
- }>;
80
- /**
81
- * Sign data (for authentication, etc.)
82
- * Note: Not all wallets support signData. This is a TON Connect extension.
83
- */
84
- signData(data: string | Uint8Array, version?: string): Promise<{
85
- signature: string;
86
- timestamp: number;
87
89
  }>;
88
90
  /**
89
91
  * Disconnect from wallet
@@ -103,79 +105,38 @@ export declare class TonConnectMobile {
103
105
  getCurrentWallet(): WalletDefinition;
104
106
  /**
105
107
  * Check if a wallet is available on the current platform
106
- * Note: This is a best-effort check and may not be 100% accurate
107
- * CRITICAL FIX: On web, if wallet has universalLink, it's considered available
108
- * because universal links can open in new tabs/windows
109
108
  */
110
109
  isWalletAvailable(walletName?: string): Promise<boolean>;
111
110
  /**
112
- * Set preferred wallet for connections
111
+ * Set preferred wallet
113
112
  */
114
113
  setPreferredWallet(walletName: string): void;
115
114
  /**
116
115
  * Subscribe to status changes
117
116
  */
118
117
  onStatusChange(callback: StatusChangeCallback): () => void;
119
- /**
120
- * Notify all status change callbacks
121
- */
122
118
  private notifyStatusChange;
123
- /**
124
- * Emit event to all listeners
125
- */
126
119
  private emit;
127
- /**
128
- * Add event listener
129
- */
130
120
  on<T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>): () => void;
131
- /**
132
- * Remove event listener
133
- */
134
121
  off<T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>): void;
135
- /**
136
- * Remove all listeners for an event
137
- */
138
122
  removeAllListeners(event?: TonConnectEventType): void;
139
- /**
140
- * Validate session ID format
141
- */
142
- private validateSessionId;
143
- /**
144
- * Save session to storage
145
- */
146
- private saveSession;
147
- /**
148
- * Load session from storage
149
- */
150
- private loadSession;
151
- /**
152
- * Clear session from storage
153
- */
154
- private clearSession;
155
123
  /**
156
124
  * Cleanup resources
157
125
  */
158
126
  destroy(): void;
159
- /**
160
- * Get current network
161
- */
162
127
  getNetwork(): Network;
163
- /**
164
- * Set network (mainnet/testnet)
165
- */
166
128
  setNetwork(network: Network): void;
167
129
  /**
168
130
  * Get wallet balance
169
131
  */
170
132
  getBalance(address?: string): Promise<BalanceResponse>;
171
133
  /**
172
- * Get transaction status
173
- */
174
- getTransactionStatus(boc: string, maxAttempts?: number, intervalMs?: number): Promise<TransactionStatusResponse>;
175
- /**
176
- * Get transaction status by hash (more reliable than BOC)
134
+ * Get transaction status by hash
177
135
  */
178
136
  getTransactionStatusByHash(txHash: string, address: string): Promise<TransactionStatusResponse>;
137
+ private saveSession;
138
+ private loadSession;
139
+ private clearSession;
179
140
  }
180
141
  export * from './types';
181
142
  export type { WalletDefinition } from './core/wallets';