Or: what broke on my first three attempts so you don't have to repeat it
Step 0. Before you write a single line of code
I've built two prediction markets from scratch. The first one crashed on testnet. The second one launched but had zero users for two months. The third one? Actually works. Here's what I learned in the process.
Ask yourself three boring but critical questions:
- Binary outcomes (Yes/No) or multiple choices?
- Who decides the truth? (centralized admin, oracle, token holders?)
- How does liquidity enter the system on day one?
Don't skip these. I skipped #2 on my first try and ended up hardcoding the resolution logic. Guess what happened when a market ended in a tie? Panic. Tears. Rewrites.
Step 1. The simplest market math that doesn't suck
You'll hear about LMSR, logarithmic markets, and constant function market makers. Ignore the noise. Start with this — a basic LMSR implementation you can actually run:
python
import math
class SimpleMarket:
def __init__(self, liquidity=100):
self.b = liquidity
self.yes_shares = 0
self.no_shares = 0
def cost(self, yes_q, no_q):
exp_yes = math.exp(yes_q / self.b)
exp_no = math.exp(no_q / self.b)
return self.b * math.log(exp_yes + exp_no)
def price_yes(self):
cost_current = self.cost(self.yes_shares, self.no_shares)
cost_if_buy = self.cost(self.yes_shares + 1, self.no_shares)
return cost_if_buy - cost_current
market = SimpleMarket(liquidity=50)
print(f"Current Yes price: {market.price_yes():.4f}")
# Output: Current Yes price: 0.5000 (symmetric at start)
This is ugly but it works. No external dependencies. Run it in a Jupyter notebook. Tweak the liquidity parameter. See how prices move when you change shares.
What I learned: LMSR is safe but expensive to compute on-chain. You'll want to move price calculations off-chain later. But for a prototype? Ship it.
Step 2. The dumb oracle that saved my weekends
Oracles are the silent killer of prediction market projects. Everyone talks about them last. That's a mistake.
Here's a minimal oracle contract in Solidity — just enough to resolve a market without over-engineering:
solidity
// Simplified for clarity — not production ready!
contract SimpleOracle {
address public admin;
mapping(uint256 => bool) public marketResolved;
mapping(uint256 => bool) public marketOutcome; // true = Yes won
event Resolved(uint256 marketId, bool outcome);
constructor() {
admin = msg.sender;
}
function resolve(uint256 marketId, bool outcome) external {
require(msg.sender == admin, "only admin");
require(!marketResolved[marketId], "already resolved");
marketResolved[marketId] = true;
marketOutcome[marketId] = outcome;
emit Resolved(marketId, outcome);
}
}
Is this centralized? Yes. Is it fine for a prototype? Absolutely. You can replace the admin with a multisig or an optimistic oracle later. But start here. One of the most reliable prediction market development company setups I consulted for used exactly this pattern for their first three months. They swapped to UMA after hitting 10k users. Don't optimize for scale you don't have.
Step 3. The backend that won't collapse at 3 AM
You don't need GraphQL, microservices, or a Redis cluster. You need:
- One PostgreSQL database — stores market metadata (description, end time, resolution status)
- One Node.js relayer — batches trades, signs transactions, gossips prices
- One WebSocket server — pushes price updates to the frontend so users don't refresh like animals
Here's the relayer core — copy-paste this into relayer.js:
javascript
const { ethers } = require('ethers');
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST,
database: 'prediction_market'
});
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
async function settleExpiredMarkets() {
const now = Math.floor(Date.now() / 1000);
const expired = await pool.query(`
SELECT * FROM markets
WHERE end_time < $1 AND resolved = false
`, [now]);
for (const market of expired.rows) {
// Fetch result from oracle
const outcome = await checkOracleResolution(market.id);
// Call smart contract to resolve
const contract = new ethers.Contract(market.address, abi, wallet);
const tx = await contract.resolve(outcome);
await tx.wait();
await pool.query(
'UPDATE markets SET resolved = true WHERE id = $1',
[market.id]
);
}
}
setInterval(settleExpiredMarkets, 60000); // check every minute
This runs. It settles markets automatically. It won't win awards for elegance, but it will let you sleep through the night.
Step 4. Gas costs will humble you
First week on Ethereum mainnet: someone placed a
5bet.Theypaid
5bet.Theypaid18 in gas. They never came back.
Here's what actually works:
| Chain | Gas per trade | My experience |
|---|---|---|
| Ethereum | $5–20 | Don't bother for MVP |
| Polygon | $0.01–0.05 | Solid, but feels "cheap" to degens |
| Arbitrum | $0.05–0.15 | My current choice. Good balance. |
| Base | $0.01–0.03 | Rising star. Cheap. Fewer tools. |
I moved to Arbitrum and never looked back. But here's the hack: batching trades saved me more than switching chains.
Instead of settling each bet individually, collect 10–20 signed orders off-chain, then settle them in one contract call:
solidity
function batchTrade(Trade[] calldata trades) external {
uint256 totalCost = 0;
for(uint i = 0; i < trades.length; i++) {
require(verifySignature(trades[i]), "invalid sig");
totalCost += trades[i].amount;
_updateShares(trades[i].user, trades[i].outcome, trades[i].shares);
}
require(msg.value >= totalCost, "insufficient payment");
}
Gas cost per trade dropped from
0.50to
0.50to0.03. Your users won't notice. Your wallet will.
Step 5. The mistake that killed my first launch
I spent three months building an automated market maker with dynamic fees, a beautiful UI, and a governance token nobody asked for.
Zero users.
Why? Because I never asked a single bettor what they actually wanted.
Turns out, real users want three things:
- Capped markets — "Max 1000 shares total" so whales can't manipulate
- Mobile-friendly — half my testers opened the site on an iPhone 12
- Clear resolution rules — "What timezone? What source? What if it's a tie?"
I added market caps in one afternoon. Usage went up 3x.
So before you add another feature: find one person who'll bet $10 real dollars on your market. If they won't, stop building and start talking.
Step 6. Launch checklist (print this and tape it to your monitor)
- Smart contract verified on Etherscan/Arbiscan
- At least $500 in initial liquidity (from your own pocket if needed)
- Admin key in a multisig (Gnosis Safe is fine)
- One market created and resolved on testnet successfully
- Database backup cron job (I learned this one the hard way)
- Rate limiting on your relayer (someone will spam you)
- A "Report bug" button that actually emails you
Where to go from here
This guide won't make you the next Polymarket. But it will get you to a working MVP that you can show to friends, investors, or just yourself in the mirror.
The code snippets here are simplified — I left out reentrancy guards, access controls, and edge cases for clarity. If you're deploying real money, get a security audit. Yes, it's expensive. No, you can't skip it.
But for a prototype? You're ready.
Now go break things. Then fix them. Then release.
Want me to add a section on frontend integration (React + ethers) or the actual smart contract that ties all this together?
Top comments (0)