Jetton Token Development on TON: Architecture, Contracts, and Deployment
When porting tokens from EVM to TON, many developers are caught off guard by asynchrony: a holder's balance resides not in a single contract but in a separate Jetton Wallet. This breaks familiar patterns — transfer becomes a chain of messages, not an atomic call. Our team has over 5 years of experience in blockchain development and guarantees a correct implementation of the Jetton standard turnkey. We have successfully completed 10+ projects on TON, including DeFi protocols and NFT marketplaces. Gas optimization savings can reach 30% when using Tact, reducing token ownership costs. Contact us for a Jetton token turnkey development — we ensure security and gas optimization.
Architecture: Two Contracts
Jetton Master — the central contract, stores token metadata (name, symbol, decimals, total_supply) and can mint new Jetton Wallets. The full specification is described in TEP-74.
Jetton Wallet — one instance per holder. Stores the balance of a specific address. On transfer, the sender's Jetton Wallet sends a message to the recipient's Jetton Wallet. This is not an atomic call but a chain of asynchronous messages. If the recipient's Jetton Wallet does not yet exist, it is created on first token receipt, and the sender pays for deployment (roughly 0.04 TON storage deposit). Storage on TON incurs storage fees — about 0.005 TON per kilobyte per month, so each wallet must maintain a positive TON balance. In practice, we add a minimum of 0.05 TON per wallet — approximately 0.1 USD at current rates — to avoid freezing.
How Does the Jetton Contract Work?
The TEP-74 standard defines message structures and transfer logic. Let's look at transfer processing in a Jetton Wallet:
;; Jetton Wallet: handling transfer message
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (op == op::transfer()) {
int query_id = in_msg_body~load_uint(64);
int jetton_amount = in_msg_body~load_coins();
slice to_owner_address = in_msg_body~load_msg_addr();
slice response_address = in_msg_body~load_msg_addr();
cell custom_payload = in_msg_body~load_maybe_ref();
int forward_ton_amount = in_msg_body~load_coins();
slice forward_payload = in_msg_body;
throw_unless(error::not_enough_jettons, jetton_amount <= balance);
balance -= jetton_amount;
save_data();
var msg_body = begin_cell()
.store_uint(op::internal_transfer(), 32)
.store_uint(query_id, 64)
.store_coins(jetton_amount)
.store_slice(my_address())
.store_slice(response_address)
.store_coins(forward_ton_amount)
.store_slice(forward_payload)
.end_cell();
var to_wallet_address = calc_jetton_wallet_address(to_owner_address);
send_raw_message(
begin_cell()
.store_uint(0x18, 6)
.store_slice(to_wallet_address)
.store_coins(forward_ton_amount + min_ton_for_storage)
.store_uint(1, 107)
.store_ref(msg_body)
.end_cell(),
64
);
}
}
The code above is the basis of a standard Jetton Wallet. Note the call to calc_jetton_wallet_address — the recipient's address is deterministically computed from the holder's address and wallet code.
Why Choose Tact Over FunC?
Tact is a high-level language that compiles to FunC. Its syntax is similar to TypeScript, significantly accelerating development. Tact is 5 times faster for creating Jetton tokens compared to FunC, especially for teams with EVM experience. Example contract in Tact:
import "@stdlib/deploy";
import "@stdlib/jetton";
contract JettonMaster with Deployable, Jetton {
totalSupply: Int as coins;
owner: Address;
content: Cell;
mintable: Bool;
init(owner: Address, content: Cell) {
self.totalSupply = 0;
self.owner = owner;
self.content = content;
self.mintable = true;
}
receive(msg: TokenMint) {
require(sender() == self.owner, "Not owner");
require(self.mintable, "Not mintable");
self.totalSupply += msg.amount;
let winit: StateInit = self.getJettonWalletInit(msg.receiver);
let walletAddress: Address = contractAddress(winit);
send(SendParameters{
to: walletAddress,
value: ton("0.05"),
mode: SendIgnoreErrors,
bounce: false,
body: TokenTransferInternal{
queryId: 0,
amount: msg.amount,
from: myAddress(),
responseAddress: msg.receiver,
forwardTonAmount: 0,
forwardPayload: emptySlice(),
}.toCell(),
code: winit.code,
data: winit.data,
});
}
}
| Characteristic | FunC | Tact |
|---|---|---|
| Abstraction level | Low | High |
| Syntax | Specific | Similar to TypeScript |
| Development speed | Slow | High (5x faster) |
| Control | Full | Partial (via FunC inserts) |
How We Ensure Jetton Token Security
Reentrancy is a major threat in TON's asynchronous environment. Unlike EVM, where state is locked until transaction end, in TON each call is a separate message. If the transfer handler does not check balance before and after operations, an attacker can initiate a reentrant call before state changes. In our projects, we use the "check-effects-interactions" pattern and add reentrancy protection via flags in wallet data. We also thoroughly test all scenarios using fuzzing: Echidna for FunC and built-in fuzzers in Blueprint.
Process: Stages
- Analysis: Discuss token requirements (standard or custom), choose stack (Tact/FunC).
- Design: Contract architecture, define mechanics (transfer tax, whitelist, vesting).
- Implementation: Write Jetton Master and Jetton Wallet, unit tests via Blueprint sandbox.
- Testing: Simulate all scenarios, check for reentrancy and gas optimization.
- Deployment and verification: Deploy to mainnet, verify code on tonviewer.com, integrate with wallets.
| Stage | Duration | Result |
|---|---|---|
| Analysis | 1–2 days | Technical specification |
| Design | 1–2 days | Contract architecture |
| Implementation | 3–7 days | Source code + tests |
| Testing | 1–2 days | Test report |
| Deployment + verification | 1–2 days | Working token on mainnet |
Gas and Storage: TON Specifics
On TON, gas works differently than on EVM. Key differences:
- Storage fee — accounts pay rent for storing data. If the TON balance on a Jetton Wallet drops to zero, the account is frozen and data is lost. Recommended minimum deposit for a Jetton Wallet is 0.05 TON.
-
Forward TON — when sending Jetton with
forward_ton_amount > 0, the recipient contract receives a notification with attached TON. This is analogous toapprove + transferFrom, but in TON style. - Gas for a transfer transaction is about 0.001 TON, significantly cheaper than Ethereum at current prices.
Testing
The official Sandbox (Blueprint) framework allows testing contracts in TypeScript. Example test:
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { JettonMaster } from '../wrappers/JettonMaster';
import { JettonWallet } from '../wrappers/JettonWallet';
describe('Jetton', () => {
let blockchain: Blockchain;
let deployer: SandboxContract<TreasuryContract>;
let jettonMaster: SandboxContract<JettonMaster>;
beforeEach(async () => {
blockchain = await Blockchain.create();
deployer = await blockchain.treasury('deployer');
jettonMaster = blockchain.openContract(
await JettonMaster.fromInit(deployer.address, buildMetadataCell())
);
await jettonMaster.send(deployer.getSender(), { value: toNano('0.1') }, {
$$type: 'Deploy',
queryId: 0n,
});
});
it('should mint tokens', async () => {
const receiver = await blockchain.treasury('receiver');
const mintResult = await jettonMaster.send(
deployer.getSender(),
{ value: toNano('0.2') },
{ $$type: 'TokenMint', queryId: 0n, amount: toNano('1000'), receiver: receiver.address }
);
expect(mintResult.transactions).toHaveTransaction({
from: jettonMaster.address,
deploy: true,
success: true,
});
const walletAddress = await jettonMaster.getGetWalletAddress(receiver.address);
const wallet = blockchain.openContract(JettonWallet.fromAddress(walletAddress));
const data = await wallet.getGetWalletData();
expect(data.balance).toBe(toNano('1000'));
});
});
What's Included in the Work
Development of Jetton Master and Jetton Wallet in Tact (or FunC on request), testing via Blueprint sandbox, deployment to TON mainnet, verification through tonviewer.com, and TypeScript wrapper scripts for integration. Timelines: 5–10 days for a standard Jetton, 2–4 weeks for custom mechanics. Development cost savings when using Tact can reach 30% due to faster implementation.
We guarantee security and gas optimization. If you need a reliable Jetton token implementation with a security guarantee, contact us for a preliminary assessment. Get a consultation on Jetton tokens and evaluate the possibilities for your business.







