
Ton Tact
- 4 installs
- 4 repo stars
- Updated February 25, 2026
- hairyf/blockchain-master
Write TON smart contracts in Tact - type system, contracts and receivers, send/receive messages, cells, stdlib, and security practices.
About
Tact is a statically typed smart-contract language for TON using message-based communication, structs/messages, and traits. A developer uses it to write or review TON contracts.
- Types, contracts, receivers, and message send/receive
- Cells serialization, standard libraries, and security practices
Ton Tact by the numbers
- 4 all-time installs (skills.sh)
- Ranked #347 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hairyf/blockchain-master --skill ton-tactAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 4 |
| Last updated | February 25, 2026 |
| Repository | hairyf/blockchain-master ↗ |
What it does
Write TON smart contracts in Tact - type system, contracts and receivers, send/receive messages, cells, stdlib, and security practices.
Files
Skill is based on Tact (TON) v1.6.13, generated 2026-02-25.
Tact is a statically typed smart contract language for the TON blockchain. Contracts use message-based communication (receive/send), structs and messages for data, and traits for reuse. This skill focuses on agent-oriented usage: type system, contracts and receivers, sending/receiving messages, cells and serialization, standard libraries, and security practices.
Core references
| Topic | Description | Reference |
|---|---|---|
| Type system | Primitives, optionals, maps, structs, messages, contracts, traits | core-types |
| Contracts and traits | init, parameters, receivers, getters, interfaces, BaseTrait | core-contracts |
| Structs and messages | Definition, instantiation, toCell/fromCell, TL-B layout | core-structs-messages |
| Receiving messages | receive(), text/binary/slice receivers, order, external/bounced | core-receive |
| Sending messages | send(), SendParameters, reply, forward, notify, cashback, deploy, emit | core-send |
| Cells, Builders, Slices | Cell/Builder/Slice, beginCell, store/load, Struct/Message helpers | core-cells |
| Message mode | Base modes and optional flags (SendRemainingValue, SendIgnoreErrors, etc.) | core-message-mode |
| Gas and fees | getStorageFee, getComputeFee, getForwardFee, setGasLimit, acceptMessage | core-gas |
| Context and state | sender, context, myAddress, myBalance, now, inMsg, setData, commit, getConfigParam, nativeReserve | core-context-state |
| Addresses | newAddress, contractAddress, forceBasechain, parseStdAddress, BasechainAddress | core-addresses |
| Cryptography | checkSignature, sha256, keccak256, SignedBundle | core-crypto |
| Strings | StringBuilder, beginString, beginComment, String extensions, Int toFloatString | core-strings |
| Math | min, max, abs, sign, sqrt, log, log2, pow, pow2, divc, muldivc | core-math |
| Exit codes | TVM/Tact exit codes, compute/action phases, developer range 256–65535 | core-exit-codes |
| Random | random, randomInt, getSeed, setSeed, nativeRandomize, nativeRandomizeLt | core-random |
| Debug and throw | require, dump, throw, throwIf, throwUnless | core-debug |
| Compile-time | address(), cell(), slice(), rawSlice(), ascii(), crc32(), ton() | core-comptime |
| Message lifecycle | Receive phase, compute phase, action phase (no revert) | core-lifecycle |
Features
| Topic | Description | Reference |
|---|---|---|
| Optionals | T?, null, !!, constraints (no optional keys, no nested optionals) | features-optionals |
| Maps | map<K,V>, emptyMap(), get/set, allowed types, serialization | features-maps |
| initOf and deploy | initOf, contractAddress, StateInit, send/deploy deployment | features-initof-deploy |
| Standard libraries | @stdlib/config, content, deploy, dns, ownable, stoppable | features-stdlib |
| Configuration | tact.config.json — projects, options (debug, external, safety, mode) | features-config |
| External messages | external(), acceptMessage, no sender/context, config external | features-external |
| Constants | const, virtual/abstract/override in traits | features-constants |
Best practices
| Topic | Description | Reference |
|---|---|---|
| Security | Sensitive data, signed ints, exit codes, random, auth, replay, bounce, excess gas | best-practices-security |
| Gas | Contract params, binary receivers, message/cashback/deploy, sender(), throwUnless, SignedBundle | best-practices-gas |
Advanced
| Topic | Description | Reference |
|---|---|---|
| Bounced messages | bounced<T>, 224-bit limit, fallback Slice receiver, unrecognized bounces | advanced-bounced |
Generation Info
- Source:
sources/ton-tact - Git SHA:
a4a0f6732a10a3ed280f9a4eae080c0ac541905d - Generated: 2026-02-25
Bounced messages
When a message is sent with `bounce: true` and the recipient fails to process it, the message bounces back to the sender. Use `bounced` receivers to handle these and revert or adjust state (e.g. restore balance).
Bounced receiver and payload limit
- Bounced message body: at most 256 bits total; first 32 bits are opcode, so at most 224 bits of payload.
- Use `bounced<M>` so Tact enforces that only fields fitting within the limit are accessible:
bounced(msg: bounced<TokenBurnNotification>) {
self.balance = self.balance + msg.amount; // only if amount fits in 224 bits
}- Put important fields first in the message; fields that don’t fit (or fit only partially) are not available in
bounced<M>. - `bounced<M>` inner type cannot be optional (
bounced<M?>is invalid).
Fallback bounced receiver
For messages that exceed the safe layout, use a fallback that receives the raw body as `Slice`:
bounced(rawMsg: Slice) {
let opcode = rawMsg.loadUint(32);
// handle truncated data with care
}Unrecognized bounces
If there is no matching bounced receiver (and no fallback), unrecognized bounced messages are ignored: they do not cause a non-zero exit code. Value is still credited and fees paid. This matches common TON patterns.
Key points
- Design messages so critical bounce-handling fields fit in the first 224 bits if you use
bounced<M>. - Prefer
bounced<M>when the layout fits; usebounced(rawMsg: Slice)only when you need to read truncated data. - Bouncing incurs forward fees; messages sent with
value: 0andSendPayFwdFeesSeparatelycannot bounce (no funds to return).
<!-- Source references:
- https://docs.tact-lang.org/book/bounced
- sources/ton-tact/docs/src/content/docs/book/bounced.mdx
-->
Gas best practices
Practical patterns to reduce gas: contract design, receivers, sending, and assertions.
Contract design
- Prefer contract parameters for initial state instead of
init()and extra fields; avoids lazy-init bit and storage write optimizations. - Do not deploy with deprecated Deployable trait; use a simple empty-body receiver and deploy with it.
- Use BasechainAddress and
hasSameBasechainAddress()for basechain sender checks instead ofcontractAddress(init) == sender()when both are basechain. - Inline rarely-called functions to save call overhead; balance with code size. Consider
experimental.inlinein tact.config.json for full inlining. - Avoid internal contract functions when they don't touch state; move to global (module-level) functions to reduce stack push/pop.
Receiving
- Prefer binary receivers and message structs with opcodes over text receivers; text uses body hash (500+ gas).
- Prefer inMsg() over
msg.toSlice()for raw body access (Tact 1.6.7+). - Use sender() instead of
context().senderwhen only the sender is needed. - Use throwUnless(code, condition) with constants (256–2048) instead of require(condition, "msg") for production.
- For external messages, use SignedBundle as first field and SignedBundle.verifySignature(publicKey) for efficient verification.
Sending
- Prefer message() and cashback() over
self.forward(),self.reply(),self.notify()(BaseTrait internals are costly). - Use deploy() for on-chain deployments and message() for non-deployment messages instead of generic send().
- Pay attention to 500+ gas badges in docs; prefer cheaper alternatives when possible.
Other
- Prefer arithmetic over branching (e.g.
1 + sign(x)vs ternary). - Prefer log2 over
log(_, 2)and pow2 overpow(2, _). - Prefer off-chain string manipulation; minimize on-chain string work.
- For well-tested contracts only: safety.nullChecks: false in config to reduce gas of
!!(weaker safety). - Consider asm functions for critical paths when stack layout and instruction choice matter.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/book/gas-best-practices.mdx
-->
Security best practices
Summary of anti-patterns and practices for safer Tact contracts.
Sensitive data
- Do not send or store private keys or other secrets on-chain; computation is transparent and can be replayed to extract values.
Signed integers
- Prefer unsigned serialization (
Int as uint32, etc.). Signed integers can introduce bugs (e.g. negative votes). Use signed only when necessary.
Exit codes
- Exit codes 0 and 1 mean successful execution. Do not use
throw(0)orthrow(1); use `require(condition, "message")` for validation so failures are distinguishable.
Random numbers
- `random()` is predictable (depends on logical time); do not use it alone for critical outcomes (e.g. rewards). Prefer commit–reveal schemes or off-chain randomness when security matters. Do not rely on random in
externalreceivers.
Message parsing
- Parse human-friendly formats off-chain. Send only compact binary messages (structs/messages) and parse on-chain from Slice/Cell to save gas and avoid abuse.
Gas
- Gas exhaustion cannot be caught; precompute gas with tests and require minimum value when needed:
require(context().value > getComputeFee(self.voteGasUsage, false), "Not enough gas!").
Authentication
- Always verify sender when logic is trust-based. Use `@stdlib/ownable` and
requireOwner(), or verify state init / Jetton/NFT sender per cookbook and NFT validation.
Replay protection
- For external messages, include and verify a unique identifier (e.g. seqno); update it after successful handling. Without it, signed messages can be replayed.
Bounced messages
- Send with `bounce: true` (default) so failed processing returns value. Handle bounces in `bounced(msg: bounced<M>)` to revert or adjust state (e.g. restore balance in a Jetton wallet).
Excess gas
- Return excess value to the sender (e.g. via
cashback(sender()), or message with opcode0xd53276dbfor Jetton-style excesses, orself.notify/self.forward). Otherwise funds accumulate in the contract.
Cross-contract data
- Contracts cannot call getters of other contracts on-chain (different shards). All cross-contract interaction is asynchronous via messages; request data by sending a message and handling the reply.
<!-- Source references:
- https://docs.tact-lang.org/book/security-best-practices
- sources/ton-tact/docs/src/content/docs/book/security-best-practices.mdx
- sources/ton-tact/docs/src/content/docs/zh-cn/book/security-best-practices.mdx
-->
Addresses
Creating and validating contract addresses, parsing address slices, and basechain-specific helpers.
Usage
Build address from chain + hash (account ID):
let addr: Address = newAddress(chain, hash); // chain: 0 basechain, -1 masterchainAddress from StateInit (basechain):
let s: StateInit = initOf SomeContract();
let addr: Address = contractAddress(s); // workchain 0
let addr2: Address = contractAddressExt(chain, s.code, s.data);Account ID (SHA-256 of code+data in standard cell representation):
let accountId: Int = contractHash(code, data);Enforce basechain / workchain:
forceBasechain(addr); // throws 138 if not basechain (Tact 1.6.3+)
forceWorkchain(addr, workchain, errorCode); // Tact 1.6.4+Parse slice to StdAddress (workchain + address Int):
let parsed: StdAddress = parseStdAddress(slice);
// parsed.workchain, parsed.address
let addr2: Address = newAddress(parsed.workchain, parsed.address);BasechainAddress (Tact 1.6): emptyBasechainAddress(), newBasechainAddress(hash), contractBasechainAddress(StateInit). Use for basechain-only checks; StateInit.hasSameBasechainAddress(addr) is cheaper than contractAddress(init) == addr.
Extension: address.asSlice(), address.toString() (500+ gas).
Key points
newAddressonly allows chain 0 or -1 at compile-time for uncommon chains.parseVarAddressis deprecated (Tact 1.6.8); useparseStdAddressfor standard addresses.- Use BasechainAddress and
hasSameBasechainAddressfor gas-efficient sender checks on basechain.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-addresses.mdx
-->
Cells, Builders, and Slices
Cell — Immutable 1023-bit data + up to 4 references; standard data unit on TON. Builder — Mutable buffer to build a cell. Slice — Mutable view over a cell for reading.
Common functions
- `beginCell(): Builder` — New empty builder.
- `emptyCell(): Cell` — Empty cell (same as
beginCell().endCell()). - `emptySlice(): Slice` — Empty slice (same as
emptyCell().asSlice()). - `c.beginParse()` / `c.asSlice()` — Get Slice from Cell.
- `c.hash(): Int` — SHA-256 hash of cell’s standard representation.
Builder: .storeUint(n, bits), .storeInt(n, bits), .storeRef(cell), .storeAddress(addr), etc., then .endCell(). Slice: .loadUint(bits), .loadInt(bits), .loadRef(), .loadAddress(), .preloadUint(bits), etc.
Struct and Message helpers
Prefer these over manual Builder/Slice when the type is a struct or message:
- `MyStruct.toCell()` / `MyMessage.toCell()` — Serialize to Cell.
- `MyStruct.fromCell(c: Cell)` / `MyStruct.fromSlice(s: Slice)` — Deserialize; same for Message. Throws if layout doesn’t match; use try/catch if needed.
Key points
- Document TL-B layout when building/parsing manually; structs/messages act as living schemas.
- Bounced message bodies are limited (256 bits total, 224 bits usable after opcode); design message layout so critical fields fit.
- Use
.toCell()/.fromCell()for structs/messages to avoid layout mistakes.
<!-- Source references:
- https://docs.tact-lang.org/ref/core-cells
- sources/ton-tact/docs/src/content/docs/zh-cn/ref/core-cells.mdx
- sources/ton-tact/docs/src/content/docs/book/cells
-->
Compile-time functions
Functions evaluated at build time only; arguments must be constant. Use for embedding addresses, BoC, and literals into the contract.
Usage
Address (string to Address):
let addr: Address = address("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N");Cell / Slice from base64 BoC:
let c: Cell = cell("te6cckEBAQEAYgAA...");
let s: Slice = slice("te6cckEBAQEADgAAG..."); // Tact 1.5+Raw slice from hex (optional bit-padding with trailing `_`):
let s: Slice = rawSlice("4a"); // 8 bits
let padded: Slice = rawSlice("4a_"); // trailing zeros + 1 removed; up to 1023 bits (Tact 1.5+)ASCII string to Int (up to 32 bytes, for opcodes/actions):
message(ascii("NstK")) Action { } // opcode from string
if (msg.action == ascii("start")) { }CRC-32 checksum (compile-time):
let checksum: Int = crc32("000DEADBEEF000"); // Tact 1.5+Toncoin string to nanoToncoin:
let one: Int = ton("1");
let pointOne: Int = ton("0.1");
let nano: Int = ton("0.000000001");Key points
- All of these are compile-time only; non-constant arguments are not allowed.
rawSlice("hex_")pads: trailing zeros and last 1 bit are removed.asciiassumes UTF-8; result fits in 256 bits (up to 32 bytes).- Other APIs (e.g. sha256 with constant string) may also resolve at compile-time when possible.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-comptime.mdx
-->
Context and state
Incoming message context, contract identity, balance, time, and transaction/blockchain state.
Usage
Incoming message:
let who: Address = sender(); // prefer over context().sender (cheaper)
let ctx: Context = context(); // bounceable, sender, value, raw (Slice)
let body: Slice = inMsg(); // raw body (Tact 1.6.7+); prefer over msg.toSlice()Time:
let unix: Int = now();
let lt: Int = curLt(); // logical time of current tx (Tact 1.6+)
let blockLt: Int = blockLt(); // block start logical time (Tact 1.6+)Contract state:
let me: Address = myAddress();
let code: Cell = myCode(); // from c7 (Tact 1.6+)
let balance: Int = myBalance(); // at start of compute phase (unchanged by sends)
let debt: Int = myStorageDue();
let gas: Int = gasConsumed();Reserve (RAWRESERVE):
nativeReserve(amount, mode); // mode: ReserveExact | ReserveAllExcept | ReserveAtMost
// optional flags: ReserveAddOriginalBalance, ReserveInvertSign, ReserveBounceIfActionFailReplace state / commit (advanced):
setData(data: Cell); // DANGEROUS: replaces c4; use with throw(0) to avoid auto-save (Tact 1.7+)
commit(); // commit c4/c5 so later throw doesn't revertConfig:
let cell: Cell? = getConfigParam(id); // e.g. 0 = config address, 18 = storage fee configContext extension: context().readForwardFee() — original forward fee of incoming message.
Key points
- Use
sender()instead ofcontext().senderwhen only the sender is needed (saves gas). myBalance()does not reflect sends made in the same execution.- getters have no sender; behavior of
sender()/context there is undefined. - Reserve modes: 0 = Exact, 1 = AllExcept, 2 = AtMost; combine with optional flags via bitwise OR.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-contextstate.mdx
-->
Contracts and traits
Contracts are the main entry point for TON smart contracts. They hold persistent state, init, receivers, getters, and internal functions. Traits provide reusable behavior (like abstract classes) and cannot initialize persistent state themselves.
Structure
- `self` — Built-in identifier for contract/trait fields and methods.
- Traits —
contract MyContract with Ownable, Stoppable { ... }. Multiple traits allowed; contract must implementinit()or use contract parameters if traits declare state. - Supported interfaces —
@interface("org.ton.ownable")before contract/trait; enables off-chain introspection viasupported_interfacesgetter (requiresinterfacesGetter: truein config). - Contract parameters (Tact 1.6+) — Initialize state at deploy time without
init():
contract Counter(val: Int as uint32, owner: Address) {
receive("inc") { /* ... */ }
}- Persistent state — Declare as contract fields; initialize in
init()or via contract parameters. - `init()` — Constructor; runs once after deployment. Not allowed in traits.
- Getter functions —
get fun name(): Type { return self.name; }; callable off-chain. - Receiver functions —
receive(),receive("text"),receive(msg: MyMessage),receive(s: String),receive(s: Slice); see Receive messages. Order: empty → text → string catch-all → binary message → slice catch-all. - Internal functions —
fun name() { ... }; only callable from within the contract/trait.
Virtual and abstract
In traits, functions and constants can be virtual or abstract and overridden in contracts. Traits can use with BaseTrait (required for traits since 1.6.0) to get self.reply, self.forward, etc.
Key points
- Use contract parameters when you don’t need one-time on-chain init logic; it’s cheaper than
init(). - Standard traits (e.g.
@stdlib/ownable) declare interfaces; enableinterfacesGetterso explorers can show supported interfaces. @interfaceis a promise only; it does not enforce implementation.
<!-- Source references:
- https://docs.tact-lang.org/book/contracts
- sources/ton-tact/docs/src/content/docs/book/contracts.mdx
-->
Cryptography
Ed25519 signature verification, SHA-256 and Keccak-256 hashes, and SignedBundle for signed message data.
Usage
Ed25519 signature check (hash + signature + publicKey):
let valid: Bool = checkSignature(hash, signature, publicKey);Ed25519 over data slice (hashes data internally):
let valid: Bool = checkDataSignature(data, signature, publicKey);Hashes (data bits must be divisible by 8):
let h: Int = sha256(sliceOrString); // 256-bit unsigned Int
let k: Int = keccak256(slice); // Ethereum-compatible Keccak-256 (Tact 1.6.6+)SignedBundle (Tact 1.6.6+): struct with signature: Slice as bytes64 and signedData: Slice as remaining. Use in message as first field, then verify:
message MessageWithSignedData {
bundle: SignedBundle;
walletId: Int as int32;
seqno: Int as uint32;
}
// In receiver:
throwUnless(35, msg.bundle.verifySignature(self.publicKey));Key points
- First 10 calls to
checkSignature/checkDataSignatureare cheap; 11th and onward cost 4000+ gas. sha256andkeccak256are 500+ gas; prefer compile-time resolution for constant strings when possible.- Cell/Builder/Slice crypto extension methods (e.g. hash) are documented in core-cells.
checkDataSignaturethrows exit code 9 if data bit length not divisible by 8.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-crypto.mdx
-->
Debug and control flow
Assertions and unconditional throw for control flow and debugging.
Usage
Require (generates exit code from message hash; > 2048):
require(condition, "Error message");Throw (exit code 0–65535):
throw(code);
throwIf(code, condition); // throw if condition is true (Tact 1.6+)
throwUnless(code, condition); // throw if condition is false (Tact 1.6+)Debug (only when config debug = true; 500+ gas):
dump(expr); // prints location and value to debug console
dumpStack(); // prints stack depth and up to 255 valuesFor production checks with fixed exit codes (e.g. 256–2048), prefer throwUnless(code, condition) over require(condition, "msg") to save gas and use stable codes. Declare codes as constants.
Key points
throwstops execution; control goes to enclosing try/catch or terminates transaction.- Code outside 0–65535 causes exit code 5.
nativeThrow/nativeThrowIf/nativeThrowUnlessare deprecated aliases (Tact 1.6).- Use
throw(0)aftersetData()to avoid Tact's implicit state save overwriting manual data.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-debug.mdx
-->
Exit codes
32-bit signed integer indicating success or failure of compute/action phase. 0 and 1 = success (compute); 0 = success (action). Other values = exception.
Ranges
- 0–127: TVM reserved (compute/action).
- 128–255: Tact compiler (compute phase).
- 256–65535: Developer-defined (use constants; keep 256–2048 for throwUnless).
- Throwing outside 0–65535 causes exit code 5. Out-of-gas is reported as -14 (bitwise NOT of 13).
Key compute-phase codes
| Code | Description |
|---|---|
| 0, 1 | Success |
| 2 | Stack underflow |
| 3 | Stack overflow |
| 4 | Integer overflow / div by zero |
| 5 | Integer out of expected range |
| 8 | Cell overflow |
| 9 | Cell underflow |
| 13 / -14 | Out of gas |
| 128 | Null reference (!! on null) |
| 129 | Invalid serialization prefix (opcode mismatch) |
| 130 | Invalid incoming message (no receiver for opcode) |
| 132 | Access denied (e.g. Ownable) |
| 133 | Contract stopped (Stoppable) |
| 134 | Invalid argument |
| 136 | Invalid standard address |
| 138 | Not a basechain address |
Action-phase codes (examples)
32 invalid action list, 33 too many actions (max 255), 34 invalid/unsupported action, 35/36 invalid source/dest address, 37 not enough Toncoin, 50 account state limits exceeded.
Usage
try {
risky();
} catch (exitCode) {
// exitCode in 0..65535
}
throwUnless(MY_CODE, condition); // use constants for MY_CODE in 256..2048require(cond, "msg") generates exit codes > 2048 from message hash; see compile report. In Blueprint tests use exitCode and actionResultCode in toHaveTransaction().
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/book/exit-codes.mdx
-->
Gas and fees
Storage, compute, and forward fee helpers, plus gas limit and accept-message for external messages.
Usage
Storage fee (config param 18):
let fee: Int = getStorageFee(cells, bits, seconds, isMasterchain);Compute fee (config params 20/21; flat_gas_limit / flat_gas_price apply):
let fee: Int = getComputeFee(gasUsed, isMasterchain);
let simpleFee: Int = getSimpleComputeFee(gasUsed, isMasterchain); // no flat minimumForward fee (config params 24/25; lump_price applies):
let fee: Int = getForwardFee(cells, bits, isMasterchain);
let simpleFee: Int = getSimpleForwardFee(cells, bits, isMasterchain); // no lump
let originalFwd: Int = getOriginalFwdFee(fwdFee, isMasterchain); // approximate original from fwdFeeGas limit and accept:
setGasLimit(42000); // cap gas and reset gas_credit
acceptMessage(); // set gas_limit to max, reset gas_credit (required for external msgs)context().readForwardFee() returns the original forward fee of the incoming message (uses getOriginalFwdFee internally).
Key points
myStorageDue()andgasConsumed()are in context/state; fee calculation helpers are here.- Use
isMasterchain: falsewhen source and destination are basechain. - Negative cells/bits/seconds/gasUsed throw exit code 5.
acceptMessage()is required to process external messages (they bring no value/gas).
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-gas.mdx
- https://docs.ton.org/develop/smart-contracts/guidelines/accept
-->
Message lifecycle
Stages of processing an incoming message: receive phase, compute phase, action phase.
Receive phase
- Message value added to contract balance — this value is the effective gas budget for the transaction (capped by chain limit, e.g. 1M gas ≈ 0.4 TON basechain). Zero value aborts.
- Storage fee deducted — small nanotons subtracted; balance changes are not fully predictable.
- Deploy if needed — if contract not deployed and message carries StateInit, deployment runs; otherwise skipped.
Compute phase
- Contract code runs and produces an action list or an exception.
- Supported actions: send message and reserve (e.g.
nativeReserve). - Send can use fixed value or remaining value;
SendIgnoreErrorsskips send failures and continues. - Value for a send is taken from the incoming message value first, then from contract balance if needed.
Action phase
- Actions are executed in order.
- Exceptions during action phase do not revert the transaction. State changes (e.g. balance updates) from earlier actions are kept even if a later action fails. Design flows so that partial execution is safe or use modes (e.g.
SendIgnoreErrors) intentionally.
Key points
- Receive phase: balance += message value, then storage deduction; then deploy if init present.
- Compute phase: build action list; exit code 0/1 = success, else bounce/revert.
- Action phase: run actions sequentially; failures do not roll back earlier actions.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/book/lifecycle.mdx
-->
Math
Numeric helpers for contracts. Prefer specialized functions (log2, pow2) over general (log, pow) for gas.
Usage
min(x, y);
max(x, y);
abs(x);
sign(x); // 1, -1, or 0 (Tact 1.6+)
sqrt(num); // 500+ gas; rounds to nearest, tie to evenDivision and multiplication:
divc(x, y); // ceil(x/y); div by 0 → exit 4
muldivc(x, y, z); // ceil((x*y)/z)
mulShiftRight(x, y, z); // floor((x*y)/2^z); z in 0..256
mulShiftRightRound(x, y, z);
mulShiftRightCeil(x, y, z);Logs and powers:
log(num, base); // floor; num>0, base≥2; prefer log2 for base 2
log2(num); // cheaper than log(num, 2)
pow(base, exp); // exp≥0; compile-time when constant
pow2(exp); // cheaper than pow(2, exp)Key points
- Negative
numin sqrt, or invalid range in log/pow, throws exit code 5. - Constant arguments may be resolved at compile-time (see core-comptime).
- Use arithmetic over branching when possible (e.g.
1 + sign(x)instead of ternary) for gas.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-math.mdx
-->
Message mode
The mode field of SendParameters (and similar structs) is an Int built from one base mode and optional flags, combined with bitwise OR |.
Base modes
| Value | Constant | Description |
|---|---|---|
| 0 | SendDefaultMode (1.6+) | Default. |
| 64 | SendRemainingValue | Carry all remaining value of the inbound message (not reduced by earlier sends in the same tx). |
| 128 | SendRemainingBalance | Use entire contract balance (dangerous: can drain the contract). |
| 1024 | SendOnlyEstimateFee (1.5+) | Don’t send; only estimate forward fees. |
Optional flags
| Value | Constant | Description |
|---|---|---|
| +1 | SendPayFwdFeesSeparately | Pay forward fees separately; message with value: 0 carries no TON and cannot bounce. |
| +2 | SendIgnoreErrors | Do not abort on errors during action phase for this message. |
| +16 | SendBounceIfActionFail | Bounce transaction on action-phase errors (no effect if SendIgnoreErrors is set). |
| +32 | SendDestroyIfZero | Destroy contract if balance is zero after send (often used with 128). |
Examples
mode: SendIgnoreErrors
mode: SendRemainingValue | SendIgnoreErrors
mode: SendRemainingBalance | SendDestroyIfZeroUse one base mode; combine with any set of flags. Prefer | for combining; avoid using + for mode composition.
Functions with fixed mode
emit()— uses 0 (SendDefaultMode).self.reply,self.notify,self.forward— use SendRemainingValue (or SendRemainingBalance ifself.storageReserve> 0).
Key points
- SendRemainingValue is based on inbound message value; SendRemainingBalance is current balance — use the latter with care.
- SendIgnoreErrors prevents a failed send from aborting the transaction; later sends still execute.
- SendPayFwdFeesSeparately with value 0 means the message cannot bounce (no funds to return).
<!-- Source references:
- https://docs.tact-lang.org/book/message-mode
- sources/ton-tact/docs/src/content/docs/book/message-mode.mdx
-->
Random number generation
Pseudo-random values for contracts. Seed is block-dependent; use for non-critical randomness only.
Usage
random(min, max); // semi-closed: min ≤ x < max (or reversed if both negative)
randomInt(); // 256-bit unsigned; uses sha512(seed), updates seedSeed control (tests only; do not use in production):
let seed: Int = getSeed();
setSeed(seed); // negative seed → exit 5Randomize with value or logical time:
nativeRandomize(x); // mix x into seed (SHA-256 of seed||x)
nativeRandomizeLt(); // same as nativeRandomize(curLt())
nativePrepareRandom(); // calls nativeRandomizeLt(); called automatically by random/randomIntAvoid nativeRandom() and nativeRandomInterval(max); use randomInt() and random(0, max) instead.
Key points
random(min, max)never returnsmax; interval is semi-closed.- Seed is derived from block/transaction; validators can influence it.
- Use
getSeed/setSeedonly in tests for reproducibility. - First call to
randomInt()/random()triggersnativePrepareRandom().
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-random.mdx
-->
Receiving messages
TON is message-based. Contracts handle internal messages (from other contracts or wallets) with receiver functions. Receivers cannot be called directly; reuse logic by calling internal functions from receivers.
Receiver kinds and order
Handling order:
1. `receive()` — Empty body. 2. `receive("exact")` — Exact text comment. 3. `receive(str: String)` — Any text comment. 4. `receive(msg: MyMessage)` — Binary message of type MyMessage. 5. `receive(raw: Slice)` — Unknown binary (fallback).
Example:
message MyMessage { value: Int; }
contract MyContract {
receive() { /* ... */ }
receive("message") { /* ... */ }
receive(str: String) { /* ... */ }
receive(msg: MyMessage) { /* ... */ }
receive(msg: Slice) { /* ... */ }
}Ignoring message body
Use _ to discard the value when only the opcode matters:
message(42) UniverseCalls {}
receive(_: UniverseCalls) { /* got opcode 42 */ }Other receivers
- `bounced(msg: bounced<M>)` — Handles bounced-back outgoing messages; see Bounced messages.
- `external(msg: ExtMsg)` — External messages (no sender); require explicit
acceptMessage()and replay protection (e.g. seqno).
Key points
- One receiver per message shape; use the most specific receiver that matches.
- For external messages always verify sender/signature and use replay protection (seqno or similar).
- Parse and validate in the receiver; prefer binary messages over on-chain string parsing for gas.
<!-- Source references:
- https://docs.tact-lang.org/book/receive
- sources/ton-tact/docs/src/content/docs/book/receive.mdx
- sources/ton-tact/docs/src/content/docs/book/functions.mdx
-->
Sending messages
Messages are queued during the compute phase and actually sent in the action phase. Failures in the action phase do not revert the transaction; use SendIgnoreErrors to continue on send failure.
SendParameters
Used with send(SendParameters { ... }):
| Field | Type | Description |
|---|---|---|
to | Address | Recipient. |
value | Int | nanoTON to send. |
bounce | Bool | Default true; message bounces back on recipient failure. |
mode | Int | Base mode + optional flags (see Message mode). |
body | Cell? | Message body. |
code | Cell? | Contract code (deploy). |
data | Cell? | Initial data (deploy). |
Common sending patterns
- Reply (bounceable):
self.reply("Hi".asComment());— same asself.forward(sender(), body, true, null). - Notify (non-bounceable):
self.notify("Hi".asComment());— same asself.forward(sender(), body, false, null). - Generic send:
send(SendParameters { to, value, mode: SendIgnoreErrors, body: MyMsg{}.toCell() }); - Deploy: Set
codeanddatafrominitOf Contract(args); get address withcontractAddress(init). - Cashback (Tact 1.6.1+):
cashback(sender());— most gas-efficient way to send remaining value to an address (SendRemainingValue | SendIgnoreErrors). No effect if other message-sending functions were already used in the same receiver. - Cheaper non-deploy messages (1.6+):
message(MessageParameters { ... });— like SendParameters but withoutcode/data. - Deploy (1.6+):
deploy(DeployParameters { init: initOf C(), value, mode, ... });— cheaper thansend()for deployment. - Logging:
emit(body);— no recipient; for off-chain analysis. Mode is 0.
Advanced
- sendRawMessage(msg: Cell, mode: Int) — Send a raw message cell (1.6.6+).
- self.forward(to, body, bounce, init) — Queues message; respects
self.storageReservewhen using remaining balance.
Key points
- Outbound messages are evaluated and queued in order during compute; actual sends happen in action phase. If balance is insufficient for a later message, that send can fail without reverting (use SendIgnoreErrors to ignore).
- Prefer
cashback(sender())when returning excess value; prefermessage()/deploy()oversend()when not needing code/data for gas savings. - For deploy, use
initOf Contract(args)to getStateInit(code + data) andcontractAddress(init)for the address.
<!-- Source references:
- https://docs.tact-lang.org/book/send
- https://docs.tact-lang.org/ref/core-send
- sources/ton-tact/docs/src/content/docs/ref/core-send.mdx
- sources/ton-tact/docs/src/content/docs/ref/core-base.mdx
-->
Strings and StringBuilders
Immutable strings, StringBuilder for concatenation, and conversions to/from cells/slices. Prefer off-chain string manipulation; on-chain strings are slices and costly.
Usage
StringBuilder:
let sb: StringBuilder = beginString();
sb.append("a").append("b");
let s: String = sb.toString();
let cell: Cell = sb.toCell();
let slice: Slice = sb.toSlice();Comment/tail format (NFT/Jetton etc.):
let comment: StringBuilder = beginComment(); // 4 null bytes prefix
let tail: StringBuilder = beginTailString(); // 1 null byte prefixString extensions:
let h: Int = "text".hashData(); // SHA-256 of data, up to 127 bytes (gas-efficient)
let sl: Slice = str.asSlice();
let cell: Cell = str.asComment(); // 4-byte prefix (500+ gas)
let decoded: Slice = str.fromBase64(); // exit 134 if invalid Base64Int to string:
let s: String = (42).toString();
let floatStr: String = (42).toFloatString(9); // "0.000000042"; digits in 0..78
let coinsStr: String = nanotons.toCoinsString(); // alias toFloatString(9)Key points
beginStringFromBuilder(b)creates a new StringBuilder from an existing one.- Many string/cell conversions are 500+ gas; minimize on-chain string work.
String.hashData()only hashes first 127 bytes; longer strings can collide.- Use binary message structs in production instead of text receivers (see best-practices-gas).
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/ref/core-strings.mdx
-->
Structs and messages
Structs and messages group multiple fields into one type. Structs are plain data; messages add a 32-bit opcode so contracts can route by message type.
Structs
struct Point {
x: Int as int64;
y: Int as int64;
}
struct Line { start: Point; end: Point; }- Fields can have defaults:
name: String = "Satoshi";and optionals:age: Int?;. - No circular types (A with field of type B, B with field of type A).
- Field order defines TL-B layout; no padding.
Messages
message Add { point: Point; }
message(0x7362d09c) TokenNotification { forwardPayload: Slice as remaining; }- Opcode is auto-generated or set explicitly with
message(0x....). - Use explicit opcodes when implementing standards (e.g. Jetton) that require fixed opcodes.
Instantiation
Use braces; trailing commas allowed. Variable names matching field names allow shorthand (field punning):
StA{ field1: 42, field2: 68 };
PopQuiz{ vogonsCount, nicestNumber }; // field punningCell/Slice conversion
- `.toCell()` — Serialize struct or message to
Cell. - `.fromCell(c: Cell)` / `.fromSlice(s: Slice)` — Deserialize; layout must match or errors can occur (use try/catch if needed).
- Round-trip:
X.fromCell(inst.toCell())equalsinst; for a cell with same TL-B layout,X.fromCell(c).toCell()equalsc.
Key points
- Prefer structs/messages and
.toCell()/.fromCell()over manual Builder/Slice for consistency and maintainability. - Important message fields for bounce handling should be first; bounced payload is at most 224 bits (see bounced messages).
<!-- Source references:
- https://docs.tact-lang.org/book/structs-and-messages
- sources/ton-tact/docs/src/content/docs/book/structs-and-messages.mdx
- sources/ton-tact/docs/src/content/docs/zh-cn/ref/core-cells.mdx
-->
Tact type system
Tact is statically typed. Every variable and value has a type: either a primitive or a composite type. Many types can be optional (T?).
Primitive types
- `Int` — All numbers are 257-bit signed integers. Use serialization (e.g.
Int as uint32) to reduce storage cost. - `Bool` —
true/false. Storing booleans is cheap (1 bit). - `Address` — TON smart contract address.
- `Cell`, `Builder`, `Slice` — TVM primitives for data; see Cells, Builders, Slices.
- `String` — Immutable text.
- `StringBuilder` — Gas-efficient string concatenation.
No implicit type conversion; e.g. adding two booleans is invalid.
Composite types
- `map<K, V>` — Keys of type
Kto values of typeV. Keys:IntorAddress; values: primitives, structs, or message types. Create withemptyMap(). - Structs and messages — Combine multiple fields; see Structs and messages. Structs are plain data; messages have a 32-bit opcode for routing.
- Optionals — Any primitive or struct/message type can be nullable:
T?(includesnull). Use!!for non-null assertion. - `bounced<M>` — Only in bounced message receivers; partial representation of message
Mthat fits bounce payload limits (≤224 bits of payload).
Contracts and traits are part of the type system but cannot be passed like structs; use initOf to get a contract’s initial state.
Key points
- Prefer unsigned integer serialization (
Int as uint32, etc.) to avoid sign-related bugs. - Message opcodes can be auto-generated or set manually:
message(0x7362d09c) TokenNotification { ... }. - Map key/value types cannot be optional; nested optionals (
Int??) are not allowed.
<!-- Source references:
- https://docs.tact-lang.org/book/types
- sources/ton-tact/docs/src/content/docs/book/types.mdx
- sources/ton-tact/docs/src/content/docs/zh-cn/book/types.mdx
-->
Configuration
tact.config.json configures the Tact compiler per project. Use $schema for editor support.
Projects
Each entry is one Tact file (one project):
{
"$schema": "https://raw.githubusercontent.com/tact-lang/tact/main/src/config/configSchema.json",
"projects": [
{
"name": "my_contract",
"path": "./contract.tact",
"output": "./output",
"mode": "full",
"verbose": 1,
"options": {}
}
]
}- name: Prefix for generated files.
- path: Path to the single
.tactfile. - output: Directory for generated artifacts (Blueprint overrides).
- mode:
"full"(default),"fullWithDecompilation","funcOnly","checkOnly". - verbose: Verbosity level (default 1).
Options
- debug:
trueenablesdump()and implies nullChecks. - external:
trueenables external message receivers; required forexternal("...")/external(msg: T). - ipfsAbiGetter / interfacesGetter: Generate getters for ABI/interfaces.
- experimental.inline:
trueinlines all inlinable functions (larger code, less gas per call). - safety.nullChecks:
falsedisables runtime null checks on!!(saves gas; use only when safe). - optimizations.alwaysSaveContractData:
truesaves contract data every receiver (extra gas; for debugging/safety). - optimizations.internalExternalReceiversOutsideMethodsMap:
falsekeeps receivers in methods map (better explorer compatibility, more gas). - enableLazyDeploymentCompletedGetter:
trueaddslazy_deployment_completed()getter when not using contract parameters.
Key points
- Blueprint uses
wrappers/ContractName.compile.ts(orcompilables/) for path/output; config options still apply as defaults. - External receivers require
options.external: trueor compilation fails. - Config file can have any name but must be valid JSON matching the schema.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/book/config.mdx
-->
Constants
Immutable compile-time values. Can be simple, virtual (overridable in contract), or abstract (must be provided by contract).
Usage
Simple constant (top-level or in contract/trait):
const MY_CONSTANT: Int = 42;
contract C {
const FEE: Int = ton("0.01");
}Virtual and abstract in traits:
trait MyTrait {
virtual const MY_FEE: Int = ton("1.0");
}
trait MyAbstractTrait {
abstract const MY_DEV_FEE: Int;
}
contract MyContract with MyTrait, MyAbstractTrait {
override const MY_FEE: Int = ton("0.5");
override const MY_DEV_FEE: Int = ton("1000");
}Use for exit codes, feature flags, or config that the compiler can fold:
trait Treasure {
virtual const ENABLE_TIMELOCK: Bool = true;
receive("Execute") {
if (self.ENABLE_TIMELOCK) { }
}
}
contract MyContract with Treasure {
override const ENABLE_TIMELOCK: Bool = false; // branch removed at compile time
}Key points
- Constants are compile-time; no reassignment.
- Virtual: default in trait, overridable with
overridein contract. - Abstract: no default; contract must declare
override const .... - Trait constructors are not allowed; use constants (or fields) to pass config into traits.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/book/constants.mdx
-->
External messages
External messages have no sender and are sent from off-chain. The contract pays for gas and must accept the message explicitly.
Enabling
In tact.config.json set options.external: true for the project. Without it, compilation fails if external receivers are used.
External receivers
Use external instead of receive; same ordering and matching (text, binary, slice):
contract SampleContract {
external("Check Timeout") {
require(self.timeout > now(), "Not timed out");
acceptMessage();
self.onTimeout();
}
external(msg: SignedMessage) {
throwUnless(35, msg.bundle.verifySignature(self.publicKey));
acceptMessage();
// ...
}
}Differences from internal
- Contract pays gas — sender does not; minimize work before
acceptMessage(). - Must call acceptMessage() — otherwise the message is rejected (anti-spam).
- ~10k gas before accept — small limit before your code; validate and accept quickly.
- Unbounded gas after accept — test and guard against draining balance.
- No context/sender —
context()andsender()are not available in external receivers; do not use them.
Storage handling (including return and throw(0)) is the same as for internal receivers.
Key points
- Always call
acceptMessage()when the message is valid and you intend to process it. - Use
SignedBundle+verifySignaturefor authenticated external actions (e.g. wallets). - Test gas usage; external flows can drain the contract if logic or fees are wrong.
<!-- Source references:
- sources/ton-tact/docs/src/content/docs/book/external.mdx
-->
initOf and deployment
To deploy a contract you need its initial code and data (StateInit). Tact provides `initOf` to compute that from a contract type and constructor arguments.
initOf and contractAddress
let init: StateInit = initOf SecondContract(arg1, arg2);
let address: Address = contractAddress(init);- `initOf ContractName(args)` — Returns
StateInit(.codeand.datacells) for the contract. Use when the contract has aninit()or contract parameters; args match that signature. - `contractAddress(init)` — Derives the contract address from a StateInit (deterministic).
Deploying via send()
send(SendParameters {
to: address,
value: ton("1"),
mode: SendIgnoreErrors,
code: init.code,
data: init.data,
body: "Hello".asComment(), // optional
});Deploying via deploy() (Tact 1.6+)
Cheaper for deployments; uses DeployParameters (no separate to; address comes from init):
deploy(DeployParameters {
init: initOf SomeContract(),
mode: SendIgnoreErrors,
value: ton("1"),
});Key points
- Contract parameters (e.g.
contract C(x: Int)) set initial state at deploy; noinit()needed and deployment is cheaper. - Use
initOffor both lazy init and direct deploy flows;contractAddress(init)when you need the address before sending. - For factory patterns, combine
initOfwith custom code/data or use stdlib@stdlib/deploy(Deployable / FactoryDeployable).
<!-- Source references:
- https://docs.tact-lang.org/book/send
- https://docs.tact-lang.org/book/deploy
- https://docs.tact-lang.org/book/expressions (initOf)
- sources/ton-tact/docs/src/content/docs/ref/core-send.mdx
-->
Maps
The composite type `map<K, V>` associates keys of type K with values of type V. On TVM, maps are represented as Cells and are gas-intensive; nested maps hit limits sooner.
Allowed types
- Keys:
Int,Address(not optional). - Values:
Int,Bool,Cell,Address, any struct type, any message type (not optional, no map literals as value type).
Usage
let m: map<Int, Int> = emptyMap();
m.set(key, value);
let v: Int? = m.get(key);
// Nested via wrapper struct:
struct AllowanceMap { unbox: map<Address, Int> }
let allowances: map<Address, AllowanceMap> = emptyMap();- `emptyMap()` — Create empty map.
- `.set(k, v)` — Set entry.
- `.get(k)` — Returns optional value; use
!!after null check or when certain.
Serialization
Keys and/or values can use integer serialization (e.g. Int as uint32) to reduce storage. See the Maps reference for exact syntax (key/value serialization in map type declaration).
Key points
- Maps are stored as Cells; minimize map usage and depth for gas.
- Use struct wrappers for nested maps:
map<Address, WrapperStruct>whereWrapperStructcontains a map. - Key/value types cannot be optional.
<!-- Source references:
- https://docs.tact-lang.org/book/maps
- sources/ton-tact/docs/src/content/docs/book/maps.mdx
-->
Optionals
Any primitive, [struct][struct], or [message][message] type can be nullable by adding ?: e.g. Int?, Address?, MyStruct?. The value can be null or a value of the inner type.
Declaration and use
struct StOpt { opt: Int?; }
message MsOpt { opt: StOpt?; }
contract Optionals(opt: Int?, address: Address?) {
fun reset(opt: Int?) {
self.opt = opt;
self.address = null;
}
receive(msg: MsOpt) {
let opt: Int? = 12;
if (msg.opt != null) {
self.reset(msg.opt!!.opt); // !! = non-null assertion
}
}
}- Optional struct/message fields default to
nullif not provided. - Local variables of optional type must be initialized (e.g.
let x: Int? = null;). - Use `!!` to assert non-null when you've already checked.
Constraints
- Map key and value types cannot be optional:
map<Int?, Int>is invalid. - Nested optionals are not allowed:
Int??is invalid. - `bounced<M>` inner type cannot be optional.
Key points
- Prefer
if (x != null) { use(x!!) }or explicit checks over blind!!. - For
let x: T? = nullyou must give the type; it cannot be inferred.
<!-- Source references:
- https://docs.tact-lang.org/book/optionals
- sources/ton-tact/docs/src/content/docs/book/optionals.mdx
-->
Standard libraries
Stdlibs are bundled with the Tact compiler but not included until you import them:
import "@stdlib/ownable";Libraries
| Library | Purpose | Notable APIs |
|---|---|---|
| @stdlib/config | Config and elector addresses | getConfigAddress(), getElectorAddress() |
| @stdlib/content | Encode off-chain link strings to Cell | createOffchainContent() |
| @stdlib/deploy | Unified deployment | Deployable, FactoryDeployable |
| @stdlib/dns | DNS resolution | DNSResolver, dnsInternalVerify() |
| @stdlib/ownable | Ownership trait | Ownable, requireOwner(), OwnableTransferable, ChangeOwner / ChangeOwnerOk |
| @stdlib/stoppable | Pause/resume (requires ownable) | Stoppable, Resumable |
Ownable example
import "@stdlib/ownable";
contract Counter with Ownable {
owner: Address;
init(owner: Address) { self.owner = owner; }
receive("admin-double") {
self.requireOwner();
// ...
}
}Key points
- Each stdlib is opt-in; import only what you need.
- Ownable declares
owner: AddressandrequireOwner(); use for access control. - Stoppable/Resumable depend on Ownable; enable pausing by owner.
<!-- Source references:
- https://docs.tact-lang.org/ref/standard-libraries
- https://docs.tact-lang.org/ref/stdlib-ownable
- sources/ton-tact/docs/src/content/docs/zh-cn/ref/standard-libraries.mdx
- sources/ton-tact/docs/src/content/docs/ref/stdlib-ownable.mdx
-->