@ton/sandbox 0.38.0 → 0.40.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/blockchain/Blockchain.d.ts +15 -1
  3. package/dist/blockchain/Blockchain.js +45 -8
  4. package/dist/blockchain/MessageQueueManager.d.ts +2 -2
  5. package/dist/blockchain/MessageQueueManager.js +6 -3
  6. package/dist/blockchain/SmartContract.d.ts +3 -2
  7. package/dist/blockchain/SmartContract.js +5 -4
  8. package/dist/executor/emulator-emscripten.debugger.bpatch.gzip.js +1 -1
  9. package/dist/executor/emulator-emscripten.debugger.js +2 -2
  10. package/dist/executor/emulator-emscripten.js +2 -20
  11. package/dist/executor/emulator-emscripten.wasm.js +1 -1
  12. package/dist/jest/uiSetup.d.ts +1 -0
  13. package/dist/jest/uiSetup.js +38 -0
  14. package/dist/ui/UIManager.d.ts +18 -0
  15. package/dist/ui/UIManager.js +31 -0
  16. package/dist/ui/connection/UIConnector.d.ts +3 -0
  17. package/dist/ui/connection/UIConnector.js +2 -0
  18. package/dist/ui/connection/websocket/ManagedWebSocketConnector.d.ts +10 -0
  19. package/dist/ui/connection/websocket/ManagedWebSocketConnector.js +38 -0
  20. package/dist/ui/connection/websocket/OneTimeWebSocketConnector.d.ts +7 -0
  21. package/dist/ui/connection/websocket/OneTimeWebSocketConnector.js +31 -0
  22. package/dist/ui/connection/websocket/constants.d.ts +3 -0
  23. package/dist/ui/connection/websocket/constants.js +6 -0
  24. package/dist/ui/connection/websocket/types.d.ts +4 -0
  25. package/dist/ui/connection/websocket/types.js +2 -0
  26. package/dist/ui/protocol.d.ts +48 -0
  27. package/dist/ui/protocol.js +48 -0
  28. package/dist/utils/environment.d.ts +7 -0
  29. package/dist/utils/environment.js +15 -0
  30. package/dist/utils/noop.d.ts +1 -0
  31. package/dist/utils/noop.js +4 -0
  32. package/dist/utils/require.d.ts +1 -1
  33. package/dist/utils/require.js +5 -8
  34. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ 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
+ ## [0.40.0] - 2025-12-04
9
+
10
+ ### Added
11
+
12
+ - Sandbox UI support
13
+
14
+ ### Changed
15
+
16
+ - Updated emulator WASM binary
17
+ - Updated dependencies
18
+ - `Blockchain` now allows null code and data in state init
19
+
20
+ ### Fixed
21
+
22
+ - Fixed `@ton/test-utils` import
23
+
24
+ ## [0.39.0] - 2025-10-29
25
+
26
+ ### Changed
27
+
28
+ - Updated emulator WASM binary
29
+
8
30
  ## [0.38.0] - 2025-10-15
9
31
 
10
32
  ### Added
@@ -10,6 +10,9 @@ import { Coverage } from '../coverage';
10
10
  import { MessageQueueManager } from './MessageQueueManager';
11
11
  import { AsyncLock } from '../utils/AsyncLock';
12
12
  import { BlockchainSnapshot } from './BlockchainSnapshot';
13
+ import { IUIConnector } from '../ui/connection/UIConnector';
14
+ import { WebSocketConnectionOptions } from '../ui/connection/websocket/types';
15
+ import { IUIManager } from '../ui/UIManager';
13
16
  export type ExternalOutInfo = {
14
17
  type: 'external-out';
15
18
  src: Address;
@@ -59,6 +62,7 @@ export type SandboxContract<F> = {
59
62
  export declare function toSandboxContract<T>(contract: OpenedContract<T>): SandboxContract<T>;
60
63
  export type PendingMessage = (({
61
64
  type: 'message';
65
+ callStack?: string;
62
66
  mode?: number;
63
67
  } & Message) | {
64
68
  type: 'ticktock';
@@ -84,6 +88,10 @@ export type BlockchainConfig = Cell | 'default' | 'slim';
84
88
  export type SendMessageIterParams = MessageParams & {
85
89
  allowParallel?: boolean;
86
90
  };
91
+ export type UIOptions = {
92
+ enabled?: boolean;
93
+ connector?: IUIConnector;
94
+ } & WebSocketConnectionOptions;
87
95
  export declare class Blockchain {
88
96
  protected lock: AsyncLock;
89
97
  protected storage: BlockchainStorage;
@@ -102,6 +110,7 @@ export declare class Blockchain {
102
110
  protected autoDeployLibs: boolean;
103
111
  protected transactions: BlockchainTransaction[];
104
112
  protected defaultQueueManager: MessageQueueManager;
113
+ protected uiManager: IUIManager;
105
114
  protected collectCoverage: boolean;
106
115
  protected readonly coverageTransactions: BlockchainTransaction[][];
107
116
  protected readonly coverageGetMethodResults: GetMethodResult[];
@@ -155,7 +164,9 @@ export declare class Blockchain {
155
164
  storage: BlockchainStorage;
156
165
  meta?: ContractsMeta;
157
166
  autoDeployLibs?: boolean;
167
+ uiOptions?: UIOptions;
158
168
  });
169
+ protected createUiManager(opts?: UIOptions): IUIManager;
159
170
  protected createQueueManager(): MessageQueueManager;
160
171
  /**
161
172
  * @returns Config used in blockchain.
@@ -332,11 +343,12 @@ export declare class Blockchain {
332
343
  * Opens contract. Returns proxy that substitutes the blockchain Provider in methods starting with get and set.
333
344
  *
334
345
  * @param contract Contract to open.
346
+ * @param name Name of the contract.
335
347
  *
336
348
  * @example
337
349
  * const contract = blockchain.openContract(new Contract(address));
338
350
  */
339
- openContract<T extends Contract>(contract: T): SandboxContract<T>;
351
+ openContract<T extends Contract>(contract: T, name?: string): SandboxContract<T>;
340
352
  protected startFetchingContract(address: Address): Promise<SmartContract>;
341
353
  /**
342
354
  * Retrieves {@link SmartContract} from {@link BlockchainStorage}.
@@ -444,6 +456,7 @@ export declare class Blockchain {
444
456
  * @param [opts.storage] Contracts storage used for blockchain. If omitted {@link LocalBlockchainStorage} is used.
445
457
  * @param [opts.meta] Optional contracts metadata provider. If not provided, {@link @ton/test-utils.contractsMeta} will be used to accumulate contracts metadata.
446
458
  * @param [opts.autoDeployLibs] Optional flag. If set to true, libraries will be collected automatically
459
+ * @param [opts.uiOptions] Optional object to configure the UI connector.
447
460
  * @example
448
461
  * const blockchain = await Blockchain.create({ config: 'slim' });
449
462
  *
@@ -462,6 +475,7 @@ export declare class Blockchain {
462
475
  storage?: BlockchainStorage;
463
476
  meta?: ContractsMeta;
464
477
  autoDeployLibs?: boolean;
478
+ uiOptions?: UIOptions;
465
479
  }): Promise<Blockchain>;
466
480
  }
467
481
  export {};
@@ -19,6 +19,10 @@ const coverage_1 = require("../coverage");
19
19
  const MessageQueueManager_1 = require("./MessageQueueManager");
20
20
  const AsyncLock_1 = require("../utils/AsyncLock");
21
21
  const require_1 = require("../utils/require");
22
+ const noop_1 = require("../utils/noop");
23
+ const environment_1 = require("../utils/environment");
24
+ const OneTimeWebSocketConnector_1 = require("../ui/connection/websocket/OneTimeWebSocketConnector");
25
+ const UIManager_1 = require("../ui/UIManager");
22
26
  const CREATE_WALLETS_PREFIX = 'CREATE_WALLETS';
23
27
  function createWalletsSeed(idx) {
24
28
  return `${CREATE_WALLETS_PREFIX}${idx}`;
@@ -72,6 +76,7 @@ class Blockchain {
72
76
  autoDeployLibs;
73
77
  transactions = [];
74
78
  defaultQueueManager;
79
+ uiManager;
75
80
  collectCoverage = false;
76
81
  coverageTransactions = [];
77
82
  coverageGetMethodResults = [];
@@ -180,6 +185,18 @@ class Blockchain {
180
185
  this.meta = opts.meta;
181
186
  this.autoDeployLibs = opts.autoDeployLibs ?? false;
182
187
  this.defaultQueueManager = this.createQueueManager();
188
+ this.uiManager = this.createUiManager(opts.uiOptions);
189
+ }
190
+ createUiManager(opts) {
191
+ if (!opts?.enabled) {
192
+ // noop implementation
193
+ return { publishTransactions: noop_1.noop };
194
+ }
195
+ const connector = opts.connector ?? new OneTimeWebSocketConnector_1.OneTimeWebSocketConnector(opts);
196
+ return new UIManager_1.UIManager(connector, {
197
+ getMeta: (address) => this.meta?.get(address),
198
+ knownContracts: () => this.storage.knownContracts(),
199
+ });
183
200
  }
184
201
  createQueueManager() {
185
202
  return new MessageQueueManager_1.MessageQueueManager(this.lock, {
@@ -189,8 +206,11 @@ class Blockchain {
189
206
  getLibs: () => this.libs,
190
207
  setLibs: (value) => (this.libs = value),
191
208
  getAutoDeployLibs: () => this.autoDeployLibs,
192
- registerTxsForCoverage: (txs) => this.registerTxsForCoverage(txs),
193
- addTransaction: (transaction) => this.transactions.push(transaction),
209
+ onTransactions: (txs) => {
210
+ this.registerTxsForCoverage(txs);
211
+ this.uiManager.publishTransactions(txs);
212
+ },
213
+ onTransaction: (transaction) => this.transactions.push(transaction),
194
214
  });
195
215
  }
196
216
  /**
@@ -456,11 +476,12 @@ class Blockchain {
456
476
  * Opens contract. Returns proxy that substitutes the blockchain Provider in methods starting with get and set.
457
477
  *
458
478
  * @param contract Contract to open.
479
+ * @param name Name of the contract.
459
480
  *
460
481
  * @example
461
482
  * const contract = blockchain.openContract(new Contract(address));
462
483
  */
463
- openContract(contract) {
484
+ openContract(contract, name) {
464
485
  let address;
465
486
  let init = undefined;
466
487
  if (!core_1.Address.isAddress(contract.address)) {
@@ -468,15 +489,19 @@ class Blockchain {
468
489
  }
469
490
  address = contract.address;
470
491
  if (contract.init) {
471
- if (!(contract.init.code instanceof core_1.Cell)) {
492
+ if (contract.init.code !== undefined &&
493
+ contract.init.code !== null &&
494
+ !(contract.init.code instanceof core_1.Cell)) {
472
495
  throw Error('Invalid init.code');
473
496
  }
474
- if (!(contract.init.data instanceof core_1.Cell)) {
497
+ if (contract.init.data !== undefined &&
498
+ contract.init.data !== null &&
499
+ !(contract.init.data instanceof core_1.Cell)) {
475
500
  throw Error('Invalid init.data');
476
501
  }
477
502
  init = contract.init;
478
503
  }
479
- this.meta?.upsert(address, { wrapperName: contract?.constructor?.name, abi: contract.abi });
504
+ this.meta?.upsert(address, { wrapperName: name ?? contract?.constructor?.name, abi: contract.abi });
480
505
  const provider = this.provider(address, init);
481
506
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
482
507
  return new Proxy(contract, {
@@ -596,6 +621,7 @@ class Blockchain {
596
621
  */
597
622
  enableCoverage(enable = true) {
598
623
  this.collectCoverage = enable;
624
+ this.verbosity.print = false;
599
625
  this.verbosity.vmLogs = 'vm_logs_verbose';
600
626
  }
601
627
  /**
@@ -678,6 +704,7 @@ class Blockchain {
678
704
  * @param [opts.storage] Contracts storage used for blockchain. If omitted {@link LocalBlockchainStorage} is used.
679
705
  * @param [opts.meta] Optional contracts metadata provider. If not provided, {@link @ton/test-utils.contractsMeta} will be used to accumulate contracts metadata.
680
706
  * @param [opts.autoDeployLibs] Optional flag. If set to true, libraries will be collected automatically
707
+ * @param [opts.uiOptions] Optional object to configure the UI connector.
681
708
  * @example
682
709
  * const blockchain = await Blockchain.create({ config: 'slim' });
683
710
  *
@@ -691,12 +718,22 @@ class Blockchain {
691
718
  * });
692
719
  */
693
720
  static async create(opts) {
694
- return new Blockchain({
721
+ const uiEnabled = opts?.uiOptions?.enabled ?? (0, environment_1.getOptionalEnv)('SANDBOX_UI_ENABLED', 'boolean');
722
+ const blockchain = new Blockchain({
695
723
  executor: opts?.executor ?? (await Executor_1.Executor.create()),
696
724
  storage: opts?.storage ?? new BlockchainStorage_1.LocalBlockchainStorage(),
697
- meta: opts?.meta ?? (0, require_1.requireOptional)('@ton/test-utils')?.contractsMeta,
725
+ meta: opts?.meta ?? (0, require_1.requireTestUtils)()?.contractsMeta,
698
726
  ...opts,
727
+ uiOptions: {
728
+ enabled: uiEnabled,
729
+ ...opts?.uiOptions,
730
+ },
699
731
  });
732
+ if (uiEnabled) {
733
+ blockchain.verbosity.print = false;
734
+ blockchain.verbosity.vmLogs = 'vm_logs_verbose';
735
+ }
736
+ return blockchain;
700
737
  }
701
738
  }
702
739
  exports.Blockchain = Blockchain;
@@ -14,8 +14,8 @@ export declare class MessageQueueManager {
14
14
  getLibs(): Cell | undefined;
15
15
  setLibs(libs: Cell | undefined): void;
16
16
  getAutoDeployLibs(): boolean;
17
- registerTxsForCoverage(txs: BlockchainTransaction[]): void;
18
- addTransaction(transaction: BlockchainTransaction): void;
17
+ onTransactions(txs: BlockchainTransaction[]): void;
18
+ onTransaction(transaction: BlockchainTransaction): void;
19
19
  });
20
20
  pushMessage(message: Message | Cell): Promise<void>;
21
21
  pushTickTock(on: Address, which: TickOrTock): Promise<void>;
@@ -65,7 +65,7 @@ class MessageQueueManager {
65
65
  }
66
66
  return result;
67
67
  });
68
- this.blockchain.registerTxsForCoverage(results);
68
+ this.blockchain.onTransactions(results);
69
69
  return results;
70
70
  }
71
71
  async processMessage(params) {
@@ -73,16 +73,18 @@ class MessageQueueManager {
73
73
  let done = this.messageQueue.length == 0;
74
74
  while (!done) {
75
75
  const message = this.messageQueue.shift();
76
+ let callStack;
76
77
  let tx;
77
78
  let smartContract;
78
79
  if (message.type === 'message') {
80
+ callStack = message.callStack;
79
81
  if (message.info.type === 'external-out') {
80
82
  done = this.messageQueue.length == 0;
81
83
  continue;
82
84
  }
83
85
  this.blockchain.increaseLt();
84
86
  smartContract = await this.blockchain.getContract(message.info.dest);
85
- tx = await smartContract.receiveMessage(message, params);
87
+ tx = await smartContract.receiveMessage(message, params, callStack);
86
88
  }
87
89
  else {
88
90
  this.blockchain.increaseLt();
@@ -98,7 +100,7 @@ class MessageQueueManager {
98
100
  mode: message.type === 'message' ? message.mode : undefined,
99
101
  };
100
102
  transaction.parent?.children.push(transaction);
101
- this.blockchain.addTransaction(transaction);
103
+ this.blockchain.onTransaction(transaction);
102
104
  result = transaction;
103
105
  done = true;
104
106
  const sendMsgActions = (transaction.outActions?.filter((action) => action.type === 'sendMsg') ??
@@ -122,6 +124,7 @@ class MessageQueueManager {
122
124
  type: 'message',
123
125
  parentTransaction: transaction,
124
126
  mode: sendMsgActions[index]?.mode,
127
+ callStack,
125
128
  ...message,
126
129
  });
127
130
  if (message.info.type === 'internal') {
@@ -30,6 +30,7 @@ export type SmartContractTransaction = Transaction & {
30
30
  blockchainLogs: string;
31
31
  vmLogs: string;
32
32
  debugLogs: string;
33
+ callStack?: string;
33
34
  oldStorage?: Cell;
34
35
  newStorage?: Cell;
35
36
  outActions?: OutActionExtended[];
@@ -109,9 +110,9 @@ export declare class SmartContract {
109
110
  }): SmartContract;
110
111
  static empty(blockchain: Blockchain, address: Address): SmartContract;
111
112
  protected createCommonArgs(params?: MessageParams): RunCommonArgs;
112
- receiveMessage(message: Message, params?: MessageParams): Promise<SmartContractTransaction>;
113
+ receiveMessage(message: Message, params?: MessageParams, callStack?: string): Promise<SmartContractTransaction>;
113
114
  runTickTock(which: TickOrTock, params?: MessageParams): Promise<SmartContractTransaction>;
114
- protected runCommon(run: () => Promise<EmulationResult>): Promise<SmartContractTransaction>;
115
+ protected runCommon(run: () => Promise<EmulationResult>, callStack?: string): Promise<SmartContractTransaction>;
115
116
  get(method: string | number, stack?: TupleItem[], params?: GetMethodParams): Promise<GetMethodResult>;
116
117
  get verbosity(): LogsVerbosity;
117
118
  set verbosity(value: LogsVerbosity);
@@ -294,7 +294,7 @@ class SmartContract {
294
294
  prevBlocksInfo: this.blockchain.prevBlocks,
295
295
  };
296
296
  }
297
- async receiveMessage(message, params) {
297
+ async receiveMessage(message, params, callStack) {
298
298
  const args = {
299
299
  ...this.createCommonArgs(params),
300
300
  message: (0, core_1.beginCell)().store((0, core_1.storeMessage)(message)).endCell(),
@@ -304,14 +304,14 @@ class SmartContract {
304
304
  const { uninitialized, debugInfo } = debugContext.getDebugInfo(this.account);
305
305
  if (debugInfo !== undefined) {
306
306
  const executor = await this.blockchain.getDebuggerExecutor();
307
- return await this.runCommon(() => debugContext.debugTransaction(executor, args, debugInfo));
307
+ return await this.runCommon(() => debugContext.debugTransaction(executor, args, debugInfo), callStack);
308
308
  }
309
309
  else if (uninitialized) {
310
310
  // eslint-disable-next-line no-console
311
311
  console.log('Debugging uninitialized accounts is unsupported in debugger beta');
312
312
  }
313
313
  }
314
- return await this.runCommon(() => this.blockchain.executor.runTransaction(args));
314
+ return await this.runCommon(() => this.blockchain.executor.runTransaction(args), callStack);
315
315
  }
316
316
  async runTickTock(which, params) {
317
317
  return await this.runCommon(() => this.blockchain.executor.runTickTock({
@@ -319,7 +319,7 @@ class SmartContract {
319
319
  which,
320
320
  }));
321
321
  }
322
- async runCommon(run) {
322
+ async runCommon(run, callStack) {
323
323
  let oldStorage = undefined;
324
324
  if (this.blockchain.recordStorage && this.account.account?.storage.state.type === 'active') {
325
325
  oldStorage = this.account.account?.storage.state.state.data ?? undefined;
@@ -357,6 +357,7 @@ class SmartContract {
357
357
  blockchainLogs: res.logs,
358
358
  vmLogs: res.result.vmLog,
359
359
  debugLogs: res.debugLogs,
360
+ callStack,
360
361
  oldStorage,
361
362
  newStorage,
362
363
  outActions,