@ton/sandbox 0.36.0-dev.20250804114000.4e90ae1 → 0.36.0

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 CHANGED
@@ -5,10 +5,11 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## Unreleased
8
+ ## [0.36.0] - 2025-08-04
9
9
 
10
10
  ### Added
11
11
 
12
+ - Added `Blockchain.getTransactions` method to fetch transactions
12
13
  - Added coverage
13
14
  - Added `allowParallel` flag to `Blockchain.sendMessageIter` method, allowing for testing MITM attack
14
15
  - Added `autoDeployLibs` flag, that allow to detect libraries, deployed by contract in masterchain automatically
@@ -91,6 +91,7 @@ export type BlockchainSnapshot = {
91
91
  prevBlocksInfo?: PrevBlocksInfo;
92
92
  randomSeed?: Buffer;
93
93
  autoDeployLibs: boolean;
94
+ transactions: BlockchainTransaction[];
94
95
  };
95
96
  export type SendMessageIterParams = MessageParams & {
96
97
  allowParallel?: boolean;
@@ -111,10 +112,11 @@ export declare class Blockchain {
111
112
  protected randomSeed?: Buffer;
112
113
  protected shouldDebug: boolean;
113
114
  protected autoDeployLibs: boolean;
115
+ protected transactions: BlockchainTransaction[];
114
116
  protected defaultQueueManager: MessageQueueManager;
115
117
  protected collectCoverage: boolean;
116
- protected readonly txs: BlockchainTransaction[][];
117
- protected readonly getMethodResults: GetMethodResult[];
118
+ protected readonly coverageTransactions: BlockchainTransaction[][];
119
+ protected readonly coverageGetMethodResults: GetMethodResult[];
118
120
  readonly executor: IExecutor;
119
121
  protected debuggerExecutor?: Executor;
120
122
  getDebuggerExecutor(): Promise<Executor>;
@@ -262,6 +264,33 @@ export declare class Blockchain {
262
264
  * const now = res.stackReader.readNumber();
263
265
  */
264
266
  runGetMethod(address: Address, method: number | string, stack?: TupleItem[], params?: GetMethodParams): Promise<GetMethodResult>;
267
+ /**
268
+ * Retrieves transactions for the specified address. Transactions are ordered from newest to oldest.
269
+ *
270
+ * If both `lt` and `hash` are provided, the result will include transactions up to and including the one matching them.
271
+ *
272
+ * @param {Address} address - The address to retrieve transactions for.
273
+ * @param opts - Options to fetch transactions
274
+ * @param [opts.lt] - Logical time of the transaction to start from. Must be used together with `hash`.
275
+ * @param [opts.hash] - Hash of the transaction to start from. Must be used together with `lt`.
276
+ * @param [opts.limit] - Maximum number of transactions to return.
277
+ *
278
+ * @returns {Promise<BlockchainTransaction[]>} Promise resolving to an array of transactions involving the given address.
279
+ *
280
+ * @throws {Error} If both `lt` and `hash` are provided but no matching transaction is found.
281
+ *
282
+ * @example
283
+ * const transactions = await blockchain.getTransactions(Address.parse(...), {
284
+ * lt: '1234567890',
285
+ * hash: 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
286
+ * limit: 10
287
+ * });
288
+ */
289
+ getTransactions(address: Address, opts?: {
290
+ limit?: number;
291
+ lt?: string | bigint;
292
+ hash?: string | Buffer;
293
+ }): Promise<BlockchainTransaction[]>;
265
294
  protected increaseLt(): void;
266
295
  /**
267
296
  * Creates new {@link ContractProvider} for contract address.
@@ -69,10 +69,11 @@ class Blockchain {
69
69
  randomSeed;
70
70
  shouldDebug = false;
71
71
  autoDeployLibs;
72
+ transactions = [];
72
73
  defaultQueueManager;
73
74
  collectCoverage = false;
74
- txs = [];
75
- getMethodResults = [];
75
+ coverageTransactions = [];
76
+ coverageGetMethodResults = [];
76
77
  executor;
77
78
  debuggerExecutor;
78
79
  async getDebuggerExecutor() {
@@ -100,6 +101,7 @@ class Blockchain {
100
101
  prevBlocksInfo: (0, deepcopy_1.deepcopy)(this.prevBlocksInfo),
101
102
  randomSeed: (0, deepcopy_1.deepcopy)(this.randomSeed),
102
103
  autoDeployLibs: this.autoDeployLibs,
104
+ transactions: this.transactions.map((tx) => tx),
103
105
  };
104
106
  }
105
107
  /**
@@ -124,6 +126,7 @@ class Blockchain {
124
126
  this.prevBlocksInfo = (0, deepcopy_1.deepcopy)(snapshot.prevBlocksInfo);
125
127
  this.randomSeed = (0, deepcopy_1.deepcopy)(snapshot.randomSeed);
126
128
  this.autoDeployLibs = snapshot.autoDeployLibs;
129
+ this.transactions = snapshot.transactions.map((tx) => tx);
127
130
  }
128
131
  get recordStorage() {
129
132
  return this.shouldRecordStorage;
@@ -186,6 +189,7 @@ class Blockchain {
186
189
  setLibs: (value) => (this.libs = value),
187
190
  getAutoDeployLibs: () => this.autoDeployLibs,
188
191
  registerTxsForCoverage: (txs) => this.registerTxsForCoverage(txs),
192
+ addTransaction: (transaction) => this.transactions.push(transaction),
189
193
  });
190
194
  }
191
195
  /**
@@ -315,6 +319,45 @@ class Blockchain {
315
319
  this.registerGetMethodForCoverage(result);
316
320
  return result;
317
321
  }
322
+ /**
323
+ * Retrieves transactions for the specified address. Transactions are ordered from newest to oldest.
324
+ *
325
+ * If both `lt` and `hash` are provided, the result will include transactions up to and including the one matching them.
326
+ *
327
+ * @param {Address} address - The address to retrieve transactions for.
328
+ * @param opts - Options to fetch transactions
329
+ * @param [opts.lt] - Logical time of the transaction to start from. Must be used together with `hash`.
330
+ * @param [opts.hash] - Hash of the transaction to start from. Must be used together with `lt`.
331
+ * @param [opts.limit] - Maximum number of transactions to return.
332
+ *
333
+ * @returns {Promise<BlockchainTransaction[]>} Promise resolving to an array of transactions involving the given address.
334
+ *
335
+ * @throws {Error} If both `lt` and `hash` are provided but no matching transaction is found.
336
+ *
337
+ * @example
338
+ * const transactions = await blockchain.getTransactions(Address.parse(...), {
339
+ * lt: '1234567890',
340
+ * hash: 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
341
+ * limit: 10
342
+ * });
343
+ */
344
+ async getTransactions(address, opts) {
345
+ const transactionByAddress = this.transactions.reverse().filter((transaction) => {
346
+ const dst = transaction.inMessage?.info?.dest;
347
+ return core_1.Address.isAddress(dst) && dst.equals(address);
348
+ });
349
+ const { lt, hash, limit } = opts ?? {};
350
+ let resultTransactions = transactionByAddress;
351
+ if (lt !== undefined && hash !== undefined) {
352
+ const hashBuffer = typeof hash === 'string' ? Buffer.from(hash, 'hex') : hash;
353
+ const transaction = transactionByAddress.find((tx) => tx.lt === BigInt(lt) && tx.hash() === hashBuffer);
354
+ if (!transaction) {
355
+ throw new Error('Transaction with provided lt and hash not found.');
356
+ }
357
+ resultTransactions = resultTransactions.filter((tx) => tx.lt <= transaction.lt);
358
+ }
359
+ return resultTransactions.slice(0, limit);
360
+ }
318
361
  increaseLt() {
319
362
  this.currentLt += LT_ALIGN;
320
363
  }
@@ -329,6 +372,7 @@ class Blockchain {
329
372
  */
330
373
  provider(address, init) {
331
374
  return new BlockchainContractProvider_1.BlockchainContractProvider({
375
+ getTransactions: (address, opts) => this.getTransactions(address, opts),
332
376
  getContract: (addr) => this.getContract(addr),
333
377
  pushMessage: (msg) => this.defaultQueueManager.pushMessage(msg),
334
378
  runGetMethod: (addr, method, args) => this.runGetMethod(addr, method, args),
@@ -589,12 +633,12 @@ class Blockchain {
589
633
  registerTxsForCoverage(txs) {
590
634
  if (!this.collectCoverage)
591
635
  return;
592
- this.txs.push(txs);
636
+ this.coverageTransactions.push(txs);
593
637
  }
594
638
  registerGetMethodForCoverage(get) {
595
639
  if (!this.collectCoverage)
596
640
  return;
597
- this.getMethodResults.push(get);
641
+ this.coverageGetMethodResults.push(get);
598
642
  }
599
643
  /**
600
644
  * Returns coverage analysis for the specified code cell.
@@ -620,8 +664,8 @@ class Blockchain {
620
664
  if (!this.collectCoverage || this.verbosity.vmLogs !== 'vm_logs_verbose') {
621
665
  return undefined;
622
666
  }
623
- const txs = this.txs.flatMap((tx) => (0, coverage_1.collectTxsCoverage)(code, address, tx));
624
- const gets = this.getMethodResults.flatMap((get) => (0, coverage_1.collectAsmCoverage)(code, get.vmLogs));
667
+ const txs = this.coverageTransactions.flatMap((tx) => (0, coverage_1.collectTxsCoverage)(code, address, tx));
668
+ const gets = this.coverageGetMethodResults.flatMap((get) => (0, coverage_1.collectAsmCoverage)(code, get.vmLogs));
625
669
  const coverages = [...txs, ...gets];
626
670
  return new coverage_1.Coverage((0, coverage_1.mergeCoverages)(...coverages));
627
671
  }
@@ -1,6 +1,7 @@
1
1
  import { Address, Cell, Contract, ContractGetMethodResult, ContractProvider, ContractState, ExtraCurrency, Message, OpenedContract, Sender, SendMode, StateInit, Transaction, TupleItem } from '@ton/core';
2
2
  import { TickOrTock } from '../executor/Executor';
3
3
  import { GetMethodResult, SmartContract } from './SmartContract';
4
+ import { BlockchainTransaction } from './Blockchain';
4
5
  export interface SandboxContractProvider extends ContractProvider {
5
6
  tickTock(which: TickOrTock): Promise<void>;
6
7
  }
@@ -12,6 +13,11 @@ export declare class BlockchainContractProvider implements SandboxContractProvid
12
13
  private readonly address;
13
14
  private readonly init?;
14
15
  constructor(blockchain: {
16
+ getTransactions(address: Address, opts?: {
17
+ limit?: number;
18
+ lt?: string | bigint;
19
+ hash?: string | Buffer;
20
+ }): Promise<BlockchainTransaction[]>;
15
21
  getContract(address: Address): Promise<SmartContract>;
16
22
  pushMessage(message: Message): Promise<void>;
17
23
  runGetMethod(address: Address, method: string, args: TupleItem[]): Promise<GetMethodResult>;
@@ -30,12 +36,16 @@ export declare class BlockchainContractProvider implements SandboxContractProvid
30
36
  */
31
37
  get(name: string, args: TupleItem[]): Promise<ContractGetMethodResult>;
32
38
  /**
33
- * Dummy implementation of getTransactions. Sandbox does not store transactions, so its ContractProvider cannot fetch any.
34
- * Throws error in every call.
39
+ * Retrieves transactions for the specified address using the provided logical time (lt), hash, and optional limit.
40
+ * This implementation fetches transactions directly from the underlying blockchain instance.
35
41
  *
36
- * @throws {Error}
42
+ * @param address - The address to retrieve transactions for.
43
+ * @param lt - Logical time of transaction to start with, must be used with hash.
44
+ * @param hash - Hash of transaction to start with, in buffer or hex encoding, must be sent with lt.
45
+ * @param limit - Optional maximum number of transactions to fetch.
46
+ * @returns An array of transactions.
37
47
  */
38
- getTransactions(_address: Address, _lt: bigint, _hash: Buffer, _limit?: number | undefined): Promise<Transaction[]>;
48
+ getTransactions(address: Address, lt: bigint, hash: Buffer, limit?: number | undefined): Promise<Transaction[]>;
39
49
  /**
40
50
  * Pushes external-in message to message queue.
41
51
  * @param message Message to push
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BlockchainContractProvider = void 0;
4
4
  const core_1 = require("@ton/core");
5
+ const transaction_1 = require("../utils/transaction");
5
6
  function bigintToBuffer(x, n = 32) {
6
7
  const b = Buffer.alloc(n);
7
8
  for (let i = 0; i < n; i++) {
@@ -80,13 +81,18 @@ class BlockchainContractProvider {
80
81
  return ret;
81
82
  }
82
83
  /**
83
- * Dummy implementation of getTransactions. Sandbox does not store transactions, so its ContractProvider cannot fetch any.
84
- * Throws error in every call.
84
+ * Retrieves transactions for the specified address using the provided logical time (lt), hash, and optional limit.
85
+ * This implementation fetches transactions directly from the underlying blockchain instance.
85
86
  *
86
- * @throws {Error}
87
+ * @param address - The address to retrieve transactions for.
88
+ * @param lt - Logical time of transaction to start with, must be used with hash.
89
+ * @param hash - Hash of transaction to start with, in buffer or hex encoding, must be sent with lt.
90
+ * @param limit - Optional maximum number of transactions to fetch.
91
+ * @returns An array of transactions.
87
92
  */
88
- getTransactions(_address, _lt, _hash, _limit) {
89
- throw new Error('`getTransactions` is not implemented in `BlockchainContractProvider`, do not use it in the tests');
93
+ async getTransactions(address, lt, hash, limit) {
94
+ const blockchainTransactions = await this.blockchain.getTransactions(address, { lt, hash, limit });
95
+ return blockchainTransactions.map((blockchainTx) => (0, transaction_1.extractTransaction)(blockchainTx));
90
96
  }
91
97
  /**
92
98
  * Pushes external-in message to message queue.
@@ -15,6 +15,7 @@ export declare class MessageQueueManager {
15
15
  setLibs(libs: Cell | undefined): void;
16
16
  getAutoDeployLibs(): boolean;
17
17
  registerTxsForCoverage(txs: BlockchainTransaction[]): void;
18
+ addTransaction(transaction: BlockchainTransaction): void;
18
19
  });
19
20
  pushMessage(message: Message | Cell): Promise<void>;
20
21
  pushTickTock(on: Address, which: TickOrTock): Promise<void>;
@@ -98,6 +98,7 @@ class MessageQueueManager {
98
98
  mode: message.type === 'message' ? message.mode : undefined,
99
99
  };
100
100
  transaction.parent?.children.push(transaction);
101
+ this.blockchain.addTransaction(transaction);
101
102
  result = transaction;
102
103
  done = true;
103
104
  const sendMsgActions = (transaction.outActions?.filter((action) => action.type === 'sendMsg') ??
@@ -0,0 +1,2 @@
1
+ import { Transaction } from '@ton/core';
2
+ export declare function extractTransaction(transactionLike: Transaction & Record<string, unknown>): Transaction;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractTransaction = extractTransaction;
4
+ function extractTransaction(transactionLike) {
5
+ return {
6
+ address: transactionLike.address,
7
+ lt: transactionLike.lt,
8
+ prevTransactionHash: transactionLike.prevTransactionHash,
9
+ prevTransactionLt: transactionLike.prevTransactionLt,
10
+ now: transactionLike.now,
11
+ outMessagesCount: transactionLike.outMessagesCount,
12
+ oldStatus: transactionLike.oldStatus,
13
+ endStatus: transactionLike.endStatus,
14
+ inMessage: transactionLike.inMessage,
15
+ outMessages: transactionLike.outMessages,
16
+ totalFees: transactionLike.totalFees,
17
+ stateUpdate: transactionLike.stateUpdate,
18
+ description: transactionLike.description,
19
+ raw: transactionLike.raw,
20
+ hash: transactionLike.hash,
21
+ };
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ton/sandbox",
3
- "version": "0.36.0-dev.20250804114000.4e90ae1",
3
+ "version": "0.36.0",
4
4
  "description": "TON transaction emulator",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",