DEV Community

Bergnadette Viliam
Bergnadette Viliam

Posted on

From Zero to MVP: A Practical Guide to Prediction Market Development

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:

  1. Binary outcomes (Yes/No) or multiple choices?
  2. Who decides the truth? (centralized admin, oracle, token holders?)
  3. 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)

Enter fullscreen mode Exit fullscreen mode

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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Capped markets — "Max 1000 shares total" so whales can't manipulate
  2. Mobile-friendly — half my testers opened the site on an iPhone 12
  3. 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)