Skip to main content

Smart Contracts

All contracts are deployed on Base (Ethereum L2), written in Solidity 0.8.24, and use OpenZeppelin libraries for security primitives.


Contract Overview


BaseEscrow

The abstract base contract that every game engine inherits. It handles everything related to money — staking, settlement, rake, refunds, timeouts. Game engines only need to implement game-specific logic.

Constants

ConstantValueDescription
RAKE_BPS100010% total rake on every settled pot
DEV_ROYALTY_BPS2502.5% of pot to the game developer
MUTUAL_TIMEOUT_PENALTY_BPS1001% penalty on mutual timeout refunds
JOIN_WINDOW1 hourTime for opponents to join after match creation
MIN_STAKE100,000Minimum stake: 0.10 USDC (6 decimals)

Match Lifecycle

Every match follows the same state machine regardless of game type:

OPEN — Match created, waiting for opponents. Creator has staked USDC.

PENDING — All players joined. Spectator betting window is open. Match activates after bettingDeadline passes.

ACTIVE — Game in progress. Players submit moves, referee settles rounds.

SETTLED — Winner determined. Pot distributed minus rake.

VOIDED — Match cancelled. All players refunded.

Match Creation

When a game engine calls _initMatch(), BaseEscrow:

  1. Validates the stake meets MIN_STAKE
  2. Validates player count (2-6)
  3. Validates maxRounds >= winsRequired
  4. Increments matchCounter for a new match ID
  5. Pulls USDC from the creator via safeTransferFrom
  6. Adds creator as players[0]
  7. Initializes the wins[] array

Joining

joinMatch() pulls the same stake amount from the joining player. When the match is full:

  • If bettingWindow > 0 — match goes to PENDING, prediction pool is auto-created, match activates after the deadline
  • If bettingWindow == 0 — match goes directly to ACTIVE

Settlement

BaseEscrow provides three settlement paths:

Single winner_settleMatchSingleWinner(matchId, winnerIndex)

  • Calculates 10% rake (7.5% protocol + 2.5% developer)
  • Sends developer royalty to the address from LogicRegistry
  • Sends protocol rake to treasury
  • Sends remaining pot to winner
  • Records volume in LogicRegistry

Draw_settleMatchDraw(matchId)

  • Same rake calculation
  • Remaining pot split equally among all players
  • Rounding dust goes to last player

Split pot_settleMatch(matchId, resolution)

  • Supports arbitrary winner splits via basis points (must sum to 10,000)
  • Used for poker split pots

Rake Math

For a 2-player match with 1 USDC stakes each (2 USDC pot):

Total pot:        2,000,000  (2.00 USDC)
Total rake (10%): 200,000 (0.20 USDC)
Dev royalty: 50,000 (0.05 USDC) → game developer
Protocol rake: 150,000 (0.15 USDC) → treasury
Winner payout: 1,800,000 (1.80 USDC)

If no developer is registered for the game logic, the full 10% goes to treasury.

Timeouts

BaseEscrow exposes two timeout mechanisms:

  • claimTimeout() — any player in an active match can call this. The game engine's _claimTimeout() override determines who timed out and awards the win.
  • mutualTimeout() — both players get refunded minus a 1% penalty. The game engine's _mutualTimeout() override enforces timing requirements.

Safety Features

  • ReentrancyGuard on all external fund-moving functions
  • Ownable2Step — ownership transfer requires acceptance (prevents accidental transfers)
  • Pausable — owner can freeze all match creation and gameplay
  • Pull payments — if a USDC transfer fails (blocklisted address), funds go to a withdrawal queue via pendingWithdrawals
  • adminVoidMatch() — emergency escape hatch with pro-rata refunds accounting for mid-game distributions

PokerEngineV5

The poker-specific game engine. Inherits BaseEscrow and adds a complex phase machine for fair card games with hidden information.

Supported Variants

VariantPlayersPrivate CardsBoard CardsDraw Phase
5-Card Draw250Yes
Texas Hold'em2-625No
Omaha2-645No
7-Card Stud2-630No

Phase Machine

Poker matches follow a strict phase progression:

Entropy Commit/Reveal

This is how FALKEN ensures fair dealing without trusting the referee:

  1. Commit — each player submits keccak256(salt) on-chain. Nobody can see anyone's salt.
  2. Reveal — each player reveals their actual salt. The contract verifies it matches the commit.
  3. Seed — the referee combines all salts with the match ID and round number to derive a deterministic deck shuffle.

Since both players contribute entropy, neither the referee nor any single player can control the deck.

Delegatecall Pattern

PokerEngineV5 exceeds the EIP-170 contract size limit (24KB), so betting actions, resolution logic, and timeout handling are split into PokerEngineV5Actions — a separate contract called via delegatecall. This executes the Actions code in the context of PokerEngineV5's storage.

Key Functions

FunctionCallerDescription
createMatch()PlayerStake USDC, pick variant, set rounds
commitEntropy()PlayerSubmit salt hash
submitPrivateEntropy()RefereeReveal player salts
publishInitialHandReceipt()RefereeCommit to dealt hands
publishDealCheckpoint()RefereeAdvance deal stage
check() / call() / raise() / fold()PlayerBetting actions
submitDiscardMask()PlayerDraw phase discards
resolveRound()RefereeDeclare round winner
resolveShowdownSplit()RefereeSplit pot between winners

TurnEngineV1

A generic engine for any 2-player turn-based game with open state — chess, checkers, Go, Connect Four, RPG battles. Much simpler than PokerEngineV5 because there's no hidden information.

How It Works

  1. Players alternate submitting moves as strings (e.g. "e2e4" for chess)
  2. The contract records each move as an event and enforces turn order
  3. Move validation happens off-chain in the FISE sandbox
  4. The referee calls settleRound() when a terminal state is detected

Multi-Round Support

TurnEngineV1 supports best-of-N matches:

createMatch(stake, logicId, winsRequired=2, maxRounds=3)
  • Starting player alternates each round for fairness
  • Move count resets each round
  • Match auto-settles when a player reaches winsRequired or maxRounds is exhausted
  • If tied after max rounds, _settleByMostWins() picks the leader or declares a draw

Timeouts

  • MOVE_TIMEOUT — 10 minutes. If the active player doesn't move in time, the opponent can claim the round.
  • In multi-round matches, a timeout awards a round win (not an instant match win).
  • Mutual timeout — if both players are inactive, match settles as a draw with refunds.

Key Functions

FunctionCallerDescription
createMatch()PlayerStake USDC, pick game, set rounds
submitMove()PlayerRecord a move (turn enforced)
settleRound()RefereeDeclare round winner
claimTimeout()PlayerClaim win if opponent is inactive
getTurnState()AnyoneView current turn, move count, last move time

LogicRegistry

The on-chain registry that maps game logic to IPFS content hashes. This is how the protocol knows which JavaScript to execute for each game.

Registration

// Register a new game
bytes32 logicId = registry.registerLogic(
"QmPokerV7...", // IPFS CID
developerAddr, // Receives 2.5% royalty
true, // bettingEnabled
4 // maxStreets (Hold'em)
);

// Register a simple game (no betting phases)
bytes32 logicId = registry.registerSimpleGame(
"QmChessV1...", // IPFS CID
developerAddr // Receives 2.5% royalty
);

The logicId is keccak256(ipfsCid) — deterministic and verifiable.

Volume Tracking

Every time a match settles, the game engine calls recordVolume(logicId, amount) on the registry. Only authorized escrow contracts can call this function. This tracks total USDC volume per game for analytics and developer leaderboards.

Access Control

  • Only the protocol owner can register new games (curated beta)
  • Games can be deactivated (setActive(logicId, false)) — existing matches continue but no new ones can be created
  • Escrow contracts must be whitelisted (setAuthorizedEscrow) to record volume

PredictionPool

Parimutuel betting pools where spectators predict match outcomes.

How It Works

  1. When a match goes to PENDING, the game engine auto-creates a prediction pool with outcomes like ["Player 1", "Player 2", "Draw"]
  2. Spectators place USDC bets on their predicted outcome during the betting window
  3. After the match settles, the contract resolves the pool
  4. Winners split the losing side's bets proportionally to their stake (minus 10% rake)
  5. If the match is a draw, all bets are refunded (no rake)

Key Properties

PropertyValue
Rake10% on winning pool
Minimum bet0.10 USDC
Max outcomes10 per pool
Draw handlingFull refund, no rake

Security

  • Only authorized escrow contracts can create match-linked pools
  • Betting deadline enforced on-chain — no bets after the window closes
  • Duplicate pool prevention per match
  • Pull-payment fallback for failed claim transfers