@dynamic-labs/ton 4.60.1 → 4.61.1
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/CHANGELOG.md +22 -0
- package/package.cjs +1 -1
- package/package.js +1 -1
- package/package.json +8 -8
- package/src/TonWalletConnector.cjs +1 -1
- package/src/TonWalletConnector.js +1 -1
- package/src/connectors/TonConnectConnector/TonConnectConnector.cjs +709 -0
- package/src/connectors/TonConnectConnector/TonConnectConnector.d.ts +169 -0
- package/src/connectors/TonConnectConnector/TonConnectConnector.js +705 -0
- package/src/connectors/TonConnectConnector/index.d.ts +1 -0
- package/src/consts.d.ts +4 -0
- package/src/index.cjs +11 -5
- package/src/index.d.ts +6 -1
- package/src/index.js +8 -3
- package/src/types.d.ts +61 -1
- package/src/utils/debugLog/debugLog.cjs +55 -0
- package/src/utils/debugLog/debugLog.d.ts +34 -0
- package/src/utils/debugLog/debugLog.js +49 -0
- package/src/utils/debugLog/index.d.ts +1 -0
- package/src/utils/fetchTonWalletConnectors/fetchTonWalletConnectors.cjs +113 -0
- package/src/utils/fetchTonWalletConnectors/fetchTonWalletConnectors.d.ts +41 -0
- package/src/utils/fetchTonWalletConnectors/fetchTonWalletConnectors.js +108 -0
- package/src/utils/fetchTonWalletConnectors/index.d.ts +1 -0
- package/src/utils/index.d.ts +1 -0
- package/src/waas/connector/DynamicWaasTonConnector.d.ts +1 -2
- package/src/wallet/TonWallet/TonWallet.cjs +140 -0
- package/src/wallet/TonWallet/TonWallet.d.ts +65 -0
- package/src/wallet/TonWallet/TonWallet.js +136 -0
- package/src/wallet/TonWallet/index.d.ts +1 -0
- package/src/wallet/WaasTonWallet.cjs +1 -1
- package/src/wallet/WaasTonWallet.js +1 -1
- package/src/wallet/isTonWallet/isTonWallet.cjs +7 -1
- package/src/wallet/isTonWallet/isTonWallet.d.ts +7 -1
- package/src/wallet/isTonWallet/isTonWallet.js +7 -1
- package/src/TonWalletConnectors.cjs +0 -13
- package/src/TonWalletConnectors.d.ts +0 -2
- package/src/TonWalletConnectors.js +0 -9
- package/src/wallet/TonWallet.cjs +0 -109
- package/src/wallet/TonWallet.d.ts +0 -60
- package/src/wallet/TonWallet.js +0 -105
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5
|
+
|
|
6
|
+
var _tslib = require('../../../_virtual/_tslib.cjs');
|
|
7
|
+
var sdk = require('@tonconnect/sdk');
|
|
8
|
+
var core = require('@ton/core');
|
|
9
|
+
var ton = require('@ton/ton');
|
|
10
|
+
var walletConnectorCore = require('@dynamic-labs/wallet-connector-core');
|
|
11
|
+
var utils = require('@dynamic-labs/utils');
|
|
12
|
+
var TonWallet = require('../../wallet/TonWallet/TonWallet.cjs');
|
|
13
|
+
var debugLog = require('../../utils/debugLog/debugLog.cjs');
|
|
14
|
+
var TonUiTransaction = require('../../utils/TonUiTransaction/TonUiTransaction.cjs');
|
|
15
|
+
var prepareTonTransfer = require('../../utils/prepareTonTransfer/prepareTonTransfer.cjs');
|
|
16
|
+
var prepareJettonTransfer = require('../../utils/prepareJettonTransfer/prepareJettonTransfer.cjs');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* TON Connect connector implementation
|
|
20
|
+
*
|
|
21
|
+
* Uses the TON Connect SDK to connect with TON wallets that support
|
|
22
|
+
* the TON Connect protocol (Tonkeeper, MyTonWallet, etc.)
|
|
23
|
+
*/
|
|
24
|
+
class TonConnectConnector extends walletConnectorCore.WalletConnectorBase {
|
|
25
|
+
/**
|
|
26
|
+
* Get the TonConnect instance (for discovering wallets and advanced usage)
|
|
27
|
+
*/
|
|
28
|
+
getTonConnect() {
|
|
29
|
+
if (!this.tonConnect) {
|
|
30
|
+
const manifestUrl = typeof window !== 'undefined'
|
|
31
|
+
? `${window.location.origin}/tonconnect-manifest.json`
|
|
32
|
+
: 'https://dynamic.xyz/tonconnect-manifest.json';
|
|
33
|
+
// Debug logging
|
|
34
|
+
debugLog.debugLogMultiline([
|
|
35
|
+
'[TON Connect] Initializing TonConnect with manifest URL:',
|
|
36
|
+
manifestUrl,
|
|
37
|
+
], [
|
|
38
|
+
'[TON Connect] Current origin:',
|
|
39
|
+
typeof window !== 'undefined' ? window.location.origin : 'SSR',
|
|
40
|
+
]);
|
|
41
|
+
this.tonConnect = new sdk.TonConnect({
|
|
42
|
+
manifestUrl,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return this.tonConnect;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new TON Connect connector
|
|
49
|
+
*
|
|
50
|
+
* @param opts - Configuration options
|
|
51
|
+
*/
|
|
52
|
+
constructor(opts) {
|
|
53
|
+
super(opts);
|
|
54
|
+
this.ChainWallet = TonWallet.TonWallet;
|
|
55
|
+
this.name = 'TON Connect';
|
|
56
|
+
this.overrideKey = 'tonconnect';
|
|
57
|
+
this.connectedChain = 'TON';
|
|
58
|
+
this.supportedChains = ['TON'];
|
|
59
|
+
this.canConnectViaQrCode = true;
|
|
60
|
+
this.connectedWallet = null;
|
|
61
|
+
this.pendingProofPayload = null;
|
|
62
|
+
this.tonNetworks = opts.tonNetworks || [];
|
|
63
|
+
this.overrideKey = opts.overrideKey || 'tonconnect';
|
|
64
|
+
// Only initialize TonConnect in browser environment
|
|
65
|
+
// This prevents SSR errors and allows the connector to be created
|
|
66
|
+
if (typeof window !== 'undefined') {
|
|
67
|
+
try {
|
|
68
|
+
const manifestUrl = opts.manifestUrl ||
|
|
69
|
+
`${window.location.origin}/tonconnect-manifest.json`;
|
|
70
|
+
// Debug logging
|
|
71
|
+
debugLog.debugLogMultiline(['[TON Connect] Constructor - manifest URL:', manifestUrl], [
|
|
72
|
+
'[TON Connect] Constructor - window.location:',
|
|
73
|
+
{ href: window.location.href, origin: window.location.origin },
|
|
74
|
+
]);
|
|
75
|
+
this.tonConnect = new sdk.TonConnect({
|
|
76
|
+
manifestUrl,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// If TonConnect fails to initialize, set to null and initialize lazily
|
|
81
|
+
walletConnectorCore.logger.error('[TON Connect] Failed to initialize TonConnect:', error);
|
|
82
|
+
debugLog.debugLogWithLevel('error', '[TON Connect] Constructor - Error initializing:', error);
|
|
83
|
+
this.tonConnect = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// For SSR, tonConnect will be null and initialized lazily in getTonConnect()
|
|
88
|
+
this.tonConnect = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Setup event listeners for TON Connect
|
|
93
|
+
*/
|
|
94
|
+
setupEventListeners() {
|
|
95
|
+
if (typeof window === 'undefined') {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const tonConnect = this.getTonConnect();
|
|
99
|
+
tonConnect.onStatusChange((wallet) => {
|
|
100
|
+
if (wallet) {
|
|
101
|
+
this.connectedWallet = wallet;
|
|
102
|
+
const userFriendlyAddress = this.convertAddressToUserFriendly(wallet.account.address);
|
|
103
|
+
this.emit('accountChange', { accounts: [userFriendlyAddress] });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.connectedWallet = null;
|
|
107
|
+
this.emit('disconnect');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if TON Connect is available
|
|
113
|
+
*/
|
|
114
|
+
isInstalledOnBrowser() {
|
|
115
|
+
// TON Connect wallets are mobile apps, not browser extensions
|
|
116
|
+
// Return false so they show as QR code/deep link wallets
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Connect to a TON wallet
|
|
121
|
+
* @param tonProofPayload - Optional payload for tonProof authentication
|
|
122
|
+
*/
|
|
123
|
+
connect(tonProofPayload) {
|
|
124
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
try {
|
|
126
|
+
const tonConnect = this.getTonConnect();
|
|
127
|
+
// If already connected, return early
|
|
128
|
+
if (tonConnect.connected) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Get available wallets
|
|
132
|
+
const wallets = yield tonConnect.getWallets();
|
|
133
|
+
if (wallets.length === 0) {
|
|
134
|
+
throw new utils.DynamicError('No TON wallets available');
|
|
135
|
+
}
|
|
136
|
+
const matchingWallet = this.findMatchingWallet(wallets);
|
|
137
|
+
if (!matchingWallet) {
|
|
138
|
+
throw new utils.DynamicError(`TON wallet "${this.overrideKey}" not found in available wallets`);
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
// Use provided payload or pending payload for tonProof
|
|
142
|
+
const proofPayload = tonProofPayload || this.pendingProofPayload;
|
|
143
|
+
const connectOptions = proofPayload
|
|
144
|
+
? { request: { tonProof: proofPayload } }
|
|
145
|
+
: undefined;
|
|
146
|
+
tonConnect.connect(matchingWallet, connectOptions);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
debugLog.debugLogWithLevel('error', '[TON Connect] Error during connect:', error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (this.isUserRejectionError(error)) {
|
|
155
|
+
throw new utils.DynamicError('User rejected connection');
|
|
156
|
+
}
|
|
157
|
+
walletConnectorCore.logger.error('[TON Connect] Connection failed:', error);
|
|
158
|
+
throw new utils.DynamicError(`Failed to connect to TON wallet: ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Find a wallet matching this connector's overrideKey
|
|
164
|
+
*/
|
|
165
|
+
findMatchingWallet(wallets) {
|
|
166
|
+
const overrideKeyLower = this.overrideKey.toLowerCase();
|
|
167
|
+
return (wallets.find((wallet) => wallet.appName.toLowerCase() === overrideKeyLower ||
|
|
168
|
+
wallet.name.toLowerCase() === overrideKeyLower) ||
|
|
169
|
+
wallets.find((wallet) => wallet.appName.toLowerCase().startsWith(overrideKeyLower) ||
|
|
170
|
+
wallet.name.toLowerCase().startsWith(overrideKeyLower)) ||
|
|
171
|
+
wallets.find((wallet) => wallet.appName.toLowerCase().includes(overrideKeyLower) ||
|
|
172
|
+
wallet.name.toLowerCase().includes(overrideKeyLower)));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the current wallet address in user-friendly format (EQ... or UQ...)
|
|
176
|
+
*/
|
|
177
|
+
getAddress() {
|
|
178
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
var _a, _b, _c;
|
|
180
|
+
const tonConnect = this.getTonConnect();
|
|
181
|
+
// If already connected, return the address immediately
|
|
182
|
+
if (tonConnect.connected && ((_a = tonConnect.account) === null || _a === void 0 ? void 0 : _a.address)) {
|
|
183
|
+
return this.convertAddressToUserFriendly(tonConnect.account.address);
|
|
184
|
+
}
|
|
185
|
+
// If not connected, initiate connection and wait for it to complete
|
|
186
|
+
try {
|
|
187
|
+
// Start the connection process (shows QR code/deep link)
|
|
188
|
+
yield this.connect();
|
|
189
|
+
// After connect() returns, the QR code is shown but connection is not yet established
|
|
190
|
+
// Wait for the connection to be established by polling or waiting for status change
|
|
191
|
+
// TON Connect SDK doesn't provide a clean way to wait, so we poll
|
|
192
|
+
const maxWaitTime = 300000; // 5 minutes
|
|
193
|
+
const pollInterval = 500; // Check every 500ms
|
|
194
|
+
const startTime = Date.now();
|
|
195
|
+
while (!tonConnect.connected && Date.now() - startTime < maxWaitTime) {
|
|
196
|
+
yield new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
197
|
+
// Check if connected after the delay
|
|
198
|
+
if (tonConnect.connected && ((_b = tonConnect.account) === null || _b === void 0 ? void 0 : _b.address)) {
|
|
199
|
+
const userFriendlyAddress = this.convertAddressToUserFriendly(tonConnect.account.address);
|
|
200
|
+
return userFriendlyAddress;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// If we get here, either timeout or still not connected
|
|
204
|
+
if (!tonConnect.connected) {
|
|
205
|
+
throw new utils.DynamicError('Connection timeout - user did not approve the connection');
|
|
206
|
+
}
|
|
207
|
+
const rawAddress = (_c = tonConnect.account) === null || _c === void 0 ? void 0 : _c.address;
|
|
208
|
+
return rawAddress
|
|
209
|
+
? this.convertAddressToUserFriendly(rawAddress)
|
|
210
|
+
: undefined;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
walletConnectorCore.logger.error('[TON Connect] Failed to get address:', error);
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get connected accounts
|
|
220
|
+
*/
|
|
221
|
+
getConnectedAccounts() {
|
|
222
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
223
|
+
var _a;
|
|
224
|
+
const tonConnect = this.getTonConnect();
|
|
225
|
+
if (!tonConnect.connected || !((_a = tonConnect.account) === null || _a === void 0 ? void 0 : _a.address)) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
return [this.convertAddressToUserFriendly(tonConnect.account.address)];
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get the current network
|
|
233
|
+
*/
|
|
234
|
+
getNetwork() {
|
|
235
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
236
|
+
var _a;
|
|
237
|
+
const tonConnect = this.getTonConnect();
|
|
238
|
+
if (!tonConnect.connected) {
|
|
239
|
+
return '-239'; // Mainnet default (chain ID -239)
|
|
240
|
+
}
|
|
241
|
+
const { account } = tonConnect;
|
|
242
|
+
// TON mainnet chain ID is -239, testnet is -3
|
|
243
|
+
return ((_a = account === null || account === void 0 ? void 0 : account.chain) === null || _a === void 0 ? void 0 : _a.toString()) || '-239';
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Check if current network is testnet
|
|
248
|
+
*/
|
|
249
|
+
isTestnet() {
|
|
250
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
const network = yield this.getNetwork();
|
|
252
|
+
// TON testnet chain ID is -3
|
|
253
|
+
return network === '-3';
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Sign a message
|
|
258
|
+
*/
|
|
259
|
+
signMessage(messageToSign) {
|
|
260
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
261
|
+
const tonConnect = this.getTonConnect();
|
|
262
|
+
if (!tonConnect.connected) {
|
|
263
|
+
throw new utils.DynamicError('Wallet not connected');
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const result = yield tonConnect.signData({
|
|
267
|
+
text: messageToSign,
|
|
268
|
+
type: 'text',
|
|
269
|
+
});
|
|
270
|
+
return result.signature;
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
if (this.isUserRejectionError(error)) {
|
|
274
|
+
throw new utils.DynamicError('User rejected message signing');
|
|
275
|
+
}
|
|
276
|
+
walletConnectorCore.logger.error('[TON Connect] Sign message failed:', error);
|
|
277
|
+
throw new utils.DynamicError(`Failed to sign message: ${error instanceof Error ? error.message : String(error)}`);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Send a transaction
|
|
283
|
+
*/
|
|
284
|
+
sendTransaction(request) {
|
|
285
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
const tonConnect = this.getTonConnect();
|
|
287
|
+
if (!tonConnect.connected) {
|
|
288
|
+
throw new utils.DynamicError('Wallet not connected');
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
// Convert addresses from raw format (0:hex) to user-friendly format (EQ...)
|
|
292
|
+
// TON Connect SDK expects user-friendly format for message addresses
|
|
293
|
+
const messages = request.messages.map((message) => (Object.assign(Object.assign({}, message), { address: this.convertAddressToUserFriendly(message.address) })));
|
|
294
|
+
// 'from' is omitted as TonConnect uses the connected wallet's address
|
|
295
|
+
const transactionRequest = {
|
|
296
|
+
messages,
|
|
297
|
+
network: request.network,
|
|
298
|
+
validUntil: request.validUntil || Math.floor(Date.now() / 1000) + 300, // 5 minutes from now
|
|
299
|
+
};
|
|
300
|
+
const result = yield tonConnect.sendTransaction(transactionRequest);
|
|
301
|
+
return {
|
|
302
|
+
boc: result.boc,
|
|
303
|
+
hash: result.boc, // TON Connect returns boc as the transaction identifier
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
if (this.isUserRejectionError(error)) {
|
|
308
|
+
throw new utils.DynamicError('User rejected transaction');
|
|
309
|
+
}
|
|
310
|
+
walletConnectorCore.logger.error('[TON Connect] Send transaction failed:', error);
|
|
311
|
+
throw new utils.DynamicError(`Failed to send transaction: ${error instanceof Error ? error.message : String(error)}`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Send Jetton transaction
|
|
317
|
+
*/
|
|
318
|
+
sendJettonTransaction(
|
|
319
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
320
|
+
_options) {
|
|
321
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
322
|
+
// Jetton transfers require constructing a specific message
|
|
323
|
+
// This is a simplified version - full implementation would need
|
|
324
|
+
// to construct the proper Jetton transfer message
|
|
325
|
+
throw new utils.DynamicError('Jetton transfers not yet implemented');
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get TonClient for the current or specified network.
|
|
330
|
+
*/
|
|
331
|
+
getTonClient(chainId) {
|
|
332
|
+
var _a, _b, _c, _d, _e;
|
|
333
|
+
const targetChainId = chainId || ((_b = (_a = this.tonNetworks) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.chainId);
|
|
334
|
+
const network = (_c = this.tonNetworks) === null || _c === void 0 ? void 0 : _c.find((net) => net.chainId === targetChainId);
|
|
335
|
+
if (!network) {
|
|
336
|
+
throw new utils.DynamicError(`Network configuration not found for chainId: ${targetChainId}`);
|
|
337
|
+
}
|
|
338
|
+
const endpoint = ((_d = network.privateCustomerRpcUrls) === null || _d === void 0 ? void 0 : _d[0]) || ((_e = network.rpcUrls) === null || _e === void 0 ? void 0 : _e[0]);
|
|
339
|
+
if (!endpoint) {
|
|
340
|
+
throw new utils.DynamicError(`No RPC endpoint found for chainId: ${targetChainId}`);
|
|
341
|
+
}
|
|
342
|
+
return new ton.TonClient({ endpoint });
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get balance of an address
|
|
346
|
+
*/
|
|
347
|
+
getBalance(address) {
|
|
348
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
349
|
+
try {
|
|
350
|
+
const client = this.getTonClient();
|
|
351
|
+
const tonAddress = core.Address.parse(address);
|
|
352
|
+
const balanceNano = yield client.getBalance(tonAddress);
|
|
353
|
+
const balance = ton.fromNano(balanceNano);
|
|
354
|
+
return balance;
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
walletConnectorCore.logger.error('[TON Connect] Failed to get balance:', error);
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get enabled networks
|
|
364
|
+
*/
|
|
365
|
+
getEnabledNetworks() {
|
|
366
|
+
return this.tonNetworks;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Switch network (TON Connect handles this automatically)
|
|
370
|
+
*/
|
|
371
|
+
switchNetwork() {
|
|
372
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
373
|
+
// TON Connect manages network switching through the wallet UI
|
|
374
|
+
throw new utils.DynamicError('Network switching must be done manually in the TON wallet');
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Disconnect and cleanup
|
|
379
|
+
*/
|
|
380
|
+
endSession() {
|
|
381
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
382
|
+
try {
|
|
383
|
+
const tonConnect = this.getTonConnect();
|
|
384
|
+
if (tonConnect.connected) {
|
|
385
|
+
yield tonConnect.disconnect();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
walletConnectorCore.logger.debug('[TON Connect] Error during disconnect:', error);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Create a UI transaction for the send balance flow
|
|
395
|
+
*/
|
|
396
|
+
createUiTransaction(from) {
|
|
397
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
398
|
+
yield this.validateActiveWallet(from);
|
|
399
|
+
const client = this.getTonClient();
|
|
400
|
+
return new TonUiTransaction.TonUiTransaction({
|
|
401
|
+
client,
|
|
402
|
+
from,
|
|
403
|
+
onSubmit: (transaction) => _tslib.__awaiter(this, void 0, void 0, function* () { return this.internalSendUiTransaction(transaction); }),
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Internal handler for UI transaction submission
|
|
409
|
+
*/
|
|
410
|
+
internalSendUiTransaction(transaction) {
|
|
411
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
412
|
+
var _a;
|
|
413
|
+
if (!transaction.to) {
|
|
414
|
+
throw new utils.DynamicError('Destination address is required');
|
|
415
|
+
}
|
|
416
|
+
const tonConnect = this.getTonConnect();
|
|
417
|
+
if (!tonConnect.connected || !((_a = tonConnect.account) === null || _a === void 0 ? void 0 : _a.address)) {
|
|
418
|
+
throw new utils.DynamicError('Wallet not connected');
|
|
419
|
+
}
|
|
420
|
+
const walletAddress = this.convertAddressToUserFriendly(tonConnect.account.address);
|
|
421
|
+
const client = this.getTonClient();
|
|
422
|
+
const network = tonConnect.account.chain;
|
|
423
|
+
// Jetton (non-native token) transfers
|
|
424
|
+
// TODO: Test it when token balances are implemented
|
|
425
|
+
if (transaction.nonNativeAddress && transaction.nonNativeValue) {
|
|
426
|
+
const request = yield prepareJettonTransfer.prepareJettonTransfer({
|
|
427
|
+
client,
|
|
428
|
+
forwardTonAmount: BigInt(0),
|
|
429
|
+
jettonAmount: transaction.nonNativeValue,
|
|
430
|
+
jettonMasterAddress: transaction.nonNativeAddress,
|
|
431
|
+
network,
|
|
432
|
+
recipientAddress: transaction.to,
|
|
433
|
+
timeout: 60,
|
|
434
|
+
walletAddress,
|
|
435
|
+
});
|
|
436
|
+
const result = yield this.sendTransaction(request);
|
|
437
|
+
return result.boc || result.hash || '';
|
|
438
|
+
}
|
|
439
|
+
// Native TON transfers
|
|
440
|
+
if (transaction.value) {
|
|
441
|
+
const tonAmount = Number(transaction.value) / TonUiTransaction.NANOTON_PER_TON;
|
|
442
|
+
const request = yield prepareTonTransfer.prepareTonTransfer({
|
|
443
|
+
amount: tonAmount,
|
|
444
|
+
client,
|
|
445
|
+
network,
|
|
446
|
+
recipient: transaction.to,
|
|
447
|
+
timeout: 60,
|
|
448
|
+
walletAddress,
|
|
449
|
+
});
|
|
450
|
+
const result = yield this.sendTransaction(request);
|
|
451
|
+
return result.boc || result.hash || '';
|
|
452
|
+
}
|
|
453
|
+
throw new utils.DynamicError('Invalid transaction parameters');
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Convert TON address from raw format (0:hex) to user-friendly format (UQ...)
|
|
458
|
+
* TON Connect SDK expects user-friendly format for message addresses
|
|
459
|
+
*/
|
|
460
|
+
convertAddressToUserFriendly(address) {
|
|
461
|
+
var _a;
|
|
462
|
+
try {
|
|
463
|
+
if (address.startsWith('EQ') || address.startsWith('UQ')) {
|
|
464
|
+
return address;
|
|
465
|
+
}
|
|
466
|
+
// Detect if testnet from connected wallet
|
|
467
|
+
const tonConnect = this.getTonConnect();
|
|
468
|
+
const isTestnet = ((_a = tonConnect.account) === null || _a === void 0 ? void 0 : _a.chain) === '-3';
|
|
469
|
+
// Convert from raw format (0:hex) to user-friendly format
|
|
470
|
+
return core.Address.parse(address).toString({
|
|
471
|
+
bounceable: false,
|
|
472
|
+
testOnly: isTestnet,
|
|
473
|
+
urlSafe: true,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
// If parsing fails, return original address
|
|
478
|
+
walletConnectorCore.logger.debug('[TON Connect] Failed to convert address format:', error);
|
|
479
|
+
return address;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Encode a comment as TON message payload
|
|
484
|
+
*
|
|
485
|
+
* TON comments are encoded as a Cell with:
|
|
486
|
+
* - First 32 bits (4 bytes): 0x00000000 (comment opcode)
|
|
487
|
+
* - Then the UTF-8 bytes of the comment
|
|
488
|
+
*
|
|
489
|
+
* For long messages, the comment is split across multiple cells using references.
|
|
490
|
+
* The Cell is then serialized to base64 BOC format for TON Connect SDK
|
|
491
|
+
*/
|
|
492
|
+
encodeComment(comment) {
|
|
493
|
+
const encoder = new TextEncoder();
|
|
494
|
+
const commentBytes = encoder.encode(comment);
|
|
495
|
+
// TON Cell can hold max 1023 bits
|
|
496
|
+
// We use 32 bits for the opcode, leaving ~991 bits (~123 bytes) for data
|
|
497
|
+
// For longer messages, we split into multiple cells using references
|
|
498
|
+
const maxBytesPerCell = 123; // Conservative estimate to avoid overflow
|
|
499
|
+
if (commentBytes.length <= maxBytesPerCell) {
|
|
500
|
+
// Short message fits in one cell
|
|
501
|
+
const cell = core.beginCell()
|
|
502
|
+
.storeUint(0, 32) // Comment opcode
|
|
503
|
+
.storeBuffer(Buffer.from(commentBytes))
|
|
504
|
+
.endCell();
|
|
505
|
+
return cell.toBoc().toString('base64');
|
|
506
|
+
}
|
|
507
|
+
// Long message: split across multiple cells
|
|
508
|
+
// First cell: opcode + first chunk
|
|
509
|
+
// Remaining chunks go in reference cells
|
|
510
|
+
const firstChunk = commentBytes.slice(0, maxBytesPerCell);
|
|
511
|
+
const remainingBytes = commentBytes.slice(maxBytesPerCell);
|
|
512
|
+
// Build reference cells for remaining data
|
|
513
|
+
let remainingData = remainingBytes;
|
|
514
|
+
// Create cells for remaining data (each can hold ~127 bytes)
|
|
515
|
+
const referenceCells = [];
|
|
516
|
+
while (remainingData.length > 0) {
|
|
517
|
+
const chunk = remainingData.slice(0, 127);
|
|
518
|
+
remainingData = remainingData.slice(127);
|
|
519
|
+
referenceCells.push(core.beginCell().storeBuffer(Buffer.from(chunk)).endCell());
|
|
520
|
+
}
|
|
521
|
+
// Build the main cell with opcode, first chunk, and references
|
|
522
|
+
let mainCellBuilder = core.beginCell()
|
|
523
|
+
.storeUint(0, 32) // Comment opcode
|
|
524
|
+
.storeBuffer(Buffer.from(firstChunk));
|
|
525
|
+
// Add reference cells
|
|
526
|
+
for (const refCell of referenceCells) {
|
|
527
|
+
mainCellBuilder = mainCellBuilder.storeRef(refCell);
|
|
528
|
+
}
|
|
529
|
+
const mainCell = mainCellBuilder.endCell();
|
|
530
|
+
// Serialize to base64 BOC (Bag of Cells) format
|
|
531
|
+
return mainCell.toBoc().toString('base64');
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Check if error is a user rejection
|
|
535
|
+
*/
|
|
536
|
+
isUserRejectionError(error) {
|
|
537
|
+
return (error !== null &&
|
|
538
|
+
typeof error === 'object' &&
|
|
539
|
+
'name' in error &&
|
|
540
|
+
error.name === 'UserRejectsError');
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Generate a TON Connect proof for authentication
|
|
544
|
+
* @param payload - The payload string to include in the proof
|
|
545
|
+
* @returns TON Connect proof object
|
|
546
|
+
*/
|
|
547
|
+
generateTonConnectProof(payload) {
|
|
548
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
549
|
+
var _a, _b, _c, _d, _e, _f;
|
|
550
|
+
const tonConnect = this.getTonConnect();
|
|
551
|
+
// If already connected with a proof, return it
|
|
552
|
+
if ((_b = (_a = this.connectedWallet) === null || _a === void 0 ? void 0 : _a.connectItems) === null || _b === void 0 ? void 0 : _b.tonProof) {
|
|
553
|
+
const { tonProof } = this.connectedWallet.connectItems;
|
|
554
|
+
if ('proof' in tonProof) {
|
|
555
|
+
debugLog.debugLog('[TON Connect] Returning existing proof from connection');
|
|
556
|
+
return {
|
|
557
|
+
address: this.connectedWallet.account.address,
|
|
558
|
+
domain: tonProof.proof.domain,
|
|
559
|
+
payload: tonProof.proof.payload,
|
|
560
|
+
signature: tonProof.proof.signature,
|
|
561
|
+
timestamp: tonProof.proof.timestamp,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// If connected but no proof, disconnect and reconnect with proof
|
|
566
|
+
if (tonConnect.connected) {
|
|
567
|
+
debugLog.debugLog('[TON Connect] Already connected without proof, disconnecting to reconnect with tonProof');
|
|
568
|
+
yield tonConnect.disconnect();
|
|
569
|
+
this.connectedWallet = null;
|
|
570
|
+
}
|
|
571
|
+
// Store the payload for the connect call
|
|
572
|
+
this.pendingProofPayload = payload;
|
|
573
|
+
// Connect with tonProof
|
|
574
|
+
yield this.connect(payload);
|
|
575
|
+
// Wait for the connection to be established with the proof
|
|
576
|
+
const maxWaitTime = 300000; // 5 minutes
|
|
577
|
+
const pollInterval = 500;
|
|
578
|
+
const startTime = Date.now();
|
|
579
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
580
|
+
yield new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
581
|
+
if (tonConnect.connected &&
|
|
582
|
+
((_d = (_c = tonConnect.wallet) === null || _c === void 0 ? void 0 : _c.connectItems) === null || _d === void 0 ? void 0 : _d.tonProof) &&
|
|
583
|
+
'proof' in tonConnect.wallet.connectItems.tonProof) {
|
|
584
|
+
this.connectedWallet = tonConnect.wallet;
|
|
585
|
+
const { tonProof } = tonConnect.wallet.connectItems;
|
|
586
|
+
// Clear the pending payload
|
|
587
|
+
this.pendingProofPayload = null;
|
|
588
|
+
return {
|
|
589
|
+
address: tonConnect.wallet.account.address,
|
|
590
|
+
domain: tonProof.proof.domain,
|
|
591
|
+
payload: tonProof.proof.payload,
|
|
592
|
+
signature: tonProof.proof.signature,
|
|
593
|
+
timestamp: tonProof.proof.timestamp,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
// Check if connected but proof was rejected or not supported
|
|
597
|
+
if (tonConnect.connected && !((_f = (_e = tonConnect.wallet) === null || _e === void 0 ? void 0 : _e.connectItems) === null || _f === void 0 ? void 0 : _f.tonProof)) {
|
|
598
|
+
this.pendingProofPayload = null;
|
|
599
|
+
throw new utils.DynamicError('Wallet connected but did not provide proof. The wallet may not support tonProof.');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
this.pendingProofPayload = null;
|
|
603
|
+
throw new utils.DynamicError('Connection timeout - user did not approve the connection');
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Override proveOwnership to use tonProof for wallet verification
|
|
608
|
+
*
|
|
609
|
+
* For TON wallets, we use the tonProof mechanism during connection
|
|
610
|
+
* which is the standard way to prove wallet ownership in TON Connect.
|
|
611
|
+
* The backend expects a specific format with state_init and public_key.
|
|
612
|
+
*/
|
|
613
|
+
proveOwnership(address, messageToSign) {
|
|
614
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
615
|
+
yield this.validateActiveWallet(address);
|
|
616
|
+
return this.proveOwnershipWithTonProof(messageToSign);
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* proveOwnership using tonProof
|
|
621
|
+
*
|
|
622
|
+
* Returns the proof in the format expected by the backend:
|
|
623
|
+
* {
|
|
624
|
+
* address: string,
|
|
625
|
+
* proof: { domain, payload, signature, state_init, timestamp },
|
|
626
|
+
* public_key: string
|
|
627
|
+
* }
|
|
628
|
+
*/
|
|
629
|
+
proveOwnershipWithTonProof(messageToSign) {
|
|
630
|
+
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
631
|
+
var _a, _b;
|
|
632
|
+
// Extract the nonce from the SIWE message to use as tonProof payload
|
|
633
|
+
// The tonProof payload has a 128 byte limit, so we can't use the full message
|
|
634
|
+
const payload = this.extractNonceFromMessage(messageToSign);
|
|
635
|
+
// Use tonProof for authentication - this ensures we have connectedWallet set
|
|
636
|
+
const proof = yield this.generateTonConnectProof(payload);
|
|
637
|
+
// Get state_init from the connected wallet account
|
|
638
|
+
const stateInit = (_a = this.connectedWallet) === null || _a === void 0 ? void 0 : _a.account.walletStateInit;
|
|
639
|
+
if (!stateInit) {
|
|
640
|
+
throw new utils.DynamicError('Wallet did not provide state_init required for verification');
|
|
641
|
+
}
|
|
642
|
+
// Get public key from wallet - this is the key that signed the proof
|
|
643
|
+
// The wallet provides this in account.publicKey (hex without 0x prefix)
|
|
644
|
+
const walletProvidedKey = (_b = this.connectedWallet) === null || _b === void 0 ? void 0 : _b.account.publicKey;
|
|
645
|
+
const publicKey = walletProvidedKey || this.extractPublicKeyFromStateInit(stateInit);
|
|
646
|
+
return JSON.stringify({
|
|
647
|
+
address: proof.address,
|
|
648
|
+
proof: {
|
|
649
|
+
domain: proof.domain,
|
|
650
|
+
payload: proof.payload,
|
|
651
|
+
signature: proof.signature,
|
|
652
|
+
state_init: stateInit,
|
|
653
|
+
timestamp: proof.timestamp,
|
|
654
|
+
},
|
|
655
|
+
public_key: publicKey,
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Extract public key from state_init cell
|
|
661
|
+
*
|
|
662
|
+
* For standard TON wallets, the public key is stored in the data cell:
|
|
663
|
+
* - First 32 bits: seqno (skipped)
|
|
664
|
+
* - Next 256 bits (32 bytes): public key
|
|
665
|
+
*
|
|
666
|
+
* This matches the extraction logic in the backend.
|
|
667
|
+
*/
|
|
668
|
+
extractPublicKeyFromStateInit(stateInitBase64) {
|
|
669
|
+
try {
|
|
670
|
+
const cell = core.Cell.fromBase64(stateInitBase64);
|
|
671
|
+
const stateInit = core.loadStateInit(cell.beginParse());
|
|
672
|
+
const dataCell = stateInit.data;
|
|
673
|
+
if (!dataCell) {
|
|
674
|
+
throw new Error('No data cell in state_init');
|
|
675
|
+
}
|
|
676
|
+
const dataSlice = dataCell.beginParse();
|
|
677
|
+
// Skip first 32 bits (seqno for most wallets)
|
|
678
|
+
dataSlice.loadUint(32);
|
|
679
|
+
// Next 256 bits (32 bytes) is the public key
|
|
680
|
+
const publicKeyBuffer = dataSlice.loadBuffer(32);
|
|
681
|
+
// Convert to hex string (no 0x prefix, as expected by backend)
|
|
682
|
+
const hexKey = Buffer.from(publicKeyBuffer).toString('hex');
|
|
683
|
+
return hexKey;
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
walletConnectorCore.logger.error('[TON Connect] Failed to extract public key from state_init:', error);
|
|
687
|
+
debugLog.debugLog('[TON Connect] state_init that failed:', stateInitBase64);
|
|
688
|
+
throw new utils.DynamicError('Failed to extract public key from wallet state_init');
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Extract the nonce from a SIWE-style message
|
|
693
|
+
*
|
|
694
|
+
* @param message - The full message to extract nonce from
|
|
695
|
+
* @returns A short payload suitable for tonProof
|
|
696
|
+
*/
|
|
697
|
+
extractNonceFromMessage(message) {
|
|
698
|
+
const nonceMatch = message.match(/Nonce:\s*([^\n\r]+)/i);
|
|
699
|
+
if (nonceMatch === null || nonceMatch === void 0 ? void 0 : nonceMatch[1]) {
|
|
700
|
+
const nonce = nonceMatch[1].trim();
|
|
701
|
+
if (nonce.length <= 128) {
|
|
702
|
+
return nonce;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return message.slice(0, 64);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
exports.TonConnectConnector = TonConnectConnector;
|