TEST MODE — Play money, not real dollars
90picksDocumentation

How 90Picks Works

90Picks is an on-chain parimutuel betting platform for FIFA World Cup 2026, built on Base L2. This page covers the architecture, smart contracts, fee model, and security approach.

1. Parimutuel Model

90Picks uses a parimutuel model — the same system used by horse racing and tote betting for over 150 years. There are no fixed odds and no bookmaker. Instead:

  1. Pick a side — Home, Draw, or Away for any World Cup match
  2. Put dollars in — Your dollars go into the match pot (minimum $1, maximum $10,000)
  3. Winners split the pot — After the match, everyone who picked correctly splits the total pot proportionally

The less popular your pick, the bigger the payout if you win. All multipliers shown during the open phase are estimates (marked with ~). Final payouts are determined only when betting closes, 30 minutes before kickoff.

2. Architecture

The system has three components:

Users (Coinbase Smart Wallet)
    │
    ├── deposit / claim
    │
    ▼
Frontend (Next.js on Vercel)
    │
    ├── reads pool data + sends transactions
    │
    ▼
Base L2 Blockchain
    │
    ├── PoolFactory → creates MatchPool per match
    ├── MatchPool #1 (Mexico vs Canada)
    ├── MatchPool #2 (USA vs Morocco)
    └── MatchPool #N ...
          │
          ├── resolve() ← CRE Resolver (polls match results)
          └── cancel()  ← CRE Resolver or Owner

Base L2 provides sub-cent gas costs and fast finality. All transactions are sponsored by the Coinbase paymaster — users never need to hold ETH or pay gas fees.

3. Smart Contracts

Two Solidity contracts, built with OpenZeppelin:

PoolFactory.sol~100 lines

Deploys one MatchPool per match using CREATE2 (deterministic addresses from match ID). Owner-controlled pool creation.

MatchPool.sol~215 lines

Handles deposits, resolution, claims, fees, and cancellation for a single match. Uses ReentrancyGuard and SafeERC20.

Key Functions

deposit(outcome, amount, referrer)Place a bet during the open phase
resolve(outcome)CRE resolver sets the match winner
cancel()CRE resolver or owner cancels — everyone gets full refunds
claim()Winners collect proportional payout, or full refund on cancel
resolveManual(outcome)Owner safety valve — only after deadline + 48 hours
collectFees()Fee recipient withdraws 2.5% of losing pools after 7-day claim window
sweep()Owner cleans up remaining dust after 90 days

Constants

FEE_BPS = 2502.5% fee on losing pools
MIN_BET = $11,000,000 USDC units (6 decimals)
MAX_BET = $10,00010,000,000,000 USDC units

4. Pool Lifecycle

[CREATED] ──deposit()──▶ [OPEN] ──deadline──▶ [CLOSED]
                                                    │
                                         resolve() or cancel()
                                                    │
                                    ┌───────────────┼───────────────┐
                                    ▼                               ▼
                              [RESOLVED]                      [CANCELLED]
                                    │                               │
                                claim()                         claim()
                              (winners)                       (full refund)
                                    │
                             collectFees()
                            (after 7 days)
                                    │
                               sweep()
                           (after 90 days)
  • Open phase: Users can deposit until 30 minutes before kickoff
  • No withdrawals: Bets lock at deposit — this prevents odds manipulation
  • 90-minute result: Only the 90-minute score counts. Extra time and penalties don't affect the outcome — Draw is always a valid pick
  • Safety valve: If the automated resolver fails, the owner can resolve manually after 48 hours

5. Fee Model

A 2.5% fee is charged on losing pools only. Winners' deposits are never taxed.

Example: $10,000 total pot
  Home: $6,000 (60%)  ←  wins
  Draw: $1,500 (15%)
  Away: $2,500 (25%)

Losing pool = $4,000 (Draw + Away)
Fee = $4,000 × 2.5% = $100
Distributable to winners = $9,900

A $100 bet on Home → payout = $9,900 × ($100 / $6,000) = $165

Referral Rebate

If a user was referred, they receive a 50% rebate on their proportional fee share. The rebate comes from the platform fee — it does not affect other users' payouts.

6. Security

  • OpenZeppelin libraries — ReentrancyGuard on deposit/claim, SafeERC20 for all USDC transfers. Battle-tested, industry-standard.
  • Key separation — Three distinct roles with different permissions: Factory Owner (creates pools, emergency controls), CRE Resolver (resolves/cancels matches), and Users (deposit/claim only). Compromise of one key doesn't give full control.
  • Safety valves — Owner can cancel any pool for full refunds. Manual resolution available after 48-hour waiting period. Fee collection delayed 7 days after resolution for claim window.
  • No admin withdrawal — There is no function for the owner to withdraw user deposits. The only outflows are claim() (to users), collectFees() (to fee recipient, after 7 days), and sweep() (dust cleanup after 90 days).
  • Deterministic addresses — Pool addresses are derived from match IDs via CREATE2. Anyone can verify a pool's authenticity on-chain.

7. Test Suite

59 tests covering all contract functions, edge cases, and mathematical invariants. Built with Foundry (Forge).

Coverage

  • Deposit flows — valid deposits, boundary amounts ($1 / $10,000), referral tracking, paused pool, closed pool, invalid outcomes
  • Resolution — CRE resolve, manual resolve (48h delay), resolve with all three outcomes, resolution when one outcome has zero deposits
  • Cancellation — CRE cancel, owner cancel, full refund verification
  • Claims — winner payouts, proportional math, double-claim prevention, refunds on cancel
  • Fee math — 2.5% calculation, referral rebate deduction, fee collection timing (7-day window), zero-fee edge cases
  • Invariant testing — conservation of funds (payout + fees = totalPool), cancel = exact refund, fee bounds, double-claim prevention, referral rebate conservation
  • Sweep — 90-day timing, dust recovery, unauthorized access

Source code: github.com/djehuty94/Golazo

8. Audit Status

No formal third-party audit has been completed yet.

The contracts use battle-tested OpenZeppelin libraries (ReentrancyGuard, SafeERC20) and have 59 tests including invariant testing. The source code is fully open-source. A formal security audit is planned before mainnet launch.

9. Contract Addresses

Contract addresses will be published here once mainnet deployment is complete.

PoolFactoryBase (Chain ID: 8453)
Deploying to Base mainnet — coming June 2026

Individual MatchPool addresses are deterministic — derived from the match ID via CREATE2. Once pools are created, each match page will link directly to its contract on BaseScan.