Skip to main content

Overview

MouthBetFactory is the core protocol contract. It deploys individual MouthBet escrow contracts and stores protocol-wide configuration. Deployed behind a UUPS proxy so the address remains fixed across upgrades. Inherits from Initializable, UUPSUpgradeable, and OwnableUpgradeable (OpenZeppelin v5).

State Variables

VariableTypeDescription
resolveraddressAddress authorized to resolve bets
treasuryaddressAddress receiving protocol fees
feePercentageuint256Protocol fee in basis points (200 = 2%), snapshotted per-bet at creation and collected per-deposit
usdcaddressUSDC token address on Base
betCountuint256Total number of bets created
betsmapping(uint256 => address)Bet ID → Bet contract address
isMouthBetmapping(address => bool)Verify if an address is a legitimate Mouth bet
owner is inherited from OwnableUpgradeable and is not listed separately. Storage layout must be preserved across upgrades — new variables are only appended at the end. The MAX_FEE (500 = 5%) and MIN_POT (10e6 = 10 USDC) constants are also defined.

Functions

initialize

Replaces the constructor. Called once at deployment via the proxy.
function initialize(
    address _resolver,
    address _treasury,
    address _usdc
) external initializer
Logic:
  1. Initialize OwnableUpgradeable with msg.sender as owner
  2. Initialize UUPSUpgradeable
  3. Set resolver, treasury, usdc
  4. Set feePercentage to 200 (2%)

createBet

Deploys a new MouthBet contract, calls its initialize(), and transfers the Challenger’s USDC deposit to the bet contract.
function createBet(
    address _challenger,
    address _opponent,          // address(0) if Open PvP
    uint256 _challengerAmount,  // USDC amount the Challenger deposits
    uint256 _odds,              // Challenger's perceived probability (10-90, default 50)
    uint256 _expirationDate,    // unix timestamp
    uint256 _acceptanceDeadline, // unix timestamp (mandatory for all bet types)
    string calldata _title,
    bool _disputeable           // If true, 48h dispute window. If false, immediately finalized.
) external returns (uint256 betId, address betAddress)
Access: Only callable by the Mouth backend (via server-side wallet) or directly by users. The Challenger must have approved the Factory contract to spend their USDC before calling. Logic:
  1. Validate parameters:
    • _challengerAmount must be greater than 0
    • _odds must be between 10 and 90 (inclusive)
    • _expirationDate must be in the future
    • _acceptanceDeadline must be in the future (> block.timestamp) and before _expirationDate
  2. Calculate totalOpponentAmount: _challengerAmount * (100 - _odds) / _odds
  3. Validate minimum pot: _challengerAmount + totalOpponentAmount >= 10 USDC (10e6 with 6 decimals)
  4. Deploy new MouthBet contract: MouthBet bet = new MouthBet()
  5. Call bet.initialize(...) with all parameters including _disputeable (sets challengerDeposited = true)
  6. Transfer Challenger’s USDC to the bet contract: IERC20(usdc).safeTransferFrom(_challenger, address(bet), _challengerAmount)
  7. Store bet address in bets mapping
  8. Mark address in isMouthBet
  9. Emit BetCreated event
  10. Return bet ID and contract address
The Challenger’s USDC is transferred in the same transaction as bet creation. The Challenger must approve the Factory contract (not the bet contract) to spend their USDC. The opponent address can be a pre-generated Privy wallet for users who haven’t signed up yet — see Identity & Wallets.

updateResolver

Updates the resolver address.
function updateResolver(address _newResolver) external onlyOwner

updateTreasury

Updates the treasury address.
function updateTreasury(address _newTreasury) external onlyOwner

updateFeePercentage

Updates the protocol fee.
function updateFeePercentage(uint256 _newFee) external onlyOwner
Constraint: Fee cannot exceed 500 basis points (5%).

_authorizeUpgrade

UUPS hook — restricts upgrades to the contract owner.
function _authorizeUpgrade(address newImplementation) internal override onlyOwner

View Functions

function getBetAddress(uint256 _betId) external view returns (address)
function getBetCount() external view returns (uint256)
function verifyBet(address _bet) external view returns (bool)

Events

event BetCreated(
    uint256 indexed betId,
    address indexed betAddress,
    address indexed challenger,
    address opponent,
    uint256 challengerAmount,
    uint256 totalOpponentAmount,
    uint256 odds,
    uint256 expirationDate
);

event ResolverUpdated(address indexed oldResolver, address indexed newResolver);
event TreasuryUpdated(address indexed oldTreasury, address indexed newTreasury);
event FeeUpdated(uint256 oldFee, uint256 newFee);

Upgrade Path

The UUPS proxy allows upgrading the Factory implementation without changing its address or losing state:
v1 (now):    new MouthBet() + initialize()       ← full deploy
v2 (future): Clones.clone(impl) + initialize()   ← minimal proxy, ~10x cheaper
To migrate to clones, add a mouthBetImplementation state variable and update createBet() to use Clones.clone(). No changes needed to MouthBet itself.