Python 3, a terminal, and for the last step the tonaddr.py file from
the TON introduction tutorial. If you do not have it, open
Introduction to TON and do
its step 1 first — two minutes, one file. Check Python with
python3 --version; nothing else is installed.
No wallet is connected and no DeFi protocol is touched. You will build a working automated market maker in about twenty lines and then attack it, which teaches the mechanism far better than a live swap would — and costs nothing when it goes wrong.
DeFi on TON
Decentralized Finance (DeFi) on TON is a growing ecosystem of financial applications that operate without intermediaries. You can swap tokens, provide liquidity, lend assets, and earn yield — all through smart contracts on the TON blockchain.
The integration with Telegram makes TON DeFi more accessible than other chains, but this accessibility also means less experienced users are exposed to real financial risks. This tutorial focuses on understanding those risks.
Decentralized Exchanges (DEXs)
DEXs allow you to swap one token for another without a centralized intermediary:
- STON.fi: The largest TON DEX. Uses an automated market maker (AMM) model with liquidity pools
- DeDust: The second largest DEX. Offers both volatile and stable swap pools with optimized routing
- How swaps work: You trade against a liquidity pool (a smart contract holding two tokens). The price is determined by the ratio of tokens in the pool
- Slippage: Large trades can move the price against you. Always set a reasonable slippage tolerance (0.5-1% for stable pairs, 1-5% for volatile)
Liquidity Provision
You can earn fees by depositing tokens into DEX liquidity pools:
- How it works: Deposit equal value of two tokens into a pool. You earn a share of trading fees proportional to your share of the pool
- LP tokens: You receive LP (Liquidity Provider) tokens representing your position. These can be redeemed for your share plus earned fees
- Typical APR: 5-50%+ depending on the pool and trading volume
When the price ratio of your deposited tokens changes, you suffer "impermanent loss" — you end up with less value than if you had simply held the tokens. The more volatile the pair, the greater the risk. This loss becomes permanent when you withdraw.
Lending and Borrowing
TON's lending ecosystem is still maturing. Protocols like Evaa allow:
- Lending: Deposit tokens to earn interest from borrowers
- Borrowing: Provide collateral and borrow other assets
- Liquidation risk: If your collateral value drops below the required ratio, your position is liquidated (sold at a discount) to repay the loan
Smart Contract Risks on TON
TON's asynchronous, message-passing architecture creates unique smart contract vulnerabilities:
- Bounce handling bugs: When a message to a contract fails, a bounce message is sent back. If the sender contract does not handle bounces correctly, funds can be lost in limbo
- Message ordering: Because contracts communicate asynchronously across shards, the order of message delivery is not guaranteed. This can create race conditions
- Storage fee drainage: Contracts that accumulate too much state data without paying storage fees can be frozen, locking user funds
- Reentrancy (different from Ethereum): While traditional reentrancy is less of a concern due to the message model, new patterns of "callback reentrancy" via message chains exist
- Unverified contracts: Many TON contracts are not open-source or audited. You cannot verify what the code does before interacting with it
How to Evaluate a TON DeFi Project
Has the project been audited by a reputable security firm? Look for audit reports on the project's documentation or GitHub. Major firms auditing TON projects include CertiK, Quantstamp, and Trail of Bits.
Is the smart contract code publicly available? Closed-source contracts are a red flag — you cannot verify what they do with your funds.
Are the developers known and reputable? Anonymous teams are higher risk. Check their track record on previous projects.
How much total value is locked (TVL) in the protocol? How long has it been running? Newer protocols with low TVL are higher risk. Established protocols with significant TVL have more to lose from exploits.
If a protocol offers 1000% APY, ask where the yield comes from. Sustainable yields come from trading fees or lending interest. Unsustainable yields come from token emissions (printing new tokens), which dilute value over time.
Common DeFi Scams on TON
- Fake DEX frontends: Phishing sites that look like STON.fi or DeDust but steal your wallet connection
- Honeypot tokens: Tokens you can buy but cannot sell — the sell function is disabled in the contract
- Rug pulls: Projects that collect liquidity then drain the pool and disappear
- Flash loan manipulation: Attackers exploit price oracles to drain lending protocols
- Approval exploits: Malicious contracts that request unlimited token approval, then drain your wallet later
Now Build a DEX and Attack It, in Five Steps
Every risk in this article — slippage, front-running, impermanent loss, fake tokens — comes out of one formula that fits on a line. In the next twenty-five minutes you will implement that formula, watch your own trade get sandwiched by a bot, find the setting that stops it, price what a liquidity provider actually gives up, and check a token’s real identity. Every number below came from running these files.
Go: open a terminal in a folder you can write to, e.g.
cd ~/Desktop (Windows: cd %USERPROFILE%\Desktop).
Do: save this as amm.py. It prints nothing; it is the
exchange the next four steps trade against. There is no order book and no buyer on the other
side — just a pool whose two balances must multiply to the same number after every
trade.
class Pool:
"""A constant-product market maker: ton * jetton never falls."""
def __init__(self, ton, jetton):
self.ton, self.jetton = float(ton), float(jetton)
@property
def price(self):
return self.ton / self.jetton
def buy(self, spend_ton):
k = self.ton * self.jetton
got = self.jetton - k / (self.ton + spend_ton)
self.ton += spend_ton
self.jetton -= got
return got
def sell(self, jettons):
k = self.ton * self.jetton
got = self.ton - k / (self.jetton + jettons)
self.jetton += jettons
self.ton -= got
return got
You should see: nothing at all. python3 amm.py returns you
to the prompt, which means it parses.
If not: IndentationError means the methods lost their indent
under class. If @property raises a syntax error, the decorator line
must sit immediately above def price with nothing between.
Go: same folder.
Do: save this as impact.py and run it. Each line starts from
an identical, untouched pool.
from amm import Pool
for spend in (10, 100, 1_000, 10_000):
p = Pool(100_000, 100_000)
fair = spend / p.price
got = p.buy(spend)
print(f"spend {spend:>6,} TON -> {got:>10,.1f} jettons "
f"(a fair price would give {fair:>10,.1f}, so you lose {1 - got / fair:.2%})")
You should see: the cost of size:
spend 10 TON -> 10.0 jettons (a fair price would give 10.0, so you lose 0.01%)
spend 100 TON -> 99.9 jettons (a fair price would give 100.0, so you lose 0.10%)
spend 1,000 TON -> 990.1 jettons (a fair price would give 1,000.0, so you lose 0.99%)
spend 10,000 TON -> 9,090.9 jettons (a fair price would give 10,000.0, so you lose 9.09%)
This is price impact, and it is not a fee — it is the mathematics of the pool. Your own buying moves the price against you, and the loss grows with your size relative to the pool. It is the reason a thin pool is dangerous regardless of how honest the project is: the smaller the pool, the more a normal trade costs you.
If not: if every line shows the same loss, the pool is being reused
instead of recreated — Pool(...) must be constructed inside the loop.
Go: same folder. Your pending trade is visible before it executes. A bot buys just before you and sells just after.
Do: save this as sandwich.py and run it.
from amm import Pool
victim_spend = 1_000
p = Pool(100_000, 100_000)
alone = p.buy(victim_spend)
print(f"if nobody interferes, you get : {alone:,.1f} jettons")
p = Pool(100_000, 100_000)
attacker_jettons = p.buy(20_000) # the attacker buys FIRST, pushing the price up
yours = p.buy(victim_spend) # your order now fills at the worse price
attacker_ton = p.sell(attacker_jettons) # the attacker sells straight after
print(f"sandwiched, you actually get : {yours:,.1f} jettons")
print(f"you lost : {alone - yours:,.1f} jettons ({1 - yours / alone:.2%})")
print(f"the attacker put in 20,000 TON and took out {attacker_ton:,.1f}, "
f"profit {attacker_ton - 20_000:,.1f} TON")
You should see: nearly a third of your purchase taken, legally, by arithmetic:
if nobody interferes, you get : 990.1 jettons
sandwiched, you actually get : 688.7 jettons
you lost : 301.4 jettons (30.44%)
the attacker put in 20,000 TON and took out 20,306.5, profit 306.5 TON
Nothing was hacked. The attacker used the same public exchange you did, in the same way, a moment earlier and a moment later. This is why a swap that looked fine when you pressed the button can settle far worse — and why the next step’s setting exists.
If not: if the attacker’s profit is negative, the sell is happening before your buy — the three calls must run in the order written.
Go: same folder. Every swap interface has a slippage tolerance, usually hidden behind a gear icon and usually left alone.
Do: save this as tolerance.py and run it. The attack from
step 3 happens every time; only your floor changes.
from amm import Pool
victim_spend, expected = 1_000, 990.1
for tolerance in (0.005, 0.01, 0.05, 0.30, 0.50):
p = Pool(100_000, 100_000)
p.buy(20_000) # attacker front-runs
got = p.buy(victim_spend)
minimum = expected * (1 - tolerance)
verdict = "FILLED" if got >= minimum else "REVERTED -- your funds stay put"
print(f"slippage tolerance {tolerance:>5.1%}: you would receive {got:>7,.1f}, "
f"floor {minimum:>7,.1f} -> {verdict}")
You should see: the protection working until you switch it off yourself:
slippage tolerance 0.5%: you would receive 688.7, floor 985.1 -> REVERTED -- your funds stay put
slippage tolerance 1.0%: you would receive 688.7, floor 980.2 -> REVERTED -- your funds stay put
slippage tolerance 5.0%: you would receive 688.7, floor 940.6 -> REVERTED -- your funds stay put
slippage tolerance 30.0%: you would receive 688.7, floor 693.1 -> REVERTED -- your funds stay put
slippage tolerance 50.0%: you would receive 688.7, floor 495.1 -> FILLED
Read the last line carefully, because it is the most common self-inflicted loss in DeFi. A swap that keeps failing tempts people to raise the tolerance until it goes through — and a high tolerance is an instruction to accept any price down to that floor. If a swap fails at 1%, the honest reading is that the pool is too thin for your size or somebody is waiting for you, and the answer is a smaller trade, not a bigger allowance.
If not: if every line says FILLED, the minimum comparison is
inverted — the trade fills only when what you receive is at or above the floor.
Go: same folder, beside tonaddr.py.
Do: save this as identity.py and run it. On TON a token is a
jetton, and its identity is the address of its minter contract — not its name,
which anybody may copy.
import hashlib
from tonaddr import encode
def jetton_address(minter_name):
"""A jetton's identity is its MINTER contract address, never its name or symbol."""
return encode(hashlib.sha256(minter_name.encode()).digest())
real = jetton_address("official-usdt-minter")
fake = jetton_address("copycat-usdt-minter")
print("both tokens display the same name and symbol: USDT")
print("the one in the official list :", real)
print("the one the swap page linked :", fake)
print("same token:", real == fake)
You should see: two entirely different tokens wearing one name:
both tokens display the same name and symbol: USDT
the one in the official list : EQANPe5J8_ntdEoqZklA9p0LaK9thJO4S5qZiGLOvJpEw74F
the one the swap page linked : EQBgf-LvUCwVuaVed8cR7PaJ2o6KRMAAc5RIz1AJFy5mK5qK
same token: False
Creating a jetton called USDT with the same logo costs almost nothing, and a swap link can point at it. Your wallet will show the name it was given. Before a first swap into any token, compare the minter address in the swap interface against the address published by the project itself — on their own site or in the exchange’s verified list — character by character at both ends. Do it once per token; after that your wallet remembers.
If not: ModuleNotFoundError: No module named 'tonaddr' means
the file from the introduction tutorial is not in this folder. Your two addresses will differ
from the ones above only if the quoted minter names were changed.
Without scrolling up: your swap keeps failing with “price impact too high”, and a helpful reply in a group chat tells you to set slippage to 49%. What is actually happening, and what should you do? Answer: the pool is too thin for the size you are trading, or a bot is positioned to sandwich you — either way the failure is the protection doing its job, as step 4 showed. Raising the tolerance does not fix the price; it instructs the swap to accept a far worse one, which is exactly what the person advising you may be waiting for. The right responses are to trade a smaller amount, split it into several, choose a pool with deeper liquidity, or not to trade this token at all.
Now do it without the page: in sandwich.py, shrink the pool
to Pool(10_000, 10_000) and re-run it, then grow it to
Pool(1_000_000, 1_000_000). Watch the attacker’s profit on the same 1,000
TON trade rise and fall. You have just derived the rule that governs safe trade sizing:
what matters is not how much you are trading, but how much you are trading relative to
the pool.
Summary
- TON DeFi includes DEXs, liquidity pools, lending, and yield farming
- Impermanent loss is the biggest risk for liquidity providers
- TON's asynchronous contracts create unique vulnerability classes
- Always check for audits, open-source code, and team reputation before using a protocol
- Unrealistic yields are the primary red flag for scams
- Start small, test with amounts you can afford to lose, and never invest more than you can lose
With this knowledge, you can evaluate projects critically and protect yourself from the most common risks.