A Telegram Mini App on the TON blockchain. v3 is mint-pass-first: turn your avatar into a pixel-art NFT pass — each generation try costs $CUBE (a DB-only soft currency), you submit the draft you love, a human approves or declines it, and approved passes are minted on-chain to your wallet. The game behind the pass ("World I") opens to holders first.
- 🎨 The forge — Telegram profile photo or upload → Stability AI pixel-art img2img + ChatGPT description → pay-per-try with $CUBE (auto-refund if generation fails) → human-curated Approve/Decline → NFT minted to the bound wallet (getgems.io/cubeworlds).
- 🪙 DB-only $CUBE —
User.votes+ the append-onlyBalanceledger are canonical; no on-chain jetton. Faucets: daily claim streak, referrals (rewarded when the invitee's pass is minted), TON donations from the bound wallet, Telegram Stars top-up. Sink: generation tries. - 🔐 Cryptographic wallet binding — TON Connect
ton_proof(Ed25519 over a stateless HMAC nonce); donations are credited by an on-chain watcher matched to the bound wallet. - 📱 React Mini App — Vite + React 19 + TonConnect, pixel design system (Press Start 2P / VT323, dark purple + gold), served under
/game; a static landing owns the root. - 🧪 Tested + smoke-tested — 511 tests on the Node.js built-in runner, plus
npm run smoke:api: boots the real app with fake secrets against a throwaway Mongo and drives the API end-to-end.
┌─────────────────┐ ┌──────────────────┐ ┌────────────────┐
│ Telegram Bot │ │ Fastify Server │ │ React App │
│ (Grammy) │────▶│ (API + Static) │◀────│ (Vite, TMA) │
│ src/bot/ │ │ src/server.ts │ │ src/frontend/ │
└────────┬────────┘ └────────┬─────────┘ └────────────────┘
│ │
│ ┌────────┴─────────┐
└─────────────▶│ MongoDB │
│ (Typegoose) │
└────────┬─────────┘
│
┌────────┴─────────┐
│ TON Blockchain │
│ (watcher + mint) │
└──────────────────┘
See ARCHITECTURE.md for the full system overview and runtime flows.
| Layer | Stack |
|---|---|
| Bot | Grammy + plugins (auto-retry, hydrate, auto-chat-action, i18n) |
| HTTP API | Fastify + @fastify/multipart, rate limits, helmet, CORS |
| Frontend | React 19 + Vite 8 + @tonconnect/ui-react |
| Database | MongoDB via Typegoose (Mongoose decorators) |
| Blockchain | @ton/ton — donation watcher, wallet binding (ton_proof), NFT minting |
| Payments | Telegram Stars (XTR) top-up, idempotent on telegram_payment_charge_id |
| AI | Stability AI (pixel-art img2img) + OpenAI (pass descriptions) |
| Images | jimp (pure-JS normalize to 640×640 PNG), Pinata IPFS |
| Quality | @antfu/eslint-config, Prettier, tsc strict, Husky, Node test runner |
Prerequisites: Node 22+, MongoDB, a Telegram bot token.
# 1. Configure
cp .env.example .env
# edit .env — BOT_TOKEN, MONGO, WEB_APP_URL, …
# 2. Install (root + frontend each have their own package.json)
npm install
npm --prefix src/frontend install
# 3. Run in watch mode — full app (API + bot + frontend HMR) at http://localhost:3000
npm run devThe Telegram Mini App URL must point at /game — the landing owns the root.
| Command | Purpose |
|---|---|
npm run dev |
Full app in watch mode (landing build + tsx + embedded Vite HMR). |
npm run build:all |
Backend (tsc) + landing + frontend (vite build). |
npm run lint |
Lint with @antfu/eslint-config. |
npm run typecheck |
tsc --noEmit strict. |
npm run test:backend |
511 tests (Node built-in test runner). |
npm run test:coverage |
Coverage via Node's --experimental-test-coverage. |
npm run smoke:api |
Boots the real app (STAGING, fake secrets, throwaway Mongo) and drives the API end-to-end. SMOKE_MONGO_URI overrides the memory server. |
CHECK_MONGO_URI=… npx tsx scripts/check-prod-users.ts |
Read-only pre-cutover check of a v1 production database against the v3 models. |
The backend uses Node.js's built-in test runner (node --test) — no Jest/Vitest. Handlers are split into a pure, dependency-injected foo-handler.ts and a foo.ts composer that wires real config/bot/chain deps, so tests swap in mocks without booting infrastructure. Canonical examples: src/backend/mint-handler.test.ts (route handler), src/bot/features/topup-handler.test.ts (payment logic), src/bot/features/admin/queue-approval-handler.test.ts (mint state machine).
NODE_ENV=test turns src/config.ts into a proxy that throws on any read — tests can never transitively depend on real configuration.
src/
main.ts # entrypoint — MongoDB → bot → server → TON watcher
server.ts # Fastify: /api/* handlers, rate limits, landing + /game static
config.ts # znv + Zod, lazy proxy (throws on read when NODE_ENV=test)
subscription-core.ts # TON transaction watcher (donations → $CUBE)
bot/
index.ts # middleware chain (ORDER MATTERS) + feature registration
features/ # start, help, topup (Stars), stats, whales, line,
# admin/{queue,collection,parameters,transaction,user}
keyboards/ # /queue browser + shared Approve/Decline keyboard
backend/ # Fastify handlers (DI-split): auth, avatar, mint,
# claim, topup-invoice, balances, leaderboard, nft,
# wallet-nonce, set-wallet (ton_proof), public metrics
common/
models/ # Typegoose: User, Balance, StarsPurchase, Claim, CNFT, …
helpers/ # generation (Stability), ipfs, files (path-safe), ton, …
frontend/ # React 19 + Vite (separate package.json), served at /game
landing/ # static multi-page landing (generated by scripts/build-landing.ts)
locales/ # Fluent (.ftl) — en + ru
scripts/ # build-landing, smoke-api, check-prod-users
docs/ # dated research archives (market, NFT/token interactions)
v2 (the ancient-worlds ARPG: castles, heroes, PvP, tournaments, USDT rail) was built but never shipped; it was removed in the v3 reset and lives in git history.
removed-commands.tsstill catches the old slash commands and points users to the Mini App.
Both environments run on the same host via Kamal (config/deploy.yml; secrets come from the sibling infra repo's SOPS files through .kamal/secrets-common). Kamal builds from the committed HEAD, so commit first.
| Command | Target |
|---|---|
kamal deploy -d staging |
Staging — @cubeworldsbot at https://staging.cubeworlds.club. Own DB + data volume, same chain keys/admins as prod (an admin Approve there mints a real NFT). |
kamal deploy |
Production — cubeworlds.club. Currently still on v1; run the pre-deploy gates below first. |
Staging is the place to try the current main as a real Telegram bot; production only moves when you run the plain kamal deploy.
- ARCHITECTURE.md — system shape, startup flow, runtime flows, security model
- CLAUDE.md — compact context for AI coding agents (canonical)
- AGENTS.md / CODEX.md — the same orientation for other agent tools
- docs/ — dated research archives and the v2-era ideas backlog
- Fork and create a branch.
- Run the full gate before opening a PR:
npm run lint && npm run typecheck && npm run test:backend && npm --prefix src/frontend run build
- Follow the existing DI handler pattern for any new route or bot feature — it keeps the handler testable without booting infrastructure.
MIT — see package.json.