ZALUPA is a tap game that lives inside Telegram. You wait for the eggplant to itch, you scratch it, and you earn the in-game coin against a public leaderboard. It is small and deliberately silly, and underneath it are two problems that are not silly at all: running a game economy nobody can cheat, and building something coin-shaped without misleading a single person about what it is.
The screenshots below are the actual app, not the landing page — captured from a local instance with invented players, because a page about a product should show the product.
You cannot just tap
The single decision the whole game hangs on: the eggplant is not always tappable. It alternates between CALM and ITCHING. An itch lasts about ten seconds; the calm between them is a random thirty to a hundred and twenty. Outside the window, tapping does nothing at all.
That turns an idle game into an appointment. It also caps the economy structurally rather than by a rate limiter bolted on afterwards: with a mining rate of one and a minimum of 120 ms between scratches, a window is worth at most a couple of hundred coins no matter how fast anybody is.
The length of the calm phase is derived from a stored seed rather than rolled each time the row is read. That sounds like a detail and is not: without it, two readers of the same stale row disagreed about whether the player was itching — the server and the screen would tell different stories about the same second.
The client draws, the server decides
Anything the client is allowed to decide, the client can be made to lie about. A mini app is a web page: its JavaScript can be read, edited and replayed. If the browser reported "I earned nine thousand", the leaderboard would be fiction inside a day.
So the client sends intent and draws the result. Telegram's signature on the launch parameters is verified server-side, and a launch payload that has been seen before is refused as a replay. Every balance change, every rate limit, every leaderboard position is computed where the player cannot reach it.
- Four earning buckets, not one number
- A balance is split across tapping, battles, helping and multiply bonuses, with referrals and admin adjustments kept separate. The wallet shows where the coins came from — folding referral payouts into the bonus bucket had told a player that a support credit was a Multiply reward.
- A balance that has to match its own history
- The stored balance is a cache of the four buckets, which is itself a cache of the ledger rows. Both invariants are asserted by tests, because a denormalised total nobody checks is exactly how a balance quietly drifts away from what happened.
- Levels are a sink, not a recycler
- Tap levels 1 to 10 are bought with coins, and the spend is a negative ledger entry deliberately NOT credited back to the emission counter. Those coins were mined and are now gone, which is what makes the ladder deflationary rather than a loop.
- A level buys pay, never power
- A higher level multiplies what a tap PAYS and never what a battle SCORES. A level that won fights would be pay-to-win; one that only earns faster is progression.
- The project has an account
- Level payments land in a treasury account rather than vanishing, so the ten-billion supply stays whole. Exactly one such row can exist — enforced by a partial unique index in the database, not by application code remembering.
- A referral is paid once, atomically
- The inviter is paid when the invitee actually starts playing, and the flag is claimed by a conditional UPDATE rather than read-then-write — so two concurrent first-scratches cannot both pay for the same person.
- Passive income is its own line
- A daily cut of what your invitees farm is recorded under its own source rather than folded into referral bonuses, because the wallet has to answer two different questions: what have I been paid once, and what is my invite list earning me while I sleep.
- The lottery cannot mint
- A win is a positive entry; a loss is a negative entry paired with a positive one for the treasury. The pair sums to zero, so every coin that was ever minted still sits in somebody's balance.
- Scripted opponents, excluded everywhere
- Promo arrivals get a scripted opponent so two people clicking the same advert never contend for one bot. Those rows must be excluded from every leaderboard, count and fan-out — and a build check fails if a query remembers to filter the treasury and forgets the bots.
The withdraw button is disabled, and that is the product
The in-game coin is a score counter. No payouts, no cash-out, no blockchain. The landing page says it, the app says it, and the Withdraw button exists, is visibly disabled, and explains why.
That is not caution for its own sake. A game where people stake real money for a real prize is a lottery in essentially every jurisdiction and needs a licence. A currency that cannot be cashed out keeps this a digital good — which is exactly what Telegram Stars are designed to pay for.
The alternative is worse than it looks: an ambiguous coin, a "withdrawal coming soon", a hint that this might one day be worth something. That is how small games end up with angry users and a legal problem. Saying it plainly costs one button and removes the entire class.
What actually made people come back
The onboarding was rebuilt around one measurement, and it is worth quoting because it inverted the obvious design. Over the first week: of the players who ever played against another human, four out of four came back the next day. Of those who only ever tapped alone, zero out of eleven did.
So a new arrival's first minute is no longer the front page. It is a fight with the person who invited them. The moment a player finishes their first battle is stored on the account — stamped by the payout, so an invitation nobody answered does not count, because the point is a battle that happened rather than one that was offered.
Several other things live on the account for the same reason: which version of the tour someone has finished, whether Telegram will let the bot message them at all, and which hours of the day they are actually awake. The tour used to be a browser flag, and the day the game gained its own domain every existing player was shown it again — because a browser had forgotten, while the account had not.
Talking to a player without becoming noise
The bot can invite people back when the eggplant itches, and that is the easiest feature in the world to ruin. So sending is governed by a warmth score from −3 to +3: answering an invitation warms it, letting one expire cools it, and it decays back toward zero while nothing is being sent.
That decay is the part that matters. A counter that only ever went down would write somebody off permanently after one bad week; here, sending less means nothing new to ignore, which means warmth drifts back, which means they get a proper attempt again — with nobody scheduling a retry.
Quiet hours are handled without ever asking for a timezone: the account counts which UTC hours it has been active in, and messages only go out in hours that player has actually been seen in.
Ten languages, enforced by the build
The app ships in ten languages and translation completeness is a gate in the build, not something somebody remembers. A missing key fails the build rather than shipping an English string into the middle of a Ukrainian screen.
It is a small thing that decides whether a translated product is still translated after the third feature.
At a glance
- Live at
- zalupa.quest · game at app.zalupa.quest
- Platform
- Telegram Mini App, opened from the bot
- Core loop
- A ten-second itch window every 30–120 seconds; outside it, tapping does nothing
- Screens
- Tap · wallet · friends · battles · leaderboard · events · admin
- Economy
- Server-authoritative, capped supply, four earning buckets, a treasury holding what levels cost
- The coin
- A score counter — no payouts, no cash-out, no blockchain, and the Withdraw button says so
- Stack
- Fastify · TypeScript · Prisma · PostgreSQL · React + Vite
- Payments
- Telegram Stars, for digital goods only
- Languages
- 10, with a completeness gate in the build
Questions people actually ask
Can the coin be withdrawn or exchanged for money?
No. It is a score counter. There are no payouts and no blockchain, and the Withdraw button is deliberately disabled and says why.
Why not make it a real cryptocurrency?
A game where people stake real money for a real prize is a lottery almost everywhere and needs a licence. A non-cashable score keeps it a digital good, which is what Telegram Stars are for.
Can the game be cheated by editing the client?
The client draws; the server decides. Every balance change and leaderboard position is computed server-side, and Telegram's signature on the launch parameters is verified there.
Do I need to install anything?
No, it opens inside Telegram from the bot.