A Telegram bot that gives every group its own gambling economy. Pick Points (free, instant) or ICRC-1 (real ICP, ckBTC, etc. on the Internet Computer). Same dice games, same house edge, swap the backend in one command.
- Create a bot with @BotFather, copy the token.
cp .env.example .envand fill inTELEGRAM_BOT_TOKENand your PostgreSQL credentials.cargo run --release- In your Telegram group, type
/setupas a group admin. - Any user types
/startand gets a starter balance. They type a number to bet.
Total time from cargo run to a working game: under 60 seconds.
| Command | What it does |
|---|---|
/start |
Create your account and receive starter balance |
/balance |
Show your balance |
/profile |
Your stats and achievements |
/achievements |
Unlocked achievements |
/leaderboard |
Top 10 players |
/deposit |
Get your deposit address (ICRC-1 rooms) |
/withdraw AMOUNT PRINCIPAL |
Withdraw to external wallet (ICRC-1 rooms) |
/help |
All user commands |
Playing: Type any number (e.g. 5) — that's your bet. Pick a game from the buttons the bot sends back.
| Command | What it does |
|---|---|
/setup |
Show room config + treasury health + suggested next step |
/treasury |
Treasury balance and house profit |
/stats |
Economy statistics (24h and all-time) |
/setup starter AMOUNT |
Change the new-user starter balance |
/setup edge PERCENT |
Change the house edge (0–100) |
/setup games LIST |
Enable/disable games (e.g. dice darts slot) |
/setup fund AMOUNT |
Points only: credit (positive) or drain (negative) treasury |
/setup mode icrc1|points |
Switch between real tokens and virtual points |
/setup ledger CANISTER |
ICRC-1 ledger canister (required for icrc1 mode) |
/setup token SYM DEC FEE |
Token symbol, decimals, transfer fee (ICRC-1 only) |
/botaddress |
Show on-chain treasury address (ICRC-1 only) |
/configure is supported as a permanent alias for /setup.
| Points | ICRC-1 | |
|---|---|---|
| Setup time | Instant (one /setup) |
Ledger canister ID required |
| Treasury | Bot database | On-chain ledger |
| Deposit | Not applicable — points come from the bot's treasury | User transfers from external wallet to their personal address |
| Withdraw | Not applicable | User receives to any ICRC-1 principal |
| Transfer fee | 0 | Ledger fee (set by the token) |
| Best for | Casual groups, demos, no-crypto audiences | Real-money games, ckBTC/ICP/ICRC-1 token communities |
- User sends
/start— bot credits the room's starter balance from the treasury. - (ICRC-1 only) User tops up via
/depositfrom their own wallet. - User types a number — bot locks it as the bet.
- User picks a game from the inline keyboard.
- Bot rolls the dice. Win → bot credits reward from treasury. Lose → bet stays with treasury.
- Balance is always
SUM(credits) − SUM(debits)from an immutable ledger. It can never go negative.
- Create a bot via @BotFather.
cp .env.example .env, fill inTELEGRAM_BOT_TOKEN+ PostgreSQL.cargo run --release- Add the bot to a Telegram group, promote it to admin.
- In the group, type
/setup(you're the admin). Bot seeds a Points treasury and starter balance. - From another account, type
/start→ receive starter balance → type a number → pick a game.
For ICRC-1 mode, swap step 5 with /setup mode icrc1 followed by /setup ledger <canister-id>.
| Guarantee | How it's enforced |
|---|---|
| No double-settlement | PostgreSQL UNIQUE constraint on SHA256(room_id‖user_id‖round_millis) in settlement_keys |
| Ledger always reconciles | balance_after stored on every entry; SUM(credits) − SUM(debits) always equals live balance |
| Balance never goes negative | CHECK (balance_after >= 0) on every ledger_entries row — enforced at the schema level |
| Room isolation | Every table and query is room_id-scoped; no cross-room join possible |
| Achievement writes never block settlement | tokio::spawn for async achievement writes |
| On-chain finality before DB commit | ICRC-1 transfer executes first; DB commits only after chain confirmation |
| Treasury never silently fails | /treasury shows ❌ empty / |
| Deterministic subaccount derivation | icrc1_account_id(treasury_principal, Sha256(telegram_user_id)) — same user always gets same address |
Bet arrives from Telegram
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. Idempotency Check │
│ INSERT INTO settlement_keys (key) VALUES (sha256(...)) │
│ ON CONFLICT (key) DO UPDATE SET result = existing.result │
│ ← PostgreSQL UNIQUE constraint is the only guard needed │
│ If conflict: return cached GameSettlement immediately │
└──────────────────────────────┬──────────────────────────────┘
│ (first caller wins the lock)
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Asset Transfer (ICRC-1 or Points) │
│ If ICRC-1: icrc1_transfer() on-chain FIRST │
│ If Points: UPDATE ledger + CHECK balance_after >= 0 │
│ On failure: roll back, return error │
└──────────────────────────────┬──────────────────────────────┘
│ (transfer confirmed)
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Ledger Write (immutable, append-only) │
│ INSERT INTO ledger_entries (room_id, from, to, amount, │
│ type, balance_after) VALUES (...) │
│ balance_after = SUM(credits) - SUM(debits) always │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Achievement (async, non-blocking) │
│ tokio::spawn(evaluate_achievements(...)) │
│ Never delays settlement response │
└─────────────────────────────────────────────────────────────┘
Criterion benchmarks — measured on PostgreSQL 16, single-node, release build:
| Benchmark | Throughput | p50 Latency | p99 Latency |
|---|---|---|---|
balance_of/db_read |
~1,660 ops/sec | 593 µs | ~800 µs |
settle_game/losing_bet |
~230 ops/sec | 4.27 ms | ~5.8 ms |
settle_game/winning_bet |
~155 ops/sec | 6.20 ms | ~9.1 ms |
ICRC-1 chain calls (~400 ms/transfer on live ICP mainnet) are not included — the PointsProvider path above measures DB + in-process latency only. Chain latency dominates in production when using real ICP tokens.
Run: cargo bench --benches
| Limitation | Impact | Fix path |
|---|---|---|
settlement_keys table has no TTL |
Long-running deployments accumulate keys; query overhead grows | Periodic DELETE WHERE created_at < NOW() - INTERVAL '7 days' migration |
| Single-process rate limiter | Multi-instance deployments share state via Redis | Swap RateLimiter (src/router/mod.rs:46) for Redis-backed equivalent |
| Leaderboard aggregates on read | High-traffic rooms compute leaderboard from ledger on every call | Materialized view refreshed on settlement, or Redis cache with TTL |
| ICRC-1 deposit polling not implemented | Users who transfer ICP directly to their subaccount (bypassing /deposit) are not auto-credited |
Background task calling icrc1_get_transactions per known account |
| Achievement writes are fire-and-forget | A DB failure after settlement means the achievement is silently dropped | failed_achievement_writes outbox table + background reconciler |
Telegram Bot API
│
▼
Event Handlers
│
▼
┌─────────────────────────────────┐
│ EconomyEngine │ Core orchestrator: coordinates
│ (balance, settle, transfer, │ ledger writes, provider calls,
│ achievements, analytics) │ and room config in one place
└────────────┬───────────────────┘
│
┌────────┴────────┐
▼ ▼
Achievement Analytics
System DAO
│ │
└────────┬────────┘
▼
┌──────────────────────────┐
│ AssetProvider Trait │ Unified interface for all asset types:
│ balance_of / transfer │ PostgreSQL points or ICRC-1 blockchain
│ account_exists / meta │ — interchangeable at runtime
└────┬────────────────┬───┘
│ │
▼ ▼
PointsProvider ICRC1Provider
│ │
▼ ▼
PostgreSQL Internet Computer
Ledger Ledger (ic-agent SDK)
Multi-room: every table and query is room_id-scoped.
One deployed bot = thousands of independent economies.
Prerequisites: Rust 1.75+, PostgreSQL 14+ (16 recommended), a Telegram bot token from @BotFather.
# Clone
git clone https://github.com/cordova7/telegram-economy-engine
cd telegram-economy-engine
# Configure
cp .env.example .env
$EDITOR .env # fill in TELEGRAM_BOT_TOKEN and DATABASE_URL
# Build
cargo build --release
# Run migrations + start the bot
cargo run --release
# In a Telegram group, as admin:
# /setup# Unit + integration tests (needs Postgres)
cargo test
# Benchmarks (optional, takes a few minutes)
cargo benchsrc/
main.rs — entry point, dispatcher wiring
lib.rs — library exports
router/ — message routing, command dispatch, engine cache
handlers/ — one fn per Telegram command
views/ — message-text rendering (help text, HTML escape)
state.rs — PersistedState (state.json), SharedState
config.rs — env-var-driven Config
room.rs — Room configuration (provider, token, games, edge)
game.rs — Telegram dice → win condition tables
economy/ — EconomyEngine, AssetProvider, TreasuryReport
providers/
points.rs — PostgreSQL-backed PointsProvider
icrc1.rs — ICRC-1 ledger-backed ICRC1Provider
onboarding.rs — auto-fund for new users
database/ — SQL DAOs, schema migrations
account_id.rs — ICRC-1 account identifier (CDC) encoding
benches/ — Criterion benchmarks
tests/ — integration tests
migrations/ — SQL schema migrations
See CONTRIBUTING.md for the workflow, code style, and audit conventions.