Skip to content

Latest commit

 

History

364 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TON Escrow Bot

CI CodeQL Secret scan Docker Publish License: AGPL v3 Telegram Mini App TON TypeScript

A peer-to-peer escrow service for Telegram: a grammY bot plus a Telegram Mini App that lets two parties trade TON or USDT (jettons) safely. Funds are held in a custodial W5 signer wallet (off-chain ledger in Postgres tracks every deal) — there is no on-chain per-deal smart contract; trust rests in the custodial backend wallet + guarded DB transitions, not in Tact code.

Architecture — micro-architecture (7 services + Postgres)

 Telegram users (chat + Mini App:8080)          Bot API
        │  HTTPS (WEBAPP_URL)                    │
        ▼                                        ▼
┌──────────────────┐      ┌──────────────────────────┐
│  frontend        │      │  backend :3000 (API-only)│
│  webapp nginx    │─────►│  grammY bot + REST API   │
│  :8080 -> :80    │ /api │  /api/*, /docs, /api/info│
│  proxies /api    │      └──────────────────────────┘
└──────────────────┘                 │
                      ┌───────────────┼──────────────────┐
                      ▼               ▼                  ▼
                ┌──────────┐    ┌──────────────┐   ┌──────────────────┐
                │ Postgres │    │ signer (W5)  │   │ Custodial W5     │
                │  :5432   │    │ V5R1 wallet  │◄─►│ wallet: deposits,│
                │ (deals,  │    │ microservice │   │ release/refund   │
                │ msgs,    │    │ :3001        │   │ (off-chain ledger│
                │ trades)  │    └──────────────┘   │  is source of    │
                └──────────┘          │             │  truth)          │
                                      │             └──────────────────┘
                     ┌───────────────┼──────────────────┐
                     ▼               ▼                  ▼
               ┌──────────┐    ┌──────────────┐   ┌──────────────────┐
               │  ubot    │    │  utradebot   │   │  (frontend docs) │
               │ :3002    │    │  :3003       │   │  /docs + openapi │
               │ channel/ │    │  account     │   └──────────────────┘
               │ takeover │    │  sale escrow │
               └──────────┘    └──────────────┘
  escrow-net (bridge) isolates all; published: frontend :8080, backend :3000 (API)
  • frontend/ (webapp/) — separate nginx microservice (nginx:alpine, :8080→:80), serves static Mini App, proxies /api/*http://backend:3000 (micro-architecture, not same port). Telegram Web App requires WEBAPP_URL/FRONTEND_URL HTTPS in prod.
  • backend/API-only (SERVE_STATIC=false) grammY bot + Express REST (:3000), TON listener/deployer via signer, docs at /api/docs, /docs, /api/openapi.json, health /api/info, CORS allows FRONTEND_URL + localhost:8080.
  • signer/ — isolated W5 (V5R1) Wallet (SIGNER_MNEMONIC 24 words in signer/.env), internal http://signer:3001, x-api-key.
  • ubot/ — Telegram userbot (teleproto@1.229.0, QR login) for channel/group takeover (channels.editCreator, channels.editAdmin, messages.migrateChat), API_ID/API_HASH/TWO_FA_PASSWORD in ubot/.env, :3002.
  • utradebot/ — account sale escrow (teleproto), holds StringSession/phone+code trades, revokes seller, buyer code handoff, auth.LogOut, :3003.
  • Trust model — custodial off-chain: Postgres is the deal ledger, the isolated signer W5 wallet moves funds. There is no contracts/ Tact contract in this repo and no on-chain escrow enforcement.

Quickstart

A. Local (npm) — micro-architecture dev (frontend separate)

  1. Run a local PostgreSQL and create the database (generate a strong password per deployment — never reuse the placeholder below):
    # openssl rand -base64 32   # use output as the password
    CREATE USER escrow WITH PASSWORD 'CHANGE_ME_STRONG_RANDOM';
    CREATE DATABASE escrow OWNER escrow;
  2. Configure and run backend (API-only, :3000):
    cd backend
    cp .env.example .env    # edit BOT_TOKEN, ADMIN_TELEGRAM_IDS, SIGNER_URL, FRONTEND_URL=http://localhost:8080, SERVE_STATIC=false (or true for single-port dev)
    npm install
    npm run build
    npm start               # API + bot on http://localhost:3000 (docs at /api/docs, /docs)
    # or: npm run dev       # ts-node
  3. In another terminal, run frontend (:8080, proxies /api → backend):
    cd webapp
    npm install
    npm start               # http-server public on http://localhost:8080 (or use nginx)
    # Open Mini App at http://localhost:8080 and talk to bot (API via proxy or direct http://localhost:3000)
    For single-port dev without docker, set SERVE_STATIC=true and FRONTEND_URL=http://localhost:3000 in backend/.env, then http://localhost:3000 serves both.

B. Docker Compose — micro-architecture (robust, production-ready)

# 1) Configure every service (edit each .env, chmod 600)
cp backend/.env.example backend/.env       # BOT_TOKEN, ADMIN_TELEGRAM_IDS, SIGNER_URL, FRONTEND_URL=http://localhost:8080, WEBAPP_URL, API_KEY
cp signer/.env.example signer/.env         # SIGNER_MNEMONIC (24 words), SIGNER_API_KEY, TON_NETWORK, TONCENTER_API_KEY
cp ubot/.env.example ubot/.env             # API_ID, API_HASH, TWO_FA_PASSWORD, UBOT_SESSION_STRING, ENCRYPTION_KEY, UBOT_API_KEY
cp utradebot/.env.example utradebot/.env   # UTRADE_BOT_TOKEN, API_ID, API_HASH, ENCRYPTION_KEY

# 2) REQUIRED: set host POSTGRES_PASSWORD (compose fails fast without it — no weak default)
#    Generate per deployment, never commit (root .env is git-ignored):
#      openssl rand -base64 32
#    echo "POSTGRES_PASSWORD=<output>" > .env

# 3) Build & run (7 services, detached, healthchecks, resource limits)
docker compose up --build -d
docker compose ps          # all 7 healthy: postgres, signer, backend, frontend, ubot, utradebot, checker
docker compose logs -f backend   # or signer / frontend / ubot / utradebot

# Frontend (Mini App) — separate microservice, Nginx proxies /api → backend
curl http://localhost:8080/              # 200 HTML (Mini App)
curl http://localhost:8080/api/info      # 200 via proxy (same as backend)

# Backend API-only
curl http://localhost:3000/api/info      # health
curl http://localhost:3000/api/docs      # JSON docs
curl http://localhost:3000/docs          # HTML docs (also via http://localhost:8080/docs)

# Internal (expose only, uncomment ports in compose to reach from host)
# curl http://localhost:3001/health  # signer
# curl http://localhost:3002/health  # ubot
# curl http://localhost:3003/health  # utradebot

What the compose provides:

  • Services (7): postgres:5432, signer:3001 (W5), backend:3000 API-only (SERVE_STATIC=false), frontend:80 → host 8080 (nginx, serves Mini App, proxies /apibackend:3000), ubot:3002, utradebot:3003, checker:3004 (standalone read-only NFT ownership verifier, not in any deal flow) on escrow-net.
  • Micro-architecture: each service independently buildable/scalable, isolated code/Dockerfile, separate ports (frontend 8080, backend 3000, signer 3001 internal, ubot 3002, utradebot 3003, checker 3004 internal), healthchecks, depends_on: service_healthy (frontend waits for backend, backend for postgres+signer).
  • Security: each runs as non-root, .env never baked (env_file at runtime), ENCRYPTION_KEY + x-api-key between services, logs redacted, CORS allows FRONTEND_URL/WEBAPP_URL/localhost:8080.
  • Persistence: volumes pgdata, ubot_sessions, utrade_sessions (600 perms).
  • Ops: restart: unless-stopped, deploy.resources.limits, logging: json-file (10m/3), HEALTHCHECK per Dockerfile.
  • For TLS: put frontend + backend behind Caddy/nginx with WEBAPP_URL=https://your-domain + FRONTEND_URL same — Telegram requires HTTPS.

Environment

All variables are documented per-service:

  • backend/.env.exampleBOT_TOKEN, ADMIN_TELEGRAM_IDS, DATABASE_URL, SIGNER_URL, SIGNER_API_KEY, API_KEY, WEBAPP_URL, TON settings.
  • signer/.env.exampleSIGNER_MNEMONIC (24 words, never in backend), SIGNER_API_KEY, TON_NETWORK, TONCENTER_API_KEY.
  • ubot/.env.exampleAPI_ID, API_HASH, UBOT_SESSION_STRING, TWO_FA_PASSWORD, ENCRYPTION_KEY, UBOT_API_KEY.
  • utradebot/.env.exampleUTRADE_BOT_TOKEN, API_ID, API_HASH, ENCRYPTION_KEY, DATABASE_URL.

Minimum for off-chain: BOT_TOKEN, ADMIN_TELEGRAM_IDS, DATABASE_URL (backend) + SIGNER_MNEMONIC in signer if you need on-chain. For production add API_KEY/SIGNER_API_KEY/UBOT_API_KEY/UTRADE_API_KEY (32+ chars each), WEBAPP_URL, POSTGRES_PASSWORD, and ENCRYPTION_KEY (64 hex).

Webapp / Mini App hosting — micro-architecture

  • Frontend microservice webapp/ (nginx:alpine, Dockerfile, nginx.conf) serves public/ on :80 → host :8080, proxies /api/*, /tonconnect-manifest.json, /docshttp://backend:3000. Backend is API-only (SERVE_STATIC=false).
  • Telegram requires a public HTTPS URL for Mini Apps. In prod, put frontend behind TLS (Caddy/nginx/LB) and set WEBAPP_URL=https://your-domain + FRONTEND_URL same — the bot menu button ("Open App") only appears when WEBAPP_URL is set. Backend CORS allows FRONTEND_URL.
  • Deal cards deep-link into the app as ${WEBAPP_URL}?deal=<id> and one-time join links as ${WEBAPP_URL}?deal=<id>&join=<token>.
  • webapp/public is not copied into the backend image anymore (see backend/Dockerfile); for single-port dev set SERVE_STATIC=true.

Status & roadmap

Status Item
Bot commands and admin flows
Deal lifecycle tracked off-chain (create → deposit → confirm → release/refund)
Telegram Mini App UI
One-time join links + per-deal chat
W5 signer microservice (signer/) — isolated SIGNER_MNEMONIC (24 words)
ℹ️ Custodial model: no on-chain Tact contract — funds sit in the signer W5 wallet; Postgres + idempotent PENDING transitions are the source of truth
⚠️ Fund the W5 signer wallet (SIGNER_MNEMONIC in signer/.env, V5R1) with TON for gas
Jetton master verification pending (USDT_JETTON_ADDRESS not yet validated on-chain)

Contributing — branching, commits, releases

  • No direct pushes to main. Work on a short-lived branch, open a PR, wait for green CI, then squash-merge. Branch protection enforces this.
  • Conventional Commits (feat:, fix:, docs:, chore:, …) — enforced locally by the commit-msg hook and in CI. They drive the changelog.
  • Local gates (installed via npm install at the repo root): pre-commit runs prettier + eslint on staged files and a secret scan if gitleaks is installed. Run the full suite any time with npm run verify (typecheck + lint + format check).
  • CI gates every PR: commitlint, ESLint, prettier, tsc --noEmit and npm run build per package, blocking npm audit, Gitleaks, CodeQL, and a Docker build + Trivy scan (images push only from main/v*, never PRs).
  • Releases are automatic (release-please): merging feat:/fix: to main opens a release PR; merging it tags v*, publishes GitHub Release notes, and ships semver + latest GHCR images with provenance attestation. Deploy releases, not floating main.

Security notes

  • Rotate any credential that has ever been committed (BOT_TOKEN included). Treat everything in git history as compromised.
  • Never commit a real .env; it is git-ignored.
  • API auth (see backend/src/auth/): mutating routes require a Telegram identity — the Mini App's x-init-data is HMAC-verified server-side against BOT_TOKEN (sender/body ids are never trusted). The legacy x-api-key grants NO identity and NO admin rights. Admin-only routes (notify, withdraw, refund, notifications history, admin/*) require the caller to be in ADMIN_TELEGRAM_IDS (verified identity) or to present ADMIN_API_KEY via x-admin-api-key/Bearer. A dev fallback trusting x-telegram-user-id activates only when ALLOW_DEV_AUTH=true AND both BOT_TOKEN and API_KEY are unset — never in production.
  • Custody model: funds sit in the isolated signer W5 wallet; Postgres is the ledger. Deposits are matched by unguessable memo token + amount, sender address, and (USDT) jetton-master verification — see JETTON_MASTER_ADDRESS in backend/.env.example (unset = forgery check disabled).
  • Request bodies are capped at 256 KB. In-memory rate limits are active: global 300/min, deal creation 10/min, join/recheck 20/min, chat 60/min, deal-key 20/min, payout 10/min, utrade-code 5/min, admin-money 10/min, notify 5/min per IP+route.

License

GNU Affero General Public License v3.0 (AGPL-3.0) — Strict, Official, OSI-Approved. See LICENSE.

This is the strictest official OSI-approved license. It ensures anyone who runs a modified version over a network (e.g., as a Telegram bot / Mini App backend) must make the complete Corresponding Source available to all remote users via a network server at no charge (Section 13). Includes strong copyleft and explicit patent grant. Commercial use without source disclosure is not permitted — use the network clause to preserve user freedom for SaaS.

Other strict official alternatives: GPL-3.0 (copyleft without network clause) and Apache-2.0 (permissive, patent grant, commercial-friendly). This project uses AGPL-3.0 for maximum strictness.

About

TON P2P escrow for Telegram — grammY bot + Mini App (React/Tailwind) + Express API, isolated W5 signer, ubot/utradebot (teleproto), Postgres. Off-chain ledger with optional on-chain TON escrow.

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages