Each prediction market on VORTX is its own smart contract that manages the market's lifecycle, tokens, and resolution. This contract handles everything from initial liquidity to final payouts.
Core Functionality
Primary Purpose
Manage market metadata: Store title, description, end time, resolution criteria
Control YES/NO tokens: Mint, burn, and manage token supply
Handle liquidity: Process deposits and convert to tokens
Manage resolution: Track outcome and enable winner payouts
Integrate with OrderBook: Enable trading through central exchange
Key State Variables
string public title; // Market question
string public description; // Detailed explanation
uint256 public endTime; // When trading stops
string public resolutionCriteria; // How outcome is determined
address public creator; // Market creator address
IERC20 public usdt0Token; // Collateral token
IERC20 public yesToken; // YES outcome token
IERC20 public noToken; // NO outcome token
IOrderBook public orderBook; // Central trading engine
uint256 public marketId; // Unique identifier
bool public isResolved; // Resolution status
bool public outcome; // Final result (true = YES wins)
uint256 public totalDeposited; // Total USDT deposited
uint256 public resolutionTime; // When market was resolved
Token Management
Initial Liquidity Processing
When market is created, converts USDT to YES/NO tokens.
function processInitialLiquidity(address recipient, uint256 amount) external onlyFactory {
require(amount >= 100 * 10**6, "Minimum 100 USDT required");
// Mint equal amounts of YES and NO tokens
yesToken.mint(recipient, amount);
noToken.mint(recipient, amount);
totalDeposited += amount;
emit LiquidityAdded(recipient, amount, amount, amount);
}
Deposit Function
Anyone can deposit USDT to get YES/NO tokens.
function deposit(uint256 amount) external {
require(!isResolved, "Market already resolved");
require(block.timestamp < endTime, "Market has ended");
require(amount > 0, "Amount must be positive");
// Transfer USDT from user
require(usdt0Token.transferFrom(msg.sender, address(this), amount), "USDT transfer failed");
// Mint equal YES and NO tokens
yesToken.mint(msg.sender, amount);
noToken.mint(msg.sender, amount);
totalDeposited += amount;
emit TokensMinted(msg.sender, amount, amount);
}
Why this matters: This is how market making works - deposit USDT, get both tokens, sell the side you disagree with.
Redeem Function
Burn YES/NO token pairs to get USDT back.
function redeem(uint256 amount) external {
require(amount > 0, "Amount must be positive");
// Check user has both token types
require(yesToken.balanceOf(msg.sender) >= amount, "Insufficient YES tokens");
require(noToken.balanceOf(msg.sender) >= amount, "Insufficient NO tokens");
// Burn both token types
yesToken.burnFrom(msg.sender, amount);
noToken.burnFrom(msg.sender, amount);
// Return USDT
require(usdt0Token.transfer(msg.sender, amount), "USDT transfer failed");
totalDeposited -= amount;
emit TokensRedeemed(msg.sender, amount);
}
Use case: If you have both YES and NO tokens, you can always get your USDT back (guaranteed $1 per pair).
Market Resolution
Resolution Process
Only admin can resolve during Phase 1.
function resolve(bool _outcome) external onlyAdmin {
require(!isResolved, "Already resolved");
require(block.timestamp >= endTime, "Market still active");
isResolved = true;
outcome = _outcome;
resolutionTime = block.timestamp;
// Notify OrderBook that trading should stop
orderBook.marketResolved(marketId);
emit MarketResolved(marketId, _outcome, block.timestamp);
}
Claiming Winnings
Winners can redeem their tokens for USDT.
function claimWinnings() external {
require(isResolved, "Market not resolved yet");
IERC20 winningToken = outcome ? yesToken : noToken;
uint256 winningAmount = winningToken.balanceOf(msg.sender);
require(winningAmount > 0, "No winning tokens to claim");
// Burn winning tokens
winningToken.burnFrom(msg.sender, winningAmount);
// Calculate payout (98% after 2% resolution fee)
uint256 grossPayout = winningAmount;
uint256 resolutionFee = (grossPayout * 200) / 10000; // 2%
uint256 netPayout = grossPayout - resolutionFee;
// Transfer net amount to winner
require(usdt0Token.transfer(msg.sender, netPayout), "Payout failed");
// Send resolution fee to oracle network
require(usdt0Token.transfer(oracleManager, resolutionFee), "Fee transfer failed");
emit WinningsClaimed(msg.sender, winningAmount, netPayout, resolutionFee);
}
Integration with OrderBook
Trading Authorization
Market authorizes OrderBook to move tokens during trades.
function authorizeTrading() external onlyFactory {
// Give OrderBook permission to transfer tokens during trades
yesToken.approve(address(orderBook), type(uint256).max);
noToken.approve(address(orderBook), type(uint256).max);
}
Trade Execution Support
OrderBook calls these functions during trades.
function executeTokenTransfer(
address tokenAddress,
address from,
address to,
uint256 amount
) external onlyOrderBook {
IERC20 token = IERC20(tokenAddress);
require(token == yesToken || token == noToken, "Invalid token");
// OrderBook handles the actual transfer logic
// This function validates the market allows the trade
require(!isResolved, "Market resolved");
require(block.timestamp < endTime, "Market ended");
}
The contract enforces: Total YES supply = Total NO supply = Total USDT deposited
function verifyTokenConservation() external view returns (bool) {
uint256 yesSupply = yesToken.totalSupply();
uint256 noSupply = noToken.totalSupply();
// These should always be equal
return (yesSupply == noSupply && yesSupply == totalDeposited);
}
Arbitrage Prevention
Smart contract math prevents price manipulation:
If YES + NO ≠ $1, arbitrage opportunities exist
Users can deposit USDT → get both tokens → sell at profit
This naturally corrects any pricing imbalances
Error Handling
Common Revert Conditions
require(!isResolved, "Market already resolved");
require(block.timestamp < endTime, "Market has ended");
require(amount > 0, "Amount must be positive");
require(yesToken.balanceOf(msg.sender) >= amount, "Insufficient YES tokens");
Edge Case Handling
Market ends before resolution: Admin can still resolve
No trading volume: Market can still be resolved
Partial token holdings: Users can redeem any amount of pairs
Usage Examples
Market Making Strategy
// 1. Deposit USDT to get both tokens
await market.deposit(ethers.utils.parseEther("1000")); // Get 1000 YES + 1000 NO
// 2. Sell the side you disagree with
await yesToken.approve(orderBookAddress, ethers.utils.parseEther("1000"));
await orderBook.placeOrder(marketId, TokenType.YES, false, OrderType.MARKET, 0, ethers.utils.parseEther("1000"));
// 3. Keep NO tokens if you think outcome is NO
Claiming Winnings
// After market resolves in your favor
const winningBalance = await yesToken.balanceOf(userAddress);
if (winningBalance > 0) {
await market.claimWinnings();
// Receive 98% of token value in USDT
}
The PredictionMarket contract is the heart of each individual market, managing the entire lifecycle from creation to final payout while maintaining mathematical precision and security.