Overview
The Mouth backend consists of three main services:- Mouth API — Core REST API serving the web app
- X Bot Service — Monitors X mentions and posts updates
- Resolution Engine — Handles bet resolution at expiration
Database Schema
Core Tables
Indexes
API Endpoints
Auth
Bets
Users
Leaderboard
Challenges (X Bot Drafts)
Wallet
USDC balance is read directly on-chain by the frontend via viem — no backend endpoint needed. Wallet resolution is an internal service function (
resolveXHandleToWallet), not an API endpoint.POST /bets receives an opponent X handle, the backend:
- Looks up the
userstable byx_handle - If found → use
wallet_address - If not found:
- Check if user exists in Privy via
privy.users().getByTwitterUsername() - If not in Privy either, call Privy server SDK to create user + pre-generate embedded wallet
- Store in
userstable withpre_registered = true - Use the new
wallet_address
- Check if user exists in Privy via
- Pass the resolved address to
factory.createBet(..., opponent: walletAddress, ...)
pre_registered = false on first login.
See Identity & Wallets for details on Privy pre-generated wallets.
Transaction Signing
The backend uses two separate signing mechanisms depending on who initiates the transaction:User Transactions (Gasless via Privy)
All user-initiated on-chain operations go through thesendSponsored() relay function in services/tx-relayer.ts:
These transactions are:
- Sent from the user’s embedded wallet (identified by
privyWalletId) - Gas-sponsored by Mouth via Privy’s infrastructure
- Authorized using a P256 authorization key registered as a key quorum in the Privy Dashboard, with session signer consent from the frontend
Admin Transactions (Resolver Account)
Resolution and finalization are performed by a dedicated backend wallet:
These transactions:
- Use a standard Viem
WalletClientwith the resolver’s private key (RESOLVER_PRIVATE_KEY) - Pay gas from the resolver wallet’s ETH balance
- Are protected by a wallet address check (only the resolver address can call these endpoints)
Environment Variables
X Bot Service
The bot runs as a standalone Node.js service (x-bot/) that polls X for mentions of @Mouth_App, parses challenge intents using Claude Haiku, creates draft challenges in the database, and replies with a link to pvp.mouth.app/challenge/{id}.
It writes to a dedicated draft_challenges table (no FK to users) and has no dependency on the backend API.
See X Bot Service for the full technical architecture, database schema, LLM parsing, and deployment details.
Resolution Engine
Architecture
The resolution engine runs as a cron job (or event-driven worker) that:- Every minute: Checks for bets where
expiration_date <= NOW()andstatus = 'active' - Categorizes each bet:
- Auto-resolvable: Has oracle-compatible resolution criteria → Fetch data, resolve
- Manual: Requires human review → Flag for Mouth team
- Submits resolution transaction to the smart contract
- Starts the 48-hour dispute window
Oracle Integration
For auto-resolution, the engine queries:- Chainlink Price Feeds: Token prices (ETH, BTC, etc.)
- Pyth Network: Additional price data, faster updates
- On-chain queries: TVL, supply, contract state (via RPC calls)
