Overview
MarkIt is built on a minimal set of Solidity contracts. Each market is a self-contained unit — a single MarketEngine contract that holds all USDC, all state, and all logic.
Contract Architecture
LpVault (holds aggregated LP capital, owns factory)
└── MarketFactory (deploys new markets)
└── MarketEngine (one per market)
├── owns → YES OutcomeToken (ERC-20)
└── owns → NO OutcomeToken (ERC-20)LpVault
The aggregated liquidity layer. LPs deposit USDC into the vault and receive shares priced at NAV. The vault owns the MarketFactory and deploys capital into markets via createAndFundBatch(). After markets resolve, the vault collects capital + fees. LPs withdraw through a FIFO queue that processes as float becomes available. The vault tracks share price, NAV, trailing APR, and deployed capital per market.
MarketEngine
The core per-market contract. Each market is a separate MarketEngine deployment that manages the full lifecycle: LP deposits, position placement, pricing, resolution, redemption, and LP withdrawal. The MarketEngine holds all USDC for its market directly.
OutcomeToken
Standard ERC-20 tokens (18 decimals) representing YES and NO outcomes. Each market deploys its own pair. Only the parent MarketEngine can mint or burn tokens — this is enforced by a one-time engine binding set at deployment.
MarketFactory
A factory that deploys new markets (owned by LpVault). It creates a MarketEngine and its two OutcomeTokens in a single transaction, configuring them with default parameters. The factory tracks all deployed markets.
Supporting Libraries
WadMath — 18-decimal fixed-point math (toWad, fromWad, wadMul, wadDiv). Used internally by MarketEngine.
MockUSDC — A test ERC-20 with 6 decimals and public minting. Used on testnet only.
Design Principles
One contract per market. No shared state between individual markets. If one market has an issue, others are unaffected. The LpVault aggregates LP capital above the market level but each funded market holds its own USDC independently.
No external dependencies at runtime. MarketEngine does not call oracles, price feeds, or other protocols during normal operation. Resolution is operator-triggered.
Actual balance, not shadow accounting. The solvency invariant checks USDC.balanceOf(address(this)) — the real on-chain balance. This eliminates an entire class of accounting bugs.
Minimal privileged operations. Market creation and resolution flow through the LpVault (which owns the factory). Trading, redemptions, and LP withdrawals at the market level are permissionless. Vault deposits and withdrawal requests are permissionless; only vault admin functions (batch creation, resolution, emergency ops) are restricted.
Security
All state-changing functions are protected with reentrancy guards (
nonReentrant)LP withdrawals reserve USDC for unredeemed winners before calculating LP payouts
renounceOwnership()is disabled to prevent accidental fund lockupThe solvency invariant is checked after every state change using the actual USDC balance
The dynamic skew cap limits extreme one-sided exposure
Piecewise integration (16 steps) prevents single large positions from extracting value at stale prices
Tech Stack
Language
Solidity 0.8.24
Framework
Foundry (forge)
Compiler
via_ir = true, EVM target: Paris
Dependencies
OpenZeppelin (ERC20, Ownable)
Chain
Base L2 (Base Sepolia for testnet)
Collateral
USDC (6 decimals)
Last updated