TON Explained: Architecture, Smart Contracts, and Integration

At first glance, TON can look familiar: a native cryptocurrency, smart contracts, tokens, and a wallet connection button. Underneath that interface, however, the system works very differently from Ethereum.

TON was not designed as one global virtual machine through which every transaction passes in sequence. Its architecture combines multiple chains with asynchronous messages between accounts. A smart contract behaves like an actor: it receives a message, processes it, updates its own state, and may send additional messages.

That distinction is not academic. It changes how I would design payments, token transfers, error handling, retries, tests, and the user interface.

After 25 years in traditional finance, I am particularly sensitive to the difference between a payment instruction and a settled payment. On TON, that distinction belongs in the architecture from day one. A wallet approving a request is not the same thing as a recipient receiving the expected asset.

For developers, TON becomes especially interesting where three requirements meet:

  • The application needs to reach users through a Telegram Mini App.
  • Users need to connect wallets without handing their private keys to the application.
  • The system needs to process many relatively small operations with a fee budget that can be modeled and monitored.

The practical opportunities extend well beyond launching another decentralized exchange. They include games, loyalty programs, bulk payouts, digital-item markets, payment services, and applications working with USDT as a Jetton.

There is an important platform restriction: digital goods and services sold inside Telegram Mini Apps must use Telegram Stars. TON Connect supports permitted blockchain interactions, but it does not override Telegram’s payment rules. I will keep that boundary explicit in the subscription example later in this guide.

To build a reliable TON integration, adding TonConnectButton is only the beginning. You need to understand how TON identifies accounts, how a message becomes a transaction, why a Jetton transfer spans multiple transactions, and why exit_code = 0 does not prove that an entire business operation succeeded.

The official TON developer documentation explains the individual components. My goal here is to connect them into one practical mental model.

What Are TON, Toncoin, and TON Coin?

TON is the blockchain network. Toncoin, often informally called TON Coin or simply TON, is the familiar name for its native currency.

A terminology update matters here. As of October 18, 2026, the documentation also uses Gram, GRAM, and nanogram, including in its introductory overview of the TON network. Those terms should not be treated as exclusively historical references.

I use the familiar TON, Toncoin, and nanoton labels in the examples below. When implementing an integration, verify the terminology used by your wallet and API, but keep the underlying network and integer denomination unambiguous.

The native currency is used to:

  • Pay for smart contract execution.
  • Pay for storing contract code and data in network state.
  • Pay for forwarding messages between accounts and shards.
  • Support staking and the economic security of validators.
  • Transfer value between wallet contracts.
  • Attach value to a message sent to a smart contract.

One TON equals 1,000,000,000 nanotons.

SDKs and wallet interfaces therefore commonly expect amounts as decimal strings expressed in base units. Sending 0.1 TON usually means passing "100000000".

Across codebases, you may encounter variables named grams, nanograms, tonAmount, or nanoTonAmount. The important question is not what the variable is called. It is which asset and unit it represents.

My rule is simple: calculate blockchain amounts as integers in base units.

Do not use JavaScript number for arbitrarily large blockchain amounts. Use bigint for integer calculations, then convert the result to a decimal string when the wallet or API requires one.

Likewise, avoid parsing a user-entered decimal amount through floating-point arithmetic before converting it to base units.

Why TON Is Not an EVM With Different Addresses

TON contracts communicate through asynchronous messages, not synchronous cross-contract calls.

The biggest mistake an Ethereum developer can make is to carry the entire EVM mental model into TON unchanged.

A typical EVM interaction looks like this:

  1. A user calls contract A.
  2. Contract A synchronously calls contract B.
  3. Contract B returns a result.
  4. If a failure propagates through the call stack, the transaction’s contract-state changes revert together.

Gas costs still apply, and EVM contracts can catch certain failures. But the familiar model is a synchronous execution context with transaction-level atomicity.

TON works differently:

  1. Account A receives a message.
  2. Processing that message creates a transaction on account A.
  3. A may generate an outgoing message addressed to B.
  4. B processes that message in a separate transaction.
  5. B may send another message to C or a response back to A.
  6. A failure at a later step does not automatically undo earlier transactions.

One user intention can therefore produce a chain of causally related transactions. TON calls that connected flow a trace.

The TON execution-model comparison for Ethereum developers makes this distinction especially clear: what fits into one Ethereum transaction can become several TON transactions.

ConceptTypical EVM mental modelTON mental model
Contract interactionSynchronous callAsynchronous message
Complex operationA call tree within one transactionA trace spanning multiple transactions
Error recoveryAtomic rollback when a failure propagatesPer-transaction failure, bounces, or application-level compensation
Token balancesUsually a mapping in one token contractA separate Jetton wallet contract for each owner
StorageStorage slotsA graph of cells
Binary encodingABI-encoded calls and dataCell layouts, BoC serialization, and TL-B schemas
Contract addressEVM deployment rules, including CREATE and CREATE2Workchain identifier plus an account identifier derived from StateInit
Reading stateRPC requests or view functionsGet methods executed through an API or liteserver

Ethereum addresses can also be deterministic. The address comparison describes TON’s standard initialization model, not a claim that deterministic deployment is unique to TON.

Another important difference: a TON contract cannot synchronously call another contract’s get method during execution. An off-chain application can run that getter against blockchain state, but an on-chain interaction requires messages.

I find it more useful to think of TON as a distributed actor system than as a conventional call stack.

That architecture supports parallel processing. It also makes the protocol designer responsible for situations in which a response arrives late, a remote step fails, an application retries a request, or only part of a business operation completes.

How TON’s Masterchain, Workchains, and Shardchains Fit Together

TON scales by distributing account state and execution across a hierarchy of chains.

The architecture covered in the overview of TON’s blockchain foundations has three essential components: the masterchain, workchains, and shardchains.

The Masterchain

The masterchain coordinates the network. It contains global configuration, validator information, active workchains, shard information, and references to relevant shardchain blocks.

Its workchain_id is -1.

The masterchain is not the default place for an application’s user-facing contracts. Operations there are more expensive, helping limit traffic that could interfere with system-level responsibilities.

I think of it as coordination infrastructure rather than the place to deploy an ordinary checkout contract.

The Basechain and Other Workchains

A workchain is a blockchain with its own rules and account space.

Ordinary TON applications generally use the Basechain, whose workchain_id is 0.

The protocol design allows additional workchains with different account formats, transaction rules, or virtual machines. For most application developers, that remains an architectural possibility rather than an everyday integration concern.

User wallets and application contracts generally live in the Basechain.

Shardchains

A workchain can be divided into shardchains. Each shardchain processes part of the address space, allowing different validator groups to process different portions of the network in parallel.

TON’s dynamic blockchain-sharding model assigns accounts to shards according to address prefixes.

When load increases, a shard can split into two shards covering more specific prefixes. When load decreases, compatible sibling shards can merge.

An application should therefore never assume that two accounts will remain in the same shard forever.

The protocol handles message routing. The application still needs to accommodate asynchronous execution and variable delivery time.

The practical point is that TON does not obtain scale simply by making one global execution queue faster. It distributes state and processing across independent shards.

That does not make one heavily used contract infinitely parallel. If every operation must update the same account, that account remains a coordination point. The application’s state model still matters.

TON Accounts, Wallets, and Smart Contracts Explained

An account is TON’s basic unit of state, and a wallet is a smart contract operating within that account model.

An address identifies an account. Depending on its status, that account can have:

  • A native-coin balance.
  • Contract code.
  • Persistent data.
  • An account status.
  • Information identifying its most recent transaction.

Not every account is an active, deployed contract. But TON does not use the familiar EVM distinction in which ordinary user wallets must be externally owned accounts while application logic lives in a separate account category.

A TON wallet is a contract whose basic job is to validate authorization and generate internal messages.

When someone sends TON from a wallet application, the usual sequence is:

  1. The application constructs an external message for the user’s wallet contract.
  2. It signs the request.
  3. The wallet contract checks the signature.
  4. It checks a sequence number or another replay-protection mechanism.
  5. It generates one or more internal messages to the intended recipients.

That separates three things people often casually call “the wallet”:

  • The wallet application: Software providing the interface and managing signing.
  • The wallet contract: On-chain code that validates requests and sends messages.
  • The wallet address: The address of that on-chain account.

There are multiple wallet contract versions.

If I were designing custody infrastructure or a withdrawal service, I would not assume every user has identical wallet code or capabilities. Request formats, replay protection, supported features, and the number of messages a wallet can send in one request can differ.

TON Address Formats: Raw, Bounceable, and Non-Bounceable

Different TON address strings can identify the same account while carrying different handling flags.

For the account types used by ordinary Basechain applications, an internal address includes a workchain_id and a 256-bit account identifier.

An abbreviated raw address looks like this:

0:2fcb1b...

This example is intentionally incomplete and cannot be used as a payment destination.

User interfaces generally display a base64 or base64url representation with a checksum and flags. For common Basechain addresses without the test-only flag, you will often see:

  • EQ... for a bounceable representation.
  • UQ... for a non-bounceable representation.

These are not necessarily different accounts. The underlying workchain and account identifier can be identical.

The TON address-format specification and examples explain how the friendly representation wraps the underlying address.

What Bounceable Means

A bounceable message is generally appropriate when interacting with an active smart contract expected to process a particular request.

If processing fails under the relevant protocol conditions, the network may generate a bounce message back to the sender.

A bounce is not a guaranteed full refund. Fees may already have been consumed, and the failure path must support generating the return message.

The friendly address provides a handling instruction to sending software. The actual message’s bounce behavior is determined by the message that gets constructed.

What Non-Bounceable Means

A non-bounceable representation is commonly used when funding a wallet address that has not yet been initialized, particularly when the transfer does not include the StateInit needed to deploy it.

It can also be appropriate when returning the message is not desired.

Funding an undeployed account does not, by itself, guarantee that the intended contract has been deployed or that the recipient can spend the funds.

Normalize Addresses Before Comparing Them

Do not construct friendly addresses by hand. Use parsing and formatting functions from @ton/core or the relevant TON Connect libraries.

Those functions should handle:

  • Address parsing.
  • Checksum validation.
  • Raw-to-friendly conversion.
  • Bounceable and non-bounceable formatting.
  • Test-only flags.

A TON Connect request may require a user-friendly destination, while an API response or connected-account object may contain a raw address.

That mismatch can look like a wallet connection bug when the real problem is formatting.

For database comparisons, I normalize addresses to their underlying workchain and account identifier. I also retain network context separately, because the same account address can exist on different networks.

A valid checksum helps detect input mistakes. It does not establish that the recipient is trustworthy.

Deterministic Contract Addresses and StateInit

A TON contract’s address can be calculated before deployment from its initialization structure.

That structure is called StateInit. It includes the contract’s initial code and data, and the serialized structure determines the account identifier within the selected workchain.

The TON blockchain specification describing contract initialization provides the underlying protocol detail.

Deterministic addresses enable several useful patterns:

  • Deposit addresses known before deployment.
  • Jetton wallet addresses derived for a particular master contract and owner.
  • Deployment through an initial message carrying the required code and data.
  • Verification that an address matches an expected initialization structure.

There is a security consequence: an address alone does not prove that a contract belongs to a legitimate token or application.

A backend may need to verify the expected master contract, initialization data, wallet derivation, current code, or standard get-method results.

The exact checks depend on the protocol.

I also distinguish initial code from current code. If a contract can upgrade its implementation, verifying the original StateInit does not automatically establish what the live contract does today.

How Messages Trigger TON Smart Contract Execution

A TON message is an instruction or event that can trigger execution on an account.

Receiving a message is the most common reason an account processes a transaction.

Three message types matter for application development:

  • External-in: A message entering the blockchain from outside and addressed to a contract.
  • Internal: A message sent from one blockchain account to another.
  • External-out: A message emitted by a contract for consumption outside the blockchain.

An external-out message is not an HTTP request or a synchronous callback to your server. An off-chain consumer still needs to observe and process it.

A user’s wallet contract typically receives a signed external-in message. After validating the request, it generates one or more internal messages.

An internal message can carry:

  • A source address.
  • A destination address.
  • Native-coin value.
  • A bounce flag.
  • A message body.
  • Optional StateInit.
  • Forwarding and fee-related fields.

The TON message and transaction overview describes how these messages fit into account execution.

The body is represented using cells. In many standard protocols, it starts with a 32-bit operation code, followed by a query_id and operation-specific fields.

The contract does not invoke a remote function and wait for its return value. It constructs a message in the expected binary format and sends it.

That is the interaction model the wrapper, tests, backend, and user interface all need to respect.

Why a TON Message Is Not the Same as a Transaction

A message represents an instruction or event. A transaction records its processing and the resulting state transition of one account.

A transaction can:

  • Consume an incoming message.
  • Charge storage and computation fees.
  • Update persistent data.
  • Change contract code.
  • Reserve part of the account balance.
  • Generate multiple outgoing messages.

Each outgoing message can later trigger a separate transaction on another account.

That is why one transaction hash is not always enough to track a business operation.

For a Jetton transfer, a decentralized-exchange swap, or an NFT mint, the application may need to follow the entire trace and identify the specific event that establishes business success.

From a financial-control perspective, this is familiar territory. “Instruction accepted” and “beneficiary credited” are different milestones. TON makes that distinction visible at the protocol level.

The Five Phases of a TON Transaction

A normal TON transaction can pass through up to five phases, and success in one phase does not guarantee success in the next.

The TON transaction execution-phase model separates storage, credit, compute, action, and bounce processing.

Storage Phase

The network calculates the cost of keeping the account’s code and data in blockchain state.

Storage is not simply a one-time charge paid when data is written. Costs accrue according to the amount of stored data and the time it remains in state.

That changes how I evaluate contract design.

A mapping that grows forever is not just a software-maintenance concern. It creates an ongoing financial obligation for the account, and protocol storage limits still apply.

Credit Phase

The incoming message’s value is credited to the account balance.

The ordering of credit and storage depends on the message’s bounce characteristics:

  • For a non-bounceable message, credit generally precedes storage.
  • For a bounceable message, storage generally precedes credit.

That ordering can affect whether the account can cover outstanding storage costs and continue processing.

Compute Phase

TVM executes the contract code.

The result can include:

  • An exit code.
  • Gas consumption.
  • Proposed persistent state.
  • A list of actions to process afterward.

Compute can be skipped, including when executable code is unavailable, StateInit does not satisfy initialization requirements, or there is insufficient funding for execution.

A familiar successful compute result is exit_code = 0. TVM also supports an alternative successful termination code, 1, in relevant contexts.

Neither value, by itself, proves that the entire payment or application workflow completed.

Action Phase

Actions produced during computation are processed afterward.

They can include:

  • Sending messages.
  • Updating contract code.
  • Reserving funds.
  • Changing library references.

Here is the trap: successful computation does not guarantee successful actions.

A contract can finish computation with exit_code = 0 and still fail to send an outgoing message because it lacks funds or uses an inappropriate send mode.

Some modes suppress certain action errors. A broad success indicator can therefore hide the fact that the intended outgoing message was never sent.

Bounce Phase

A failed incoming bounceable message may result in a new bounce message addressed to its sender.

Certain action failures can also trigger bounce behavior when the relevant flags are used.

A bounce does not reverse every transaction in the business process. It creates another message, which the sending contract must handle correctly.

If a contract changes its internal accounting before attempting a remote operation, it needs a recovery strategy. Otherwise, its local records can say “completed” while the remote contract never accepted the operation.

Designing TON Applications for Asynchronous Execution

A reliable TON protocol models a business operation as a process with explicit states, not as one function assumed to finish everything.

Consider a subscription-related process that:

  1. Records a user’s request.
  2. Sends a Jetton payment-related message toward a treasury.
  3. Records that it is awaiting confirmation.
  4. Activates an entitlement after a verified response.

The contract does not remain suspended while it waits. Later messages drive later transactions.

A useful state flow is:

  1. requested: The operation has been recorded.
  2. payment_sent: The payment-related message has been issued.
  3. A subsequent outcome is recorded:
    • confirmed when the required result is verified.
    • bounced when the relevant failure message is handled.
    • expired when the application’s waiting period ends.

The expiry branch needs care. A timeout does not prove that payment never happened, and it does not automatically cancel an internal message already in flight.

Before retrying, the application may need to reconcile the existing operation.

A well-designed TON protocol generally includes:

  • A query_id unique within a defined application scope.
  • Explicit operation state.
  • Idempotent handlers.
  • Bounce handling.
  • Time limits where appropriate.
  • An administrative or user-driven recovery path.
  • Separate records for technical message submission and business completion.

I treat query_id as a correlation field, not as a universal replay-protection guarantee. A protocol carrying that field does not necessarily deduplicate it for you.

Idempotency is equally important outside the contract.

The blockchain prevents certain protocol-level duplicates. Your backend can still process the same event twice after a restart, reconnect, overlapping historical query, or local index rebuild.

Financial posting logic must remain correct when that happens.

TVM, Cells, and Bag of Cells Explained

TVM is a stack-based virtual machine, and cells are the fundamental data structure used for TON code, persistent state, and messages.

TON does not use EVM storage slots as its underlying data model.

A TON cell used for storage and serialization can contain:

  • Up to 1,023 bits.
  • Up to four references to other cells.

Cells form a directed acyclic graph. Circular references are not allowed.

A cell’s hash depends on its contents and referenced structure. This makes cells useful for storing, identifying, and verifying structured data.

When a cell graph is serialized for storage or network transfer, the resulting format is called Bag of Cells, abbreviated BoC.

The Bag of Cells serialization format is commonly represented as a base64 string in API responses and TON Connect payloads.

What TL-B Does

TL-B is a schema language describing how TON structures are encoded into bits and cell references.

As a rough analogy, it serves a role similar to a binary schema system such as Protocol Buffers. However, it is designed around cell graphs and bit-level layouts rather than ordinary byte-oriented message objects.

That distinction matters when implementing a parser or constructing a message manually.

Building a Message Body With TypeScript

For TypeScript developers, @ton/core provides the basic building blocks.

It can:

  • Parse and format addresses.
  • Construct cells.
  • Read slices.
  • Serialize messages.
  • Work with dictionaries.
  • Generate and parse BoC data.

Here is a minimal message-body example:

import { beginCell } from '@ton/core';

const body = beginCell()
  .storeUint(0x12345678, 32)
  .storeUint(42n, 64)
  .storeStringTail('premium-plan')
  .endCell();

const payload = body.toBoc().toString('base64');

This does not call a standard TON endpoint or implement a standard TON operation.

It simply shows how an application-specific opcode, a query_id, and a string can be packed into a cell.

The receiving contract must understand exactly that layout.

In production, define the wire format formally and keep it stable. Changing field order, integer width, or whether data is stored inline or by reference changes the serialized message.

A TypeScript object that “looks equivalent” is not enough. The binary layout has to match.

Why TON Dictionaries Are Not Free Databases

A TON dictionary is a tree stored in cells, so every update has computational and storage consequences.

Dictionaries are useful for mapping identifiers to values. They are not a reason to put an unlimited application database into one contract.

An update can create multiple new cells. Iterating over a large dictionary or performing many modifications in one transaction can exhaust the available gas.

For a large workload, I would look for a way to divide the work across messages and accounts.

Instead of one contract maintaining a global map of millions of users, consider:

  • A child contract for each user.
  • Bucket contracts grouped by address prefix.
  • Deterministically derived contract instances.
  • An off-chain index backed by verifiable on-chain state.
  • Messages that process a bounded number of items per transaction.

There are limits on account state as well as transaction execution. Paying storage fees does not make a single account an unlimited database.

The Jetton architecture follows the same principle: each holder’s balance is not simply another entry in a giant global mapping inside the master contract.

Tolk and the TON Smart Contract Development Toolchain

Tolk is the recommended language for new TON smart contracts, and Acton is the recommended all-in-one toolchain for new projects.

The Tolk smart contract language overview describes a statically typed language with declarative data structures, automatic cell serialization, and message-handling primitives.

Tolk compiles for TVM. It improves the development experience without making TON behave like a synchronous EVM system.

You still need to understand:

  • Cells.
  • Incoming message formats.
  • Persistent storage.
  • Outgoing actions.
  • Asynchronous execution.

The current smart contract development guidance directs new projects toward Tolk and Acton for building, testing, deploying, and debugging contracts.

Languages You Will Encounter in Existing Projects

Existing TON repositories may use:

  • FunC: An older, C-like smart contract language.
  • Tact: A higher-level language used in many existing projects.
  • Fift: A stack-oriented language closely associated with assembly and tooling.
  • Tolk: The recommended choice for new contract code.

I would not choose a language solely because an older tutorial ranks well in search or a repository has accumulated a large number of stars.

Maintaining an existing contract and choosing a stack for a new project are different decisions.

Where Blueprint Still Fits

The Blueprint development-environment overview explains that Blueprint remains supported for existing projects, even though Acton is recommended for new ones.

Blueprint is particularly relevant when working with an established TypeScript-based development workflow.

A typical project structure includes:

contracts/
scripts/
tests/
wrappers/
build/

The wrapper is an important part of TON development. It connects a TypeScript application to the binary formats used by contract messages and get methods.

A good wrapper hides repetitive cell packing. It should not hide the asynchronous meaning of the operation.

A method called sendPayment() should not encourage the rest of the application to assume that the payment has already settled.

How to Test TON Smart Contracts Beyond the Exit Code

TON tests should validate the full trace and resulting contract states, not just one transaction’s compute result.

The TON Sandbox testing guide describes how to execute contracts locally from TypeScript without deploying them to a public network or running a full node.

At a conceptual level, a check like this is not enough:

transaction.success === true

The exact success fields and matchers depend on your testing library or API. More importantly, one success flag cannot replace checks of the intended behavior.

I would also verify:

  • Who sent the incoming message.
  • Which contract received it.
  • How many outgoing messages were created.
  • Where those messages were sent.
  • Whether a message bounced.
  • The state of every participating contract.
  • The compute phase’s exitCode.
  • The action phase’s result code, often exposed as actionResultCode.
  • Total fees.
  • Final account status.
  • The opcode and query_id fields.

TON’s smart contract debugging techniques help inspect individual transactions, get-method results, and execution details when an assertion fails.

What Sandbox Does Not Replace

Sandbox does not reproduce every property of a live network.

In particular, it does not recreate real block production and sharding behavior. Time is controlled, external dependencies need deliberate setup, and a local execution environment cannot establish wallet compatibility.

Testnet still matters for checking:

  • Deployment.
  • Wallet behavior.
  • API indexing.
  • Manifest accessibility.
  • Frontend request handling.
  • The complete application flow.

A Minimum Failure-Test Checklist

For a contract that receives payments, I would include at least these scenarios:

  1. A valid message carrying enough native currency.
  2. A message with insufficient funding for execution.
  3. An incorrect opcode.
  4. A duplicate query_id, where the application requires deduplication.
  5. A bounce from a remote operation.
  6. An unauthorized or unknown sender.
  7. The maximum expected payload size.
  8. State after a failed action phase.
  9. Multiple messages arriving in different orders.
  10. Backend indexing that processes the same trace again.

The happy path tells me the application can work. Failure tests tell me whether its accounting remains trustworthy when it does not.

How Jettons Differ From ERC-20 Tokens

A Jetton is a distributed fungible-token system, not one ERC-20-style contract with a different ABI.

TON’s Jetton standard overview describes the distributed architecture, while the TEP-74 fungible-token specification defines the standard interfaces and message formats.

In a typical ERC-20 model, one contract maintains a mapping of all balances.

A Jetton system has at least two contract roles:

  • Jetton master: Represents the token type and exposes metadata, supply information, and relevant administrative or minting logic.
  • Jetton wallet: A separate contract holding the balance for one owner and one Jetton.

If Alice and Bob hold the same Jetton, each has a separate Jetton wallet contract.

Their ordinary TON wallet addresses are not the same as their Jetton wallet addresses.

A Typical Jetton Transfer

A transfer generally proceeds as follows:

  1. The user sends a transfer request to their own Jetton wallet.
  2. That wallet validates the request and reduces the sender’s Jetton balance.
  3. It sends an internal_transfer to the recipient’s Jetton wallet.
  4. The recipient’s Jetton wallet increases its balance.
  5. If forward_ton_amount is positive, it sends a transfer_notification to the recipient owner address.
  6. Excess native currency may be returned to the designated response address.

The recipient’s Jetton wallet may also need to be deployed during this process.

That means multiple contracts, multiple messages, and multiple transactions.

A backend must not treat the first wallet request as final proof that the recipient received the tokens.

How to Process Jetton Payments Safely

A Jetton payment is only trustworthy after the application verifies the token’s identity, the relevant wallet derivation, and the payment event itself.

The most dangerous mistake is trusting any contract that claims to be a wallet for a particular token.

An attacker can deploy a fake contract, return arbitrary data from getters, and send a message resembling transfer_notification.

If the backend checks only the ticker, name, image, or message body, it can credit a payment that carries no genuine value.

The official Jetton payment-processing guidance emphasizes validating the relationship between an approved master contract and the Jetton wallet sending the notification.

A safe flow includes:

  1. Maintain an allowlist of approved Jetton master addresses.
  2. Read the owner and master information from the relevant Jetton wallet as a consistency check.
  3. Independently derive the expected wallet address through the approved master’s get_wallet_address method and verify the match.
  4. Parse the standard transfer_notification and verify its actual message source.
  5. Validate the amount in integer base units.
  6. Confirm the approved token’s decimals configuration.
  7. Record the event idempotently.
  8. Store transaction or trace references for later reconciliation.

Reading a master address from an untrusted wallet is not enough. The verification must be anchored in a master contract your application already trusts.

The ticker USDT is not a token identity. The expected master contract, network, and verified Jetton wallet relationship establish which asset is being processed.

Why forward_ton_amount Matters

A service relying on transfer_notification needs the transfer to request that notification.

The standard sends it when forward_ton_amount is greater than zero. The documented minimum is one nanoton.

That is the notification-triggering minimum, not the total execution budget for the Jetton transfer. Deployment, message forwarding, and any receiving-contract logic need appropriate funding as well.

A transfer with zero forwarding value can move tokens without generating the notification your service expects.

I would define a recovery and reconciliation policy for that situation rather than equating “no notification observed” with “no tokens received.”

How TON NFTs Use the Same Distributed Design

TON NFTs generally use separate collection and item contracts instead of storing every item’s ownership in one global contract.

The TON NFT architecture overview describes a collection contract coordinating shared information and individual item contracts representing the NFTs.

Each item has its own address and ownership state.

This reduces the need for one contract to maintain every owner and item in a growing global map. The trade-off is that an indexer has to follow more accounts.

If an application needs a searchable catalog with verifiable ownership, it does not have to put every searchable attribute into contract storage.

A common arrangement is:

  • On-chain ownership and essential identifiers.
  • Off-chain metadata and search.
  • A URI or content reference in the standard format.
  • A backend index that can be rebuilt from blockchain data.

I prefer this separation when it preserves the ownership guarantees users need without turning expensive persistent state into a general-purpose content database.

How TON Connect Links a dApp to a Wallet

TON Connect lets a dApp request wallet actions without receiving the user’s private key.

The TON Connect protocol overview describes the standard connection layer between TON applications and wallets.

Depending on wallet capabilities, a dApp can:

  • Read the connected address.
  • Request a data signature.
  • Request a transaction.
  • Restore an existing connection.
  • Request proof of wallet control through ton_proof.

The dApp does not receive the private key and does not sign on the user’s behalf.

Sessions, Bridges, and Encryption

The TON Connect protocol specification and implementation materials define the connection and request mechanisms.

There is an important distinction between transports:

  • HTTP bridge communication uses end-to-end encrypted messages after the initial connection request.
  • An injected JavaScript bridge communicates locally with the wallet environment and does not use the same session-encryption transport.

The TON Connect session and bridge architecture explains why an HTTP relay should not be trusted with readable request contents.

Encryption does not make an application trustworthy by itself. Users still need to understand which application they are connecting and which action they are approving.

Installing TON Connect for React

For React, install:

npm install @tonconnect/ui-react

The TON Connect integration quickstart covers React, Next.js, and other supported approaches.

Creating the Manifest

A minimal manifest looks like this:

{
  "url": "https://app.example.com",
  "name": "Example App",
  "iconUrl": "https://app.example.com/icon-180.png"
}

The manifest must be publicly accessible over HTTPS.

The wallet fetches it to display the application’s name and icon before establishing the connection.

The endpoint must not:

  • Require authentication.
  • Block wallet requests through restrictive access rules.
  • Return an anti-bot challenge instead of JSON.
  • Return an application error page with a successful HTTP status.

Use a supported icon format and verify that both the manifest and image are accessible outside your own browser session.

Adding the Provider and Connection Button

The provider and button can look like this:

import {
  TonConnectButton,
  TonConnectUIProvider,
} from '@tonconnect/ui-react';

export function App() {
  return (
    <TonConnectUIProvider
      manifestUrl="https://app.example.com/tonconnect-manifest.json"
    >
      <Header />
      <Checkout />
    </TonConnectUIProvider>
  );
}

function Header() {
  return <TonConnectButton />;
}

Here, Checkout represents your application’s checkout component, implemented elsewhere in the project.

For Next.js, the provider and components using wallet hooks need to run on the client. Use an appropriate client component boundary.

TON Connect relies on browser storage and other browser APIs. The connected-wallet state is not simply available during server-side rendering.

I would also distinguish “connection restoration in progress” from “no wallet connected,” so the interface does not briefly display the wrong state on every reload.

How to Send a Simple TON Transaction

A TON Connect transaction request asks the wallet to sign and broadcast a message. It does not prove that the resulting business operation succeeded.

The following example requests a transfer of 0.1 TON.

Do not run this example unchanged with real funds. It contains a burn-address placeholder and explicitly selects mainnet. Replace the recipient with an address you control or have verified. For testing, use network '-3' and an appropriate testnet recipient.

import {
  useTonAddress,
  useTonConnectUI,
  useTonWallet,
} from '@tonconnect/ui-react';

export function PayButton() {
  const [tonConnectUi] = useTonConnectUI();
  const wallet = useTonWallet();
  const sender = useTonAddress();

  async function pay() {
    if (!wallet || !sender) {
      return;
    }

    await tonConnectUi.sendTransaction({
      validUntil: Math.floor(Date.now() / 1000) + 300,
      network: '-239',
      messages: [
        {
          address: 'UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJKZ',
          amount: '100000000',
        },
      ],
    });
  }

  return (
    <button type="button" disabled={!wallet} onClick={pay}>
      Pay 0.1 TON
    </button>
  );
}

The example uses the zero-account burn address only as a syntactically valid placeholder. A real application needs a verified treasury or contract address.

The network value '-239' identifies TON mainnet. The request expires after five minutes.

The TON Connect transaction request and response documentation describes the returned BoC as the serialized external message broadcast by the wallet.

That response is useful for locating the operation on-chain. It is not a receipt proving that the expected recipient, asset, amount, payload, and final result have been verified.

The backend still needs to perform those checks.

The returned message identifier is also not automatically interchangeable with every transaction or trace identifier exposed by an indexer.

Finally, a wallet may support only a limited number of messages in one request. Read its advertised and runtime feature information rather than assuming one universal maximum.

Production code should also handle user rejection, connection changes, expired requests, and errors from the wallet.

Why ton_proof Is Different From Connecting a Wallet

A connected address is not enough to authenticate a user to your backend. ton_proof supplies a cryptographic proof tied to an application-issued challenge.

Frontend state can tell you which wallet is selected. It cannot, by itself, establish to a server that the person making an HTTP request controls that wallet.

A typical authentication flow is:

  1. The backend generates a one-time nonce.
  2. The frontend passes the challenge to the wallet through TON Connect.
  3. The wallet signs the defined proof payload.
  4. The frontend submits the proof and account information to the backend.
  5. The backend validates the domain, timestamp, payload, wallet-address relationship, public key, and signature.
  6. The nonce is consumed atomically so it cannot be reused.
  7. The backend issues its own application session.

The server-side ton_proof verification guide explains the required message reconstruction and verification checks.

The nonce should be:

  • Cryptographically unpredictable.
  • Short-lived.
  • Bound to the login attempt.
  • Single-use.
  • Validated on the server.

The public key must also be tied to the claimed account through a supported wallet implementation and the appropriate initialization or on-chain checks.

Do not accept a public key simply because the client supplied it alongside a valid signature.

The backend should separately enforce the networks the application accepts. Do not assume the proof alone distinguishes every network context.

Wallet Sessions and Login Sessions Are Separate

A TON Connect session and an application login session have different lifecycles.

A user might:

  • Disconnect a wallet while remaining logged in.
  • Switch wallets.
  • Restore a previous wallet connection.
  • Log out of the application without revoking the wallet connection.

Define those behaviors explicitly.

I would not let an accidental frontend state transition decide which account owns a user’s subscription, withdrawal rights, or financial history.

Real-World Example: A Telegram Mini App Subscription and a TON Payment Backend

A subscription system needs a verified payment event, an order ledger, and an entitlement update. The allowed payment rail depends on where and what you are selling.

Imagine a Mini App offering monthly access to premium content.

For the purchase of that digital content inside Telegram, the application must use Stars under the Telegram payment rules for digital goods and services. It cannot offer Toncoin or USDT as an alternative in-app payment rail for that sale.

TON Connect can still support permitted wallet and blockchain features under Telegram’s blockchain integration guidelines.

To explain the TON settlement architecture, I will keep the same order-and-entitlement backend and examine a separate, permitted on-chain payment workflow. This is not a workaround for Stars requirements, and moving a payment link does not automatically resolve platform restrictions.

The TON payment-processing architecture overview describes why many business integrations combine on-chain settlement with off-chain order processing.

The Application Flow Without a Diagram Plugin

The permitted TON workflow has four technical layers: frontend, wallet, backend, and blockchain. The user initiates the process.

  1. The user opens the application. In a Telegram Mini App, Telegram supplies session information that the backend must validate.
  2. The frontend retrieves an order from the backend. It displays the asset, amount, recipient, and expiration.
  3. TON Connect opens the wallet request. The user reviews and approves the requested operation.
  4. The wallet submits the message to TON. The blockchain processes the resulting transaction or trace.
  5. The backend observes and validates settlement. It updates the order and entitlement records, and the frontend displays the verified result.

Telegram establishes the Telegram session. TON Connect links the wallet. TON provides the blockchain settlement layer. The backend maintains orders, access rights, and idempotent payment records.

These roles should not be collapsed into one “payment successful” callback.

Creating the Order

The backend creates the order first:

order_id
telegram_user_id
expected_asset
expected_amount
recipient
expires_at
status = pending

For a permitted native-coin payment, the application can use:

  • A unique deposit address.
  • A shared address with an invoice identifier in the comment.
  • A dedicated payment contract.

The legacy Toncoin payment-processing page has moved. The current native-coin deposit-processing guide compares invoice-based deposits with unique deposit addresses.

For a permitted Jetton payment, the application can use:

  • A unique deposit-wallet strategy.
  • An invoice identifier in forward_payload.
  • A dedicated contract receiving transfer notifications.

Do not identify an order by amount alone.

Two orders can have the same price. A user can pay from a different address than the one originally connected. A payment can also arrive after the order expires.

Those cases need defined matching and recovery rules.

Requesting Wallet Confirmation

For the TON payment branch, the frontend constructs a request with the exact:

  • Network identifier.
  • Expiration time.
  • Recipient.
  • Amount in base units.
  • Payload, if required.

The wallet displays the request and asks for approval.

After submission, the frontend can display submitted. It should not display paid merely because the wallet returned successfully.

A connection event and a signing response are not payment-verification events.

Monitoring the Blockchain

The backend follows relevant accounts and messages through an indexed API, streaming interface, or its own infrastructure.

For a native-coin payment, it checks:

  • The destination address.
  • The received value under the defined payment policy.
  • The message body or invoice identifier.
  • The relevant transaction result and finality.
  • Whether the event has already been processed.

For a Jetton payment, it checks:

  • The expected Jetton master.
  • The legitimacy of the relevant Jetton wallet.
  • The transfer_notification, when using notification-based processing.
  • The token amount and approved decimals.
  • The invoice information in forward_payload.
  • The complete trace when necessary.

The Stars purchase branch uses its own verified Telegram payment event. A blockchain observation must never be used to label a Stars invoice as paid.

Activating the Subscription or Entitlement

Only after validation should the backend perform an atomic database operation that:

  1. Records the payment event.
  2. Marks the order as paid.
  3. Extends the user’s access.
  4. Stores the transaction or trace references.

If the same event is delivered again, a unique constraint on the financial event identifier prevents duplicate posting.

Choose that identifier carefully. A trace can contain multiple relevant messages, so a trace ID alone may not uniquely identify one payment.

The record should retain enough network, account, transaction, and message information to reproduce the decision later.

This is where my banking background shapes the implementation: the system should be able to explain not just that access was granted, but exactly which verified payment justified it.

Processing Refunds

For an on-chain payment, a refund is a new blockchain operation, not a SQL update.

The system should track states such as:

refund_requested
refund_queued
refund_sent
refund_confirmed
refund_failed

A Jetton refund again involves a multi-message flow.

Do not mark it complete because a withdrawal worker handed a request to a wallet. Verify the recipient-side result according to the refund policy.

A Stars purchase needs the corresponding Telegram refund workflow rather than an unrelated on-chain transfer.

TON API v2, API v3, Streaming API, and Liteservers

TON applications can access blockchain data at different layers, from direct protocol-oriented queries to indexed histories and live events.

The right choice depends on what the application needs to verify and how much infrastructure it is prepared to operate.

Liteservers

A liteserver exposes access closer to the underlying network.

It is useful when you want direct state access and cryptographic proofs, but it requires more knowledge of blocks, shards, TL-B structures, and proof verification.

Receiving proof data is not the same thing as verifying it. The client needs to perform the appropriate checks if it wants the security benefits.

API v2

The TON Center API v2 overview describes REST and JSON-RPC access to operations close to the liteserver model.

Typical uses include:

  • Reading account state.
  • Broadcasting messages.
  • Running get methods.
  • Retrieving basic transaction and block information.

Because this layer is closer to raw blockchain structures, an application may need to perform more parsing and data correlation itself.

API v3

The TON Center API v3 overview describes an indexed data layer better suited to application queries.

Typical uses include:

  • Searching transactions and traces.
  • Filtering messages.
  • Inspecting account-related data.
  • Retrieving historical activity.
  • Working with decoded Jetton and NFT information.

An indexer can lag behind the network.

A not found response immediately after submission does not necessarily mean that the operation failed.

Streaming API

The previous TON Center streaming overview has moved to the current Streaming API documentation.

Streaming delivers live updates without aggressive polling. The current interface supports Server-Sent Events and WebSockets.

Your consumer still needs:

  • Reconnection handling.
  • Durable processing checkpoints.
  • Deduplication.
  • Historical gap recovery.

A particularly important limitation is that the live stream does not automatically replay all events missed during an outage.

After reconnecting, use historical API queries to recover the missing interval.

I would never make a payment ledger depend on the assumption that one WebSocket connection will remain open forever.

Should You Use a Hosted TON API or Run Your Own Node?

A hosted API is a sensible starting point, but production infrastructure should match the financial and operational risk of the application.

For a prototype, hosted infrastructure usually removes unnecessary work. For production, the decision is less about ideology and more about control, availability, and recovery.

ApproachAdvantagesTrade-offs
Hosted APIFast setup and convenient indexed dataRate limits, provider dependency, and indexing lag
Self-hosted liteserverMore operational control and direct accessNode maintenance and greater protocol complexity
Self-hosted indexer and APIControl over data models and historical queriesStorage, maintenance, monitoring, and recovery work
Hybrid architectureFailover and independent checksMore implementation and reconciliation complexity

My baseline for a payment service would include:

  • Retries with backoff.
  • Idempotent processing.
  • Monitoring of indexing delay.
  • Alerts when the consumer falls behind.
  • Periodic reconciliation between the database and blockchain records.
  • The ability to reindex a selected range.
  • A backup provider or independent verification path for critical operations.

One unexplained HTTP response should not be the only evidence behind a financial posting.

A second provider is useful, but it is not a substitute for understanding what each provider is returning and which stage of the operation it has observed.

How TON Transaction Fees Are Calculated

TON does not have one universal transaction price. The cost depends on execution, storage, message size, and network configuration.

The TON transaction-fee documentation separates the main components:

  • Storage fees.
  • Compute fees.
  • Forwarding fees.
  • Action fees.
  • Import fees.

The total cost of a business operation can span several transactions and messages.

Storage Fees

Storage cost depends on:

  • The number of stored bits.
  • The number of stored cells.
  • The time during which the data remains in state.

More persistent state generally creates a higher long-term cost.

Identical referenced cell subtrees can be deduplicated in relevant fee accounting. I would not treat that optimization as a replacement for a bounded, deliberate data model.

The account also needs sufficient funding to remain operational over time.

Compute Fees

TVM instructions consume gas.

The network configuration sets the gas price. Users do not choose an arbitrary gas price in the same way as a conventional gas-bidding interface.

The pricing model includes a flat component covering execution up to a configured threshold. Additional execution is charged according to consumption.

A function that works in a small test can become expensive when its dictionaries, payloads, or loop counts grow.

Forwarding and Action Fees

An internal message must travel from its source account to its destination.

Its cost depends on configured parameters and the message’s size in bits and cells.

Larger payloads, additional references, and attached StateInit can increase the cost.

Action fees are part of the forwarding-fee accounting. When working with API fee breakdowns, avoid counting the same forwarding cost twice.

If a business operation generates five internal messages, those messages all contribute to its overall cost.

Import Fees

An incoming external message must be imported for execution.

The import-fee component is related to the message’s serialized size and uses forwarding-style pricing parameters.

This is another reason the cost of a wallet request is not explained solely by the compute gas used by its destination contract.

Why I Would Not Hardcode a Universal Fee

Fee parameters live in blockchain configuration and can change.

A fixed value that passes a local test may later be insufficient, especially for a multi-message operation or a transfer that deploys a new recipient Jetton wallet.

A better approach is to:

  • Emulate the message before submission when the wallet or API supports it.
  • Read current configuration parameters when precise calculations are necessary.
  • Include a reasonable reserve for multi-message flows.
  • Return excess value through an appropriate response address where the protocol supports it.
  • Measure actual consumption in tests.
  • Avoid oversized payloads and unnecessary StateInit.

There is no universal price for “calling a contract.”

A simple native-coin transfer and a Jetton transfer requiring recipient-wallet deployment do not have the same execution path or fee budget.

TON Message Modes and Value Management

Send modes determine how outgoing messages handle value, fees, and certain failures. They are part of the contract’s security design.

Depending on the mode and flags, a contract can:

  • Send a specified amount normally.
  • Pay forwarding fees separately from the transferred value.
  • Carry the remaining value of the incoming message.
  • Send the account’s remaining balance.
  • Ignore certain action errors.
  • Request bounce behavior for action failure.
  • Destroy the account when the relevant conditions are met.

The TON sending-mode reference defines these behaviors and the restrictions on their combinations.

This is an area where “it worked in the happy-path test” is not enough.

Sending nearly the entire balance can leave the contract without the funds needed for storage or later messages.

Ignoring an eligible action error can allow execution to continue even though the intended outgoing message was not sent.

The ignore-errors flag does not suppress every possible error, and it certainly does not establish business success.

It is also important to distinguish the outgoing message’s bounce flag from send-mode flags governing action-phase failure. They address different parts of the execution flow.

I would document each chosen mode by its intended behavior, test the failure paths, and reserve the account’s required operating balance deliberately.

A magic number copied from another project is not a fee-management strategy.

TON Confirmation, Finality, and Frontend Status

A useful payment interface distinguishes wallet approval, blockchain observation, network finality, and business verification.

The frontend should have more than loading and success.

An application-level status model might include:

idle
wallet_confirmation
submitted
observed
confirmed
finalized
business_verified
failed
expired

These are application statuses, not a promise that every SDK exposes these exact names.

Their meanings should be explicit:

  • submitted: The wallet has returned a successful submission response.
  • observed: The backend has located the relevant message or transaction.
  • confirmed: The operation has reached the defined included-in-block stage, which may still precede finality in the API’s model.
  • finalized: The relevant transaction has reached TON’s network-finalized state, not merely an application-defined waiting period.
  • business_verified: The application has checked the recipient, asset, amount, payload, and required outcome.

TON’s Catchain 2.0 and Simplex consensus specification explains how validators reach agreement on finalized blocks.

For payment processing, distinguish a candidate shardchain observation from the masterchain-committed finality state exposed by the relevant infrastructure.

Pending or speculative API results can be invalidated before finality. A frontend showing those results needs a way to update its state correctly.

Even after one transaction is finalized, a later step in a multi-contract trace may still fail.

For a complicated operation, “Processing” is more honest and useful than a premature green success screen.

The Most Common TON Production Mistakes

Most serious integration errors come from confusing identity, submission, execution, and settlement.

These are the mistakes I would put on a production-readiness checklist.

Trusting a Token Ticker

A name and symbol do not identify a Jetton.

Verify the approved master contract on the intended network, then validate the relevant wallet relationship.

A convincing image or familiar ticker is not evidence of value.

Treating a Wallet Signature as Payment

A signed or broadcast message can expire before execution, fail to meet wallet requirements, lack sufficient funding, or lead to a failed downstream transaction.

The application needs blockchain verification, not just a successful signing callback.

Checking Only the Compute Exit Code

Successful computation does not establish that the action phase sent the expected messages.

Later transactions in the trace may fail as well.

Inspect the relevant phases, outgoing messages, and final recipient-side result.

Using Floating-Point Amounts

Keep blockchain amounts as integer base units.

The display amount belongs in the presentation layer. Financial comparisons and ledger entries should use exact integer values.

Mixing Raw and Friendly Addresses

One account can have several textual representations.

Parse and normalize before comparing addresses, and keep the network context explicit.

Do not compare two unprocessed strings and assume a mismatch means different recipients.

Omitting Idempotency Protection

Polling, reconnects, historical backfills, and index rebuilds can deliver the same event again.

Financial posting needs a durable unique event identifier and atomic database behavior.

In-memory deduplication is not enough after a restart.

Ignoring Bounce Messages

A contract can update local state and then fail to complete the remote step.

Without bounce handling or another recovery mechanism, the process can remain stuck with inconsistent assumptions.

Hardcoding Fees

A multi-message trace or a newly deployed Jetton wallet may need more funding than a simple transfer.

Estimate the relevant operation and maintain a reserve appropriate to its failure paths.

Writing Unbounded Loops

Dictionary processing and batch payouts need a per-transaction limit.

Split large workloads into bounded messages instead of assuming one transaction can process an arbitrarily large list.

Linking Telegram Identity to a Wallet Without Proof

Telegram session validation and wallet-control verification answer different questions.

The Telegram Mini App data-validation procedure establishes whether the Telegram session data is authentic and fresh.

ton_proof establishes control of the connected wallet under its verification rules.

Do not trust unvalidated initData or initDataUnsafe, and do not assume that a Telegram user ID proves ownership of a wallet address.

The backend needs an explicit, authenticated process for linking the two identities.

When TON Makes Sense for an Application

TON is a strong candidate when its distribution advantages and asynchronous architecture match the application’s actual requirements.

I would look closely at TON when the project has:

  • Telegram as an important distribution channel.
  • Frequent, relatively small transfers.
  • Digital assets or user-owned tokens.
  • Many independent user operations.
  • A need for wallet connection without taking custody of user keys.
  • Business processes that can be modeled asynchronously.
  • A backend capable of indexing and reconciling on-chain events.

Potential use cases include:

  • Telegram Mini App games with permitted blockchain features.
  • Content and membership platforms where on-chain settlement is permitted.
  • Creator tipping and payouts where allowed by the relevant platform rules.
  • Loyalty points represented as Jettons.
  • Digital-item marketplaces.
  • Bulk payouts.
  • Custody and payment services.
  • Applications receiving native currency or approved stablecoin Jettons.

The qualification matters: selling digital goods or services inside Telegram still requires Stars.

TON Connect can be an excellent wallet interface without being the permitted checkout method for every product.

I would also evaluate operational costs, not just headline transaction fees. A cheap transfer does not automatically make an unreliable payment system cheap to operate.

When TON Is Probably Not the Right Choice

TON is not automatically the best platform just because individual operations can have low fees.

Another platform may fit better if the project requires:

  • Direct redeployment of an existing Solidity system.
  • Deep EVM composability.
  • Synchronous calls across multiple protocols.
  • An audit and monitoring stack built exclusively for EVM execution.
  • A business process that cannot tolerate partial completion.
  • Liquidity available only on another chain.
  • A straightforward centralized payment service without a need for blockchain settlement.

An EVM application cannot usually be migrated by changing its wallet connector and rewriting the address parser.

The execution model may require a redesign of the contracts, recovery paths, indexer, and frontend states.

If an application does not need on-chain state, verifiable coordination, or user-controlled asset ownership, an ordinary database and conventional payment provider may be simpler and less expensive to maintain.

I would rather make that decision early than force a blockchain into a problem it does not improve.

A Practical TON Development Workflow

The most reliable path to production starts with the business event and works backward to the messages, contracts, and verification rules.

Here is the sequence I would use.

1. Define the Business Event

Before writing a contract, decide exactly what each statement means:

  • The payment was submitted.
  • The payment was confirmed.
  • The entitlement was activated.
  • The refund was completed.

Without those definitions, you cannot choose the message or transaction that represents success.

Also decide how the system handles underpayments, overpayments, late payments, and unmatched invoices.

2. Build a TON Connect Prototype

Host the manifest, connect a test wallet, and send a simple testnet transaction.

This validates the wallet experience before custom contract logic creates more variables.

If the product runs inside Telegram, confirm the permitted payment and blockchain features at this stage rather than after the checkout has been built.

3. Model the Entire Trace

Write down every account and every message involved in the operation.

Use a numbered flow or a simple process document that includes:

  • The expected path.
  • Bounce handling.
  • Timeout behavior.
  • Duplicate application requests.
  • Late responses.
  • Recovery or compensation.

A missing failure branch on paper usually becomes a missing recovery path in production.

4. Choose the Simplest Settlement Model

If a direct native-coin transfer solves the permitted use case, do not immediately write a custom contract.

Introduce a contract when you need automatic on-chain rules, shared state, or verifiable coordination between participants.

Extra code creates extra execution paths to fund, test, audit, and maintain.

5. Write the Wrapper Alongside the Contract

Define the wire format in one authoritative place.

The frontend, backend, and tests should not each hand-pack opcodes independently.

Use generated or shared bindings where appropriate, while keeping the asynchronous meaning of each operation visible.

6. Test Failures Deliberately

The happy path is only the beginning.

Test:

  • Bounces.
  • Insufficient execution funding.
  • Unauthorized senders.
  • Duplicate identifiers.
  • Malformed payloads.
  • Failed action phases.
  • Interleaved operations.
  • Late confirmations.

Verify both the contract state and the financial interpretation of each outcome.

7. Add a Durable Blockchain Consumer

The consumer must survive restarts, process events idempotently, and recover missed intervals.

Store durable progress only in a way consistent with the database updates it represents.

Otherwise, a crash between “checkpoint saved” and “payment posted” can create a permanent gap.

8. Test the Complete Flow on Testnet

Exercise the integration with different wallets and realistic interruptions.

Check:

  • Reconnection.
  • Request expiration.
  • Rejected signatures.
  • API lag.
  • Frontend reloads.
  • Mini App restarts.
  • Delayed downstream messages.

Use testnet equivalents rather than assuming a mainnet asset contract or recipient address has a usable testnet counterpart.

9. Implement Reconciliation

Run a periodic job that compares internal records with blockchain evidence.

It should identify:

  • Missing events.
  • Duplicate posting attempts.
  • Orders stuck in an intermediate state.
  • Unmatched deposits.
  • Refunds not yet verified.
  • Differences between expected and observed balances.

Reconciliation is not a patch for bad event processing. It is a separate control that helps detect when processing or infrastructure has failed.

10. Launch Mainnet With Limits

Start with limits on:

  • Payment amounts.
  • Withdrawal amounts.
  • Operation counts.
  • Supported assets.
  • Concurrent or batched activity.

Expand only after monitoring shows that trace processing, fee reserves, reconciliation, and recovery behave as intended.

For a financial application, a controlled launch is a feature, not a lack of confidence.

The Bottom Line: Think in Messages, Not Call Stacks

TON’s scaling approach is built into its architecture: workchains, dynamic shards, distributed account state, and asynchronous messages. The price is a more demanding execution model.

A developer coming from EVM needs to stop treating every business operation as one synchronous call stack.

A Jetton is not one token contract. A wallet is not just a browser extension. A successful transaction on one account is not necessarily a successful business operation. Storage is an ongoing cost, and message forwarding is part of the fee budget rather than a minor implementation detail.

For me, the most useful first experiment would not be launching a token or building a decentralized exchange.

I would build a small end-to-end system: an application interface, TON Connect, a permitted testnet operation, a backend consumer, and an idempotent order-confirmation process. If the product sells digital content inside Telegram, its purchase rail would remain Stars, with TON features kept within the permitted scope.

That prototype reveals the important questions quickly.

Can users complete the wallet flow? Can the backend follow the trace? Can the system distinguish submission from settlement? Can it recover without paying twice or granting access twice?

If the answers are solid, you have a foundation worth expanding. If they are not, adding more contracts will not solve the underlying problem.


Sponsored Partner: Volet.com

Looking for a simple, everyday way to buy, hold, and sell TON and Bitcoin alongside your traditional currencies and payment cards?