@tonappchain/sdk 0.7.2-alpha-13 → 0.7.2-alpha-14
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/src/adapters/BaseContractOpener.d.ts +5 -0
- package/dist/src/adapters/BaseContractOpener.js +204 -49
- package/dist/src/adapters/LiteClientOpener.d.ts +7 -2
- package/dist/src/adapters/LiteClientOpener.js +32 -8
- package/dist/src/adapters/RetryableContractOpener.d.ts +5 -0
- package/dist/src/adapters/RetryableContractOpener.js +130 -29
- package/dist/src/adapters/TonClient4Opener.d.ts +7 -5
- package/dist/src/adapters/TonClient4Opener.js +11 -10
- package/dist/src/adapters/TonClientOpener.d.ts +5 -4
- package/dist/src/adapters/TonClientOpener.js +12 -10
- package/dist/src/interfaces/ContractOpener.d.ts +4 -2
- package/dist/src/interfaces/ITacSDK.d.ts +6 -1
- package/dist/src/sdk/Configuration.js +1 -0
- package/dist/src/sdk/Consts.d.ts +6 -2
- package/dist/src/sdk/Consts.js +7 -3
- package/dist/src/sdk/StartTracking.d.ts +4 -0
- package/dist/src/sdk/StartTracking.js +10 -5
- package/dist/src/sdk/TONTransactionManager.js +1 -1
- package/dist/src/sdk/TxFinalizer.js +4 -3
- package/dist/src/sdk/Utils.d.ts +5 -0
- package/dist/src/sdk/Utils.js +44 -15
- package/dist/src/structs/InternalStruct.d.ts +1 -1
- package/dist/src/structs/Struct.d.ts +33 -6
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import { ContractState, TrackTransactionTreeParams, TrackTransactionTreeResult }
|
|
|
9
9
|
export declare abstract class BaseContractOpener implements ContractOpener {
|
|
10
10
|
protected logger?: ILogger;
|
|
11
11
|
protected constructor(logger?: ILogger);
|
|
12
|
+
setLogger(logger: ILogger): void;
|
|
12
13
|
abstract open<T extends Contract>(contract: T): OpenedContract<T> | SandboxContract<T>;
|
|
13
14
|
abstract getContractState(address: Address): Promise<ContractState>;
|
|
14
15
|
abstract getTransactions(address: Address, opts: GetTransactionsOptions): Promise<Transaction[]>;
|
|
@@ -60,6 +61,10 @@ export declare abstract class BaseContractOpener implements ContractOpener {
|
|
|
60
61
|
* Find transaction by hash type
|
|
61
62
|
*/
|
|
62
63
|
private findTransactionByHashType;
|
|
64
|
+
/**
|
|
65
|
+
* Retry lookup for root transaction because it may appear in indexers with a delay.
|
|
66
|
+
*/
|
|
67
|
+
private findRootTransactionWithRetry;
|
|
63
68
|
/**
|
|
64
69
|
* Track transaction tree and validate all transactions
|
|
65
70
|
*/
|
|
@@ -12,6 +12,11 @@ class BaseContractOpener {
|
|
|
12
12
|
constructor(logger) {
|
|
13
13
|
this.logger = logger;
|
|
14
14
|
}
|
|
15
|
+
setLogger(logger) {
|
|
16
|
+
if (!this.logger) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
15
20
|
/**
|
|
16
21
|
* Common pagination logic for scanning transaction history
|
|
17
22
|
* @param addr Account address
|
|
@@ -21,28 +26,84 @@ class BaseContractOpener {
|
|
|
21
26
|
*/
|
|
22
27
|
async scanTransactionHistory(addr, opts, predicate) {
|
|
23
28
|
const limit = opts?.limit ?? Consts_1.DEFAULT_FIND_TX_LIMIT;
|
|
29
|
+
const inclusive = opts?.inclusive ?? true;
|
|
30
|
+
const toLt = opts?.to_lt ? BigInt(opts.to_lt) : undefined;
|
|
24
31
|
let currentLt = opts?.lt;
|
|
25
|
-
let currentHash = opts?.hash;
|
|
32
|
+
let currentHash = opts?.hash ? (0, Utils_1.normalizeHashToBase64)(opts.hash) : undefined;
|
|
33
|
+
const seenCursors = new Set();
|
|
34
|
+
let scannedTransactions = 0;
|
|
35
|
+
const maxScannedTransactions = opts?.maxScannedTransactions ?? Consts_1.DEFAULT_MAX_SCANNED_TRANSACTIONS;
|
|
26
36
|
while (true) {
|
|
27
|
-
const
|
|
37
|
+
const list = await this.getTransactions(addr, {
|
|
28
38
|
limit,
|
|
29
39
|
lt: currentLt,
|
|
30
40
|
hash: currentHash,
|
|
41
|
+
to_lt: opts?.to_lt,
|
|
42
|
+
inclusive,
|
|
31
43
|
archival: opts?.archival ?? Consts_1.DEFAULT_FIND_TX_ARCHIVAL,
|
|
44
|
+
timeoutMs: opts?.timeoutMs,
|
|
45
|
+
retryDelayMs: opts?.retryDelayMs,
|
|
32
46
|
});
|
|
33
|
-
if (
|
|
47
|
+
if (list.length === 0) {
|
|
34
48
|
break;
|
|
35
|
-
|
|
49
|
+
}
|
|
50
|
+
let startIndex = 0;
|
|
51
|
+
const firstTx = list[0];
|
|
52
|
+
const firstTxMatchesCursor = currentLt &&
|
|
53
|
+
currentHash &&
|
|
54
|
+
firstTx.lt.toString() === currentLt &&
|
|
55
|
+
firstTx.hash().toString('base64') === currentHash;
|
|
56
|
+
if (firstTxMatchesCursor) {
|
|
57
|
+
startIndex = 1;
|
|
58
|
+
if (list.length === 1) {
|
|
59
|
+
if (firstTx.prevTransactionLt === 0n) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
const nextLt = firstTx.prevTransactionLt.toString();
|
|
63
|
+
const nextHash = Buffer.from(firstTx.prevTransactionHash.toString(16).padStart(64, '0'), 'hex').toString('base64');
|
|
64
|
+
if (currentLt && BigInt(nextLt) >= BigInt(currentLt)) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
const cursorKey = `${nextLt}:${nextHash}`;
|
|
68
|
+
if (seenCursors.has(cursorKey)) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
seenCursors.add(cursorKey);
|
|
72
|
+
currentLt = nextLt;
|
|
73
|
+
currentHash = nextHash;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (let i = startIndex; i < list.length; i++) {
|
|
78
|
+
const tx = list[i];
|
|
79
|
+
scannedTransactions += 1;
|
|
80
|
+
if (scannedTransactions > maxScannedTransactions) {
|
|
81
|
+
this.logger?.debug(`Scan limit reached (${maxScannedTransactions} transactions), stopping history scan`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
36
84
|
if (predicate(tx)) {
|
|
37
85
|
return tx;
|
|
38
86
|
}
|
|
39
87
|
}
|
|
40
|
-
const oldestTx =
|
|
88
|
+
const oldestTx = list[list.length - 1];
|
|
41
89
|
if (oldestTx.prevTransactionLt === 0n)
|
|
42
90
|
break;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
91
|
+
if (toLt !== undefined) {
|
|
92
|
+
if (inclusive ? oldestTx.lt <= toLt : oldestTx.lt < toLt)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
const nextLt = oldestTx.lt.toString();
|
|
96
|
+
const nextHash = oldestTx.hash().toString('base64');
|
|
97
|
+
if (currentLt && BigInt(nextLt) >= BigInt(currentLt)) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
const cursorKey = `${nextLt}:${nextHash}`;
|
|
101
|
+
if (seenCursors.has(cursorKey)) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
seenCursors.add(cursorKey);
|
|
105
|
+
currentLt = nextLt;
|
|
106
|
+
currentHash = nextHash;
|
|
46
107
|
}
|
|
47
108
|
return null;
|
|
48
109
|
}
|
|
@@ -65,6 +126,10 @@ class BaseContractOpener {
|
|
|
65
126
|
if (msgHash === targetHashB64) {
|
|
66
127
|
return true;
|
|
67
128
|
}
|
|
129
|
+
const rawMsgHash = (0, ton_1.beginCell)().store((0, ton_1.storeMessage)(tx.inMessage)).endCell().hash().toString('base64');
|
|
130
|
+
if (rawMsgHash === targetHashB64) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
68
133
|
}
|
|
69
134
|
// 3. check incoming message(internal)
|
|
70
135
|
if (tx.inMessage && tx.inMessage.info.type === 'internal') {
|
|
@@ -74,6 +139,14 @@ class BaseContractOpener {
|
|
|
74
139
|
return true;
|
|
75
140
|
}
|
|
76
141
|
}
|
|
142
|
+
// 4. check outcoming message
|
|
143
|
+
for (const outMsg of tx.outMessages.values()) {
|
|
144
|
+
const messageCell = (0, ton_1.beginCell)().store((0, ton_1.storeMessage)(outMsg)).endCell();
|
|
145
|
+
const hash = messageCell.hash();
|
|
146
|
+
if (hash.toString('base64') === targetHashB64) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
77
150
|
return false;
|
|
78
151
|
});
|
|
79
152
|
}
|
|
@@ -100,6 +173,10 @@ class BaseContractOpener {
|
|
|
100
173
|
if (hash === targetHashB64) {
|
|
101
174
|
return true;
|
|
102
175
|
}
|
|
176
|
+
const rawHash = (0, ton_1.beginCell)().store((0, ton_1.storeMessage)(tx.inMessage)).endCell().hash().toString('base64');
|
|
177
|
+
if (rawHash === targetHashB64) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
103
180
|
}
|
|
104
181
|
// Check incoming message(internal) - uses full message cell hash
|
|
105
182
|
if (tx.inMessage && tx.inMessage.info.type === 'internal') {
|
|
@@ -166,53 +243,98 @@ class BaseContractOpener {
|
|
|
166
243
|
* Validate transaction phases and return error details
|
|
167
244
|
*/
|
|
168
245
|
validateTransactionWithResult(tx, ignoreOpcodeList) {
|
|
169
|
-
if (tx.description.type !== 'generic'
|
|
170
|
-
return null;
|
|
171
|
-
// Skip validation for 1 nano messages
|
|
172
|
-
if (tx.inMessage.info.type === 'internal' && tx.inMessage.info.value.coins === Consts_1.IGNORE_MSG_VALUE_1_NANO) {
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
const bodySlice = tx.inMessage.body.beginParse();
|
|
176
|
-
if (bodySlice.remainingBits < 32)
|
|
246
|
+
if (tx.description.type !== 'generic')
|
|
177
247
|
return null;
|
|
178
|
-
const opcode = bodySlice.loadUint(32);
|
|
179
|
-
if (ignoreOpcodeList.includes(opcode)) {
|
|
180
|
-
this.logger?.debug(`Skipping validation for tx: ${tx.hash().toString('base64')} (opcode in ignore list)`);
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
248
|
const { aborted, computePhase, actionPhase } = tx.description;
|
|
184
249
|
const txHash = tx.hash().toString('base64');
|
|
185
250
|
const exitCode = computePhase && computePhase.type !== 'skipped' ? computePhase.exitCode : 'N/A';
|
|
186
251
|
const resultCode = actionPhase ? actionPhase.resultCode : 'N/A';
|
|
187
252
|
if (aborted) {
|
|
188
|
-
if (!computePhase || computePhase.type === 'skipped') {
|
|
189
|
-
return { txHash, exitCode, resultCode, reason: 'compute_phase_missing' };
|
|
190
|
-
}
|
|
191
|
-
if (!computePhase.success || computePhase.exitCode !== 0) {
|
|
192
|
-
return { txHash, exitCode, resultCode, reason: 'compute_phase_failed' };
|
|
193
|
-
}
|
|
194
|
-
if (actionPhase && (!actionPhase.success || actionPhase.resultCode !== 0)) {
|
|
195
|
-
return { txHash, exitCode, resultCode, reason: 'action_phase_failed' };
|
|
196
|
-
}
|
|
197
253
|
return { txHash, exitCode, resultCode, reason: 'aborted' };
|
|
198
254
|
}
|
|
255
|
+
if (!computePhase) {
|
|
256
|
+
return { txHash, exitCode, resultCode, reason: 'compute_phase_missing' };
|
|
257
|
+
}
|
|
258
|
+
if (computePhase.type !== 'skipped' && (!computePhase.success || computePhase.exitCode !== 0)) {
|
|
259
|
+
return { txHash, exitCode, resultCode, reason: 'compute_phase_failed' };
|
|
260
|
+
}
|
|
261
|
+
if (actionPhase && (!actionPhase.success || actionPhase.resultCode !== 0)) {
|
|
262
|
+
return { txHash, exitCode, resultCode, reason: 'action_phase_failed' };
|
|
263
|
+
}
|
|
264
|
+
if (!tx.inMessage)
|
|
265
|
+
return null;
|
|
266
|
+
// Log optional skip hints (does not bypass phase validation)
|
|
267
|
+
if (tx.inMessage.info.type === 'internal' && tx.inMessage.info.value.coins === Consts_1.IGNORE_MSG_VALUE_1_NANO) {
|
|
268
|
+
this.logger?.debug(`Skipping extra checks for tx: ${txHash} (1 nano message)`);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
const bodySlice = tx.inMessage.body.beginParse();
|
|
272
|
+
if (bodySlice.remainingBits >= 32) {
|
|
273
|
+
const opcode = bodySlice.loadUint(32);
|
|
274
|
+
if (ignoreOpcodeList.includes(opcode)) {
|
|
275
|
+
this.logger?.debug(`Skipping extra checks for tx: ${txHash} (opcode in ignore list)`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
199
278
|
return null;
|
|
200
279
|
}
|
|
201
280
|
/**
|
|
202
281
|
* Find transaction by hash type
|
|
203
282
|
*/
|
|
204
|
-
async findTransactionByHashType(address, hash, hashType,
|
|
205
|
-
const
|
|
283
|
+
async findTransactionByHashType(address, hash, hashType, opts) {
|
|
284
|
+
const searchOpts = { archival: true, ...opts };
|
|
206
285
|
if (hashType === 'in') {
|
|
207
|
-
return this.getTransactionByInMsgHash(address, hash,
|
|
286
|
+
return this.getTransactionByInMsgHash(address, hash, searchOpts);
|
|
208
287
|
}
|
|
209
288
|
else if (hashType === 'out') {
|
|
210
|
-
return this.getTransactionByOutMsgHash(address, hash,
|
|
289
|
+
return this.getTransactionByOutMsgHash(address, hash, searchOpts);
|
|
211
290
|
}
|
|
212
291
|
else {
|
|
213
|
-
return this.getTransactionByHash(address, hash,
|
|
292
|
+
return this.getTransactionByHash(address, hash, searchOpts);
|
|
214
293
|
}
|
|
215
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Retry lookup for root transaction because it may appear in indexers with a delay.
|
|
297
|
+
*/
|
|
298
|
+
async findRootTransactionWithRetry(address, hash, hashType, limit, maxScannedTransactions, waitForRootTransaction) {
|
|
299
|
+
if (!waitForRootTransaction) {
|
|
300
|
+
return this.findTransactionByHashType(address, hash, hashType, {
|
|
301
|
+
limit,
|
|
302
|
+
archival: true,
|
|
303
|
+
maxScannedTransactions,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const attempts = Math.ceil(Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION_TIMEOUT_MS / Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION_RETRY_DELAY_MS);
|
|
307
|
+
const broadSearchOpts = { limit, archival: true, maxScannedTransactions };
|
|
308
|
+
const baselineInfo = await this.getAddressInformation(address);
|
|
309
|
+
let seenLt = baselineInfo.lastTransaction.lt || undefined;
|
|
310
|
+
// Capture baseline before first search.
|
|
311
|
+
const firstTx = await this.findTransactionByHashType(address, hash, hashType, broadSearchOpts);
|
|
312
|
+
if (firstTx) {
|
|
313
|
+
this.logger?.debug(`Root transaction found on attempt 1/${attempts}`);
|
|
314
|
+
return firstTx;
|
|
315
|
+
}
|
|
316
|
+
this.logger?.debug(`Root transaction not found yet (attempt 1/${attempts}), retrying in ${Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION_RETRY_DELAY_MS}ms`);
|
|
317
|
+
await (0, Utils_1.sleep)(Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION_RETRY_DELAY_MS);
|
|
318
|
+
for (let attempt = 2; attempt <= attempts; attempt++) {
|
|
319
|
+
const info = await this.getAddressInformation(address);
|
|
320
|
+
const currentLt = info.lastTransaction.lt || undefined;
|
|
321
|
+
if (!currentLt || currentLt === seenLt) {
|
|
322
|
+
this.logger?.debug(`Root transaction not found yet (attempt ${attempt}/${attempts}), lastTx unchanged, retrying in ${Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION_RETRY_DELAY_MS}ms`);
|
|
323
|
+
await (0, Utils_1.sleep)(Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION_RETRY_DELAY_MS);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
seenLt = currentLt;
|
|
327
|
+
const tx = await this.findTransactionByHashType(address, hash, hashType, broadSearchOpts);
|
|
328
|
+
if (tx) {
|
|
329
|
+
this.logger?.debug(`Root transaction found on attempt ${attempt}/${attempts}`);
|
|
330
|
+
return tx;
|
|
331
|
+
}
|
|
332
|
+
this.logger?.debug(`Root transaction not found yet (attempt ${attempt}/${attempts}), retrying in ${Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION_RETRY_DELAY_MS}ms`);
|
|
333
|
+
await (0, Utils_1.sleep)(Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION_RETRY_DELAY_MS);
|
|
334
|
+
}
|
|
335
|
+
this.logger?.debug(`Root transaction not found after ${attempts} attempts`);
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
216
338
|
/**
|
|
217
339
|
* Track transaction tree and validate all transactions
|
|
218
340
|
*/
|
|
@@ -221,11 +343,18 @@ class BaseContractOpener {
|
|
|
221
343
|
ignoreOpcodeList: Consts_1.IGNORE_OPCODE,
|
|
222
344
|
limit: Consts_1.DEFAULT_FIND_TX_LIMIT,
|
|
223
345
|
direction: 'both',
|
|
346
|
+
waitForRootTransaction: Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION,
|
|
224
347
|
}) {
|
|
225
348
|
const result = await this.trackTransactionTreeWithResult(address, hash, params);
|
|
349
|
+
if (this.logger && typeof result.checkedCount === 'number') {
|
|
350
|
+
this.logger.debug(`Transaction tree checked: ${result.checkedCount} unique transaction(s)`);
|
|
351
|
+
}
|
|
226
352
|
if (!result.success && result.error) {
|
|
227
|
-
const { txHash, exitCode, resultCode, reason } = result.error;
|
|
228
|
-
|
|
353
|
+
const { txHash, exitCode, resultCode, reason, address: errorAddress, hashType } = result.error;
|
|
354
|
+
const context = reason === 'not_found'
|
|
355
|
+
? ` address=${errorAddress ?? 'unknown'} hashType=${hashType ?? 'unknown'}`
|
|
356
|
+
: '';
|
|
357
|
+
throw (0, errors_1.txFinalizationError)(`${txHash}: reason=${reason} (exitCode=${exitCode}, resultCode=${resultCode})${context}`);
|
|
229
358
|
}
|
|
230
359
|
}
|
|
231
360
|
/**
|
|
@@ -236,29 +365,56 @@ class BaseContractOpener {
|
|
|
236
365
|
ignoreOpcodeList: Consts_1.IGNORE_OPCODE,
|
|
237
366
|
limit: Consts_1.DEFAULT_FIND_TX_LIMIT,
|
|
238
367
|
direction: 'both',
|
|
368
|
+
waitForRootTransaction: Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION,
|
|
239
369
|
}) {
|
|
240
|
-
const { maxDepth = Consts_1.DEFAULT_FIND_TX_MAX_DEPTH, ignoreOpcodeList = Consts_1.IGNORE_OPCODE, limit = Consts_1.DEFAULT_FIND_TX_LIMIT, direction = 'both', } = params;
|
|
370
|
+
const { maxDepth = Consts_1.DEFAULT_FIND_TX_MAX_DEPTH, ignoreOpcodeList = Consts_1.IGNORE_OPCODE, limit = Consts_1.DEFAULT_FIND_TX_LIMIT, maxScannedTransactions = Consts_1.DEFAULT_MAX_SCANNED_TRANSACTIONS, direction = 'both', waitForRootTransaction = Consts_1.DEFAULT_WAIT_FOR_ROOT_TRANSACTION, } = params;
|
|
241
371
|
const parsedAddress = ton_1.Address.parse(address);
|
|
242
|
-
const
|
|
243
|
-
const
|
|
372
|
+
const normalizedRootHash = (0, Utils_1.normalizeHashToBase64)(hash);
|
|
373
|
+
const visitedSearchKeys = new Set();
|
|
374
|
+
const processedTxHashes = new Set();
|
|
375
|
+
let checkedCount = 0;
|
|
376
|
+
const searchOpts = { limit, archival: true, maxScannedTransactions };
|
|
377
|
+
const queue = [
|
|
378
|
+
{ address: parsedAddress, hash: normalizedRootHash, depth: 0, hashType: 'unknown' },
|
|
379
|
+
];
|
|
244
380
|
while (queue.length > 0) {
|
|
245
381
|
const { hash: currentHash, depth: currentDepth, address: currentAddress, hashType } = queue.shift();
|
|
246
|
-
|
|
382
|
+
const visitedKey = `${currentAddress.toString()}:${currentHash}:${hashType}`;
|
|
383
|
+
if (visitedSearchKeys.has(visitedKey))
|
|
247
384
|
continue;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
385
|
+
visitedSearchKeys.add(visitedKey);
|
|
386
|
+
const tx = currentDepth === 0
|
|
387
|
+
? await this.findRootTransactionWithRetry(currentAddress, currentHash, hashType, limit, maxScannedTransactions, waitForRootTransaction)
|
|
388
|
+
: await this.findTransactionByHashType(currentAddress, currentHash, hashType, searchOpts);
|
|
251
389
|
if (!tx) {
|
|
252
|
-
this.logger?.debug(`Transaction not found for hash: ${currentHash}`);
|
|
390
|
+
this.logger?.debug(`Transaction not found for hash: ${currentHash} (address=${currentAddress?.toString()}, hashType=${hashType ?? 'unknown'})`);
|
|
391
|
+
return {
|
|
392
|
+
success: false,
|
|
393
|
+
checkedCount,
|
|
394
|
+
error: {
|
|
395
|
+
txHash: currentHash,
|
|
396
|
+
exitCode: 'N/A',
|
|
397
|
+
resultCode: 'N/A',
|
|
398
|
+
reason: 'not_found',
|
|
399
|
+
address: currentAddress?.toString(),
|
|
400
|
+
hashType: hashType ?? 'unknown',
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
const txHash = tx.hash().toString('base64');
|
|
405
|
+
if (processedTxHashes.has(txHash)) {
|
|
253
406
|
continue;
|
|
254
407
|
}
|
|
408
|
+
processedTxHashes.add(txHash);
|
|
409
|
+
checkedCount += 1;
|
|
410
|
+
this.logger?.debug(`Checking tx (depth ${currentDepth}): ${txHash}`);
|
|
255
411
|
// Validate transaction and return error if found
|
|
256
412
|
const validationError = this.validateTransactionWithResult(tx, ignoreOpcodeList);
|
|
257
413
|
if (validationError) {
|
|
258
|
-
return { success: false, error: validationError };
|
|
414
|
+
return { success: false, checkedCount, error: validationError };
|
|
259
415
|
}
|
|
260
416
|
// Add adjacent transactions to queue
|
|
261
|
-
if (currentDepth
|
|
417
|
+
if (currentDepth < maxDepth) {
|
|
262
418
|
if ((direction === 'forward' || direction === 'both') && tx.outMessages.size > 0) {
|
|
263
419
|
for (const msg of tx.outMessages.values()) {
|
|
264
420
|
const dst = msg.info.dest;
|
|
@@ -282,9 +438,8 @@ class BaseContractOpener {
|
|
|
282
438
|
});
|
|
283
439
|
}
|
|
284
440
|
}
|
|
285
|
-
this.logger?.debug(`Finished checking hash (depth ${currentDepth}): ${currentHash}`);
|
|
286
441
|
}
|
|
287
|
-
return { success: true };
|
|
442
|
+
return { success: true, checkedCount };
|
|
288
443
|
}
|
|
289
444
|
}
|
|
290
445
|
exports.BaseContractOpener = BaseContractOpener;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Address, Contract, OpenedContract, Transaction } from '@ton/ton';
|
|
2
|
+
import { ILogger } from '../interfaces';
|
|
2
3
|
import { AddressInformation, ContractState, GetTransactionsOptions, Network } from '../structs/Struct';
|
|
3
4
|
import { BaseContractOpener } from './BaseContractOpener';
|
|
4
5
|
type LiteServer = {
|
|
@@ -12,13 +13,17 @@ type LiteServer = {
|
|
|
12
13
|
export declare class LiteClientOpener extends BaseContractOpener {
|
|
13
14
|
private readonly client;
|
|
14
15
|
private readonly engine;
|
|
16
|
+
private readonly singleEngines;
|
|
17
|
+
private isClosing;
|
|
18
|
+
private isClosed;
|
|
15
19
|
private constructor();
|
|
16
20
|
static create(options: {
|
|
17
21
|
liteservers: LiteServer[];
|
|
18
22
|
} | {
|
|
19
23
|
network: Network;
|
|
20
|
-
}): Promise<LiteClientOpener>;
|
|
24
|
+
}, logger?: ILogger): Promise<LiteClientOpener>;
|
|
21
25
|
open<T extends Contract>(contract: T): OpenedContract<T>;
|
|
26
|
+
private disableReconnect;
|
|
22
27
|
closeConnections(): void;
|
|
23
28
|
getContractState(addr: Address): Promise<ContractState>;
|
|
24
29
|
getTransactions(address: Address, opts: GetTransactionsOptions): Promise<Transaction[]>;
|
|
@@ -29,5 +34,5 @@ export declare function liteClientOpener(options: {
|
|
|
29
34
|
liteservers: LiteServer[];
|
|
30
35
|
} | {
|
|
31
36
|
network: Network;
|
|
32
|
-
}): Promise<LiteClientOpener>;
|
|
37
|
+
}, logger?: ILogger): Promise<LiteClientOpener>;
|
|
33
38
|
export {};
|
|
@@ -24,12 +24,15 @@ async function getDefaultLiteServers(network) {
|
|
|
24
24
|
return liteClients.liteservers;
|
|
25
25
|
}
|
|
26
26
|
class LiteClientOpener extends BaseContractOpener_1.BaseContractOpener {
|
|
27
|
-
constructor(client, engine) {
|
|
28
|
-
super();
|
|
27
|
+
constructor(client, engine, logger, singleEngines = []) {
|
|
28
|
+
super(logger);
|
|
29
29
|
this.client = client;
|
|
30
30
|
this.engine = engine;
|
|
31
|
+
this.isClosing = false;
|
|
32
|
+
this.isClosed = false;
|
|
33
|
+
this.singleEngines = singleEngines;
|
|
31
34
|
}
|
|
32
|
-
static async create(options) {
|
|
35
|
+
static async create(options, logger) {
|
|
33
36
|
const liteservers = 'liteservers' in options ? options.liteservers : await getDefaultLiteServers(options.network);
|
|
34
37
|
const engines = [];
|
|
35
38
|
for (const server of liteservers) {
|
|
@@ -41,13 +44,32 @@ class LiteClientOpener extends BaseContractOpener_1.BaseContractOpener {
|
|
|
41
44
|
}
|
|
42
45
|
const engine = new ton_lite_client_1.LiteRoundRobinEngine(engines);
|
|
43
46
|
const client = new ton_lite_client_1.LiteClient({ engine });
|
|
44
|
-
return new LiteClientOpener(client, engine);
|
|
47
|
+
return new LiteClientOpener(client, engine, logger, engines);
|
|
45
48
|
}
|
|
46
49
|
open(contract) {
|
|
47
50
|
return this.client.open(contract);
|
|
48
51
|
}
|
|
52
|
+
disableReconnect(singleEngine) {
|
|
53
|
+
const engine = singleEngine;
|
|
54
|
+
engine.connect = async () => undefined;
|
|
55
|
+
engine.onClosed = () => undefined;
|
|
56
|
+
}
|
|
49
57
|
closeConnections() {
|
|
50
|
-
this.
|
|
58
|
+
if (this.isClosing || this.isClosed) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.isClosing = true;
|
|
62
|
+
try {
|
|
63
|
+
for (const singleEngine of this.singleEngines) {
|
|
64
|
+
this.disableReconnect(singleEngine);
|
|
65
|
+
singleEngine.close();
|
|
66
|
+
}
|
|
67
|
+
this.engine.close();
|
|
68
|
+
this.isClosed = true;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
this.isClosing = false;
|
|
72
|
+
}
|
|
51
73
|
}
|
|
52
74
|
async getContractState(addr) {
|
|
53
75
|
const block = await this.client.getMasterchainInfo();
|
|
@@ -98,10 +120,12 @@ class LiteClientOpener extends BaseContractOpener_1.BaseContractOpener {
|
|
|
98
120
|
async getAddressInformation(addr) {
|
|
99
121
|
const block = await this.client.getMasterchainInfo();
|
|
100
122
|
const state = await this.client.getAccountState(addr, block.last);
|
|
123
|
+
const lastHashHex = state.lastTx ? state.lastTx.hash.toString(16).padStart(64, '0') : '';
|
|
124
|
+
const lastHashB64 = lastHashHex ? Buffer.from(lastHashHex, 'hex').toString('base64') : '';
|
|
101
125
|
return {
|
|
102
126
|
lastTransaction: {
|
|
103
127
|
lt: state.lastTx?.lt.toString() ?? '',
|
|
104
|
-
hash:
|
|
128
|
+
hash: lastHashB64,
|
|
105
129
|
},
|
|
106
130
|
};
|
|
107
131
|
}
|
|
@@ -112,6 +136,6 @@ class LiteClientOpener extends BaseContractOpener_1.BaseContractOpener {
|
|
|
112
136
|
}
|
|
113
137
|
}
|
|
114
138
|
exports.LiteClientOpener = LiteClientOpener;
|
|
115
|
-
async function liteClientOpener(options) {
|
|
116
|
-
return LiteClientOpener.create(options);
|
|
139
|
+
async function liteClientOpener(options, logger) {
|
|
140
|
+
return LiteClientOpener.create(options, logger);
|
|
117
141
|
}
|
|
@@ -14,6 +14,8 @@ export declare class RetryableContractOpener implements ContractOpener {
|
|
|
14
14
|
private readonly openerConfigs;
|
|
15
15
|
private logger?;
|
|
16
16
|
constructor(openerConfigs: OpenerConfig[], logger?: ILogger);
|
|
17
|
+
setLogger(logger: ILogger): void;
|
|
18
|
+
private applyLoggerToOpeners;
|
|
17
19
|
getTransactions(address: Address, opts: GetTransactionsOptions): Promise<Transaction[]>;
|
|
18
20
|
getTransactionByHash(address: Address, hash: string, opts?: GetTransactionsOptions): Promise<Transaction | null>;
|
|
19
21
|
getAdjacentTransactions(address: Address, hash: string, opts?: GetTransactionsOptions): Promise<Transaction[]>;
|
|
@@ -28,7 +30,10 @@ export declare class RetryableContractOpener implements ContractOpener {
|
|
|
28
30
|
trackTransactionTree(address: string, hash: string, params?: TrackTransactionTreeParams): Promise<void>;
|
|
29
31
|
trackTransactionTreeWithResult(address: string, hash: string, params?: TrackTransactionTreeParams): Promise<TrackTransactionTreeResult>;
|
|
30
32
|
private executeWithFallback;
|
|
33
|
+
private trySingleAttempt;
|
|
31
34
|
private tryWithRetries;
|
|
35
|
+
private isContractExecutionError;
|
|
36
|
+
private isTransportError;
|
|
32
37
|
private createRetryableContract;
|
|
33
38
|
private callMethodAcrossOpeners;
|
|
34
39
|
}
|