Introduction
In the world of prediction markets, precision in language is crucial. When Polymarket's $345 million Iran peace deal bet became stuck due to a semantic dispute over the term 'permanent,' it highlighted the importance of clear definitions in smart contracts and decentralized applications. This tutorial will teach you how to build a smart contract-based prediction market system using Ethereum and Solidity that handles such semantic ambiguities through configurable parameters and clear dispute resolution mechanisms.
Prerequisites
- Basic understanding of Ethereum and Solidity
- Node.js and npm installed
- Truffle framework or Hardhat for smart contract development
- MetaMask wallet extension
- Familiarity with JavaScript and web development concepts
Step-by-step Instructions
1. Setting Up the Development Environment
1.1 Initialize Project Structure
First, create a new directory for our prediction market project and initialize it with npm:
mkdir prediction-market
cd prediction-market
npm init -y
This creates a basic project structure that we'll build upon.
1.2 Install Required Dependencies
Install the necessary development tools:
npm install @truffle/contract @openzeppelin/contracts web3
We're using OpenZeppelin contracts for secure, reusable components and web3 for Ethereum interaction.
2. Creating the Prediction Market Smart Contract
2.1 Define the Contract Structure
Let's create our main prediction market contract that handles bets with configurable timeframes and dispute resolution:
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract PredictionMarket is Ownable {
struct Bet {
uint256 amount;
bool outcome;
uint256 timestamp;
bool resolved;
uint256 disputeResolution;
}
struct Market {
string question;
string definition;
uint256 deadline;
bool resolved;
uint256 totalBets;
uint256 totalAmount;
mapping(address => Bet) bets;
}
mapping(uint256 => Market) public markets;
uint256 public marketCount;
event BetPlaced(address indexed user, uint256 marketId, bool outcome, uint256 amount);
event MarketResolved(uint256 marketId, bool result);
function createMarket(string memory _question, string memory _definition, uint256 _deadline) public onlyOwner {
marketCount++;
markets[marketCount] = Market({
question: _question,
definition: _definition,
deadline: _deadline,
resolved: false,
totalBets: 0,
totalAmount: 0
});
}
function placeBet(uint256 marketId, bool outcome, uint256 amount) public {
Market storage market = markets[marketId];
require(!market.resolved, "Market already resolved");
require(block.timestamp < market.deadline, "Market deadline passed");
market.bets[msg.sender] = Bet({
amount: amount,
outcome: outcome,
timestamp: block.timestamp,
resolved: false,
disputeResolution: 0
});
market.totalBets++;
market.totalAmount += amount;
emit BetPlaced(msg.sender, marketId, outcome, amount);
}
}
This contract sets up the basic structure for prediction markets with clear definitions and time constraints, essential for avoiding semantic disputes.
2.2 Add Dispute Resolution Mechanism
Now, let's enhance our contract with a dispute resolution system:
function resolveMarket(uint256 marketId, bool result) public onlyOwner {
Market storage market = markets[marketId];
require(!market.resolved, "Market already resolved");
require(block.timestamp >= market.deadline, "Market deadline not passed");
market.resolved = true;
market.bets[msg.sender].disputeResolution = result ? 1 : 2;
emit MarketResolved(marketId, result);
}
function getMarketResult(uint256 marketId) public view returns (bool) {
Market storage market = markets[marketId];
return market.bets[msg.sender].disputeResolution == 1;
}
The dispute resolution mechanism allows for official resolution of semantic ambiguities through trusted ownership.
3. Building the Frontend Interface
3.1 Create Basic HTML Structure
Build a simple interface to interact with our contract:
<!DOCTYPE html>
<html>
<head>
<title>Prediction Market</title>
</head>
<body>
<h1>Prediction Market Platform</h1>
<div id="marketForm">
<h2>Create Market</h2>
<input type="text" id="question" placeholder="Question"><br>
<input type="text" id="definition" placeholder="Definition"><br>
<input type="number" id="deadline" placeholder="Deadline (timestamp)"><br>
<button onclick="createMarket()">Create Market</button>
</div>
<div id="betForm">
<h2>Place Bet</h2>
<input type="number" id="marketId" placeholder="Market ID"><br>
<input type="number" id="amount" placeholder="Amount"><br>
<button onclick="placeBet(true)">Bet Yes</button>
<button onclick="placeBet(false)">Bet No</button>
</div>
</body>
</html>
This basic interface allows users to create markets and place bets, which is essential for real-world application.
3.2 Implement Web3 Integration
Connect our frontend to the Ethereum network:
const Web3 = require('web3');
const web3 = new Web3(window.ethereum);
let contract;
let accounts;
async function init() {
accounts = await web3.eth.getAccounts();
const networkId = await web3.eth.net.getId();
const deployedNetwork = PredictionMarket.networks[networkId];
contract = new web3.eth.Contract(
PredictionMarket.abi,
deployedNetwork && deployedNetwork.address
);
}
async function createMarket() {
const question = document.getElementById('question').value;
const definition = document.getElementById('definition').value;
const deadline = document.getElementById('deadline').value;
await contract.methods.createMarket(question, definition, deadline).send({
from: accounts[0]
});
}
async function placeBet(outcome) {
const marketId = document.getElementById('marketId').value;
const amount = document.getElementById('amount').value;
await contract.methods.placeBet(marketId, outcome, amount).send({
from: accounts[0]
});
}
This integration allows users to interact with the smart contract directly from their browser, making the system accessible and user-friendly.
4. Testing and Deployment
4.1 Write Test Cases
Create a test file to verify our contract logic:
const PredictionMarket = artifacts.require("PredictionMarket");
contract("PredictionMarket", (accounts) => {
it("should create a market with proper definition", async () => {
const instance = await PredictionMarket.new();
const deadline = Math.floor(Date.now() / 1000) + 86400;
await instance.createMarket("Iran Peace Deal", "A permanent peace agreement between US and Iran", deadline);
const market = await instance.markets(1);
assert.equal(market.question, "Iran Peace Deal");
assert.equal(market.definition, "A permanent peace agreement between US and Iran");
});
});
Proper testing ensures that semantic definitions are properly enforced and disputes can be resolved correctly.
4.2 Deploy to Testnet
Deploy your contract to a test network like Rinkeby or Goerli:
npx truffle migrate --network rinkeby
Deployment to testnet allows you to test real-world scenarios without risking real funds.
Summary
This tutorial demonstrated how to build a smart contract-based prediction market system that handles semantic ambiguities through clear definitions, time constraints, and dispute resolution mechanisms. The system addresses the core issue from the Polymarket incident by ensuring that all market parameters are clearly defined upfront, reducing ambiguity in outcomes. By using Ethereum's smart contracts, we create a trustless system where disputes can be resolved through predetermined mechanisms, preventing situations where $345 million in bets become stuck due to semantic disagreements. The frontend interface provides user accessibility while the testing framework ensures reliability. This approach can be extended to include more sophisticated dispute resolution, multi-signature approvals, and automated outcome determination based on official sources.



