@tonappchain/sdk 0.7.3-rc6 → 0.7.3-rc8

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.
Files changed (34) hide show
  1. package/README.md +0 -3
  2. package/dist/src/adapters/BaseContractOpener.d.ts +5 -4
  3. package/dist/src/adapters/BaseContractOpener.js +45 -43
  4. package/dist/src/adapters/RetryableContractOpener.d.ts +1 -1
  5. package/dist/src/adapters/RetryableContractOpener.js +12 -1
  6. package/dist/src/adapters/TonClientOpener.d.ts +2 -0
  7. package/dist/src/adapters/TonClientOpener.js +35 -1
  8. package/dist/src/adapters/ToncenterV3Indexer.d.ts +1 -0
  9. package/dist/src/adapters/ToncenterV3Indexer.js +30 -11
  10. package/dist/src/adapters/ToncenterV3Opener.d.ts +11 -0
  11. package/dist/src/adapters/ToncenterV3Opener.js +110 -0
  12. package/dist/src/adapters/index.d.ts +1 -0
  13. package/dist/src/adapters/index.js +1 -0
  14. package/dist/src/errors/index.d.ts +1 -1
  15. package/dist/src/errors/index.js +4 -3
  16. package/dist/src/errors/instances.d.ts +1 -0
  17. package/dist/src/errors/instances.js +2 -1
  18. package/dist/src/interfaces/IOperationTracker.d.ts +14 -1
  19. package/dist/src/interfaces/ITacSDK.d.ts +1 -21
  20. package/dist/src/interfaces/IToncenterV3Indexer.d.ts +1 -0
  21. package/dist/src/sdk/Configuration.js +6 -1
  22. package/dist/src/sdk/Consts.d.ts +1 -1
  23. package/dist/src/sdk/Consts.js +3 -3
  24. package/dist/src/sdk/OperationTracker.d.ts +6 -3
  25. package/dist/src/sdk/OperationTracker.js +44 -19
  26. package/dist/src/sdk/StartTracking.d.ts +8 -0
  27. package/dist/src/sdk/StartTracking.js +19 -1
  28. package/dist/src/sdk/TONTransactionManager.d.ts +1 -0
  29. package/dist/src/sdk/TONTransactionManager.js +18 -7
  30. package/dist/src/sdk/TacSdk.d.ts +1 -5
  31. package/dist/src/sdk/TacSdk.js +3 -37
  32. package/dist/src/structs/InternalStruct.d.ts +10 -2
  33. package/dist/src/structs/Struct.d.ts +27 -0
  34. package/package.json +3 -1
package/README.md CHANGED
@@ -67,9 +67,6 @@ The TAC SDK enables you to create frontends that:
67
67
  - [`TonConnectSender`](./docs/sdks/sender.md#tonconnectsender): Implements sending via TonConnect UI.
68
68
  - [`RawSender`](./docs/sdks/sender.md#rawsender): Implements sending using a raw private key.
69
69
 
70
- - **[`Utilities`](./docs/sdks/utilities.md)**: Helper functions and interfaces.
71
- - [`startTracking`](./docs/sdks/utilities.md#starttracking): Utility function to poll and log operation status to the console.
72
-
73
70
  - **[`AgnosticSdk`](./docs/sdks/agnostic_proxy_sdk.md)**: Agnostic SDK for cross-chain interactions.
74
71
 
75
72
  - **[`Simulator`](./docs/sdks/simulator.md)**: Transaction simulation capabilities.
@@ -1,8 +1,9 @@
1
1
  import { SandboxContract } from '@ton/sandbox';
2
+ import type { Message } from '@ton/ton';
2
3
  import { Address, Contract, OpenedContract, Transaction } from '@ton/ton';
3
4
  import { ContractOpener, ILogger } from '../interfaces';
4
5
  import { AddressInformation, GetTransactionsOptions } from '../structs/Struct';
5
- import { ContractState, TrackTransactionTreeParams, TrackTransactionTreeResult } from '../structs/Struct';
6
+ import { ContractState, TrackTransactionTreeParams, TrackTransactionTreeResult, TransactionValidationError } from '../structs/Struct';
6
7
  /**
7
8
  * Base class for ContractOpener implementations with common functionality
8
9
  */
@@ -56,15 +57,15 @@ export declare abstract class BaseContractOpener implements ContractOpener {
56
57
  /**
57
58
  * Validate transaction phases and return error details
58
59
  */
59
- private validateTransactionWithResult;
60
+ protected validateTransactionWithResult(tx: Transaction, ignoreOpcodeList: number[]): TransactionValidationError | null;
60
61
  /**
61
62
  * Find transaction by hash type
62
63
  */
63
- private findTransactionByHashType;
64
+ protected findTransactionByHashType(address: Address, hash: string, hashType: 'unknown' | 'in' | 'out' | undefined, opts: GetTransactionsOptions, message?: Message): Promise<Transaction | null>;
64
65
  /**
65
66
  * Find transaction with retry logic
66
67
  */
67
- private findTransactionWithRetry;
68
+ protected findTransactionWithRetry(address: Address, hash: string, hashType: 'unknown' | 'in' | 'out' | undefined, opts: GetTransactionsOptions, depth: number, message?: Message): Promise<Transaction | null>;
68
69
  /**
69
70
  * Track transaction tree and validate all transactions
70
71
  */
@@ -27,24 +27,33 @@ class BaseContractOpener {
27
27
  */
28
28
  async scanTransactionHistory(addr, opts, predicate) {
29
29
  const limit = opts?.limit ?? Consts_1.DEFAULT_FIND_TX_LIMIT;
30
- const inclusive = opts?.inclusive ?? true;
30
+ const toLtInclusive = opts?.inclusive ?? true;
31
31
  const toLt = opts?.to_lt ? BigInt(opts.to_lt) : undefined;
32
32
  let currentLt = opts?.lt;
33
33
  let currentHash = opts?.hash ? (0, Utils_1.normalizeHashToBase64)(opts.hash) : undefined;
34
- const seenCursors = new Set();
34
+ let requestInclusive = toLtInclusive;
35
+ const seenRequests = new Set();
35
36
  let scannedTransactions = 0;
36
37
  const maxScannedTransactions = opts?.maxScannedTransactions ?? Consts_1.DEFAULT_MAX_SCANNED_TRANSACTIONS;
37
38
  while (true) {
38
- const list = await this.getTransactions(addr, {
39
+ const requestKey = `${currentLt ?? ''}:${currentHash ?? ''}:${requestInclusive}`;
40
+ if (seenRequests.has(requestKey)) {
41
+ break;
42
+ }
43
+ seenRequests.add(requestKey);
44
+ const requestOpts = {
39
45
  limit,
40
46
  lt: currentLt,
41
47
  hash: currentHash,
42
48
  to_lt: opts?.to_lt,
43
- inclusive,
44
- archival: opts?.archival ?? Consts_1.DEFAULT_FIND_TX_ARCHIVAL,
49
+ inclusive: requestInclusive,
45
50
  timeoutMs: opts?.timeoutMs,
46
51
  retryDelayMs: opts?.retryDelayMs,
47
- });
52
+ };
53
+ if (opts?.archival !== undefined) {
54
+ requestOpts.archival = opts.archival;
55
+ }
56
+ const list = await this.getTransactions(addr, requestOpts);
48
57
  if (list.length === 0) {
49
58
  break;
50
59
  }
@@ -54,7 +63,7 @@ class BaseContractOpener {
54
63
  currentHash &&
55
64
  firstTx.lt.toString() === currentLt &&
56
65
  firstTx.hash().toString('base64') === currentHash;
57
- if (firstTxMatchesCursor) {
66
+ if (firstTxMatchesCursor && !requestInclusive) {
58
67
  startIndex = 1;
59
68
  if (list.length === 1) {
60
69
  if (firstTx.prevTransactionLt === 0n) {
@@ -65,13 +74,9 @@ class BaseContractOpener {
65
74
  if (currentLt && BigInt(nextLt) >= BigInt(currentLt)) {
66
75
  break;
67
76
  }
68
- const cursorKey = `${nextLt}:${nextHash}`;
69
- if (seenCursors.has(cursorKey)) {
70
- break;
71
- }
72
- seenCursors.add(cursorKey);
73
77
  currentLt = nextLt;
74
78
  currentHash = nextHash;
79
+ requestInclusive = true;
75
80
  continue;
76
81
  }
77
82
  }
@@ -90,21 +95,17 @@ class BaseContractOpener {
90
95
  if (oldestTx.prevTransactionLt === 0n)
91
96
  break;
92
97
  if (toLt !== undefined) {
93
- if (inclusive ? oldestTx.lt <= toLt : oldestTx.lt < toLt)
98
+ if (toLtInclusive ? oldestTx.lt <= toLt : oldestTx.lt < toLt)
94
99
  break;
95
100
  }
96
101
  const nextLt = oldestTx.lt.toString();
97
102
  const nextHash = oldestTx.hash().toString('base64');
98
- if (currentLt && BigInt(nextLt) >= BigInt(currentLt)) {
103
+ if (currentLt && currentHash && nextLt === currentLt && nextHash === currentHash && !requestInclusive) {
99
104
  break;
100
105
  }
101
- const cursorKey = `${nextLt}:${nextHash}`;
102
- if (seenCursors.has(cursorKey)) {
103
- break;
104
- }
105
- seenCursors.add(cursorKey);
106
106
  currentLt = nextLt;
107
107
  currentHash = nextHash;
108
+ requestInclusive = false;
108
109
  }
109
110
  return null;
110
111
  }
@@ -281,37 +282,31 @@ class BaseContractOpener {
281
282
  /**
282
283
  * Find transaction by hash type
283
284
  */
284
- async findTransactionByHashType(address, hash, hashType, opts) {
285
- const searchOpts = { archival: true, ...opts };
285
+ async findTransactionByHashType(address, hash, hashType, opts, message) {
286
+ const targetHash = message ? (0, ton_1.beginCell)().store((0, ton_1.storeMessage)(message)).endCell().hash().toString('base64') : hash;
286
287
  if (hashType === 'in') {
287
- return this.getTransactionByInMsgHash(address, hash, searchOpts);
288
+ return this.getTransactionByInMsgHash(address, targetHash, opts);
288
289
  }
289
290
  else if (hashType === 'out') {
290
- return this.getTransactionByOutMsgHash(address, hash, searchOpts);
291
+ return this.getTransactionByOutMsgHash(address, targetHash, opts);
291
292
  }
292
293
  else {
293
- return this.getTransactionByHash(address, hash, searchOpts);
294
+ return this.getTransactionByHash(address, targetHash, opts);
294
295
  }
295
296
  }
296
297
  /**
297
298
  * Find transaction with retry logic
298
299
  */
299
- async findTransactionWithRetry(address, hash, hashType, opts, depth) {
300
+ async findTransactionWithRetry(address, hash, hashType, opts, depth, message) {
300
301
  const retryDelayMs = opts.retryDelayMs ?? Consts_1.DEFAULT_RETRY_ON_NOT_FOUND_DELAY_MS;
301
302
  const maxRetries = opts.retries ?? Consts_1.DEFAULT_RETRY_ON_NOT_FOUND_RETRIES;
302
303
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
303
- let errorMsg;
304
- try {
305
- const tx = await this.findTransactionByHashType(address, hash, hashType, opts);
306
- if (tx) {
307
- return tx;
308
- }
309
- }
310
- catch (error) {
311
- errorMsg = error instanceof Error ? error.message : String(error);
304
+ const tx = await this.findTransactionByHashType(address, hash, hashType, opts, message);
305
+ if (tx) {
306
+ return tx;
312
307
  }
313
308
  const isLastAttempt = attempt >= maxRetries;
314
- const reason = errorMsg ?? 'not found';
309
+ const reason = 'not found';
315
310
  const retryInfo = isLastAttempt ? '' : `, retrying in ${retryDelayMs}ms`;
316
311
  this.logger?.debug(`Transaction not found at depth ${depth} (attempt ${attempt + 1}/${maxRetries + 1}): ${reason}${retryInfo}`);
317
312
  if (isLastAttempt) {
@@ -357,7 +352,7 @@ class BaseContractOpener {
357
352
  direction: Struct_1.TransactionTreeDirection.FORWARD,
358
353
  retryOnNotFound: Consts_1.DEFAULT_RETRY_ON_NOT_FOUND,
359
354
  }) {
360
- 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 = Struct_1.TransactionTreeDirection.FORWARD, retryOnNotFound = Consts_1.DEFAULT_RETRY_ON_NOT_FOUND, retryDelayMs, retries, } = params;
355
+ 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, archival, rootLt, direction = Struct_1.TransactionTreeDirection.FORWARD, retryOnNotFound = Consts_1.DEFAULT_RETRY_ON_NOT_FOUND, retryDelayMs, retries, } = params;
361
356
  const parsedAddress = ton_1.Address.parse(address);
362
357
  const normalizedRootHash = (0, Utils_1.normalizeHashToBase64)(hash);
363
358
  const visitedSearchKeys = new Set();
@@ -365,25 +360,30 @@ class BaseContractOpener {
365
360
  let checkedCount = 0;
366
361
  const searchOpts = {
367
362
  limit,
368
- archival: true,
369
363
  maxScannedTransactions,
370
364
  retryDelayMs,
371
365
  retries,
372
366
  };
367
+ if (archival !== undefined) {
368
+ searchOpts.archival = archival;
369
+ }
373
370
  const queue = [
374
371
  { address: parsedAddress, hash: normalizedRootHash, depth: 0, hashType: 'unknown' },
375
372
  ];
376
373
  while (queue.length > 0) {
377
- const { hash: currentHash, depth: currentDepth, address: currentAddress, hashType } = queue.shift();
374
+ const { hash: currentHash, depth: currentDepth, address: currentAddress, hashType, message, } = queue.shift();
378
375
  const visitedKey = `${currentAddress.toString()}:${currentHash}:${hashType}`;
379
376
  if (visitedSearchKeys.has(visitedKey))
380
377
  continue;
381
378
  visitedSearchKeys.add(visitedKey);
379
+ const currentSearchOpts = currentDepth === 0 && hashType === 'unknown' && rootLt
380
+ ? { ...searchOpts, lt: rootLt, hash: currentHash, inclusive: true }
381
+ : searchOpts;
382
382
  const tx = retryOnNotFound
383
- ? await this.findTransactionWithRetry(currentAddress, currentHash, hashType, searchOpts, currentDepth)
384
- : await this.findTransactionByHashType(currentAddress, currentHash, hashType, searchOpts);
383
+ ? await this.findTransactionWithRetry(currentAddress, currentHash, hashType, currentSearchOpts, currentDepth, message)
384
+ : await this.findTransactionByHashType(currentAddress, currentHash, hashType, currentSearchOpts, message);
385
385
  if (!tx) {
386
- this.logger?.debug(`Transaction not found for hash: ${currentHash} (address=${currentAddress?.toString()}, hashType=${hashType ?? 'unknown'})`);
386
+ this.logger?.debug(`Transaction not found for hash: ${currentHash} (address=${currentAddress.toString()}, hashType=${hashType})`);
387
387
  return {
388
388
  success: false,
389
389
  checkedCount,
@@ -392,8 +392,8 @@ class BaseContractOpener {
392
392
  exitCode: 'N/A',
393
393
  resultCode: 'N/A',
394
394
  reason: 'not_found',
395
- address: currentAddress?.toString(),
396
- hashType: hashType ?? 'unknown',
395
+ address: currentAddress.toString(),
396
+ hashType,
397
397
  },
398
398
  };
399
399
  }
@@ -422,6 +422,7 @@ class BaseContractOpener {
422
422
  address: dst,
423
423
  depth: currentDepth + 1,
424
424
  hashType: 'in',
425
+ message: msg,
425
426
  });
426
427
  }
427
428
  }
@@ -433,6 +434,7 @@ class BaseContractOpener {
433
434
  address: tx.inMessage.info.src,
434
435
  depth: currentDepth + 1,
435
436
  hashType: 'out',
437
+ message: tx.inMessage,
436
438
  });
437
439
  }
438
440
  }
@@ -42,4 +42,4 @@ export declare class RetryableContractOpener implements ContractOpener {
42
42
  private createRetryableContract;
43
43
  private callMethodAcrossOpeners;
44
44
  }
45
- export declare function createDefaultRetryableOpener(tonRpcEndpoint: string, networkType: Network, maxRetries?: number, retryDelay?: number, logger?: ILogger): Promise<RetryableContractOpener>;
45
+ export declare function createDefaultRetryableOpener(tonRpcEndpoint: string, networkType: Network, maxRetries?: number, retryDelay?: number, logger?: ILogger, toncenterV3Endpoint?: string): Promise<RetryableContractOpener>;
@@ -7,6 +7,8 @@ const errors_1 = require("../errors");
7
7
  const instances_1 = require("../errors/instances");
8
8
  const Consts_1 = require("../sdk/Consts");
9
9
  const Struct_1 = require("../structs/Struct");
10
+ const ToncenterV3Indexer_1 = require("./ToncenterV3Indexer");
11
+ const ToncenterV3Opener_1 = require("./ToncenterV3Opener");
10
12
  const TonClient4Opener_1 = require("./TonClient4Opener");
11
13
  const TonClientOpener_1 = require("./TonClientOpener");
12
14
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -271,12 +273,21 @@ class RetryableContractOpener {
271
273
  }
272
274
  }
273
275
  exports.RetryableContractOpener = RetryableContractOpener;
274
- async function createDefaultRetryableOpener(tonRpcEndpoint, networkType, maxRetries = Consts_1.DEFAULT_RETRY_MAX_COUNT, retryDelay = Consts_1.DEFAULT_RETRY_DELAY_MS, logger) {
276
+ async function createDefaultRetryableOpener(tonRpcEndpoint, networkType, maxRetries = Consts_1.DEFAULT_RETRY_MAX_COUNT, retryDelay = Consts_1.DEFAULT_RETRY_DELAY_MS, logger, toncenterV3Endpoint) {
275
277
  const openers = [];
276
278
  const tonClient = new ton_1.TonClient({
277
279
  endpoint: new URL('api/v2/jsonRPC', tonRpcEndpoint).toString(),
278
280
  timeout: Consts_1.DEFAULT_HTTP_CLIENT_TIMEOUT_MS,
279
281
  });
282
+ if (toncenterV3Endpoint) {
283
+ openers.push({
284
+ opener: new ToncenterV3Opener_1.ToncenterV3Opener(new ToncenterV3Indexer_1.ToncenterV3Indexer(toncenterV3Endpoint), tonClient, logger),
285
+ retries: maxRetries,
286
+ retryDelay,
287
+ maxRetryDelay: Consts_1.DEFAULT_RETRY_MAX_DELAY_MS,
288
+ retryBackoffMultiplier: Consts_1.DEFAULT_RETRY_BACKOFF_MULTIPLIER,
289
+ });
290
+ }
280
291
  const opener = (0, TonClientOpener_1.tonClientOpener)(tonClient, logger);
281
292
  openers.push({
282
293
  opener,
@@ -1,3 +1,4 @@
1
+ import type { Message } from '@ton/ton';
1
2
  import { Address, Contract, OpenedContract, TonClient, Transaction } from '@ton/ton';
2
3
  import { ILogger } from '../interfaces';
3
4
  import { AddressInformation, ContractState, GetTransactionsOptions, Network } from '../structs/Struct';
@@ -12,6 +13,7 @@ export declare class TonClientOpener extends BaseContractOpener {
12
13
  getContractState(address: Address): Promise<ContractState>;
13
14
  getTransactions(address: Address, opts: GetTransactionsOptions): Promise<Transaction[]>;
14
15
  getAddressInformation(addr: Address): Promise<AddressInformation>;
16
+ protected findTransactionByHashType(address: Address, hash: string, hashType: 'unknown' | 'in' | 'out' | undefined, opts: GetTransactionsOptions, message?: Message): Promise<Transaction | null>;
15
17
  getConfig(): Promise<string>;
16
18
  }
17
19
  export declare function tonClientOpener(client: TonClient, logger?: ILogger): TonClientOpener;
@@ -38,7 +38,7 @@ class TonClientOpener extends BaseContractOpener_1.BaseContractOpener {
38
38
  hash: opts.hash ? (0, Utils_1.normalizeHashToBase64)(opts.hash) : undefined,
39
39
  to_lt: opts.to_lt,
40
40
  inclusive: opts.inclusive,
41
- archival: opts.archival,
41
+ ...(opts.archival === undefined ? {} : { archival: opts.archival }),
42
42
  };
43
43
  return this.client.getTransactions(address, clientOpts);
44
44
  }
@@ -51,6 +51,40 @@ class TonClientOpener extends BaseContractOpener_1.BaseContractOpener {
51
51
  },
52
52
  };
53
53
  }
54
+ async findTransactionByHashType(address, hash, hashType, opts, message) {
55
+ if (opts.archival === true && message?.info.type === 'internal') {
56
+ const { src, dest, createdLt } = message.info;
57
+ const targetHash = (0, Utils_1.normalizeHashToBase64)(hash);
58
+ if (hashType === 'in') {
59
+ try {
60
+ const tx = await this.client.tryLocateResultTx(src, dest, createdLt.toString());
61
+ if (tx.inMessage &&
62
+ (0, ton_1.beginCell)().store((0, ton_1.storeMessage)(tx.inMessage)).endCell().hash().toString('base64') ===
63
+ targetHash) {
64
+ return tx;
65
+ }
66
+ }
67
+ catch {
68
+ // Keep the regular hash scan semantics when Toncenter cannot locate a delivered result.
69
+ }
70
+ }
71
+ else if (hashType === 'out') {
72
+ try {
73
+ const tx = await this.client.tryLocateSourceTx(src, dest, createdLt.toString());
74
+ for (const outMessage of tx.outMessages.values()) {
75
+ if ((0, ton_1.beginCell)().store((0, ton_1.storeMessage)(outMessage)).endCell().hash().toString('base64') ===
76
+ targetHash) {
77
+ return tx;
78
+ }
79
+ }
80
+ }
81
+ catch {
82
+ // Keep the regular hash scan semantics when Toncenter cannot locate the source.
83
+ }
84
+ }
85
+ }
86
+ return super.findTransactionByHashType(address, hash, hashType, opts, message);
87
+ }
54
88
  async getConfig() {
55
89
  const info = await this.client.getMasterchainInfo();
56
90
  const url = new URL('getConfigAll', this.client.parameters.endpoint);
@@ -14,6 +14,7 @@ export declare class ToncenterV3Indexer implements IToncenterV3Indexer {
14
14
  * @returns Indexed transaction with owning account context, or null on 404 / empty result.
15
15
  */
16
16
  getTransactionByHash(transactionHash: string): Promise<ToncenterV3IndexedTransaction | null>;
17
+ getTransactionsByMessageHash(messageHash: string, direction: 'in' | 'out'): Promise<ToncenterV3IndexedTransaction[]>;
17
18
  /**
18
19
  * Queries Toncenter v3 indexed `/adjacentTransactions` endpoint by hash.
19
20
  * @param transactionHash TON transaction hash accepted by the indexer.
@@ -27,20 +27,41 @@ class ToncenterV3Indexer {
27
27
  const response = await this.httpClient.get(endpoint, {
28
28
  params: {
29
29
  hash: transactionHash,
30
- limit: 1,
30
+ limit: Consts_1.DEFAULT_FIND_TX_LIMIT,
31
31
  },
32
32
  transformResponse: [Utils_1.toCamelCaseTransformer],
33
33
  });
34
34
  return response.data.transactions?.[0] ?? null;
35
35
  }
36
36
  catch (error) {
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- if (error?.response?.status === 404) {
37
+ if (error.response?.status === Consts_1.HTTP_NOT_FOUND_STATUS_CODE) {
39
38
  return null;
40
39
  }
41
40
  throw (0, errors_1.operationFetchError)(`request ${requestLabel} failed to resolve TON indexed transaction`, error);
42
41
  }
43
42
  }
43
+ async getTransactionsByMessageHash(messageHash, direction) {
44
+ const endpoint = this.endpoint.endsWith('/')
45
+ ? `${this.endpoint}transactionsByMessage`
46
+ : `${this.endpoint}/transactionsByMessage`;
47
+ try {
48
+ const { data } = await this.httpClient.get(endpoint, {
49
+ params: {
50
+ msg_hash: messageHash,
51
+ direction,
52
+ limit: Consts_1.DEFAULT_FIND_TX_LIMIT,
53
+ },
54
+ transformResponse: [Utils_1.toCamelCaseTransformer],
55
+ });
56
+ return data.transactions ?? [];
57
+ }
58
+ catch (error) {
59
+ if (error.response?.status === Consts_1.HTTP_NOT_FOUND_STATUS_CODE) {
60
+ return [];
61
+ }
62
+ throw (0, errors_1.operationFetchError)(`request GET ${endpoint} failed to resolve TON indexed transactions by message`, error);
63
+ }
64
+ }
44
65
  /**
45
66
  * Queries Toncenter v3 indexed `/adjacentTransactions` endpoint by hash.
46
67
  * @param transactionHash TON transaction hash accepted by the indexer.
@@ -59,8 +80,7 @@ class ToncenterV3Indexer {
59
80
  return response.data.transactions ?? [];
60
81
  }
61
82
  catch (error) {
62
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
- if (error?.response?.status === 404) {
83
+ if (error.response?.status === Consts_1.HTTP_NOT_FOUND_STATUS_CODE) {
64
84
  return [];
65
85
  }
66
86
  throw (0, errors_1.operationFetchError)(`request ${requestLabel} failed to resolve adjacent TON indexed transactions`, error);
@@ -86,20 +106,19 @@ class ToncenterV3Indexer {
86
106
  return rootTransaction;
87
107
  }
88
108
  const queue = [{ hash: rootTransaction.hash, depth: 0 }];
89
- const visitedHashes = new Set([(0, Utils_1.normalizeHashToHex)(rootTransaction.hash)]);
109
+ const visitedHashes = new Set([rootTransaction.hash]);
90
110
  let scannedTransactions = 0;
91
- while (queue.length > 0) {
92
- const { hash: currentHash, depth } = queue.shift();
111
+ for (let i = 0; i < queue.length; i++) {
112
+ const { hash: currentHash, depth } = queue[i];
93
113
  if (depth >= maxDepth || scannedTransactions >= maxScannedTransactions) {
94
114
  continue;
95
115
  }
96
116
  const adjacentTransactions = await this.getAdjacentTransactions(currentHash);
97
117
  for (const transaction of adjacentTransactions) {
98
- const normalizedHash = (0, Utils_1.normalizeHashToHex)(transaction.hash);
99
- if (visitedHashes.has(normalizedHash)) {
118
+ if (visitedHashes.has(transaction.hash)) {
100
119
  continue;
101
120
  }
102
- visitedHashes.add(normalizedHash);
121
+ visitedHashes.add(transaction.hash);
103
122
  scannedTransactions += 1;
104
123
  if (this.isCollectibleEventTransactionOnAccount(transaction, crossChainLayer)) {
105
124
  return transaction;
@@ -0,0 +1,11 @@
1
+ import { TonClient } from '@ton/ton';
2
+ import { ILogger, IToncenterV3Indexer } from '../interfaces';
3
+ import { TrackTransactionTreeParams, TrackTransactionTreeResult } from '../structs/Struct';
4
+ import { TonClientOpener } from './TonClientOpener';
5
+ export declare class ToncenterV3Opener extends TonClientOpener {
6
+ private readonly indexer;
7
+ constructor(indexer: IToncenterV3Indexer, client: TonClient, logger?: ILogger);
8
+ trackTransactionTreeWithResult(address: string, hash: string, params?: TrackTransactionTreeParams): Promise<TrackTransactionTreeResult>;
9
+ private findRootTransaction;
10
+ private validateIndexedTransactionWithResult;
11
+ }
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToncenterV3Opener = void 0;
4
+ const ton_1 = require("@ton/ton");
5
+ const Consts_1 = require("../sdk/Consts");
6
+ const Utils_1 = require("../sdk/Utils");
7
+ const Struct_1 = require("../structs/Struct");
8
+ const TonClientOpener_1 = require("./TonClientOpener");
9
+ class ToncenterV3Opener extends TonClientOpener_1.TonClientOpener {
10
+ constructor(indexer, client, logger) {
11
+ super(client, logger);
12
+ this.indexer = indexer;
13
+ }
14
+ async trackTransactionTreeWithResult(address, hash, params = {}) {
15
+ const { maxDepth = Consts_1.DEFAULT_FIND_TX_MAX_DEPTH, direction = Struct_1.TransactionTreeDirection.FORWARD, retryOnNotFound = Consts_1.DEFAULT_RETRY_ON_NOT_FOUND, retryDelayMs = Consts_1.DEFAULT_RETRY_ON_NOT_FOUND_DELAY_MS, retries = Consts_1.DEFAULT_RETRY_ON_NOT_FOUND_RETRIES, } = params;
16
+ const rootAddress = ton_1.Address.parse(address);
17
+ const rootHash = (0, Utils_1.normalizeHashToBase64)(hash);
18
+ const attempts = retryOnNotFound ? retries + 1 : 1;
19
+ let rootTransaction = null;
20
+ for (let attempt = 0; attempt < attempts; attempt++) {
21
+ rootTransaction = await this.findRootTransaction(rootAddress, rootHash);
22
+ if (rootTransaction) {
23
+ break;
24
+ }
25
+ if (attempt < attempts - 1) {
26
+ await (0, Utils_1.sleep)(retryDelayMs);
27
+ }
28
+ }
29
+ if (!rootTransaction) {
30
+ return {
31
+ success: false,
32
+ checkedCount: 0,
33
+ error: {
34
+ txHash: rootHash,
35
+ exitCode: 'N/A',
36
+ resultCode: 'N/A',
37
+ reason: 'not_found',
38
+ address: rootAddress.toString(),
39
+ },
40
+ };
41
+ }
42
+ const queue = [{ transaction: rootTransaction, depth: 0 }];
43
+ const processedTxHashes = new Set();
44
+ let checkedCount = 0;
45
+ for (let i = 0; i < queue.length; i++) {
46
+ const { transaction: tx, depth } = queue[i];
47
+ if (processedTxHashes.has(tx.hash)) {
48
+ continue;
49
+ }
50
+ processedTxHashes.add(tx.hash);
51
+ checkedCount += 1;
52
+ const validationError = this.validateIndexedTransactionWithResult(tx);
53
+ if (validationError) {
54
+ return { success: false, checkedCount, error: validationError };
55
+ }
56
+ if (depth >= maxDepth) {
57
+ continue;
58
+ }
59
+ const adjacentTransactions = await this.indexer.getAdjacentTransactions(tx.hash);
60
+ for (const adjacentTransaction of adjacentTransactions) {
61
+ if ((direction !== Struct_1.TransactionTreeDirection.BACKWARD &&
62
+ tx.outMsgs.some((message) => message.hash && message.hash === adjacentTransaction.inMsg?.hash)) ||
63
+ (direction !== Struct_1.TransactionTreeDirection.FORWARD &&
64
+ adjacentTransaction.outMsgs.some((message) => message.hash && message.hash === tx.inMsg?.hash))) {
65
+ queue.push({ transaction: adjacentTransaction, depth: depth + 1 });
66
+ }
67
+ }
68
+ }
69
+ return { success: true, checkedCount };
70
+ }
71
+ async findRootTransaction(address, hash) {
72
+ const tx = await this.indexer.getTransactionByHash(hash);
73
+ if (tx && ton_1.Address.parse(tx.account).equals(address)) {
74
+ return tx;
75
+ }
76
+ for (const direction of ['in', 'out']) {
77
+ const transactions = await this.indexer.getTransactionsByMessageHash(hash, direction);
78
+ const transaction = transactions.find((tx) => ton_1.Address.parse(tx.account).equals(address));
79
+ if (transaction) {
80
+ return transaction;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ validateIndexedTransactionWithResult(tx) {
86
+ const { description } = tx;
87
+ if (description?.type && description.type !== 'generic') {
88
+ return null;
89
+ }
90
+ const computePh = description?.computePh;
91
+ const action = description?.action;
92
+ const exitCode = computePh && !computePh.skipped ? computePh.exitCode ?? 'N/A' : 'N/A';
93
+ const resultCode = action ? action.resultCode ?? 'N/A' : 'N/A';
94
+ let reason = null;
95
+ if (description?.aborted) {
96
+ reason = 'aborted';
97
+ }
98
+ else if (!computePh) {
99
+ reason = 'compute_phase_missing';
100
+ }
101
+ else if (!computePh.skipped && (computePh.success !== true || computePh.exitCode !== 0)) {
102
+ reason = 'compute_phase_failed';
103
+ }
104
+ else if (action && (action.success !== true || action.resultCode !== 0)) {
105
+ reason = 'action_phase_failed';
106
+ }
107
+ return reason ? { txHash: tx.hash, exitCode, resultCode, reason } : null;
108
+ }
109
+ }
110
+ exports.ToncenterV3Opener = ToncenterV3Opener;
@@ -4,5 +4,6 @@ export * from './OpenerUtils';
4
4
  export * from './RetryableContractOpener';
5
5
  export * from './SandboxOpener';
6
6
  export * from './ToncenterV3Indexer';
7
+ export * from './ToncenterV3Opener';
7
8
  export * from './TonClient4Opener';
8
9
  export * from './TonClientOpener';
@@ -20,5 +20,6 @@ __exportStar(require("./OpenerUtils"), exports);
20
20
  __exportStar(require("./RetryableContractOpener"), exports);
21
21
  __exportStar(require("./SandboxOpener"), exports);
22
22
  __exportStar(require("./ToncenterV3Indexer"), exports);
23
+ __exportStar(require("./ToncenterV3Opener"), exports);
23
24
  __exportStar(require("./TonClient4Opener"), exports);
24
25
  __exportStar(require("./TonClientOpener"), exports);
@@ -1,2 +1,2 @@
1
1
  export { AddressError, BitError, ContractError, EVMCallError, FetchError, FormatError, KeyError, MetadataError, SettingError, TokenError, TransactionError, WalletError, } from './errors';
2
- export { allEndpointsFailedError, blockGasLimitFetchError, emptyArrayError, emptyContractError, emptySettingError, estimatedGasExceedsBlockGasLimitError, evmAddressError, externalInMessageRequiredError, executedInTONStageFailedError, gasPriceFetchError, indexRequiredError, insufficientBalanceError, insufficientFeeParamsError, invalidTonExternalMessageBocError, invalidMethodNameError, missingDecimals, missingFeeParamsError, missingGasLimitError, missingJettonDataError, missingTvmExecutorFeeError, notMultiplyOf8Error, operationFetchError, operationIdRequiredForFinalizationError, prefixError, profilingFetchError, simulationFetchError, statusFetchError, tvmAddressError, txFinalizationError, unknownTokenTypeError, unknownWalletError, unsupportedFormatError, unsupportedKeyError, zeroRawAmountError, } from './instances';
2
+ export { allEndpointsFailedError, blockGasLimitFetchError, emptyArrayError, emptyContractError, emptySettingError, estimatedGasExceedsBlockGasLimitError, evmAddressError, executedInTONStageFailedError, externalInMessageRequiredError, gasPriceFetchError, indexRequiredError, insufficientBalanceError, insufficientFeeParamsError, invalidMethodNameError, invalidTonExternalMessageBocError, missingDecimals, missingFeeParamsError, missingGasLimitError, missingJettonDataError, missingTvmExecutorFeeError, notMultiplyOf8Error, operationFetchError, operationIdRequiredForFinalizationError, prefixError, profilingFetchError, simulationFetchError, statusFetchError, tonIndexerRequiredForTonTransactionBocError, tvmAddressError, txFinalizationError, unknownTokenTypeError, unknownWalletError, unsupportedFormatError, unsupportedKeyError, zeroRawAmountError, } from './instances';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.zeroRawAmountError = exports.unsupportedKeyError = exports.unsupportedFormatError = exports.unknownWalletError = exports.unknownTokenTypeError = exports.txFinalizationError = exports.tvmAddressError = exports.statusFetchError = exports.simulationFetchError = exports.profilingFetchError = exports.prefixError = exports.operationIdRequiredForFinalizationError = exports.operationFetchError = exports.notMultiplyOf8Error = exports.missingTvmExecutorFeeError = exports.missingJettonDataError = exports.missingGasLimitError = exports.missingFeeParamsError = exports.missingDecimals = exports.invalidMethodNameError = exports.invalidTonExternalMessageBocError = exports.insufficientFeeParamsError = exports.insufficientBalanceError = exports.indexRequiredError = exports.gasPriceFetchError = exports.executedInTONStageFailedError = exports.externalInMessageRequiredError = exports.evmAddressError = exports.estimatedGasExceedsBlockGasLimitError = exports.emptySettingError = exports.emptyContractError = exports.emptyArrayError = exports.blockGasLimitFetchError = exports.allEndpointsFailedError = exports.WalletError = exports.TransactionError = exports.TokenError = exports.SettingError = exports.MetadataError = exports.KeyError = exports.FormatError = exports.FetchError = exports.EVMCallError = exports.ContractError = exports.BitError = exports.AddressError = void 0;
3
+ exports.zeroRawAmountError = exports.unsupportedKeyError = exports.unsupportedFormatError = exports.unknownWalletError = exports.unknownTokenTypeError = exports.txFinalizationError = exports.tvmAddressError = exports.tonIndexerRequiredForTonTransactionBocError = exports.statusFetchError = exports.simulationFetchError = exports.profilingFetchError = exports.prefixError = exports.operationIdRequiredForFinalizationError = exports.operationFetchError = exports.notMultiplyOf8Error = exports.missingTvmExecutorFeeError = exports.missingJettonDataError = exports.missingGasLimitError = exports.missingFeeParamsError = exports.missingDecimals = exports.invalidTonExternalMessageBocError = exports.invalidMethodNameError = exports.insufficientFeeParamsError = exports.insufficientBalanceError = exports.indexRequiredError = exports.gasPriceFetchError = exports.externalInMessageRequiredError = exports.executedInTONStageFailedError = exports.evmAddressError = exports.estimatedGasExceedsBlockGasLimitError = exports.emptySettingError = exports.emptyContractError = exports.emptyArrayError = exports.blockGasLimitFetchError = exports.allEndpointsFailedError = exports.WalletError = exports.TransactionError = exports.TokenError = exports.SettingError = exports.MetadataError = exports.KeyError = exports.FormatError = exports.FetchError = exports.EVMCallError = exports.ContractError = exports.BitError = exports.AddressError = void 0;
4
4
  var errors_1 = require("./errors");
5
5
  Object.defineProperty(exports, "AddressError", { enumerable: true, get: function () { return errors_1.AddressError; } });
6
6
  Object.defineProperty(exports, "BitError", { enumerable: true, get: function () { return errors_1.BitError; } });
@@ -22,14 +22,14 @@ Object.defineProperty(exports, "emptyContractError", { enumerable: true, get: fu
22
22
  Object.defineProperty(exports, "emptySettingError", { enumerable: true, get: function () { return instances_1.emptySettingError; } });
23
23
  Object.defineProperty(exports, "estimatedGasExceedsBlockGasLimitError", { enumerable: true, get: function () { return instances_1.estimatedGasExceedsBlockGasLimitError; } });
24
24
  Object.defineProperty(exports, "evmAddressError", { enumerable: true, get: function () { return instances_1.evmAddressError; } });
25
- Object.defineProperty(exports, "externalInMessageRequiredError", { enumerable: true, get: function () { return instances_1.externalInMessageRequiredError; } });
26
25
  Object.defineProperty(exports, "executedInTONStageFailedError", { enumerable: true, get: function () { return instances_1.executedInTONStageFailedError; } });
26
+ Object.defineProperty(exports, "externalInMessageRequiredError", { enumerable: true, get: function () { return instances_1.externalInMessageRequiredError; } });
27
27
  Object.defineProperty(exports, "gasPriceFetchError", { enumerable: true, get: function () { return instances_1.gasPriceFetchError; } });
28
28
  Object.defineProperty(exports, "indexRequiredError", { enumerable: true, get: function () { return instances_1.indexRequiredError; } });
29
29
  Object.defineProperty(exports, "insufficientBalanceError", { enumerable: true, get: function () { return instances_1.insufficientBalanceError; } });
30
30
  Object.defineProperty(exports, "insufficientFeeParamsError", { enumerable: true, get: function () { return instances_1.insufficientFeeParamsError; } });
31
- Object.defineProperty(exports, "invalidTonExternalMessageBocError", { enumerable: true, get: function () { return instances_1.invalidTonExternalMessageBocError; } });
32
31
  Object.defineProperty(exports, "invalidMethodNameError", { enumerable: true, get: function () { return instances_1.invalidMethodNameError; } });
32
+ Object.defineProperty(exports, "invalidTonExternalMessageBocError", { enumerable: true, get: function () { return instances_1.invalidTonExternalMessageBocError; } });
33
33
  Object.defineProperty(exports, "missingDecimals", { enumerable: true, get: function () { return instances_1.missingDecimals; } });
34
34
  Object.defineProperty(exports, "missingFeeParamsError", { enumerable: true, get: function () { return instances_1.missingFeeParamsError; } });
35
35
  Object.defineProperty(exports, "missingGasLimitError", { enumerable: true, get: function () { return instances_1.missingGasLimitError; } });
@@ -42,6 +42,7 @@ Object.defineProperty(exports, "prefixError", { enumerable: true, get: function
42
42
  Object.defineProperty(exports, "profilingFetchError", { enumerable: true, get: function () { return instances_1.profilingFetchError; } });
43
43
  Object.defineProperty(exports, "simulationFetchError", { enumerable: true, get: function () { return instances_1.simulationFetchError; } });
44
44
  Object.defineProperty(exports, "statusFetchError", { enumerable: true, get: function () { return instances_1.statusFetchError; } });
45
+ Object.defineProperty(exports, "tonIndexerRequiredForTonTransactionBocError", { enumerable: true, get: function () { return instances_1.tonIndexerRequiredForTonTransactionBocError; } });
45
46
  Object.defineProperty(exports, "tvmAddressError", { enumerable: true, get: function () { return instances_1.tvmAddressError; } });
46
47
  Object.defineProperty(exports, "txFinalizationError", { enumerable: true, get: function () { return instances_1.txFinalizationError; } });
47
48
  Object.defineProperty(exports, "unknownTokenTypeError", { enumerable: true, get: function () { return instances_1.unknownTokenTypeError; } });
@@ -43,3 +43,4 @@ export declare const blockGasLimitFetchError: (msg: string, inner?: unknown) =>
43
43
  export declare const estimatedGasExceedsBlockGasLimitError: (txName: string, estimatedGas: bigint, blockGasLimit: bigint) => TransactionError;
44
44
  export declare const invalidTonExternalMessageBocError: (reason?: string) => FormatError;
45
45
  export declare const externalInMessageRequiredError: (actualType: string) => FormatError;
46
+ export declare const tonIndexerRequiredForTonTransactionBocError: SettingError;