@blazium/ton-connect-mobile 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +316 -18
- package/dist/core/wallets.js +4 -0
- package/dist/index.d.ts +38 -1
- package/dist/index.js +274 -4
- package/dist/react/TonConnectUIProvider.d.ts +21 -2
- package/dist/react/TonConnectUIProvider.js +72 -3
- package/dist/react/WalletSelectionModal.d.ts +1 -0
- package/dist/react/WalletSelectionModal.js +138 -80
- package/dist/react/index.d.ts +1 -0
- package/dist/types/index.d.ts +46 -0
- package/package.json +1 -1
- package/src/core/wallets.ts +4 -0
- package/src/index.ts +337 -6
- package/src/react/TonConnectUIProvider.tsx +109 -4
- package/src/react/WalletSelectionModal.tsx +172 -91
- package/src/react/index.ts +1 -0
- package/src/types/index.ts +52 -0
package/src/index.ts
CHANGED
|
@@ -13,6 +13,12 @@ import {
|
|
|
13
13
|
SendTransactionRequest,
|
|
14
14
|
StatusChangeCallback,
|
|
15
15
|
PlatformAdapter,
|
|
16
|
+
Network,
|
|
17
|
+
TonConnectEventType,
|
|
18
|
+
TonConnectEventListener,
|
|
19
|
+
TransactionStatus,
|
|
20
|
+
TransactionStatusResponse,
|
|
21
|
+
BalanceResponse,
|
|
16
22
|
} from './types';
|
|
17
23
|
import {
|
|
18
24
|
buildConnectionRequest,
|
|
@@ -106,8 +112,13 @@ export class TransactionInProgressError extends TonConnectError {
|
|
|
106
112
|
*/
|
|
107
113
|
export class TonConnectMobile {
|
|
108
114
|
private adapter: PlatformAdapter;
|
|
109
|
-
private config: Required<Omit<TonConnectMobileConfig, 'preferredWallet'>> & {
|
|
115
|
+
private config: Required<Omit<TonConnectMobileConfig, 'preferredWallet' | 'network' | 'tonApiEndpoint'>> & {
|
|
116
|
+
preferredWallet?: string;
|
|
117
|
+
network: Network;
|
|
118
|
+
tonApiEndpoint?: string;
|
|
119
|
+
};
|
|
110
120
|
private statusChangeCallbacks: Set<StatusChangeCallback> = new Set();
|
|
121
|
+
private eventListeners: Map<TonConnectEventType, Set<TonConnectEventListener>> = new Map();
|
|
111
122
|
private currentStatus: ConnectionStatus = { connected: false, wallet: null };
|
|
112
123
|
private urlUnsubscribe: (() => void) | null = null;
|
|
113
124
|
private currentWallet!: WalletDefinition;
|
|
@@ -136,14 +147,32 @@ export class TonConnectMobile {
|
|
|
136
147
|
throw new TonConnectError('scheme is required');
|
|
137
148
|
}
|
|
138
149
|
|
|
150
|
+
// Validate network
|
|
151
|
+
const network = config.network || 'mainnet';
|
|
152
|
+
if (network !== 'mainnet' && network !== 'testnet') {
|
|
153
|
+
throw new TonConnectError('Network must be either "mainnet" or "testnet"');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Set default TON API endpoint based on network
|
|
157
|
+
const defaultTonApiEndpoint =
|
|
158
|
+
network === 'testnet'
|
|
159
|
+
? 'https://testnet.toncenter.com/api/v2'
|
|
160
|
+
: 'https://toncenter.com/api/v2';
|
|
161
|
+
|
|
139
162
|
this.config = {
|
|
140
163
|
storageKeyPrefix: 'tonconnect_',
|
|
141
164
|
connectionTimeout: 300000, // 5 minutes
|
|
142
165
|
transactionTimeout: 300000, // 5 minutes
|
|
143
166
|
skipCanOpenURLCheck: true, // Skip canOpenURL check by default (Android issue)
|
|
144
167
|
preferredWallet: config.preferredWallet,
|
|
168
|
+
network,
|
|
169
|
+
tonApiEndpoint: config.tonApiEndpoint || defaultTonApiEndpoint,
|
|
145
170
|
...config,
|
|
146
|
-
} as Required<Omit<TonConnectMobileConfig, 'preferredWallet'>> & {
|
|
171
|
+
} as Required<Omit<TonConnectMobileConfig, 'preferredWallet' | 'network' | 'tonApiEndpoint'>> & {
|
|
172
|
+
preferredWallet?: string;
|
|
173
|
+
network: Network;
|
|
174
|
+
tonApiEndpoint?: string;
|
|
175
|
+
};
|
|
147
176
|
|
|
148
177
|
// Determine which wallet to use
|
|
149
178
|
if (this.config.preferredWallet) {
|
|
@@ -162,6 +191,7 @@ export class TonConnectMobile {
|
|
|
162
191
|
console.log('[TON Connect] Initializing SDK with config:', {
|
|
163
192
|
manifestUrl: this.config.manifestUrl,
|
|
164
193
|
scheme: this.config.scheme,
|
|
194
|
+
network: this.config.network,
|
|
165
195
|
wallet: this.currentWallet.name,
|
|
166
196
|
universalLink: this.currentWallet.universalLink,
|
|
167
197
|
});
|
|
@@ -347,6 +377,9 @@ export class TonConnectMobile {
|
|
|
347
377
|
this.currentStatus = { connected: true, wallet };
|
|
348
378
|
this.notifyStatusChange();
|
|
349
379
|
|
|
380
|
+
// Emit connect event
|
|
381
|
+
this.emit('connect', wallet);
|
|
382
|
+
|
|
350
383
|
// Resolve connection promise
|
|
351
384
|
// CRITICAL: Only resolve if promise still exists and hasn't timed out
|
|
352
385
|
if (this.connectionPromise) {
|
|
@@ -372,6 +405,14 @@ export class TonConnectMobile {
|
|
|
372
405
|
return;
|
|
373
406
|
}
|
|
374
407
|
|
|
408
|
+
const transactionResult = {
|
|
409
|
+
boc: response.boc,
|
|
410
|
+
signature: response.signature,
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// Emit transaction event
|
|
414
|
+
this.emit('transaction', transactionResult);
|
|
415
|
+
|
|
375
416
|
// Resolve transaction promise
|
|
376
417
|
// CRITICAL: Only resolve if promise still exists and hasn't timed out
|
|
377
418
|
if (this.transactionPromise) {
|
|
@@ -384,10 +425,7 @@ export class TonConnectMobile {
|
|
|
384
425
|
// Clear promise first to prevent race conditions
|
|
385
426
|
this.transactionPromise = null;
|
|
386
427
|
// Then resolve
|
|
387
|
-
promise.resolve(
|
|
388
|
-
boc: response.boc,
|
|
389
|
-
signature: response.signature,
|
|
390
|
-
});
|
|
428
|
+
promise.resolve(transactionResult);
|
|
391
429
|
}
|
|
392
430
|
}
|
|
393
431
|
|
|
@@ -395,6 +433,9 @@ export class TonConnectMobile {
|
|
|
395
433
|
* Reject current promise with error
|
|
396
434
|
*/
|
|
397
435
|
private rejectWithError(error: Error): void {
|
|
436
|
+
// Emit error event
|
|
437
|
+
this.emit('error', error);
|
|
438
|
+
|
|
398
439
|
if (this.connectionPromise) {
|
|
399
440
|
if (this.connectionPromise.timeout !== null) {
|
|
400
441
|
clearTimeout(this.connectionPromise.timeout);
|
|
@@ -772,6 +813,9 @@ export class TonConnectMobile {
|
|
|
772
813
|
// Update status
|
|
773
814
|
this.currentStatus = { connected: false, wallet: null };
|
|
774
815
|
this.notifyStatusChange();
|
|
816
|
+
|
|
817
|
+
// Emit disconnect event
|
|
818
|
+
this.emit('disconnect', null);
|
|
775
819
|
}
|
|
776
820
|
|
|
777
821
|
/**
|
|
@@ -876,6 +920,63 @@ export class TonConnectMobile {
|
|
|
876
920
|
// Ignore errors in callbacks
|
|
877
921
|
}
|
|
878
922
|
});
|
|
923
|
+
// Emit statusChange event
|
|
924
|
+
this.emit('statusChange', status);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Emit event to all listeners
|
|
929
|
+
*/
|
|
930
|
+
private emit<T>(event: TonConnectEventType, data: T): void {
|
|
931
|
+
const listeners = this.eventListeners.get(event);
|
|
932
|
+
if (listeners) {
|
|
933
|
+
listeners.forEach((listener) => {
|
|
934
|
+
try {
|
|
935
|
+
listener(data);
|
|
936
|
+
} catch (error) {
|
|
937
|
+
console.error(`[TON Connect] Error in event listener for ${event}:`, error);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Add event listener
|
|
945
|
+
*/
|
|
946
|
+
on<T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>): () => void {
|
|
947
|
+
if (!this.eventListeners.has(event)) {
|
|
948
|
+
this.eventListeners.set(event, new Set());
|
|
949
|
+
}
|
|
950
|
+
this.eventListeners.get(event)!.add(listener);
|
|
951
|
+
|
|
952
|
+
// Return unsubscribe function
|
|
953
|
+
return () => {
|
|
954
|
+
const listeners = this.eventListeners.get(event);
|
|
955
|
+
if (listeners) {
|
|
956
|
+
listeners.delete(listener);
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Remove event listener
|
|
963
|
+
*/
|
|
964
|
+
off<T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>): void {
|
|
965
|
+
const listeners = this.eventListeners.get(event);
|
|
966
|
+
if (listeners) {
|
|
967
|
+
listeners.delete(listener);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Remove all listeners for an event
|
|
973
|
+
*/
|
|
974
|
+
removeAllListeners(event?: TonConnectEventType): void {
|
|
975
|
+
if (event) {
|
|
976
|
+
this.eventListeners.delete(event);
|
|
977
|
+
} else {
|
|
978
|
+
this.eventListeners.clear();
|
|
979
|
+
}
|
|
879
980
|
}
|
|
880
981
|
|
|
881
982
|
/**
|
|
@@ -990,10 +1091,240 @@ export class TonConnectMobile {
|
|
|
990
1091
|
}
|
|
991
1092
|
|
|
992
1093
|
this.statusChangeCallbacks.clear();
|
|
1094
|
+
this.eventListeners.clear();
|
|
993
1095
|
this.connectionPromise = null;
|
|
994
1096
|
this.transactionPromise = null;
|
|
995
1097
|
this.signDataPromise = null;
|
|
996
1098
|
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Get current network
|
|
1102
|
+
*/
|
|
1103
|
+
getNetwork(): Network {
|
|
1104
|
+
return this.config.network;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Set network (mainnet/testnet)
|
|
1109
|
+
*/
|
|
1110
|
+
setNetwork(network: Network): void {
|
|
1111
|
+
if (network !== 'mainnet' && network !== 'testnet') {
|
|
1112
|
+
throw new TonConnectError('Network must be either "mainnet" or "testnet"');
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const oldNetwork = this.config.network;
|
|
1116
|
+
|
|
1117
|
+
// Warn if switching network while connected (wallet connection is network-specific)
|
|
1118
|
+
if (this.currentStatus.connected && oldNetwork !== network) {
|
|
1119
|
+
console.warn(
|
|
1120
|
+
'[TON Connect] Network changed while wallet is connected. ' +
|
|
1121
|
+
'The wallet connection may be invalid for the new network. ' +
|
|
1122
|
+
'Consider disconnecting and reconnecting after network change.'
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
this.config.network = network;
|
|
1127
|
+
|
|
1128
|
+
// Update TON API endpoint if not explicitly set
|
|
1129
|
+
if (!this.config.tonApiEndpoint || this.config.tonApiEndpoint.includes(oldNetwork)) {
|
|
1130
|
+
this.config.tonApiEndpoint =
|
|
1131
|
+
network === 'testnet'
|
|
1132
|
+
? 'https://testnet.toncenter.com/api/v2'
|
|
1133
|
+
: 'https://toncenter.com/api/v2';
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
console.log('[TON Connect] Network changed to:', network);
|
|
1137
|
+
|
|
1138
|
+
// Notify status change to update chain ID in React components
|
|
1139
|
+
this.notifyStatusChange();
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Get wallet balance
|
|
1144
|
+
*/
|
|
1145
|
+
async getBalance(address?: string): Promise<BalanceResponse> {
|
|
1146
|
+
const targetAddress = address || this.currentStatus.wallet?.address;
|
|
1147
|
+
if (!targetAddress) {
|
|
1148
|
+
throw new TonConnectError('Address is required. Either connect a wallet or provide an address.');
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Validate address format
|
|
1152
|
+
if (!/^[0-9A-Za-z_-]{48}$/.test(targetAddress)) {
|
|
1153
|
+
throw new TonConnectError('Invalid TON address format');
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
try {
|
|
1157
|
+
const apiEndpoint = this.config.tonApiEndpoint ||
|
|
1158
|
+
(this.config.network === 'testnet'
|
|
1159
|
+
? 'https://testnet.toncenter.com/api/v2'
|
|
1160
|
+
: 'https://toncenter.com/api/v2');
|
|
1161
|
+
|
|
1162
|
+
const url = `${apiEndpoint}/getAddressInformation?address=${encodeURIComponent(targetAddress)}`;
|
|
1163
|
+
|
|
1164
|
+
const response = await fetch(url, {
|
|
1165
|
+
method: 'GET',
|
|
1166
|
+
headers: {
|
|
1167
|
+
'Accept': 'application/json',
|
|
1168
|
+
},
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
if (!response.ok) {
|
|
1172
|
+
throw new TonConnectError(`Failed to fetch balance: ${response.status} ${response.statusText}`);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
const data = await response.json();
|
|
1176
|
+
|
|
1177
|
+
if (data.ok === false) {
|
|
1178
|
+
throw new TonConnectError(data.error || 'Failed to fetch balance');
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// TON Center API returns balance in nanotons
|
|
1182
|
+
const balance = data.result?.balance || '0';
|
|
1183
|
+
const balanceTon = (BigInt(balance) / BigInt(1000000000)).toString() + '.' +
|
|
1184
|
+
(BigInt(balance) % BigInt(1000000000)).toString().padStart(9, '0').replace(/0+$/, '');
|
|
1185
|
+
|
|
1186
|
+
return {
|
|
1187
|
+
balance,
|
|
1188
|
+
balanceTon: balanceTon === '0.' ? '0' : balanceTon,
|
|
1189
|
+
network: this.config.network,
|
|
1190
|
+
};
|
|
1191
|
+
} catch (error: any) {
|
|
1192
|
+
if (error instanceof TonConnectError) {
|
|
1193
|
+
throw error;
|
|
1194
|
+
}
|
|
1195
|
+
throw new TonConnectError(`Failed to get balance: ${error?.message || String(error)}`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* Get transaction status
|
|
1201
|
+
*/
|
|
1202
|
+
async getTransactionStatus(boc: string, maxAttempts: number = 10, intervalMs: number = 2000): Promise<TransactionStatusResponse> {
|
|
1203
|
+
if (!boc || typeof boc !== 'string' || boc.length === 0) {
|
|
1204
|
+
throw new TonConnectError('Transaction BOC is required');
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Extract transaction hash from BOC (simplified - in production, you'd parse the BOC properly)
|
|
1208
|
+
// For now, we'll use a polling approach with TON Center API
|
|
1209
|
+
try {
|
|
1210
|
+
const apiEndpoint = this.config.tonApiEndpoint ||
|
|
1211
|
+
(this.config.network === 'testnet'
|
|
1212
|
+
? 'https://testnet.toncenter.com/api/v2'
|
|
1213
|
+
: 'https://toncenter.com/api/v2');
|
|
1214
|
+
|
|
1215
|
+
// Try to get transaction info
|
|
1216
|
+
// Note: This is a simplified implementation. In production, you'd need to:
|
|
1217
|
+
// 1. Parse the BOC to extract transaction hash
|
|
1218
|
+
// 2. Query the blockchain for transaction status
|
|
1219
|
+
// 3. Handle different confirmation states
|
|
1220
|
+
|
|
1221
|
+
// For now, we'll return a basic status
|
|
1222
|
+
// In a real implementation, you'd query the blockchain API
|
|
1223
|
+
let attempts = 0;
|
|
1224
|
+
let lastError: Error | null = null;
|
|
1225
|
+
|
|
1226
|
+
while (attempts < maxAttempts) {
|
|
1227
|
+
try {
|
|
1228
|
+
// This is a placeholder - you'd need to implement actual transaction lookup
|
|
1229
|
+
// For now, we'll simulate checking
|
|
1230
|
+
await new Promise<void>((resolve) => setTimeout(() => resolve(), intervalMs));
|
|
1231
|
+
|
|
1232
|
+
// In production, you would:
|
|
1233
|
+
// 1. Parse BOC to get transaction hash
|
|
1234
|
+
// 2. Query TON API: GET /getTransactions?address=...&limit=1
|
|
1235
|
+
// 3. Check if transaction exists and is confirmed
|
|
1236
|
+
|
|
1237
|
+
// For now, return unknown status (as we can't parse BOC without additional libraries)
|
|
1238
|
+
return {
|
|
1239
|
+
status: 'unknown',
|
|
1240
|
+
error: 'Transaction status checking requires BOC parsing. Please use a TON library to parse the BOC and extract the transaction hash.',
|
|
1241
|
+
};
|
|
1242
|
+
} catch (error: any) {
|
|
1243
|
+
lastError = error;
|
|
1244
|
+
attempts++;
|
|
1245
|
+
if (attempts < maxAttempts) {
|
|
1246
|
+
await new Promise<void>((resolve) => setTimeout(() => resolve(), intervalMs));
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
return {
|
|
1252
|
+
status: 'failed',
|
|
1253
|
+
error: lastError?.message || 'Failed to check transaction status',
|
|
1254
|
+
};
|
|
1255
|
+
} catch (error: any) {
|
|
1256
|
+
throw new TonConnectError(`Failed to get transaction status: ${error?.message || String(error)}`);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* Get transaction status by hash (more reliable than BOC)
|
|
1262
|
+
*/
|
|
1263
|
+
async getTransactionStatusByHash(txHash: string, address: string): Promise<TransactionStatusResponse> {
|
|
1264
|
+
if (!txHash || typeof txHash !== 'string' || txHash.length === 0) {
|
|
1265
|
+
throw new TonConnectError('Transaction hash is required');
|
|
1266
|
+
}
|
|
1267
|
+
if (!address || typeof address !== 'string' || address.length === 0) {
|
|
1268
|
+
throw new TonConnectError('Address is required');
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
try {
|
|
1272
|
+
const apiEndpoint = this.config.tonApiEndpoint ||
|
|
1273
|
+
(this.config.network === 'testnet'
|
|
1274
|
+
? 'https://testnet.toncenter.com/api/v2'
|
|
1275
|
+
: 'https://toncenter.com/api/v2');
|
|
1276
|
+
|
|
1277
|
+
// Query transactions for the address
|
|
1278
|
+
const url = `${apiEndpoint}/getTransactions?address=${encodeURIComponent(address)}&limit=100`;
|
|
1279
|
+
|
|
1280
|
+
const response = await fetch(url, {
|
|
1281
|
+
method: 'GET',
|
|
1282
|
+
headers: {
|
|
1283
|
+
'Accept': 'application/json',
|
|
1284
|
+
},
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
if (!response.ok) {
|
|
1288
|
+
throw new TonConnectError(`Failed to fetch transactions: ${response.status} ${response.statusText}`);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
const data = await response.json();
|
|
1292
|
+
|
|
1293
|
+
if (data.ok === false) {
|
|
1294
|
+
throw new TonConnectError(data.error || 'Failed to fetch transactions');
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// Search for transaction with matching hash
|
|
1298
|
+
const transactions = data.result || [];
|
|
1299
|
+
const transaction = transactions.find((tx: any) =>
|
|
1300
|
+
tx.transaction_id?.hash === txHash ||
|
|
1301
|
+
tx.transaction_id?.lt === txHash ||
|
|
1302
|
+
JSON.stringify(tx.transaction_id).includes(txHash)
|
|
1303
|
+
);
|
|
1304
|
+
|
|
1305
|
+
if (transaction) {
|
|
1306
|
+
return {
|
|
1307
|
+
status: 'confirmed',
|
|
1308
|
+
hash: transaction.transaction_id?.hash || txHash,
|
|
1309
|
+
blockNumber: transaction.transaction_id?.lt,
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Transaction not found - could be pending or failed
|
|
1314
|
+
return {
|
|
1315
|
+
status: 'pending',
|
|
1316
|
+
hash: txHash,
|
|
1317
|
+
};
|
|
1318
|
+
} catch (error: any) {
|
|
1319
|
+
if (error instanceof TonConnectError) {
|
|
1320
|
+
throw error;
|
|
1321
|
+
}
|
|
1322
|
+
return {
|
|
1323
|
+
status: 'failed',
|
|
1324
|
+
error: error?.message || 'Failed to check transaction status',
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
997
1328
|
}
|
|
998
1329
|
|
|
999
1330
|
// Export types
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
|
|
7
|
-
import { TonConnectMobile, ConnectionStatus, WalletInfo, SendTransactionRequest } from '../index';
|
|
8
|
-
import type { TonConnectMobileConfig } from '../types';
|
|
7
|
+
import { TonConnectMobile, ConnectionStatus, WalletInfo, SendTransactionRequest, WalletDefinition, Network, BalanceResponse, TransactionStatusResponse } from '../index';
|
|
8
|
+
import type { TonConnectMobileConfig, TonConnectEventType, TonConnectEventListener } from '../types';
|
|
9
9
|
import { WalletSelectionModal } from './WalletSelectionModal';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -54,6 +54,7 @@ export interface SignDataResponse {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* TonConnect UI instance interface (compatible with @tonconnect/ui-react)
|
|
57
|
+
* Includes all features from @tonconnect/ui-react for full compatibility
|
|
57
58
|
*/
|
|
58
59
|
export interface TonConnectUI {
|
|
59
60
|
/** Open connection modal */
|
|
@@ -68,6 +69,24 @@ export interface TonConnectUI {
|
|
|
68
69
|
sendTransaction: (transaction: SendTransactionRequest) => Promise<TransactionResponse>;
|
|
69
70
|
/** Sign data */
|
|
70
71
|
signData: (request: SignDataRequest) => Promise<SignDataResponse>;
|
|
72
|
+
/** Restore connection from stored session */
|
|
73
|
+
restoreConnection: () => Promise<void>;
|
|
74
|
+
/** Set wallet list (customize available wallets) */
|
|
75
|
+
setWalletList: (wallets: WalletDefinition[]) => void;
|
|
76
|
+
/** Get current network */
|
|
77
|
+
getNetwork: () => Network;
|
|
78
|
+
/** Set network (mainnet/testnet) */
|
|
79
|
+
setNetwork: (network: Network) => void;
|
|
80
|
+
/** Get wallet balance */
|
|
81
|
+
getBalance: (address?: string) => Promise<BalanceResponse>;
|
|
82
|
+
/** Get transaction status */
|
|
83
|
+
getTransactionStatus: (boc: string, maxAttempts?: number, intervalMs?: number) => Promise<TransactionStatusResponse>;
|
|
84
|
+
/** Get transaction status by hash */
|
|
85
|
+
getTransactionStatusByHash: (txHash: string, address: string) => Promise<TransactionStatusResponse>;
|
|
86
|
+
/** Add event listener */
|
|
87
|
+
on: <T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>) => () => void;
|
|
88
|
+
/** Remove event listener */
|
|
89
|
+
off: <T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>) => void;
|
|
71
90
|
/** Current wallet state */
|
|
72
91
|
wallet: WalletState | null;
|
|
73
92
|
/** Modal open state */
|
|
@@ -124,14 +143,23 @@ export function TonConnectUIProvider({
|
|
|
124
143
|
const [walletState, setWalletState] = useState<WalletState | null>(null);
|
|
125
144
|
const [modalOpen, setModalOpen] = useState(false);
|
|
126
145
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
146
|
+
const [customWalletList, setCustomWalletList] = useState<WalletDefinition[] | null>(null);
|
|
147
|
+
|
|
148
|
+
// Get chain ID based on network
|
|
149
|
+
const getChainId = useCallback((network: Network): number => {
|
|
150
|
+
// TON mainnet chain ID: -239
|
|
151
|
+
// TON testnet chain ID: -3
|
|
152
|
+
return network === 'testnet' ? -3 : -239;
|
|
153
|
+
}, []);
|
|
127
154
|
|
|
128
155
|
// Update wallet state from SDK status
|
|
129
156
|
const updateWalletState = useCallback((status: ConnectionStatus) => {
|
|
130
157
|
if (status.connected && status.wallet) {
|
|
158
|
+
const network = sdk.getNetwork();
|
|
131
159
|
setWalletState({
|
|
132
160
|
account: {
|
|
133
161
|
address: status.wallet.address,
|
|
134
|
-
chain:
|
|
162
|
+
chain: getChainId(network),
|
|
135
163
|
publicKey: status.wallet.publicKey,
|
|
136
164
|
},
|
|
137
165
|
wallet: status.wallet,
|
|
@@ -144,7 +172,7 @@ export function TonConnectUIProvider({
|
|
|
144
172
|
connected: false,
|
|
145
173
|
});
|
|
146
174
|
}
|
|
147
|
-
}, []);
|
|
175
|
+
}, [sdk, getChainId]);
|
|
148
176
|
|
|
149
177
|
// Subscribe to SDK status changes
|
|
150
178
|
useEffect(() => {
|
|
@@ -257,6 +285,73 @@ export function TonConnectUIProvider({
|
|
|
257
285
|
[sdk]
|
|
258
286
|
);
|
|
259
287
|
|
|
288
|
+
// Restore connection from stored session
|
|
289
|
+
const restoreConnection = useCallback(async (): Promise<void> => {
|
|
290
|
+
try {
|
|
291
|
+
// SDK automatically loads session on initialization
|
|
292
|
+
// This method triggers a re-check of the stored session
|
|
293
|
+
const status = sdk.getStatus();
|
|
294
|
+
if (status.connected && status.wallet) {
|
|
295
|
+
updateWalletState(status);
|
|
296
|
+
}
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error('[TonConnectUIProvider] Restore connection error:', error);
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
}, [sdk, updateWalletState]);
|
|
302
|
+
|
|
303
|
+
// Set wallet list (customize available wallets)
|
|
304
|
+
const setWalletList = useCallback((wallets: WalletDefinition[]): void => {
|
|
305
|
+
if (!wallets || !Array.isArray(wallets)) {
|
|
306
|
+
throw new Error('Wallet list must be an array');
|
|
307
|
+
}
|
|
308
|
+
setCustomWalletList(wallets);
|
|
309
|
+
}, []);
|
|
310
|
+
|
|
311
|
+
// Get network
|
|
312
|
+
const getNetwork = useCallback((): Network => {
|
|
313
|
+
return sdk.getNetwork();
|
|
314
|
+
}, [sdk]);
|
|
315
|
+
|
|
316
|
+
// Set network
|
|
317
|
+
const setNetwork = useCallback((network: Network): void => {
|
|
318
|
+
sdk.setNetwork(network);
|
|
319
|
+
// Update wallet state to reflect new chain ID
|
|
320
|
+
const status = sdk.getStatus();
|
|
321
|
+
updateWalletState(status);
|
|
322
|
+
}, [sdk, updateWalletState]);
|
|
323
|
+
|
|
324
|
+
// Get balance
|
|
325
|
+
const getBalance = useCallback(async (address?: string): Promise<BalanceResponse> => {
|
|
326
|
+
return await sdk.getBalance(address);
|
|
327
|
+
}, [sdk]);
|
|
328
|
+
|
|
329
|
+
// Get transaction status
|
|
330
|
+
const getTransactionStatus = useCallback(async (
|
|
331
|
+
boc: string,
|
|
332
|
+
maxAttempts: number = 10,
|
|
333
|
+
intervalMs: number = 2000
|
|
334
|
+
): Promise<TransactionStatusResponse> => {
|
|
335
|
+
return await sdk.getTransactionStatus(boc, maxAttempts, intervalMs);
|
|
336
|
+
}, [sdk]);
|
|
337
|
+
|
|
338
|
+
// Get transaction status by hash
|
|
339
|
+
const getTransactionStatusByHash = useCallback(async (
|
|
340
|
+
txHash: string,
|
|
341
|
+
address: string
|
|
342
|
+
): Promise<TransactionStatusResponse> => {
|
|
343
|
+
return await sdk.getTransactionStatusByHash(txHash, address);
|
|
344
|
+
}, [sdk]);
|
|
345
|
+
|
|
346
|
+
// Event listeners
|
|
347
|
+
const on = useCallback(<T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>): (() => void) => {
|
|
348
|
+
return sdk.on(event, listener);
|
|
349
|
+
}, [sdk]);
|
|
350
|
+
|
|
351
|
+
const off = useCallback(<T = any>(event: TonConnectEventType, listener: TonConnectEventListener<T>): void => {
|
|
352
|
+
sdk.off(event, listener);
|
|
353
|
+
}, [sdk]);
|
|
354
|
+
|
|
260
355
|
// Create TonConnectUI instance
|
|
261
356
|
const tonConnectUI: TonConnectUI = {
|
|
262
357
|
openModal,
|
|
@@ -265,6 +360,15 @@ export function TonConnectUIProvider({
|
|
|
265
360
|
disconnect,
|
|
266
361
|
sendTransaction,
|
|
267
362
|
signData,
|
|
363
|
+
restoreConnection,
|
|
364
|
+
setWalletList,
|
|
365
|
+
getNetwork,
|
|
366
|
+
setNetwork,
|
|
367
|
+
getBalance,
|
|
368
|
+
getTransactionStatus,
|
|
369
|
+
getTransactionStatusByHash,
|
|
370
|
+
on,
|
|
371
|
+
off,
|
|
268
372
|
wallet: walletState,
|
|
269
373
|
modalState: {
|
|
270
374
|
open: modalOpen,
|
|
@@ -284,6 +388,7 @@ export function TonConnectUIProvider({
|
|
|
284
388
|
<WalletSelectionModal
|
|
285
389
|
visible={modalOpen && !walletState?.connected}
|
|
286
390
|
onClose={closeModal}
|
|
391
|
+
wallets={customWalletList || undefined}
|
|
287
392
|
/>
|
|
288
393
|
</TonConnectUIContext.Provider>
|
|
289
394
|
);
|