@pizzaton/ton 0.0.0-stage → 0.1.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 (96) hide show
  1. package/CONTRACTS.md +239 -0
  2. package/README.md +481 -2
  3. package/dist/airdrop/index.d.mts +18 -0
  4. package/dist/airdrop/index.mjs +86 -0
  5. package/dist/connect/errors.d.mts +4 -0
  6. package/dist/connect/errors.mjs +21 -0
  7. package/dist/connect/index.d.mts +23 -0
  8. package/dist/connect/index.mjs +34 -0
  9. package/dist/contracts/catalog/index.d.mts +361 -0
  10. package/dist/contracts/catalog/index.mjs +403 -0
  11. package/dist/contracts/client.d.mts +18 -0
  12. package/dist/contracts/client.mjs +31 -0
  13. package/dist/contracts/codecs.d.mts +14 -0
  14. package/dist/contracts/codecs.mjs +61 -0
  15. package/dist/contracts/hipo.d.mts +138 -0
  16. package/dist/contracts/hipo.mjs +198 -0
  17. package/dist/contracts/index.d.mts +23 -0
  18. package/dist/contracts/index.mjs +20 -0
  19. package/dist/contracts/metadata.d.mts +38 -0
  20. package/dist/contracts/metadata.mjs +50 -0
  21. package/dist/contracts/module-client.d.mts +20 -0
  22. package/dist/contracts/module-client.mjs +34 -0
  23. package/dist/contracts/module.d.mts +8 -0
  24. package/dist/contracts/module.mjs +11 -0
  25. package/dist/contracts/wrappers/base.d.mts +18 -0
  26. package/dist/contracts/wrappers/base.mjs +31 -0
  27. package/dist/contracts/wrappers/index.d.mts +12 -0
  28. package/dist/contracts/wrappers/index.mjs +12 -0
  29. package/dist/contracts/wrappers/jvault-protocols.d.mts +206 -0
  30. package/dist/contracts/wrappers/jvault-protocols.mjs +432 -0
  31. package/dist/contracts/wrappers/jvault-support.d.mts +135 -0
  32. package/dist/contracts/wrappers/jvault-support.mjs +269 -0
  33. package/dist/contracts/wrappers/jvault.d.mts +123 -0
  34. package/dist/contracts/wrappers/jvault.mjs +258 -0
  35. package/dist/contracts/wrappers/memeland.d.mts +253 -0
  36. package/dist/contracts/wrappers/memeland.mjs +396 -0
  37. package/dist/contracts/wrappers/multix.d.mts +92 -0
  38. package/dist/contracts/wrappers/multix.mjs +156 -0
  39. package/dist/contracts/wrappers/pizzabox.d.mts +66 -0
  40. package/dist/contracts/wrappers/pizzabox.mjs +105 -0
  41. package/dist/contracts/wrappers/ton-raffles.d.mts +215 -0
  42. package/dist/contracts/wrappers/ton-raffles.mjs +398 -0
  43. package/dist/contracts/wrappers/tonresistor.d.mts +189 -0
  44. package/dist/contracts/wrappers/tonresistor.mjs +276 -0
  45. package/dist/core/address.d.mts +25 -0
  46. package/dist/core/address.mjs +45 -0
  47. package/dist/core/amount.d.mts +8 -0
  48. package/dist/core/amount.mjs +34 -0
  49. package/dist/core/bytes.d.mts +5 -0
  50. package/dist/core/bytes.mjs +18 -0
  51. package/dist/core/constants.d.mts +14 -0
  52. package/dist/core/constants.mjs +15 -0
  53. package/dist/core/errors.d.mts +9 -0
  54. package/dist/core/errors.mjs +16 -0
  55. package/dist/core/http.mjs +39 -0
  56. package/dist/core/index.d.mts +8 -0
  57. package/dist/core/index.mjs +8 -0
  58. package/dist/core/metadata.d.mts +12 -0
  59. package/dist/core/metadata.mjs +35 -0
  60. package/dist/core/network.d.mts +24 -0
  61. package/dist/core/network.mjs +41 -0
  62. package/dist/emulator/index.d.mts +20 -0
  63. package/dist/emulator/index.mjs +24 -0
  64. package/dist/index.d.mts +21 -0
  65. package/dist/index.mjs +19 -0
  66. package/dist/indexer/index.d.mts +90 -0
  67. package/dist/indexer/index.mjs +279 -0
  68. package/dist/jetton/details.d.mts +27 -0
  69. package/dist/jetton/details.mjs +116 -0
  70. package/dist/jetton/index.d.mts +54 -0
  71. package/dist/jetton/index.mjs +92 -0
  72. package/dist/jetton/versions.d.mts +12 -0
  73. package/dist/jetton/versions.mjs +50 -0
  74. package/dist/nft/collection.d.mts +57 -0
  75. package/dist/nft/collection.mjs +148 -0
  76. package/dist/nft/content.d.mts +34 -0
  77. package/dist/nft/content.mjs +142 -0
  78. package/dist/nft/index.d.mts +35 -0
  79. package/dist/nft/index.mjs +51 -0
  80. package/dist/nft/versions.d.mts +11 -0
  81. package/dist/nft/versions.mjs +29 -0
  82. package/dist/provider/index.d.mts +32 -0
  83. package/dist/provider/index.mjs +1 -0
  84. package/dist/react/index.d.mts +13 -0
  85. package/dist/react/index.mjs +48 -0
  86. package/dist/server/index.d.mts +11 -0
  87. package/dist/server/index.mjs +55 -0
  88. package/dist/simulation/index.d.mts +112 -0
  89. package/dist/simulation/index.mjs +310 -0
  90. package/dist/transactions/index.d.mts +33 -0
  91. package/dist/transactions/index.mjs +51 -0
  92. package/dist/vesting/index.d.mts +12 -0
  93. package/dist/vesting/index.mjs +25 -0
  94. package/dist/wallet/index.d.mts +23 -0
  95. package/dist/wallet/index.mjs +71 -0
  96. package/package.json +169 -5
package/README.md CHANGED
@@ -1,3 +1,482 @@
1
- # Temporary Holding Version
1
+ # @pizzaton/ton
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed TON protocol infrastructure for PizzaTon applications. The package provides reusable primitives for TON addresses, amounts, messages, providers, standard Jetton and NFT contracts, wallet versions, TON Connect, emulation, simulation, Merkle airdrops, and vesting calculations.
4
+
5
+ It is intentionally protocol-focused. Application workflows, database persistence, authentication, launchpad rules, product-specific contracts, and UI components belong in the application or domain packages that consume this package.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 20 or newer, or Bun.
10
+ - TypeScript with `strict` enabled in the consuming application.
11
+ - The `@ton/core` peer dependency (npm installs compatible peers automatically).
12
+ - TypeScript consumers also need the `@types/node` peer dependency (version 20 or newer).
13
+ - A TON HTTP endpoint for server-side reads, or a `@ton/sandbox` instance for local execution.
14
+ - A real serialized wallet message when simulating a wallet transaction. The package never invents signatures or silently signs user messages.
15
+
16
+ All token and TON values are represented as `bigint` in the SDK. Convert user input with `parseUnits` or `parseTon`; never use JavaScript floating-point numbers for on-chain amounts.
17
+
18
+ ## Installation and imports
19
+
20
+ ```bash
21
+ npm install @pizzaton/ton
22
+ ```
23
+
24
+ The package is published as ESM with TypeScript declarations. Bun projects can use
25
+ `bun add @pizzaton/ton` instead. The monorepo itself uses Bun workspaces for
26
+ development.
27
+
28
+ Some feature subpaths have optional peers so consumers only install those
29
+ dependencies when they use the feature:
30
+
31
+ ```bash
32
+ npm install @ton/sandbox @ton-api/client react @types/react
33
+ ```
34
+
35
+ Install `@ton/sandbox` for `/emulator` and `/simulation`, `@ton-api/client` for
36
+ `/indexer`, `/jetton/details`, and TonAPI simulation fallback, and `react` plus
37
+ `@types/react` for `/react`.
38
+
39
+ Use the narrowest subpath that contains the feature you need. This keeps browser bundles free of server-only and emulator dependencies.
40
+
41
+ | Subpath | Use |
42
+ | --- | --- |
43
+ | `@pizzaton/ton/core` | Addresses, amounts, bytes, metadata, networks, and typed errors |
44
+ | `@pizzaton/ton/transactions` | Validated transaction intents and TON Connect request conversion |
45
+ | `@pizzaton/ton/provider` | Provider interfaces for application-defined adapters |
46
+ | `@pizzaton/ton/server` | Server-side `@ton/ton` HTTP provider |
47
+ | `@pizzaton/ton/connect` | TON Connect adapter and network validation |
48
+ | `@pizzaton/ton/react` | React hooks over the adapter |
49
+ | `@pizzaton/ton/jetton` | Jetton payloads, data readers, wallet resolution, and implementation profiles |
50
+ | `@pizzaton/ton/jetton/details` | Server-side token detail lookup through TonAPI and TonCenter |
51
+ | `@pizzaton/ton/indexer` | TonAPI-backed account and transaction indexer |
52
+ | `@pizzaton/ton/nft` | Standard NFT collection/item payloads, readers, content, royalties, and profiles |
53
+ | `@pizzaton/ton/wallet` | Versioned wallet contract/address models |
54
+ | `@pizzaton/ton/emulator` | Local `@ton/sandbox` execution adapter |
55
+ | `@pizzaton/ton/simulation` | Native transaction previews and optional TonAPI fallback |
56
+ | `@pizzaton/ton/airdrop` | Canonical Merkle trees and proofs |
57
+ | `@pizzaton/ton/vesting` | Integer-safe vesting calculations |
58
+ | `@pizzaton/ton/contracts` | Generic contract-module registry and provider-backed client |
59
+ | `@pizzaton/ton/contracts/catalog` | Versioned source and readiness descriptors |
60
+ | `@pizzaton/ton/contracts/wrappers` | Native wrappers for supported TON protocols |
61
+ | `@pizzaton/ton/contracts/hipo` | Hipo liquid-staking treasury, parent, and wallet contracts |
62
+ | `@pizzaton/ton` | Shared, platform-neutral APIs and stable application ports |
63
+
64
+ See [CONTRACTS.md](./CONTRACTS.md) for wrapper usage, source pins, and the
65
+ contract integration boundaries. The wrappers ship as part of this TON
66
+ package; there is no separate contracts workspace package.
67
+
68
+ `@pizzaton/ton/server`, `@pizzaton/ton/jetton/details`, `@pizzaton/ton/emulator`, `@pizzaton/ton/simulation`, `@pizzaton/ton/indexer`, and `@pizzaton/ton/wallet` are feature subpaths outside the root entry point. Do not import them from browser components unless the bundler is explicitly configured for those dependencies. React components should use `@pizzaton/ton/connect`, `@pizzaton/ton/transactions`, and `@pizzaton/ton/react`.
69
+
70
+ ## Official TON SDK first
71
+
72
+ The package uses the official TON libraries as its source of truth. `Address`, `Cell`, `Builder`, `ContractProvider`, `Sender`, `StateInit`, message codecs, dictionaries, and tuple types come from `@ton/core`. RPC clients, Jetton master/wallet readers, wallet-version contracts, and fee helpers come from `@ton/ton`. Cryptographic hashing comes from `@ton/crypto`; local execution comes from `@ton/sandbox`; wallet transport comes from `@tonconnect/sdk`.
73
+
74
+ The package re-exports the official Jetton and wallet contract classes through their feature subpaths. Its own code adds only PizzaTon’s stable ports, validation, version/capability metadata, standard payload builders missing from the official surface, NFT codecs, and composition helpers. When an official API exists, prefer it directly or use the package wrapper that delegates to it. Do not copy an official contract implementation into an application package.
75
+
76
+ ## Core conventions
77
+
78
+ ### Addresses
79
+
80
+ `Address` from `@ton/core` is the canonical runtime type. The package accepts either `Address` or a valid friendly/raw string at parsing boundaries. `toTonAddress` returns a branded raw address string for storage and comparison.
81
+
82
+ ```ts
83
+ import { addressesEqual, parseTonAddress, toFriendlyAddress, toTonAddress } from "@pizzaton/ton/core";
84
+
85
+ const address = parseTonAddress("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c");
86
+ const raw = toTonAddress(address);
87
+ const friendly = toFriendlyAddress(raw, { network: "mainnet", bounceable: true });
88
+
89
+ if (addressesEqual(raw, friendly)) {
90
+ console.log("Same account");
91
+ }
92
+ ```
93
+
94
+ Invalid addresses throw `TonPackageError` with code `INVALID_ADDRESS`. Use `assertWorkchain` when a contract must be deployed to a specific workchain.
95
+
96
+ ### Amounts
97
+
98
+ ```ts
99
+ import { formatTon, formatUnits, parseTon, parseUnits } from "@pizzaton/ton/core";
100
+
101
+ const oneTon = parseTon("1.000000000"); // 1_000_000_000n
102
+ const tokenAmount = parseUnits("12.50", 6); // 12_500_000n
103
+
104
+ formatTon(oneTon); // "1"
105
+ formatUnits(tokenAmount, 6); // "12.5"
106
+ ```
107
+
108
+ Parsing rejects negative values, malformed decimals, and precision loss. `formatUnits` never rounds.
109
+
110
+ ### Errors
111
+
112
+ ```ts
113
+ import { TonPackageError } from "@pizzaton/ton/core";
114
+
115
+ try {
116
+ // protocol operation
117
+ } catch (error: unknown) {
118
+ if (error instanceof TonPackageError) {
119
+ console.error(error.code, error.message, error.details);
120
+ }
121
+ throw error;
122
+ }
123
+ ```
124
+
125
+ The public error codes are `INVALID_ADDRESS`, `INVALID_AMOUNT`, `INVALID_NETWORK`, `INVALID_TRANSACTION`, `INVALID_METADATA`, `INVALID_PROOF`, `INVALID_SCHEDULE`, and `PROVIDER_ERROR`.
126
+
127
+ ## Reading the chain
128
+
129
+ Create the server provider once and inject it into domain services or contract wrappers. Keep endpoint credentials on the server.
130
+
131
+ ```ts
132
+ import { Address } from "@ton/core";
133
+ import { createTonHttpProvider } from "@pizzaton/ton/server";
134
+
135
+ const provider = createTonHttpProvider({
136
+ network: "mainnet",
137
+ endpoint: process.env.TON_RPC_ENDPOINT!,
138
+ apiKey: process.env.TON_RPC_API_KEY,
139
+ timeoutMs: 10_000,
140
+ });
141
+
142
+ const account = Address.parse("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c");
143
+ const balance = await provider.getBalance(account);
144
+ const state = await provider.getAccountState(account);
145
+ const transactions = await provider.getTransactions(account, { limit: 20 });
146
+ ```
147
+
148
+ `TonProvider` is the stable application port. Implement every method when the application uses another indexer or RPC service; contract wrappers remain independent of the transport. Keep that adapter in the application’s infrastructure layer and cover it with provider contract tests.
149
+
150
+ ## Generic contract client
151
+
152
+ `TonContractClient` is useful when a contract wrapper already exists or when a product-specific contract is supplied later.
153
+
154
+ ```ts
155
+ import { Address } from "@ton/core";
156
+ import { TonContractClient } from "@pizzaton/ton/contracts";
157
+
158
+ const contract = { address: Address.parse("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c") };
159
+ const contractClient = new TonContractClient(
160
+ contract,
161
+ provider.contractProvider(contract.address),
162
+ );
163
+
164
+ const stack = await contractClient.get("get_wallet_data");
165
+ // Add a Sender with .withSender(sender) before calling .send(...).
166
+ ```
167
+
168
+ For product-specific contracts, implement a versioned `TonContractModule<TConfig, TState, TAction>` with explicit state decoding, address derivation, and message construction. Register modules with `createContractModuleRegistry()` rather than mixing incompatible layouts.
169
+
170
+ ## Jettons
171
+
172
+ The Jetton entry point supports the standard transfer, burn, excess, wallet-address, wallet-data, and master-data operations.
173
+
174
+ ```ts
175
+ import { Address } from "@ton/core";
176
+ import {
177
+ JettonMaster,
178
+ buildJettonTransferBody,
179
+ getJettonImplementationProfile,
180
+ resolveJettonWalletAddress,
181
+ } from "@pizzaton/ton/jetton";
182
+
183
+ const masterAddress = Address.parse("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c");
184
+ const owner = Address.parse("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c");
185
+ const master = JettonMaster.create(masterAddress);
186
+ const masterProvider = provider.contractProvider(master.address);
187
+
188
+ const data = await master.getJettonData(masterProvider);
189
+ const walletAddress = await resolveJettonWalletAddress(provider, master.address, owner);
190
+ const transferBody = buildJettonTransferBody({
191
+ amount: 1_000_000n,
192
+ destination: owner,
193
+ responseDestination: owner,
194
+ forwardAmount: 0n,
195
+ });
196
+
197
+ const profile = getJettonImplementationProfile("jetton-2.0");
198
+ if (!profile.supportsCustomTransferPayload) {
199
+ throw new Error("This deployment does not support custom transfer payloads");
200
+ }
201
+ ```
202
+
203
+ Jetton layouts are not interchangeable. Select the deployment profile explicitly: `jetton-1.0`, `discoverable`, `stablecoin`, `notcoin`, `mintless`, or `jetton-2.0`. The profile is capability metadata; it does not pretend that a custom product contract is a standard Jetton.
204
+
205
+ Use the official `JettonWallet.create(walletAddress)` for the canonical balance reader. Use the package’s `JettonWalletContract.create(walletAddress)` when you also need `getWalletData`, `sendTransfer`, or `sendBurn`. Validate token decimals with `parseJettonAmount` before building a payload.
206
+
207
+ ## NFTs
208
+
209
+ The NFT entry point models standard collection and item layers independently. It supports default collection/item messages, content codecs, TEP-66 royalty parameters, SBT capability metadata, single-item capability metadata, and compressed/cNFT capability metadata.
210
+
211
+ ```ts
212
+ import { Address, beginCell } from "@ton/core";
213
+ import {
214
+ NftItemContract,
215
+ NFT_IMPLEMENTATION_PROFILES,
216
+ buildNftTransferBody,
217
+ nftContentToCell,
218
+ } from "@pizzaton/ton/nft";
219
+
220
+ const owner = Address.parse("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c");
221
+ const item = NftItemContract.create(owner);
222
+ const itemData = await item.getData(provider.contractProvider(item.address));
223
+
224
+ const metadata = nftContentToCell({ type: "offchain", uri: "ipfs://example/1.json" });
225
+ const transferBody = buildNftTransferBody({
226
+ newOwner: owner,
227
+ responseDestination: owner,
228
+ forwardPayload: beginCell().storeUint(1, 32).endCell(),
229
+ });
230
+
231
+ const sbt = NFT_IMPLEMENTATION_PROFILES.sbt;
232
+ if (!sbt.transferable) throw new Error("SBTs cannot be transferred");
233
+ ```
234
+
235
+ Use `NftCollectionContract.createFromAddress` for an existing collection or `createFromConfig` with the verified collection code for deployment. Never reuse a collection or item code across versions without verifying its data layout and opcodes.
236
+
237
+ ## Wallet versions
238
+
239
+ The wallet factory derives real contract addresses and state init for `v1r1`, `v1r2`, `v1r3`, `v2r1`, `v2r2`, `v3r1`, `v3r2`, `v4`, `v5beta`, and `v5r1`.
240
+
241
+ ```ts
242
+ import { createTonWalletContract } from "@pizzaton/ton/wallet";
243
+
244
+ const wallet = createTonWalletContract({
245
+ version: "v5r1",
246
+ publicKey: publicKeyBytes,
247
+ workchain: 0,
248
+ networkGlobalId: -239,
249
+ });
250
+
251
+ const seqno = await wallet.getSeqno(provider.contractProvider(wallet.address));
252
+ ```
253
+
254
+ The default is `v5r1`. Existing accounts must use their actual deployed version. Wallet private keys and signing are deliberately not handled by this package; keep them in an audited server or wallet flow.
255
+
256
+ ## Transactions and TON Connect
257
+
258
+ Build a validated intent in shared/domain code, then convert it at the browser boundary.
259
+
260
+ ```ts
261
+ import { Address, beginCell, toNano } from "@ton/core";
262
+ import { createTransactionIntent } from "@pizzaton/ton/transactions";
263
+
264
+ const intent = createTransactionIntent({
265
+ id: "launchpad-deposit-123",
266
+ validUntil: Math.floor(Date.now() / 1000) + 300,
267
+ messages: [
268
+ {
269
+ to: Address.parse("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"),
270
+ value: toNano("0.1"),
271
+ body: beginCell().storeUint(0x12345678, 32).endCell(),
272
+ bounce: true,
273
+ },
274
+ ],
275
+ });
276
+ ```
277
+
278
+ The validator enforces a non-empty id, a one-hour maximum lifetime, one to four messages, non-negative values, and the TON message amount range. `toTonConnectTransaction(intent, { network: "mainnet" })` returns the SDK’s current raw-message request shape with friendly addresses and base64 BoCs.
279
+
280
+ ```ts
281
+ import { TonConnect } from "@tonconnect/sdk";
282
+ import { createTonConnectAdapter } from "@pizzaton/ton/connect";
283
+
284
+ const tonConnect = new TonConnect({ manifestUrl: "https://example.com/tonconnect-manifest.json" });
285
+ const ton = createTonConnectAdapter(tonConnect, "mainnet");
286
+
287
+ ton.connect({
288
+ universalLink: "https://app.tonkeeper.com/ton-connect",
289
+ bridgeUrl: "https://connect.ton.org/bridge",
290
+ });
291
+ const wallet = ton.getWallet();
292
+ const broadcastedBoc = await ton.sendTransaction(intent);
293
+ ```
294
+
295
+ The adapter checks that a wallet is connected and that its chain id matches the selected network. React applications can use `useTonWallet(adapter)` and `useTonTransaction(adapter)` from `@pizzaton/ton/react`; the transaction hook exposes `send`, `status`, `boc`, `error`, and `reset`.
296
+
297
+ ## Native transaction simulation
298
+
299
+ The preferred simulator is native `@ton/sandbox` execution. It can run against a local blockchain or a pinned live-state snapshot hydrated through `TonClient4`; it does not call TonAPI for the primary path.
300
+
301
+ ### Local sandbox preview
302
+
303
+ ```ts
304
+ import { beginCell, type Message, toNano } from "@ton/core";
305
+ import { createTonEmulator } from "@pizzaton/ton/emulator";
306
+ import { createTonSandboxSimulation } from "@pizzaton/ton/simulation";
307
+
308
+ const emulator = await createTonEmulator();
309
+ const sender = await emulator.treasury("preview-sender");
310
+ const recipient = await emulator.treasury("preview-recipient");
311
+ const simulation = createTonSandboxSimulation(emulator.blockchain);
312
+
313
+ const message: Message = {
314
+ info: {
315
+ type: "internal",
316
+ ihrDisabled: true,
317
+ bounce: true,
318
+ bounced: false,
319
+ src: sender.address,
320
+ dest: recipient.address,
321
+ value: { coins: toNano("0.1") },
322
+ ihrFee: 0n,
323
+ forwardFee: 0n,
324
+ createdLt: 0n,
325
+ createdAt: 0,
326
+ },
327
+ body: beginCell().endCell(),
328
+ };
329
+
330
+ const result = await simulation.simulate({ message });
331
+ console.log(result.success, result.cost.totalFees, result.transactions);
332
+ ```
333
+
334
+ Preview simulations restore the blockchain snapshot by default. Set `persist: true` only for an intentional local state transition. Set `recordStorage: false` when storage cell diffs are not needed and throughput matters.
335
+
336
+ ### Live-state native preview
337
+
338
+ ```ts
339
+ import { createTonClient4SandboxSimulation } from "@pizzaton/ton/simulation";
340
+
341
+ const simulation = await createTonClient4SandboxSimulation({
342
+ endpoint: process.env.TON_CLIENT4_ENDPOINT!,
343
+ blockSeqno: pinnedMasterchainSeqno,
344
+ recordStorage: true,
345
+ });
346
+
347
+ // Use the exact external wallet message BoC produced by the wallet/signing flow.
348
+ const result = await simulation.simulate({
349
+ message: externalMessageCell,
350
+ ignoreSignatureCheck: false,
351
+ });
352
+ ```
353
+
354
+ The simulator exposes a normalized result containing:
355
+
356
+ - transaction hashes, logical time, account id, timestamps, statuses, success, exit codes, and action success;
357
+ - parent/child transaction relationships and input/output message paths;
358
+ - total, storage, gas, forwarding, and action fees as `bigint`;
359
+ - gas usage and VM step counts;
360
+ - blockchain, VM, and debug logs;
361
+ - old/new storage cells when storage recording is enabled.
362
+
363
+ Simulation is an estimate of the pinned snapshot, not a guarantee about a later live-chain execution. Account balances, configuration, libraries, fees, and contract state can change before a wallet signs or broadcasts. For external wallet messages, pass a serialized `Cell`/BoC so the wallet version, body, state init, and signature shape are the ones actually being analyzed.
364
+
365
+ ### Optional TonAPI fallback
366
+
367
+ ```ts
368
+ import {
369
+ createTonApiSimulationFallback,
370
+ createTonClient4SandboxSimulation,
371
+ createTonSimulationWithFallback,
372
+ } from "@pizzaton/ton/simulation";
373
+
374
+ const native = await createTonClient4SandboxSimulation({
375
+ endpoint: process.env.TON_CLIENT4_ENDPOINT!,
376
+ });
377
+ const fallback = createTonApiSimulationFallback({
378
+ apiKey: process.env.TONAPI_KEY,
379
+ });
380
+ const simulator = createTonSimulationWithFallback(native, fallback);
381
+ const result = await simulator.simulate({ message: externalMessageCell });
382
+ ```
383
+
384
+ The fallback is explicit and only runs when the primary adapter cannot produce a result. A legitimate simulated contract failure is returned as a result and is not hidden by fallback behavior.
385
+
386
+ ## Emulator adapter
387
+
388
+ Use `@pizzaton/ton/emulator` for deterministic contract tests and local workflows:
389
+
390
+ ```ts
391
+ import { createTonEmulator } from "@pizzaton/ton/emulator";
392
+
393
+ const emulator = await createTonEmulator();
394
+ const treasury = await emulator.treasury("test-account");
395
+ const snapshot = emulator.snapshot();
396
+
397
+ // Open contracts, call get methods, or send a real Message/Cell here.
398
+ emulator.setTime(1_700_000_000);
399
+ await emulator.loadFrom(snapshot);
400
+ ```
401
+
402
+ Use `sendMessageIter` when a test needs to observe each transaction as it is produced. Use `openContract` and `provider` to inject sandbox dependencies into contract wrappers.
403
+
404
+ ## Airdrops and vesting
405
+
406
+ ### Merkle airdrops
407
+
408
+ ```ts
409
+ import { createMerkleTree, verifyMerkleProof } from "@pizzaton/ton/airdrop";
410
+
411
+ const tree = await createMerkleTree([
412
+ { account: "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", amount: 1_000n },
413
+ ]);
414
+ const proof = await tree.proofFor("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c");
415
+ const valid = await verifyMerkleProof(
416
+ { account: "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", amount: 1_000n },
417
+ proof,
418
+ tree.root,
419
+ );
420
+ ```
421
+
422
+ Entries are canonicalized to raw addresses, sorted deterministically, and checked for duplicate accounts and negative amounts. Persist the root and the exact leaf encoding used by the distribution process.
423
+
424
+ ### Vesting
425
+
426
+ ```ts
427
+ import { claimableAmount, unlockedAmount } from "@pizzaton/ton/vesting";
428
+
429
+ const schedule = {
430
+ totalAmount: 1_000_000n,
431
+ startTime: 1_700_000_000,
432
+ cliffDuration: 86_400,
433
+ duration: 365 * 86_400,
434
+ };
435
+
436
+ const unlocked = unlockedAmount(schedule, 1_750_000_000);
437
+ const claimable = claimableAmount(schedule, 100_000n, 1_750_000_000);
438
+ ```
439
+
440
+ All calculations use integer arithmetic. Validate schedule boundaries before persisting them or constructing a contract message.
441
+
442
+ ## Versioning and extension policy
443
+
444
+ TON standards often have multiple deployed implementations with similar interfaces but incompatible code, data, or capabilities. This package therefore uses explicit profiles and versioned modules rather than guessing from a friendly name.
445
+
446
+ - Jettons: legacy, discoverable, stablecoin, Notcoin, Mintless, and Jetton 2.0 profiles.
447
+ - NFTs: default, compressed/cNFT, SBT, and single-item profiles.
448
+ - Wallets: V1R1 through V5R1, including V5 Beta.
449
+ - Arbitrary or PizzaTon-specific contracts: add a verified versioned module when the authoritative code, ABI/layout, and deployment rules are available.
450
+
451
+ The package does not claim that a generic standard wrapper supports product-specific extensions. Add those extensions as separate modules with their own tests and profile capabilities.
452
+
453
+ Merkle airdrop roots use a package-defined leaf encoding (`raw-address:decimal-amount`), sorted raw addresses, ordered left/right pair hashing, and duplicate-last handling for odd levels. Airdrop contract implementations must use this exact encoding or provide their own proof builder; TON does not define one universal airdrop Merkle format.
454
+
455
+ ## Production checklist
456
+
457
+ 1. Pin the network explicitly (`mainnet` or `testnet`) and validate endpoint configuration with `createNetworkConfig`.
458
+ 2. Keep RPC/API keys and private keys server-side; never expose them in a browser bundle.
459
+ 3. Treat all addresses, metadata, amounts, payloads, and external messages as untrusted input at application boundaries.
460
+ 4. Select the deployed wallet, Jetton, and NFT version explicitly when it is not known from trusted deployment metadata.
461
+ 5. Simulate the exact serialized external message whenever possible, then re-check balances and state immediately before signing.
462
+ 6. Treat simulation fees and paths as estimates; execution is asynchronous and state may change.
463
+ 7. Use `TonPackageError` codes for recoverable validation handling and preserve provider errors for observability.
464
+ 8. Add parity tests for every product-specific contract before enabling writes in production.
465
+
466
+ ## Type safety and testing
467
+
468
+ The package is compiled with `strict`, `noUncheckedIndexedAccess`, `noUnusedLocals`, `noUnusedParameters`, `isolatedModules`, and `exactOptionalPropertyTypes`. Public APIs use explicit generics, discriminated unions, `bigint`, `readonly` collections, and `unknown` at untrusted boundaries instead of `any`.
469
+
470
+ Run the package checks from the repository root:
471
+
472
+ ```bash
473
+ bunx biome check packages/ton
474
+ bunx tsc --noEmit -p packages/ton/tsconfig.json
475
+ bun test packages/ton/src
476
+ ```
477
+
478
+ The tests execute real sandbox transactions and validate standard payload encodings, metadata, airdrop proofs, vesting math, wallet address derivation, provider-facing contracts, and simulation rollback. Network-dependent tests should remain opt-in and use a pinned endpoint/configuration.
479
+
480
+ ## Source of truth
481
+
482
+ Standard behavior must be verified against the deployed contract code and current TON specifications. When a contract differs from a standard implementation, provide its authoritative source or verified artifact and add a dedicated versioned module rather than extending a standard wrapper implicitly.
@@ -0,0 +1,18 @@
1
+ //#region src/airdrop/index.d.ts
2
+ export interface AirdropEntry {
3
+ readonly account: string;
4
+ readonly amount: bigint;
5
+ }
6
+ export interface MerkleProof {
7
+ readonly leaf: string;
8
+ readonly siblings: readonly string[];
9
+ readonly index: number;
10
+ }
11
+ export interface MerkleTree {
12
+ readonly root: string;
13
+ readonly entries: readonly AirdropEntry[];
14
+ proofFor(account: string): Promise<MerkleProof>;
15
+ }
16
+ export declare function createMerkleTree(entries: readonly AirdropEntry[]): Promise<MerkleTree>;
17
+ export declare function verifyMerkleProof(entry: AirdropEntry, proof: MerkleProof, root: string): Promise<boolean>;
18
+ //#endregion
@@ -0,0 +1,86 @@
1
+ import { invariant } from "../core/errors.mjs";
2
+ import { toTonAddress } from "../core/address.mjs";
3
+ import { bytesToHex, hexToBytes } from "../core/bytes.mjs";
4
+ import { sha256 } from "@ton/crypto";
5
+ //#region src/airdrop/index.ts
6
+ async function createMerkleTree(entries) {
7
+ invariant(entries.length > 0, "INVALID_PROOF", "A Merkle tree needs at least one entry");
8
+ const normalizedEntries = entries.map((entry) => ({
9
+ ...entry,
10
+ account: toTonAddress(entry.account)
11
+ })).sort((left, right) => left.account < right.account ? -1 : left.account > right.account ? 1 : 0);
12
+ const seen = /* @__PURE__ */ new Set();
13
+ for (const entry of normalizedEntries) {
14
+ invariant(entry.amount >= 0n, "INVALID_AMOUNT", "Airdrop amounts cannot be negative");
15
+ invariant(!seen.has(entry.account), "INVALID_PROOF", "Airdrop accounts must be unique");
16
+ seen.add(entry.account);
17
+ }
18
+ const normalized = Object.freeze(normalizedEntries.map((entry) => Object.freeze(entry)));
19
+ const leafHashes = await Promise.all(normalized.map(hashEntry));
20
+ const levels = [leafHashes];
21
+ while (true) {
22
+ const current = levels.at(-1);
23
+ invariant(current !== void 0, "INVALID_PROOF", "Merkle levels are malformed");
24
+ if (current.length <= 1) break;
25
+ const next = [];
26
+ for (let index = 0; index < current.length; index += 2) {
27
+ const left = current[index];
28
+ const right = current[index + 1] ?? left;
29
+ invariant(left !== void 0 && right !== void 0, "INVALID_PROOF", "Merkle level is malformed");
30
+ next.push(await hashPair(left, right));
31
+ }
32
+ levels.push(next);
33
+ }
34
+ return Object.freeze({
35
+ root: levels[levels.length - 1]?.[0] ?? "",
36
+ entries: normalized,
37
+ async proofFor(account) {
38
+ const canonicalAccount = toTonAddress(account);
39
+ const index = normalized.findIndex((entry) => entry.account === canonicalAccount);
40
+ invariant(index >= 0, "INVALID_PROOF", "Airdrop account is not in the tree");
41
+ const siblings = [];
42
+ let currentIndex = index;
43
+ for (let level = 0; level < levels.length - 1; level += 1) {
44
+ const nodes = levels[level] ?? [];
45
+ const sibling = nodes[currentIndex % 2 === 0 ? currentIndex + 1 : currentIndex - 1] ?? nodes[currentIndex];
46
+ invariant(sibling !== void 0, "INVALID_PROOF", "Merkle sibling is missing");
47
+ siblings.push(sibling);
48
+ currentIndex = Math.floor(currentIndex / 2);
49
+ }
50
+ return {
51
+ leaf: leafHashes[index] ?? "",
52
+ siblings,
53
+ index
54
+ };
55
+ }
56
+ });
57
+ }
58
+ async function verifyMerkleProof(entry, proof, root) {
59
+ try {
60
+ if (await hashEntry(entry) !== proof.leaf || !Number.isSafeInteger(proof.index) || proof.index < 0) return false;
61
+ let current = proof.leaf;
62
+ let index = proof.index;
63
+ for (const sibling of proof.siblings) {
64
+ current = await hashPair(index % 2 === 0 ? current : sibling, index % 2 === 0 ? sibling : current);
65
+ index = Math.floor(index / 2);
66
+ }
67
+ return current === root;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+ async function hashEntry(entry) {
73
+ invariant(entry.amount >= 0n, "INVALID_AMOUNT", "Airdrop amounts cannot be negative");
74
+ return bytesToHex(await sha256(`${toTonAddress(entry.account)}:${entry.amount.toString()}`));
75
+ }
76
+ async function hashPair(left, right) {
77
+ const leftBytes = hexToBytes(left);
78
+ const rightBytes = hexToBytes(right);
79
+ invariant(leftBytes.length === 32 && rightBytes.length === 32, "INVALID_PROOF", "Merkle node hashes must be 32 bytes");
80
+ const pair = /* @__PURE__ */ new Uint8Array(64);
81
+ pair.set(leftBytes);
82
+ pair.set(rightBytes, leftBytes.length);
83
+ return bytesToHex(await sha256(Buffer.from(pair)));
84
+ }
85
+ //#endregion
86
+ export { createMerkleTree, verifyMerkleProof };
@@ -0,0 +1,4 @@
1
+ //#region src/connect/errors.d.ts
2
+ /** Converts TonConnect/provider errors into safe, user-facing copy. */
3
+ export declare function readableTonConnectError(error: unknown, fallback?: string): string;
4
+ //#endregion
@@ -0,0 +1,21 @@
1
+ //#region src/connect/errors.ts
2
+ const USER_REJECTED_PATTERNS = [
3
+ "user rejects the action",
4
+ "user declined",
5
+ "user rejected",
6
+ "rejected by the user",
7
+ "action rejected"
8
+ ];
9
+ /** Converts TonConnect/provider errors into safe, user-facing copy. */
10
+ function readableTonConnectError(error, fallback = "The wallet could not complete this request.") {
11
+ const message = error instanceof Error ? error.message.trim() : "";
12
+ const normalized = message.toLowerCase();
13
+ if (USER_REJECTED_PATTERNS.some((pattern) => normalized.includes(pattern))) return "Transaction cancelled in your wallet.";
14
+ if (normalized.includes("manifest") || normalized.includes("tonconnect")) return "Wallet connection is temporarily unavailable. Please try again.";
15
+ if (normalized.includes("network") || normalized.includes("timeout") || normalized.includes("timed out")) return "The network took too long to respond. Please try again.";
16
+ if (normalized.includes("insufficient") || normalized.includes("not enough") || normalized.includes("balance")) return "Your wallet balance is not enough for this transaction and its network fee.";
17
+ if (!message || normalized === "unknown error occurred") return fallback;
18
+ return fallback;
19
+ }
20
+ //#endregion
21
+ export { readableTonConnectError };
@@ -0,0 +1,23 @@
1
+ import { TonNetwork } from "../core/network.mjs";
2
+ import { TonConnectTransaction, TransactionIntent } from "../transactions/index.mjs";
3
+ import { readableTonConnectError } from "./errors.mjs";
4
+ import { Account, ITonConnect, WalletConnectionSource } from "@tonconnect/sdk";
5
+ //#region src/connect/index.d.ts
6
+ export declare const TON_CONNECT_CHAIN_IDS: {
7
+ readonly mainnet: "-239";
8
+ readonly testnet: "-3";
9
+ };
10
+ export type TonConnectWallet = Pick<Account, "address" | "chain">;
11
+ export type TonConnectClient = Pick<ITonConnect, "account" | "connect" | "disconnect" | "onStatusChange" | "sendTransaction" | "setConnectionNetwork">;
12
+ export interface TonConnectAdapter {
13
+ readonly network: TonNetwork;
14
+ getWallet(): TonConnectWallet | null;
15
+ connect(source: WalletConnectionSource): string | undefined;
16
+ subscribe(listener: (wallet: TonConnectWallet | null) => void): () => void;
17
+ disconnect(): Promise<void>;
18
+ sendTransaction(intent: TransactionIntent): Promise<string>;
19
+ }
20
+ export declare function createTonConnectAdapter(client: TonConnectClient, network: TonNetwork): TonConnectAdapter;
21
+ export declare function toWalletTransaction(intent: TransactionIntent, network: TonNetwork, now?: number): TonConnectTransaction;
22
+ //#endregion
23
+ export { readableTonConnectError };
@@ -0,0 +1,34 @@
1
+ import { toTonConnectTransaction } from "../transactions/index.mjs";
2
+ import { readableTonConnectError } from "./errors.mjs";
3
+ //#region src/connect/index.ts
4
+ const TON_CONNECT_CHAIN_IDS = {
5
+ mainnet: "-239",
6
+ testnet: "-3"
7
+ };
8
+ function createTonConnectAdapter(client, network) {
9
+ client.setConnectionNetwork(TON_CONNECT_CHAIN_IDS[network]);
10
+ return {
11
+ network,
12
+ getWallet: () => client.account,
13
+ connect: (source) => {
14
+ const result = client.connect(source);
15
+ return typeof result === "string" ? result : void 0;
16
+ },
17
+ subscribe: (listener) => client.onStatusChange((wallet) => listener(wallet?.account ?? null)),
18
+ disconnect: () => client.disconnect(),
19
+ async sendTransaction(intent) {
20
+ const wallet = client.account;
21
+ if (!wallet) throw new Error("A TON wallet must be connected before sending a transaction");
22
+ if (wallet.chain !== TON_CONNECT_CHAIN_IDS[network]) throw new Error(`Connected wallet is not on the ${network} network`);
23
+ return (await client.sendTransaction(toTonConnectTransaction(intent, { network }))).boc;
24
+ }
25
+ };
26
+ }
27
+ function toWalletTransaction(intent, network, now) {
28
+ return toTonConnectTransaction(intent, now === void 0 ? { network } : {
29
+ network,
30
+ now
31
+ });
32
+ }
33
+ //#endregion
34
+ export { TON_CONNECT_CHAIN_IDS, createTonConnectAdapter, readableTonConnectError, toWalletTransaction };