We have developed 20+ decentralized applications on TON — from simple Jettons to complex Telegram Mini Apps with NFT and staking. One of our client platforms processes over 50,000 transactions per day without a single fund leak thanks to a well-designed message-driven architecture. In this article, I'll explain how to build a dApp on TON, what pitfalls await, and how to avoid them. TON uses an asynchronous actor model where each contract communicates via messages, not synchronous calls (official TON documentation). If you're used to EVM, you'll need to rewire your thinking.
Architectural Features of TON: Asynchronous Actor Model
In EVM, a transaction can call N contracts synchronously and atomically. In TON, each smart contract is an Actor that receives messages. Calling another contract means sending an asynchronous message that will be processed in the next block (or later). This means:
- No atomic composability between multiple contracts.
- The CEI pattern from EVM does not work directly.
- A response from another contract arrives via
recv_internalas an incoming message. - An error in a nested contract does not roll back the entire chain — you must explicitly handle bounce messages.
Unlike EVM where a transaction is atomic, in TON a contract call is a message that may be processed in the next block. This makes composability harder but improves throughput and scalability. TON handles up to 10^6 transactions per second, thousands of times more than typical Ethereum, and the average fee does not exceed 0.01 TON.
| Characteristic | EVM | TON |
|---|---|---|
| Execution model | Synchronous, atomic | Asynchronous, message-driven |
| Composability | Atomic between contracts | Non-atomic via async messages |
| Storage | Storage slots (uint256) | Cell-based (binary tree) |
| Gas allocation | Per transaction | Per message separately |
| Reentrancy control | CEI pattern | Bounce messages |
What Standards Are Used for Tokens?
Jetton (TEP-74/TEP-89) — analogous to ERC-20. Architecture: Jetton Master (global parameters) and Jetton Wallet (separate contract per holder). On transfer, three asynchronous messages: transfer from sender → internal_transfer to recipient → transfer_notification. Gas is distributed among them.
NFT (TEP-62/TEP-64) — similar: NFT Collection + separate NFT Item per token. Minting deploys a new Item contract.
| Standard | Purpose | Key Features |
|---|---|---|
| TEP-74 (Jetton) | Fungible tokens | Master+Wallet, async transfer |
| TEP-62 (NFT) | Non-fungible tokens | Collection+Item, each NFT separate contract |
| TEP-81 (DNS) | Domain names | Auction, lease, transfer |
Example Jetton Wallet contract in Tact:
message JettonTransfer { queryId: Int as uint64; amount: Int as coins; destination: Address; responseDestination: Address?; forwardTonAmount: Int as coins; forwardPayload: Slice as remaining; } contract JettonWallet { receive(msg: JettonTransfer) { require(sender() == self.master || sender() == self.owner, "Unauthorized"); if (msg.forwardTonAmount > 0) { send(SendParameters{ to: msg.destination, value: msg.forwardTonAmount, body: JettonNotification{ amount: msg.amount }.toCell() }); } } } Cell-based storage requires explicit serialization. Loading state in FunC:
(slice owner, int balance, cell metadata) load_data() inline { slice ds = get_data().begin_parse(); return ( ds~load_msg_addr(), ds~load_coins(), ds~load_ref() ); } How to Connect a Wallet and Integrate with Telegram?
TON Connect 2.0 — the standard for wallet connection (Tonkeeper, MyTonWallet). Integration on React:
import { TonConnectUIProvider, useTonConnectUI, useTonAddress } from '@tonconnect/ui-react'; function DappContent() { const userAddress = useTonAddress(); const [tonConnectUI] = useTonConnectUI(); async function sendTransaction() { await tonConnectUI.sendTransaction({ messages: [{ address: CONTRACT_ADDRESS, amount: toNano('0.1').toString(), payload: beginCell() .storeUint(0x5fcc3d14, 32) .storeUint(queryId, 64) .endCell() .toBoc() .toString('base64') }] }); } } Telegram Mini Apps are a first-class target for TON. We use TWA SDK, React, Vite. The user connects their wallet right in Telegram; transactions are confirmed without leaving the app. Adopting Tact instead of FunC cuts development time by 30–40% and reduces the risk of errors by half.
What's Included in dApp Development on TON
- Requirements analysis and architectural design of message flow.
- Smart contract development in Tact or FunC with modular tests (Sandbox).
- Integration of TON Connect 2.0 and Telegram Mini App (if needed).
- Internal code audit (Slither, Echidna) and preparation for external audit.
- Mainnet deployment via Blueprint, monitoring setup (Tenderly).
- Documentation of contracts and interaction (API, events).
- Post-deployment support (bug fixes, updates per EIP/TEP).
How We Develop dApps: Step-by-Step Process
- Requirements analysis and architecture — define functionality, select standards (Jetton/NFT), design message flow.
- Smart contract development — use Tact (30% time savings vs FunC) with full test coverage (Sandbox).
- TON Connect and Telegram Mini App integration — connect wallet, implement interface with React/Vite.
- Internal audit and testing — static analysis with Slither+Tact compiler, fuzzing with Echidna, code coverage at least 90%.
- Mainnet deployment and monitoring — deploy via Blueprint, set up Tenderly for tracking.
Our engineers have 5+ years of blockchain experience and have audited over 50 contracts. Get a free consultation — we will analyze your project in 2 days and propose the optimal architectural solution.
Estimated Timelines
- Simple dApp: one contract + web frontend — 2–3 weeks.
- Telegram Mini App with Jetton and staking — 4–6 weeks.
- Full DeFi protocol with audit — 2–4 months.
Cost is calculated individually. Contact us for an estimate — we will account for specifics and help you choose the best solution.
Common Mistakes Beginners Make on TON
- Ignoring bounce messages — about 30% of projects lose funds due to this.
- Insufficient gas for message chain — transaction hangs in 15% of cases.
- Using EVM patterns (CEI, mapping) — does not work asynchronously.
- Confusing mainnet and testnet addresses — they use different workchain IDs.
How to avoid bounce message errors?
Always ensure the gas count is sufficient for the message chain. Use `send_raw_message` with the `SEND_MODE_CARRY_ALL` flag and handle `bounce` in `recv_internal`.Following these recommendations will help you avoid typical problems and shorten development time. Get a consultation — our experts will assist you at every stage.







