@blazium/ton-connect-mobile 1.2.1 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -78,6 +78,7 @@ exports.TransactionInProgressError = TransactionInProgressError;
78
78
  class TonConnectMobile {
79
79
  constructor(config) {
80
80
  this.statusChangeCallbacks = new Set();
81
+ this.eventListeners = new Map();
81
82
  this.currentStatus = { connected: false, wallet: null };
82
83
  this.urlUnsubscribe = null;
83
84
  this.connectionPromise = null;
@@ -90,12 +91,23 @@ class TonConnectMobile {
90
91
  if (!config.scheme) {
91
92
  throw new TonConnectError('scheme is required');
92
93
  }
94
+ // Validate network
95
+ const network = config.network || 'mainnet';
96
+ if (network !== 'mainnet' && network !== 'testnet') {
97
+ throw new TonConnectError('Network must be either "mainnet" or "testnet"');
98
+ }
99
+ // Set default TON API endpoint based on network
100
+ const defaultTonApiEndpoint = network === 'testnet'
101
+ ? 'https://testnet.toncenter.com/api/v2'
102
+ : 'https://toncenter.com/api/v2';
93
103
  this.config = {
94
104
  storageKeyPrefix: 'tonconnect_',
95
105
  connectionTimeout: 300000, // 5 minutes
96
106
  transactionTimeout: 300000, // 5 minutes
97
107
  skipCanOpenURLCheck: true, // Skip canOpenURL check by default (Android issue)
98
108
  preferredWallet: config.preferredWallet,
109
+ network,
110
+ tonApiEndpoint: config.tonApiEndpoint || defaultTonApiEndpoint,
99
111
  ...config,
100
112
  };
101
113
  // Determine which wallet to use
@@ -116,6 +128,7 @@ class TonConnectMobile {
116
128
  console.log('[TON Connect] Initializing SDK with config:', {
117
129
  manifestUrl: this.config.manifestUrl,
118
130
  scheme: this.config.scheme,
131
+ network: this.config.network,
119
132
  wallet: this.currentWallet.name,
120
133
  universalLink: this.currentWallet.universalLink,
121
134
  });
@@ -284,6 +297,8 @@ class TonConnectMobile {
284
297
  // Update status
285
298
  this.currentStatus = { connected: true, wallet };
286
299
  this.notifyStatusChange();
300
+ // Emit connect event
301
+ this.emit('connect', wallet);
287
302
  // Resolve connection promise
288
303
  // CRITICAL: Only resolve if promise still exists and hasn't timed out
289
304
  if (this.connectionPromise) {
@@ -307,6 +322,12 @@ class TonConnectMobile {
307
322
  this.rejectWithError(new TonConnectError('Invalid transaction response'));
308
323
  return;
309
324
  }
325
+ const transactionResult = {
326
+ boc: response.boc,
327
+ signature: response.signature,
328
+ };
329
+ // Emit transaction event
330
+ this.emit('transaction', transactionResult);
310
331
  // Resolve transaction promise
311
332
  // CRITICAL: Only resolve if promise still exists and hasn't timed out
312
333
  if (this.transactionPromise) {
@@ -319,16 +340,15 @@ class TonConnectMobile {
319
340
  // Clear promise first to prevent race conditions
320
341
  this.transactionPromise = null;
321
342
  // Then resolve
322
- promise.resolve({
323
- boc: response.boc,
324
- signature: response.signature,
325
- });
343
+ promise.resolve(transactionResult);
326
344
  }
327
345
  }
328
346
  /**
329
347
  * Reject current promise with error
330
348
  */
331
349
  rejectWithError(error) {
350
+ // Emit error event
351
+ this.emit('error', error);
332
352
  if (this.connectionPromise) {
333
353
  if (this.connectionPromise.timeout !== null) {
334
354
  clearTimeout(this.connectionPromise.timeout);
@@ -648,6 +668,8 @@ class TonConnectMobile {
648
668
  // Update status
649
669
  this.currentStatus = { connected: false, wallet: null };
650
670
  this.notifyStatusChange();
671
+ // Emit disconnect event
672
+ this.emit('disconnect', null);
651
673
  }
652
674
  /**
653
675
  * Get current connection status
@@ -738,6 +760,60 @@ class TonConnectMobile {
738
760
  // Ignore errors in callbacks
739
761
  }
740
762
  });
763
+ // Emit statusChange event
764
+ this.emit('statusChange', status);
765
+ }
766
+ /**
767
+ * Emit event to all listeners
768
+ */
769
+ emit(event, data) {
770
+ const listeners = this.eventListeners.get(event);
771
+ if (listeners) {
772
+ listeners.forEach((listener) => {
773
+ try {
774
+ listener(data);
775
+ }
776
+ catch (error) {
777
+ console.error(`[TON Connect] Error in event listener for ${event}:`, error);
778
+ }
779
+ });
780
+ }
781
+ }
782
+ /**
783
+ * Add event listener
784
+ */
785
+ on(event, listener) {
786
+ if (!this.eventListeners.has(event)) {
787
+ this.eventListeners.set(event, new Set());
788
+ }
789
+ this.eventListeners.get(event).add(listener);
790
+ // Return unsubscribe function
791
+ return () => {
792
+ const listeners = this.eventListeners.get(event);
793
+ if (listeners) {
794
+ listeners.delete(listener);
795
+ }
796
+ };
797
+ }
798
+ /**
799
+ * Remove event listener
800
+ */
801
+ off(event, listener) {
802
+ const listeners = this.eventListeners.get(event);
803
+ if (listeners) {
804
+ listeners.delete(listener);
805
+ }
806
+ }
807
+ /**
808
+ * Remove all listeners for an event
809
+ */
810
+ removeAllListeners(event) {
811
+ if (event) {
812
+ this.eventListeners.delete(event);
813
+ }
814
+ else {
815
+ this.eventListeners.clear();
816
+ }
741
817
  }
742
818
  /**
743
819
  * Validate session ID format
@@ -841,10 +917,204 @@ class TonConnectMobile {
841
917
  this.adapter.destroy();
842
918
  }
843
919
  this.statusChangeCallbacks.clear();
920
+ this.eventListeners.clear();
844
921
  this.connectionPromise = null;
845
922
  this.transactionPromise = null;
846
923
  this.signDataPromise = null;
847
924
  }
925
+ /**
926
+ * Get current network
927
+ */
928
+ getNetwork() {
929
+ return this.config.network;
930
+ }
931
+ /**
932
+ * Set network (mainnet/testnet)
933
+ */
934
+ setNetwork(network) {
935
+ if (network !== 'mainnet' && network !== 'testnet') {
936
+ throw new TonConnectError('Network must be either "mainnet" or "testnet"');
937
+ }
938
+ const oldNetwork = this.config.network;
939
+ // Warn if switching network while connected (wallet connection is network-specific)
940
+ if (this.currentStatus.connected && oldNetwork !== network) {
941
+ console.warn('[TON Connect] Network changed while wallet is connected. ' +
942
+ 'The wallet connection may be invalid for the new network. ' +
943
+ 'Consider disconnecting and reconnecting after network change.');
944
+ }
945
+ this.config.network = network;
946
+ // Update TON API endpoint if not explicitly set
947
+ if (!this.config.tonApiEndpoint || this.config.tonApiEndpoint.includes(oldNetwork)) {
948
+ this.config.tonApiEndpoint =
949
+ network === 'testnet'
950
+ ? 'https://testnet.toncenter.com/api/v2'
951
+ : 'https://toncenter.com/api/v2';
952
+ }
953
+ console.log('[TON Connect] Network changed to:', network);
954
+ // Notify status change to update chain ID in React components
955
+ this.notifyStatusChange();
956
+ }
957
+ /**
958
+ * Get wallet balance
959
+ */
960
+ async getBalance(address) {
961
+ const targetAddress = address || this.currentStatus.wallet?.address;
962
+ if (!targetAddress) {
963
+ throw new TonConnectError('Address is required. Either connect a wallet or provide an address.');
964
+ }
965
+ // Validate address format
966
+ if (!/^[0-9A-Za-z_-]{48}$/.test(targetAddress)) {
967
+ throw new TonConnectError('Invalid TON address format');
968
+ }
969
+ try {
970
+ const apiEndpoint = this.config.tonApiEndpoint ||
971
+ (this.config.network === 'testnet'
972
+ ? 'https://testnet.toncenter.com/api/v2'
973
+ : 'https://toncenter.com/api/v2');
974
+ const url = `${apiEndpoint}/getAddressInformation?address=${encodeURIComponent(targetAddress)}`;
975
+ const response = await fetch(url, {
976
+ method: 'GET',
977
+ headers: {
978
+ 'Accept': 'application/json',
979
+ },
980
+ });
981
+ if (!response.ok) {
982
+ throw new TonConnectError(`Failed to fetch balance: ${response.status} ${response.statusText}`);
983
+ }
984
+ const data = await response.json();
985
+ if (data.ok === false) {
986
+ throw new TonConnectError(data.error || 'Failed to fetch balance');
987
+ }
988
+ // TON Center API returns balance in nanotons
989
+ const balance = data.result?.balance || '0';
990
+ const balanceTon = (BigInt(balance) / BigInt(1000000000)).toString() + '.' +
991
+ (BigInt(balance) % BigInt(1000000000)).toString().padStart(9, '0').replace(/0+$/, '');
992
+ return {
993
+ balance,
994
+ balanceTon: balanceTon === '0.' ? '0' : balanceTon,
995
+ network: this.config.network,
996
+ };
997
+ }
998
+ catch (error) {
999
+ if (error instanceof TonConnectError) {
1000
+ throw error;
1001
+ }
1002
+ throw new TonConnectError(`Failed to get balance: ${error?.message || String(error)}`);
1003
+ }
1004
+ }
1005
+ /**
1006
+ * Get transaction status
1007
+ */
1008
+ async getTransactionStatus(boc, maxAttempts = 10, intervalMs = 2000) {
1009
+ if (!boc || typeof boc !== 'string' || boc.length === 0) {
1010
+ throw new TonConnectError('Transaction BOC is required');
1011
+ }
1012
+ // Extract transaction hash from BOC (simplified - in production, you'd parse the BOC properly)
1013
+ // For now, we'll use a polling approach with TON Center API
1014
+ try {
1015
+ const apiEndpoint = this.config.tonApiEndpoint ||
1016
+ (this.config.network === 'testnet'
1017
+ ? 'https://testnet.toncenter.com/api/v2'
1018
+ : 'https://toncenter.com/api/v2');
1019
+ // Try to get transaction info
1020
+ // Note: This is a simplified implementation. In production, you'd need to:
1021
+ // 1. Parse the BOC to extract transaction hash
1022
+ // 2. Query the blockchain for transaction status
1023
+ // 3. Handle different confirmation states
1024
+ // For now, we'll return a basic status
1025
+ // In a real implementation, you'd query the blockchain API
1026
+ let attempts = 0;
1027
+ let lastError = null;
1028
+ while (attempts < maxAttempts) {
1029
+ try {
1030
+ // This is a placeholder - you'd need to implement actual transaction lookup
1031
+ // For now, we'll simulate checking
1032
+ await new Promise((resolve) => setTimeout(() => resolve(), intervalMs));
1033
+ // In production, you would:
1034
+ // 1. Parse BOC to get transaction hash
1035
+ // 2. Query TON API: GET /getTransactions?address=...&limit=1
1036
+ // 3. Check if transaction exists and is confirmed
1037
+ // For now, return unknown status (as we can't parse BOC without additional libraries)
1038
+ return {
1039
+ status: 'unknown',
1040
+ error: 'Transaction status checking requires BOC parsing. Please use a TON library to parse the BOC and extract the transaction hash.',
1041
+ };
1042
+ }
1043
+ catch (error) {
1044
+ lastError = error;
1045
+ attempts++;
1046
+ if (attempts < maxAttempts) {
1047
+ await new Promise((resolve) => setTimeout(() => resolve(), intervalMs));
1048
+ }
1049
+ }
1050
+ }
1051
+ return {
1052
+ status: 'failed',
1053
+ error: lastError?.message || 'Failed to check transaction status',
1054
+ };
1055
+ }
1056
+ catch (error) {
1057
+ throw new TonConnectError(`Failed to get transaction status: ${error?.message || String(error)}`);
1058
+ }
1059
+ }
1060
+ /**
1061
+ * Get transaction status by hash (more reliable than BOC)
1062
+ */
1063
+ async getTransactionStatusByHash(txHash, address) {
1064
+ if (!txHash || typeof txHash !== 'string' || txHash.length === 0) {
1065
+ throw new TonConnectError('Transaction hash is required');
1066
+ }
1067
+ if (!address || typeof address !== 'string' || address.length === 0) {
1068
+ throw new TonConnectError('Address is required');
1069
+ }
1070
+ try {
1071
+ const apiEndpoint = this.config.tonApiEndpoint ||
1072
+ (this.config.network === 'testnet'
1073
+ ? 'https://testnet.toncenter.com/api/v2'
1074
+ : 'https://toncenter.com/api/v2');
1075
+ // Query transactions for the address
1076
+ const url = `${apiEndpoint}/getTransactions?address=${encodeURIComponent(address)}&limit=100`;
1077
+ const response = await fetch(url, {
1078
+ method: 'GET',
1079
+ headers: {
1080
+ 'Accept': 'application/json',
1081
+ },
1082
+ });
1083
+ if (!response.ok) {
1084
+ throw new TonConnectError(`Failed to fetch transactions: ${response.status} ${response.statusText}`);
1085
+ }
1086
+ const data = await response.json();
1087
+ if (data.ok === false) {
1088
+ throw new TonConnectError(data.error || 'Failed to fetch transactions');
1089
+ }
1090
+ // Search for transaction with matching hash
1091
+ const transactions = data.result || [];
1092
+ const transaction = transactions.find((tx) => tx.transaction_id?.hash === txHash ||
1093
+ tx.transaction_id?.lt === txHash ||
1094
+ JSON.stringify(tx.transaction_id).includes(txHash));
1095
+ if (transaction) {
1096
+ return {
1097
+ status: 'confirmed',
1098
+ hash: transaction.transaction_id?.hash || txHash,
1099
+ blockNumber: transaction.transaction_id?.lt,
1100
+ };
1101
+ }
1102
+ // Transaction not found - could be pending or failed
1103
+ return {
1104
+ status: 'pending',
1105
+ hash: txHash,
1106
+ };
1107
+ }
1108
+ catch (error) {
1109
+ if (error instanceof TonConnectError) {
1110
+ throw error;
1111
+ }
1112
+ return {
1113
+ status: 'failed',
1114
+ error: error?.message || 'Failed to check transaction status',
1115
+ };
1116
+ }
1117
+ }
848
1118
  }
849
1119
  exports.TonConnectMobile = TonConnectMobile;
850
1120
  // Export types
@@ -3,8 +3,8 @@
3
3
  * Provides TonConnectUIProvider, hooks, and components compatible with @tonconnect/ui-react API
4
4
  */
5
5
  import { ReactNode } from 'react';
6
- import { TonConnectMobile, WalletInfo, SendTransactionRequest } from '../index';
7
- import type { TonConnectMobileConfig } from '../types';
6
+ import { TonConnectMobile, WalletInfo, SendTransactionRequest, WalletDefinition, Network, BalanceResponse, TransactionStatusResponse } from '../index';
7
+ import type { TonConnectMobileConfig, TonConnectEventType, TonConnectEventListener } from '../types';
8
8
  /**
9
9
  * Account information (compatible with @tonconnect/ui-react)
10
10
  */
@@ -46,6 +46,7 @@ export interface SignDataResponse {
46
46
  }
47
47
  /**
48
48
  * TonConnect UI instance interface (compatible with @tonconnect/ui-react)
49
+ * Includes all features from @tonconnect/ui-react for full compatibility
49
50
  */
50
51
  export interface TonConnectUI {
51
52
  /** Open connection modal */
@@ -60,6 +61,24 @@ export interface TonConnectUI {
60
61
  sendTransaction: (transaction: SendTransactionRequest) => Promise<TransactionResponse>;
61
62
  /** Sign data */
62
63
  signData: (request: SignDataRequest) => Promise<SignDataResponse>;
64
+ /** Restore connection from stored session */
65
+ restoreConnection: () => Promise<void>;
66
+ /** Set wallet list (customize available wallets) */
67
+ setWalletList: (wallets: WalletDefinition[]) => void;
68
+ /** Get current network */
69
+ getNetwork: () => Network;
70
+ /** Set network (mainnet/testnet) */
71
+ setNetwork: (network: Network) => void;
72
+ /** Get wallet balance */
73
+ getBalance: (address?: string) => Promise<BalanceResponse>;
74
+ /** Get transaction status */
75
+ getTransactionStatus: (boc: string, maxAttempts?: number, intervalMs?: number) => Promise<TransactionStatusResponse>;
76
+ /** Get transaction status by hash */
77
+ getTransactionStatusByHash: (txHash: string, address: string) => Promise<TransactionStatusResponse>;
78
+ /** Add event listener */
79
+ on: <T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>) => () => void;
80
+ /** Remove event listener */
81
+ off: <T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>) => void;
63
82
  /** Current wallet state */
64
83
  wallet: WalletState | null;
65
84
  /** Modal open state */
@@ -67,13 +67,21 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
67
67
  const [walletState, setWalletState] = (0, react_1.useState)(null);
68
68
  const [modalOpen, setModalOpen] = (0, react_1.useState)(false);
69
69
  const [isConnecting, setIsConnecting] = (0, react_1.useState)(false);
70
+ const [customWalletList, setCustomWalletList] = (0, react_1.useState)(null);
71
+ // Get chain ID based on network
72
+ const getChainId = (0, react_1.useCallback)((network) => {
73
+ // TON mainnet chain ID: -239
74
+ // TON testnet chain ID: -3
75
+ return network === 'testnet' ? -3 : -239;
76
+ }, []);
70
77
  // Update wallet state from SDK status
71
78
  const updateWalletState = (0, react_1.useCallback)((status) => {
72
79
  if (status.connected && status.wallet) {
80
+ const network = sdk.getNetwork();
73
81
  setWalletState({
74
82
  account: {
75
83
  address: status.wallet.address,
76
- chain: -239, // TON mainnet chain ID
84
+ chain: getChainId(network),
77
85
  publicKey: status.wallet.publicKey,
78
86
  },
79
87
  wallet: status.wallet,
@@ -87,7 +95,7 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
87
95
  connected: false,
88
96
  });
89
97
  }
90
- }, []);
98
+ }, [sdk, getChainId]);
91
99
  // Subscribe to SDK status changes
92
100
  (0, react_1.useEffect)(() => {
93
101
  // Set initial state
@@ -184,6 +192,58 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
184
192
  throw error;
185
193
  }
186
194
  }, [sdk]);
195
+ // Restore connection from stored session
196
+ const restoreConnection = (0, react_1.useCallback)(async () => {
197
+ try {
198
+ // SDK automatically loads session on initialization
199
+ // This method triggers a re-check of the stored session
200
+ const status = sdk.getStatus();
201
+ if (status.connected && status.wallet) {
202
+ updateWalletState(status);
203
+ }
204
+ }
205
+ catch (error) {
206
+ console.error('[TonConnectUIProvider] Restore connection error:', error);
207
+ throw error;
208
+ }
209
+ }, [sdk, updateWalletState]);
210
+ // Set wallet list (customize available wallets)
211
+ const setWalletList = (0, react_1.useCallback)((wallets) => {
212
+ if (!wallets || !Array.isArray(wallets)) {
213
+ throw new Error('Wallet list must be an array');
214
+ }
215
+ setCustomWalletList(wallets);
216
+ }, []);
217
+ // Get network
218
+ const getNetwork = (0, react_1.useCallback)(() => {
219
+ return sdk.getNetwork();
220
+ }, [sdk]);
221
+ // Set network
222
+ const setNetwork = (0, react_1.useCallback)((network) => {
223
+ sdk.setNetwork(network);
224
+ // Update wallet state to reflect new chain ID
225
+ const status = sdk.getStatus();
226
+ updateWalletState(status);
227
+ }, [sdk, updateWalletState]);
228
+ // Get balance
229
+ const getBalance = (0, react_1.useCallback)(async (address) => {
230
+ return await sdk.getBalance(address);
231
+ }, [sdk]);
232
+ // Get transaction status
233
+ const getTransactionStatus = (0, react_1.useCallback)(async (boc, maxAttempts = 10, intervalMs = 2000) => {
234
+ return await sdk.getTransactionStatus(boc, maxAttempts, intervalMs);
235
+ }, [sdk]);
236
+ // Get transaction status by hash
237
+ const getTransactionStatusByHash = (0, react_1.useCallback)(async (txHash, address) => {
238
+ return await sdk.getTransactionStatusByHash(txHash, address);
239
+ }, [sdk]);
240
+ // Event listeners
241
+ const on = (0, react_1.useCallback)((event, listener) => {
242
+ return sdk.on(event, listener);
243
+ }, [sdk]);
244
+ const off = (0, react_1.useCallback)((event, listener) => {
245
+ sdk.off(event, listener);
246
+ }, [sdk]);
187
247
  // Create TonConnectUI instance
188
248
  const tonConnectUI = {
189
249
  openModal,
@@ -192,6 +252,15 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
192
252
  disconnect,
193
253
  sendTransaction,
194
254
  signData,
255
+ restoreConnection,
256
+ setWalletList,
257
+ getNetwork,
258
+ setNetwork,
259
+ getBalance,
260
+ getTransactionStatus,
261
+ getTransactionStatusByHash,
262
+ on,
263
+ off,
195
264
  wallet: walletState,
196
265
  modalState: {
197
266
  open: modalOpen,
@@ -204,7 +273,7 @@ function TonConnectUIProvider({ config, children, sdkInstance, }) {
204
273
  };
205
274
  return (react_1.default.createElement(TonConnectUIContext.Provider, { value: contextValue },
206
275
  children,
207
- react_1.default.createElement(WalletSelectionModal_1.WalletSelectionModal, { visible: modalOpen && !walletState?.connected, onClose: closeModal })));
276
+ react_1.default.createElement(WalletSelectionModal_1.WalletSelectionModal, { visible: modalOpen && !walletState?.connected, onClose: closeModal, wallets: customWalletList || undefined })));
208
277
  }
209
278
  /**
210
279
  * Hook to access TonConnectUI instance
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * WalletSelectionModal component
3
3
  * Provides a beautiful wallet selection UI compatible with @tonconnect/ui-react
4
+ * Matches the exact UI/UX of @tonconnect/ui-react wallet selection modal
4
5
  */
5
6
  import type { WalletDefinition } from '../index';
6
7
  export interface WalletSelectionModalProps {