BetBit has four main components that work together to handle the full wager lifecycle:
User (Browser)
|
v
+-----------+ +-------------+ +------------+
| Frontend |---->| Backend API |---->| Database |
| | | | | |
+-----------+ +-------------+ +------------+
| |
v v
+-----------+ +-------------+
| Pimlico | | The Graph |
| Bundler + | | Subgraph |
| Paymaster | | (indexing) |
+-----------+ +-------------+
| |
v v
+-------------------------------+
| Avalanche C-Chain |
| WagerEscrow | USDC |
| AccessControl (UUPS proxy) |
+-------------------------------+
Smart Contracts (On-Chain)
Three contracts deployed to Avalanche:
WagerEscrow: The core protocol. Holds deposited USDC in escrow, manages the wager state machine (11 states from PENDING through SETTLED/REFUNDED), evaluates the outcome truth table, computes fee deductions, and handles withdrawals. This contract is immutable. Once deployed, its logic cannot change.
WagerAccessControl: Governance layer deployed behind a UUPS proxy. Manages protocol fees, pause states, mediator roles, and dispute resolution escalation. Upgradeable so the team can adjust parameters without redeploying the escrow.
USDC: The settlement currency. On testnet, a MockUSDC ERC-20 is used. On mainnet, the real Avalanche USDC contract would be referenced.
Backend API (Off-Chain)
A lightweight API server handling:
User accounts: Signup, authentication, wallet linking
Wager metadata: Descriptions, match details, mediator contact info (too expensive to store on-chain)
Shareable links: Signed invite URLs that let the second party join a wager
Mediator tokens: Time-limited tokens for dispute resolution
Subgraph queries: Fetches indexed wager events for listing and history
The backend never touches funds. All financial operations go through the smart contract. The backend is purely a metadata and coordination layer.
Frontend (User Interface)
A modern web application with:
Passkey authentication: WebAuthn registration and login, no seed phrases
Smart wallet management: Account creation and transaction signing via ERC-4337
Contract interaction: Efficient ABI encoding for all escrow operations
Real-time state: Direct on-chain reads via eth_call for deposit/outcome status
State-driven UI: Each of the 11 wager states maps to a specific screen with contextual actions
The Graph Subgraph (Indexing)
Indexes all WagerEscrow events (WagerCreated, Deposited, OutcomeSubmitted, Settled, etc.) into a queryable GraphQL API. Used primarily for the dashboard's wager list and historical transaction data. Not used for real-time state. That comes from direct RPC calls.
Core Workflows
Wager Creation
Deposit Flow (Second Party)
Outcome Submission & Settlement
Withdrawal
Technical Structure
Monorepo Layout
The project is organized as a monorepo with five packages:
Database
Six core tables store off-chain data: user accounts (wallet addresses, credentials, profiles), wager metadata (descriptions, deadlines, metadata hashes), shareable invite links, mediator approval tokens, outcome descriptions, and a notification audit log.
All on-chain data (balances, wager state, deposits, outcomes) is read directly from the smart contract or subgraph. The database only stores what can't live on-chain cost-effectively.
Security
Contract level: ReentrancyGuard on all fund operations, SafeERC20 for token transfers, pull-pattern withdrawals, immutable constructor parameters, fee hardcaps, granular pause states.
Application level: Security headers, CORS restricted to the frontend origin, rate limiting on all endpoints (stricter on auth), parameterized queries, hashed passwords, JWT authentication.
The production environment runs behind a reverse proxy that routes API traffic to the backend and all other requests to the frontend. The database runs locally on the same host. A process manager keeps both services running with automatic restarts, and SSL certificates auto-renew.
1. Creator fills out form (amount, deadline, description)
2. Frontend sends metadata to backend
Backend stores metadata, computes keccak256 hash, returns temp ID + hash
3. Frontend batches UserOperation:
- approve(escrowAddress, amount + fees)
- createWager(amount, fundingDeadline, outcomeExpiration, metadataHash)
4. User taps passkey (biometric)
5. Pimlico bundler submits UserOp to Avalanche
6. Contract creates wager, pulls creation fee + deposit from creator
7. Frontend links the on-chain wager ID to the stored metadata
8. Creator gets shareable invite link
1. Taker receives invite link (WhatsApp, Telegram, etc.)
2. Opens link → invite page shows wager details (on-chain read)
3. If not logged in → signs up (passkey created) → redirected back
4. Taker clicks "Accept & Deposit"
5. Frontend batches UserOperation:
- approve(escrowAddress, amount)
- deposit(wagerId)
6. Taker taps passkey
7. Contract records deposit, state transitions:
PARTIALLY_FUNDED → ACTIVE (both deposited)
8. Both parties see "Awaiting Event" on their dashboard
1. Event happens (game ends, bet resolves, etc.)
2. Each party independently submits: WON, LOST, or DRAW
3. Submissions are blind. Neither sees the other's choice until both submit
4. Contract evaluates truth table:
Both agree (WON/LOST) → Auto-settle, winner determined
Both claim WON → Draw refund (nobody proven right)
Both claim LOST → Draw refund (mutual concession)
Both claim DRAW → Draw refund
Any disagreement → DISPUTED (mediator needed)
5. If auto-settled → winner calls withdraw()
6. If disputed → mediator submits ruling → 48hr appeal window → settlement executes
1. Wager reaches SETTLED or REFUNDED state
2. Winner (or both parties for refund) clicks "Withdraw"
3. Frontend sends withdraw(wagerId) UserOperation
4. Contract transfers USDC minus protocol fee to winner
5. Transaction hash shown on profile's transaction history