Async Python 3.11 Telegram bot for reselling Telegram Stars and Telegram Premium with MyStars FaaS fulfillment and TON-network crypto payments.
- aiogram v3 with routers, FSM, callback data, middleware, and admin filter.
- PostgreSQL via SQLAlchemy 2.0 async and Alembic.
- Redis for FSM, rate limiting, payment monitor locks, and dedupe support.
mystars-faas==0.1.3as the only Stars/Premium fulfillment API.tonutilsandpytoniq-corefor wallet and jetton transfers.- Aiohttp server (
/health+ MyStars webhook) always on; Telegram via polling (default) or webhook (TELEGRAM_UPDATE_MODE).
mystars-faas was installed before feature code and introspected. See docs/sdk_introspection.md.
Important difference: installed AsyncMyStarsClient exposes await_order(...), not wait_for_order(...). The code uses await_order(...).
Deposits are monitored through the TON Center HTTP API v3
(https://toncenter.com; the app appends /api/v3/...). You need a
TON_API_KEY — get one from the @toncenter Telegram bot:
- Open https://t.me/toncenter and press Start.
- Press Manage API Keys.
- Press Manage to choose a plan. The Free tier is ~10 req/sec with 1 token per network; paid tiers (higher rate limits) are billed in GRAM.
- Press Create API Key and copy the token.
Then set in .env:
TON_API_ENDPOINTS=https://toncenter.com
TON_API_KEY=<your key>
TON_NETWORK=mainnet
The key is sent as the X-API-Key header on every request. Any additional
comma-separated URLs in TON_API_ENDPOINTS are used as fallbacks.
Obtain/prepare each of these and put them in .env:
- Docker + Docker Compose, installed and running.
- Telegram bot token (
BOT_TOKEN) — create a bot with @BotFather (/newbot) and copy the token. - MyStars API key + webhook secret (
MYSTARS_API_KEY,MYSTARS_WEBHOOK_SECRET) — from the MyStars bot (@my_stars_tg_bot → "API access"). - TON Center API key (
TON_API_KEY) — see TON Center API key above. - Operational TON wallet (
OPERATIONAL_WALLET_MNEMONIC) — the hot wallet the bot pays suppliers from. Leave it empty to auto-generate one on first boot (the seed is persisted in the DB and the address is printed in the logs); or provide your own 24-word seed (recommended for production / secret managers). Either way you must fund it — see Why fund the operational wallet? below.⚠️ If you bring your own seed, generate it securely and keep only a small hot balance. - Your Telegram admin id(s) (
ADMIN_TELEGRAM_IDS) — your numeric user id (get it from @userinfobot); comma-separated for several. These accounts get the/adminpanel, support messages, and low-balance alerts. - (webhook mode only) a public HTTPS
PUBLIC_WEBHOOK_URL. The default polling mode needs none.
The stack boots without real values (it fails only at the external-auth boundary), but it can only fulfill real orders once the wallet is funded and the keys are valid.
This is a "service in the middle" reseller: the bot buys Stars/Premium from MyStars and resells at a margin. On every order:
- The customer pays your operational wallet (USDT or GRAM) — the customer price.
- The bot then creates the MyStars order and pays MyStars from the same wallet (the supplier cost) plus a little TON for gas.
- MyStars delivers; your profit is
customer price − supplier cost(the margin).
So the wallet needs a working float (USDT/GRAM to front supplier costs) plus
TON for gas. An empty wallet can accept customer payments but cannot pay the
supplier, so orders sit in paid and never deliver. Keep it topped up — the bot
sends low-balance alerts to admins at LOW_BALANCE_TON_THRESHOLD.
- Create
.envfrom.env.exampleand fill in the values from Prerequisites above (keep the production/mainnet defaults). - Start dependencies:
docker compose up -d postgres redis- Run everything with Docker — the
botservice runsalembic upgrade headautomatically before starting, so this is one command:
docker compose up --buildBy default the bot runs in polling mode (TELEGRAM_UPDATE_MODE=polling) — a
valid production setup that needs no public URL and works behind NAT. The
aiohttp server (/health + the MyStars webhook) still runs. See Update mode below.
Run Postgres + Redis in Docker but the app on the host. Because .env uses the
in-network postgres/redis hostnames, override the URLs to localhost when
running outside the compose network (note a host Postgres may already occupy
localhost:5432):
python -m venv .venv
./.venv/bin/pip install -r requirements.txt
DATABASE_URL=postgresql+asyncpg://stars:stars@localhost:5432/stars ./.venv/bin/alembic upgrade head
DATABASE_URL=postgresql+asyncpg://stars:stars@localhost:5432/stars \
REDIS_URL=redis://localhost:6379/0 ./.venv/bin/python -m appTELEGRAM_UPDATE_MODE controls how Telegram updates arrive, independent of
ENVIRONMENT:
polling(default) — the bot pulls updates via long polling. No public URL required; works behind NAT. Recommended for a single-instance deployment.webhook— Telegram pushes updates toPUBLIC_WEBHOOK_URL. SetTELEGRAM_UPDATE_MODE=webhookand a public HTTPSPUBLIC_WEBHOOK_URL(validated at boot). Use behind a reverse proxy or for horizontal scaling.
In both modes the aiohttp server serves /health and the MyStars webhook
(MYSTARS_WEBHOOK_PATH), and the payment monitor runs.
MyStars status sync in polling mode: with an empty PUBLIC_WEBHOOK_URL the
MyStars order is created without a callback_url, so the supplier cannot push
delivery webhooks. A background status-sync loop therefore polls
get_order every MYSTARS_STATUS_POLL_INTERVAL_SECONDS (default 15) for
orders in fulfilling, flips them to their terminal status
(delivered/failed/reversed/expired), and notifies the customer. The loop runs in
webhook mode too, as a backstop for missed webhooks.
- USDT is a jetton on TON (6 decimals) — deposits are matched from TON Center
/jetton/transfers. - GRAM is the native coin (the native token was renamed to GRAM; 9 decimals)
— deposits are plain coin transfers matched from TON Center
/transactions(op-0, memo in the comment). GRAM is not a jetton, so it never appears in the jetton feed and is monitored separately.
Each order's payment QR and "Open in wallet" button encode the correct asset +
amount + memo automatically (native GRAM omits the jetton parameter).
- Customer selects Stars quantity or Premium duration.
- Customer selects USDT or GRAM rail.
- Bot validates recipient with
AsyncMyStarsClient.check_recipient(...). - Bot quotes base cost with
get_pricing(...), applies admin margin withapply_retail_markup(...), and creates a local order with a UUID memo. - Customer pays the operational wallet with the exact asset (USDT jetton or native GRAM), amount, and memo.
- Payment monitor polls TON Center v3 for both USDT jetton transfers and native GRAM transactions, validates asset + amount + memo, and dedupes transaction events.
- After payment, bot creates the MyStars order with the local order ID as idempotency key.
- Bot pays the MyStars invoice from the operational wallet using
tonutils. - The background status-sync loop (and, in webhook mode, the signed MyStars webhook) updates the order to its terminal status, edits the customer's order card in place, and sends a short notification message.
/reconcileand the reconcile worker remain as manual backstops.
Restricted to ADMIN_TELEGRAM_IDS (put your real Telegram user id there).
/admin— inline panel (Orders, Stats, Balance, 📈 Margin, Reconcile)./margin [pct]— show or set the global margin at runtime (persisted in the DB, overrides theDEFAULT_MARGIN_PCTseed). No restart needed./product_margin <product_id> <pct>— per-product margin override;/pricinglists products and effective margins./orders [status],/stats— order list / revenue + profit, grouped per pay currency (USDT and GRAM are different units and are never summed together)./balance— operational wallet address + native gas balance./reconcile— pull terminal MyStars statuses for missed webhooks./fulfill <order_id>— manually deliver an order stuck inpaid(e.g. credited whileAUTO_FULFILL_PAID_ORDERSwas off)./refund <order_id> <destination>,/cancel_order <order_id>./broadcast <text>— message all users./reply <user_id> <text>— answer a customer's support message.
Support relay: customers use /support (or an order's 💬 Support button) to
write a message; the bot relays it (as a copy) to all admins, who answer with
/reply <user_id> <text>. SUPPORT_URL is optional and only adds an external
contact link.
- Use a small hot wallet balance; keep treasury funds in cold storage.
- Confirm
TON_NETWORK=mainnet,TON_API_ENDPOINTS=https://toncenter.com, and a validTON_API_KEY. - Verify
OPERATIONAL_WALLET_ADDRESSmatches the mnemonic-derived address. - Polling is the default and needs no public URL. Only for
TELEGRAM_UPDATE_MODE=webhook: set a public HTTPSPUBLIC_WEBHOOK_URLand aTELEGRAM_WEBHOOK_SECRET_TOKEN. - Put your real admin id(s) in
ADMIN_TELEGRAM_IDS(admin panel, support, alerts). - Store
.envin a secret manager, not in the image or repository. - Confirm MyStars API key and webhook secret rotation string.
- Run
alembic upgrade head. - Place a low-value test order for each rail before opening sales.
- Confirm Redis lock prevents multiple payment monitor instances from processing the same payment.
- Confirm admin ID allowlist and rate limits.
- Set
AUTO_REFUND_LATE_PAYMENTSonly after validating refund destination parsing on your chosen TON API.
- Quote and markup: mock
get_pricing(...)for Stars and Premium on both currencies; assertapply_retail_markup(...)total, subtotal, fee, and profit are persisted. - Recipient validation: mock eligible and ineligible
check_recipient(...); ensure ineligible users never receive payment instructions. - Deposit matching: feed monitor fixtures for correct payment (USDT jetton and native GRAM), underpayment, wrong asset, duplicate tx, missing memo, unknown memo, and expired late payment.
- Fulfillment: after a valid deposit, assert local order moves
awaiting_payment -> paid -> fulfilling, MyStarscreate_order(...)receives the local UUID idempotency key, and wallet payment uses the SDK payment memo. - Webhook: post raw body with valid and invalid
X-Faas-Signature; assert valid terminal events are deduped by MyStars order ID and update local orders once. - Reconcile: simulate a missed terminal order from
aiter_orders(...); assert local state catches up. - Admin: verify
IsAdminprotects the runtime/margin,/fulfill,/reply, order listing, reconcile, refund, broadcast, and balance commands. - Deployment: run
alembic upgrade head,python -m compileall app, and a dry-run container boot.
Released under the MIT License — free to use, modify, and distribute. See LICENSE.
The MIT license covers this project's own code only. Dependencies keep their own (permissive) licenses — see THIRD_PARTY_NOTICES.md; preserve upstream notices when redistributing.