Skip to content

The TON Ecosystem

💡
Before you start

Python 3 and a terminal. No Telegram bot, no API key, no internet. macOS and Linux ship with Python; on Windows install it from python.org with “Add python.exe to PATH” ticked. Check with python3 --version. Everything below uses modules that come with Python.

The bot token in these scripts is a made-up string — never put a real one in a file you might share, which is a point step 5 makes rather forcefully. And before building anything real, confirm the exact signing procedure against Telegram’s current Mini Apps documentation: the shape below is what these checks look like, and a security check has to match the specification exactly rather than approximately.

Understanding the TON Ecosystem

TON is not just a cryptocurrency — it is a platform with a growing ecosystem of applications, tokens, services, and infrastructure. What makes TON unique is its deep integration with Telegram, giving it instant access to hundreds of millions of users.

Jettons: TON's Token Standard

Jettons are TON's equivalent of ERC-20 tokens on Ethereum. They follow the TEP-74 standard and allow anyone to create custom tokens on the TON blockchain.

  • How they work: Each Jetton has a master contract (defines supply, metadata) and wallet contracts (one per holder). This is different from Ethereum where one contract tracks all balances
  • Popular Jettons: USDT on TON, NOT (Notcoin), DOGS, STON, DeDust tokens
  • Security note: Anyone can create a Jetton with any name. A token called "USDT" in your wallet may not be the real USDT. Always verify the contract address
⚠️
Airdrop scam tokens

Scammers regularly airdrop fake Jettons to random wallets. These tokens often have names like "1000 USDT Claim" or "Free TON Reward." Interacting with them (trying to swap or claim) can drain your wallet. Ignore unsolicited tokens.

NFTs on TON

TON supports NFTs following the TEP-62 standard. Key features:

  • Telegram Usernames: Through Fragment, Telegram usernames (@name) are NFTs on TON that can be bought, sold, and transferred
  • Anonymous Numbers: Virtual phone numbers for Telegram, also NFTs on TON
  • Art and Collectibles: Traditional NFT art collections exist on platforms like Getgems
  • SBT (Soulbound Tokens): Non-transferable tokens used for identity verification, achievements, and memberships

TON DNS

TON DNS maps human-readable names (like myname.ton) to TON addresses, websites, or other resources. It works at the protocol level, not as an external service.

  • .ton domains: Resolve to wallet addresses, making it easy to send Toncoin to "alice.ton" instead of a long address string
  • Website hosting: .ton domains can point to TON Sites (decentralized web pages)
  • Subdomains: Domain owners can create subdomains for organization

TON Proxy and TON Sites

TON includes a built-in proxy network and decentralized website hosting:

  • TON Proxy: A network layer that routes traffic through TON nodes, providing censorship resistance. Similar in concept to Tor but integrated into the blockchain
  • TON Sites: Websites hosted on the TON network, accessible through TON Proxy. These sites cannot be taken down by traditional means
  • TON Storage: Decentralized file storage using a BitTorrent-like protocol built into TON. Files are stored across network participants

Telegram Mini Apps

Mini Apps are web applications that run inside Telegram. They are the primary way users interact with the TON ecosystem without leaving the Telegram app:

  • What they are: HTML/JS web apps rendered inside Telegram's built-in browser
  • TON Connect: A protocol that lets Mini Apps request wallet connections and transaction approvals
  • Popular examples: Notcoin (tap-to-earn game), Hamster Kombat, trading bots, DeFi interfaces
  • Revenue: Mini Apps can monetize through Telegram Stars and in-app Toncoin payments
⚠️
Mini App security risks

Mini Apps can request wallet connection and transaction approval. A malicious Mini App could trick you into signing a transaction that drains your wallet. Only use Mini Apps from trusted sources and always review what you are approving before confirming.

Major TON Projects

STON.fi The largest decentralized exchange (DEX) on TON. Swap Jettons, provide liquidity, and earn trading fees.
DeDust Another major DEX on TON with a focus on capital efficiency. Offers both volatile and stable pools.
Getgems The leading NFT marketplace on TON. Buy, sell, and create NFT collections.
Fragment Telegram's official marketplace for buying/selling usernames and anonymous numbers as NFTs on TON.
Tonstakers / Bemo / Hipo Liquid staking protocols that let you earn staking rewards while keeping your TON liquid.

Now Check Who a Mini App Is Really Talking To, in Five Steps

The TON ecosystem’s biggest advantage is that its applications open inside Telegram, where hundreds of millions of people already are. That convenience creates one specific security question: when a Mini App tells its server “this is user 778899”, why should the server believe it? In the next twenty-five minutes you will implement the answer, forge it four ways, and find the one secret that holds the whole scheme together. Every line of output below came from running these files.

1
Write the signer and the checker

Go: open a terminal in a folder you can write to, e.g. cd ~/Desktop (Windows: cd %USERPROFILE%\Desktop).

Do: save this as miniapp.py. It prints nothing; it is the toolbox. The clock is passed in as an argument rather than read from your system, so every result below is reproducible.

import hmac, hashlib
from urllib.parse import parse_qsl

def data_check_string(fields: dict) -> str:
    """Every field except the hash, sorted by name, one per line."""
    return "\n".join(f"{k}={v}" for k, v in sorted(fields.items()) if k != "hash")

def sign(fields: dict, bot_token: str) -> str:
    secret = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
    return hmac.new(secret, data_check_string(fields).encode(), hashlib.sha256).hexdigest()

def verify(init_data: str, bot_token: str, now: int, max_age: int = 86_400):
    fields = dict(parse_qsl(init_data))
    if "hash" not in fields:
        return "REJECTED: no hash at all"
    if not hmac.compare_digest(sign(fields, bot_token), fields["hash"]):
        return "REJECTED: hash does not match"
    age = now - int(fields.get("auth_date", 0))
    if age > max_age:
        return f"REJECTED: {age // 3600} hours old"
    return f"ACCEPTED: user {fields.get('id')}"

You should see: nothing. python3 miniapp.py should return to the prompt in silence.

If not: ImportError: cannot import name 'parse_qsl' means the import line was mistyped — it comes from urllib.parse. Note hmac.compare_digest rather than ==: comparing secrets with == leaks information through how long the comparison takes.

2
Produce a genuine sign-in and check it

Go: same folder.

Do: save this as login.py and run python3 login.py.

from urllib.parse import urlencode
from miniapp import sign, verify

BOT_TOKEN = "1234567:practice-token-not-a-real-one"
NOW = 1_700_000_000                      # a fixed clock, so your output matches this page

fields = {"id": "778899", "username": "jane", "auth_date": str(NOW - 60)}
fields["hash"] = sign(fields, BOT_TOKEN)
init_data = urlencode(fields)

print("what Telegram hands your page:")
print(" ", init_data)
print()
print("your server checks it:", verify(init_data, BOT_TOKEN, NOW))

You should see: an ordinary-looking query string, and an accepted user:

what Telegram hands your page:
  id=778899&username=jane&auth_date=1699999940&hash=5c8caf1a7b14800df6559038ea0ab48992a5df21aee6298f073aed3e6759fbea

your server checks it: ACCEPTED: user 778899

Everything except that final hash is plain, readable text the browser can edit. The hash is the only thing separating “Telegram says this is Jane” from “the page claims to be Jane”.

If not: ModuleNotFoundError: No module named 'miniapp' means the files are not in the same folder. A different hash means a field value differs; the signature covers every one of them.

3
Try to become somebody else

Go: same folder. Editing a query string is the easiest attack there is.

Do: save this as forge.py and run it. Two attempts: change the user id, and reuse a correctly signed sign-in from days ago.

from urllib.parse import urlencode
from miniapp import sign, verify

BOT_TOKEN = "1234567:practice-token-not-a-real-one"
NOW = 1_700_000_000

fields = {"id": "778899", "username": "jane", "auth_date": str(NOW - 60)}
fields["hash"] = sign(fields, BOT_TOKEN)

genuine = urlencode(fields)
forged = urlencode({**fields, "id": "1"})              # claim to be a different user
stale = dict(fields, auth_date=str(NOW - 400_000))
stale["hash"] = sign(stale, BOT_TOKEN)                 # correctly signed, just old

print("genuine           :", verify(genuine, BOT_TOKEN, NOW))
print("user id changed   :", verify(forged, BOT_TOKEN, NOW))
print("old but well signed:", verify(urlencode(stale), BOT_TOKEN, NOW))

You should see: both attacks refused, for two different reasons:

genuine           : ACCEPTED: user 778899
user id changed   : REJECTED: hash does not match
old but well signed: REJECTED: 111 hours old

The third line is the one people leave out. That sign-in is perfectly signed — it was genuine, once. Without the age check, anyone who ever captured a valid initData (from a browser history, a log file, a shared screenshot) could replay it forever. A signature proves authenticity, never freshness.

If not: if the second line is accepted, the forged copy is being re-signed — note that {**fields, "id": "1"} deliberately keeps the old hash.

4
See the mistake that makes all of it pointless

Go: same folder. This is the most common Mini App vulnerability, and it is not subtle.

Do: save this as naive.py and run it.

from urllib.parse import parse_qsl, urlencode
from miniapp import verify

BOT_TOKEN = "1234567:practice-token-not-a-real-one"
NOW = 1_700_000_000

def naive_server(init_data):
    """Reads the user id and believes it. This is the mistake."""
    return dict(parse_qsl(init_data)).get("id")

attack = urlencode({"id": "1", "username": "admin", "auth_date": str(NOW)})
print("attacker sends    :", attack)
print("naive server sees user:", naive_server(attack))
print("checked server says   :", verify(attack, BOT_TOKEN, NOW))

You should see: the same request believed by one server and refused by the other:

attacker sends    : id=1&username=admin&auth_date=1700000000
naive server sees user: 1
checked server says   : REJECTED: no hash at all

There is no hash at all in that request, and the naive server never looked for one. This is what “the app opens inside Telegram” is worth on its own: nothing. The attacker does not have to open it inside Telegram — they send the request directly. As a user, the practical consequence is that a Mini App’s security is entirely a property of code you cannot inspect, which is a reason to connect a wallet holding pocket money rather than savings.

If not: if the naive server prints None, the field name in the attack does not match what it reads — both must say id.

5
Find the single point everything rests on

Go: same folder. The check works. What is it trusting?

Do: save this as leaked.py and run it. The attacker has the bot token — from a public repository, a screenshot in a support chat, or an error log.

from urllib.parse import urlencode
from miniapp import sign, verify

REAL_TOKEN = "1234567:practice-token-not-a-real-one"
NOW = 1_700_000_000

stolen = REAL_TOKEN
forged = {"id": "1", "username": "admin", "auth_date": str(NOW)}
forged["hash"] = sign(forged, stolen)

print("forged with the stolen token:", verify(urlencode(forged), REAL_TOKEN, NOW))
print()
print("the same forgery once the token is rotated:")
print("  ", verify(urlencode(forged), "1234567:the-new-token-after-rotation", NOW))

You should see: a perfect forgery, and the one thing that stops it:

forged with the stolen token: ACCEPTED: user 1

the same forgery once the token is rotated:
   REJECTED: hash does not match

The bot token is not merely a way to send messages — it is the key that authenticates every user of the Mini App. Whoever holds it can sign in as anybody, including whichever account the app treats as an administrator. So it belongs in an environment variable on the server and nowhere else: never in front-end code, never in a repository, never in a screenshot, and never in a log line. And if it has ever been exposed, rotating it is not optional — as the second line shows, rotation is what actually invalidates a forgery.

If not: if the second line is accepted, the same token string was passed twice — the rotated token must be a genuinely different string.

🎉
Check yourself before moving on

Without scrolling up: a Mini App asks you to connect your wallet and sign a message to “verify ownership”. Given what steps 4 and 5 showed, what does opening it inside Telegram guarantee about the app, and what should you actually check before signing? Answer: opening it inside Telegram guarantees nothing about the app — Telegram provides the app with signed data about you, but does not vet what the app does with it, and an attacker can bypass the Telegram side altogether by talking to the server directly. Before signing, read what the signature actually authorises: a plain login proof is harmless, while a transaction or a token approval is not, and a wallet showing an unlimited approval for a “verification” is the moment to stop. Connect a wallet holding only what you can afford to lose in that app.

Now do it without the page: extend verify so it also refuses initData it has seen before — keep a set of hashes already accepted — then confirm that replaying a fresh, correctly signed sign-in twice succeeds once and fails the second time. You have just built the difference between “this was genuine” and “this is a new sign-in”, which is the same distinction step 3 exposed with the age check.

Summary

  • Jettons are TON's token standard — always verify contract addresses before trusting token names
  • NFTs on TON include Telegram usernames, phone numbers, and traditional collectibles
  • TON DNS provides human-readable .ton addresses at the protocol level
  • TON Proxy and Sites enable censorship-resistant web hosting
  • Telegram Mini Apps are the primary user interface for the TON ecosystem
  • Always review transaction details when connecting wallets to Mini Apps or dApps
🎉
You now understand the TON ecosystem!

You can navigate the landscape of TON applications and services while understanding the security implications of each.