Skip to main content
T TON Adoption
Basics GUIDE · 2026

TEP-74: TON Jetton Standard Deep Dive

Technical deep dive into TEP-74: jetton master + wallet pattern, transfer messages, forward fees, mint/burn, get-methods, and how jetton differs from ERC-20.

Author
TON Adoption Team · research desk
Published
7 min read

TEP-74 is the technical document that defines the jetton token interface on TON. Whenever you see USDT, NOT, STON, hTON, or any other non-native token in your wallet, TEP-74 is almost certainly running under the hood. This article walks through how the standard works, why jetton differs fundamentally from ERC-20, what TEP-74 standardizes (and what it deliberately does not), and whether you should write your own contract from scratch or use a reference implementation.

The piece is aimed at developers preparing to launch a jetton and curious users who want to understand what really happens when they press “Send USDT”.

What a TEP is and where it comes from

TEP stands for TON Enhancement Proposal, by analogy with EIP (Ethereum) and BIP (Bitcoin). TEPs formalize interfaces, data formats, and contract behavior. They live in the ton-blockchain/TEPs repository on GitHub. Acceptance is an open-source process: someone drafts a proposal, the community discusses it, and at some point it becomes Final once it is de-facto standard.

The token/NFT TEP family looks like this:

TEPWhat it coversStatus
TEP-62NFT standard (item + collection)Final
TEP-64Token metadata (on-chain / off-chain)Final
TEP-66NFT royalty standardFinal
TEP-74Jetton standard (master + wallet)Final
TEP-85SBT — Soul-Bound TokenFinal
TEP-89Discoverable jettonsFinal

TEP-74 was finalized in 2022 and has been the baseline interface for fungible tokens on TON ever since. TEP-89 extends TEP-74 with a get_wallet_address get-method on the master, so wallets and explorers can deterministically compute jetton-wallet addresses without scanning the network.

Master + wallet: the core architectural decision

In Ethereum an ERC-20 token is a single contract. Inside that contract there is a mapping(address => uint256) balances. When Alice sends 100 USDT to Bob, she calls transfer() on that one contract and it updates two slots in the table.

TON takes a different path. Every jetton has a master contract (sometimes called the minter) and a separate jetton-wallet contract for every holder. For USDT-jetton on TON that means one master plus tens of millions of jetton-wallets — one per address that has ever held USDT.

When Alice sends 100 USDT to Bob, the flow is:

  1. Alice sends a transfer message to her USDT jetton-wallet.
  2. Her jetton-wallet decreases the local balance by 100 and sends an internal_transfer to Bob’s jetton-wallet.
  3. Bob’s jetton-wallet, upon receipt, increases its local balance by 100 and emits a transfer_notification to Bob’s regular TON wallet address.
  4. Excess gas is returned to Alice through an excess message.

The master contract is not involved in the transfer chain itself. It only handles minting, burning, and metadata. That is critical: jetton transfers are parallelizable, because they do not hit a single bottleneck contract.

Why all this complexity

The per-holder wallet idea looks excessive until you remember that TON shards. When load grows, shards split. If all USDT balances lived in a single contract, that contract would sit in one shard and USDT throughput would be capped by that single shard’s capacity. With per-holder wallet contracts, Alice’s and Bob’s balances may live in different shards and their transactions process in parallel.

The cost: the sender pays gas for two contracts (their own wallet + the recipient’s wallet) plus a forward fee on the internal message. In practice a transfer costs 0.03-0.05 TON, versus $0.5-3 on Ethereum depending on gas price. In dollar terms TON wins almost every time.

Transfer message structure

TEP-74 fixes op-codes and TL-B schemas. The key operations are:

  • 0x0f8a7ea5 — transfer (external, from user)
  • 0x178d4519 — internal_transfer (between two jetton-wallets)
  • 0x7362d09c — transfer_notification (from recipient wallet to owner)
  • 0xd53276db — excesses (return of leftover gas)
  • 0x595f07bc — burn
  • 0x178d4519 — internal mint (master to wallet during issuance)

An external transfer from the user carries: query_id, amount (tokens to send), destination (the recipient’s regular TON address, not their jetton-wallet), response_destination (where to send excess gas), custom_payload, forward_ton_amount (how much TON to attach to the recipient notification), forward_payload (arbitrary data the recipient’s dApp can read).

The forward_payload field is critical for DEXes and DeFi: that is exactly how STON.fi or DeDust receives a “swap these 100 USDT for TON” command when the user sends a jetton directly to the router.

Get-methods: reading state without gas

TEP-74 mandates two get-methods:

get_jetton_data() on the master returns: total_supply, mintable flag, admin_address, jetton_content (cell with TEP-64 metadata), jetton_wallet_code (code from which all wallet addresses are deterministically derived).

get_wallet_data() on a jetton-wallet returns: balance, owner_address, jetton (master address), jetton_wallet_code.

From this pair a useful property follows: given the owner address and the master address, any client can locally (without hitting the network) derive the corresponding jetton-wallet address. That is how Tonkeeper, MyTonWallet, and explorers display balances across jettons — they do not scan the entire chain, they take a known list of master addresses and compute the wallet address for the current user.

TEP-89 adds get_wallet_address(owner) on the master for clients that do not want to redo the derivation themselves.

Mint and burn

Minting a jetton is a message from master to the future holder’s address. The master creates the jetton-wallet (if it does not exist yet) and sends it an internal_transfer increasing the balance. TEP-74 does not fix an op-code for mint — that is intentional, because each issuer can implement any access policy: single admin, multisig, governance vote, vesting.

Burn (op 0x595f07bc) is a message from the user to their own jetton-wallet. The wallet decreases the balance and sends burn_notification back to the master, which lowers total_supply. Burn is mainly used in bridge flows: to move USDT from TON to Ethereum, USDT-jetton is burned on TON and the bridge releases an equivalent on the destination network.

What TEP-74 deliberately does NOT standardize

This is arguably the most important section. TEP-74 only defines the base interface. Everything beyond it is issuer-defined:

  • Pause / freeze. The standard has neither pause nor freeze. But Tether’s USDT-jetton can do both — because the issuer added admin-only freeze logic on top. The community debates this, but it is fully compliant with TEP-74.
  • Blacklist. Similar — bolted on top of the standard.
  • Upgrade. A contract may be upgradeable (set_code) or immutable — TEP-74 does not pick. Tether’s contract is upgradeable; most community tokens are not.
  • Royalty / transfer fee. A fee on transfer can be implemented, but it is non-standard and most ecosystem tooling will not account for it.
  • Metadata stability. TEP-64 allows off-chain metadata (IPFS, HTTPS) — but the standard gives no guarantee that the hosting outlives the issuer.

Jetton vs ERC-20: side-by-side

PropertyERC-20Jetton (TEP-74)
Contracts per token11 master + N wallets
Transfer costone storage write of gasgas of two contracts + forward
Parallelismnone, single contractyes, balances across shards
Balance lookupbalanceOf(addr) view callget-method on derived address
Approve / allowanceyesnone (replaced by forward_payload)
Freeze standardnone (impl-defined)none (impl-defined)
Finality~12-15 PoS blocks~5s masterchain

The absence of approve deserves its own note. In Ethereum a DEX requires the user to first approve, then swap — two transactions. On TON it is a single transaction: the user sends the jetton directly to the DEX router and puts the swap command in forward_payload. Simpler and safer — no perpetual allowances for drainer sites to exploit.

Should you write a TEP-74 contract from scratch?

For the overwhelming majority of cases, no. There are three practical paths:

  1. TON Foundation reference implementation in FunC — the most conservative option. The code has been audited many times and powers the vast majority of jettons.
  2. Tact — a modern language; the jetton template ships in @ton-community/jetton. Good if the team does not want to dive into FunC.
  3. Tolk — the newest TON language, also has a ready jetton template. Faster than FunC, easier to read.

Writing from scratch only makes sense for non-standard logic: vesting, rebasing, on-chain governance with dynamic supply. In that case budget for 2-3 independent audits — bugs in jetton contracts are usually irreversible.

Pre-launch checklist for your own jetton

  • TEP-64 metadata: name, symbol, decimals (typically 9 for TON-native jettons), description, image. Host on IPFS or at least your own CDN.
  • Mint policy is defined: one-shot, vesting, governance-driven.
  • Whether admin powers (freeze, upgrade) exist is decided and publicly documented.
  • TEP-89 is implemented so wallets and explorers can resolve wallet addresses quickly.
  • Contract is verified on Tonviewer / tonscan — otherwise users will not see the source.
  • Indexers (DTON, TON API) are notified so DEX aggregators pick the jetton up.

Where to go next

The standard is best studied alongside the network architecture. If you have not yet wrapped your head around how TON parallelizes transactions across shards, move on to the sharding deep dive. If the contract languages themselves are what you need — there are dedicated guides on FunC vs Tact and on Tolk. And the user-facing side of jetton (how to add a custom token to a wallet, why different wallet addresses look so similar) is covered in the beginner-friendly jetton overview.

Frequently asked

ERC-20 keeps all balances in a single contract. Jetton uses one master contract plus a dedicated wallet contract per holder. That makes the sender pay more per transfer, but enables horizontal scalability and parallel processing across shards.
Pause, freeze, blacklist, and upgrade logic. These are issuer-defined. That is why Tether's USDT-jetton has freeze authority while most community jettons do not.
No. For the vast majority of cases the reference implementation from the TON Foundation or the Tact/Tolk jetton template is enough. Writing from scratch only makes sense for non-standard logic like vesting or rebasing.
Roughly 0.03-0.05 TON per transfer. Part of it covers the forward fee to the recipient's jetton-wallet; excess gas is returned to the sender via response_destination.

Related