I Built a Solana Prediction Market From Scratch — Here’s How the Money Really Works
--
Forget complex finance. After watching a blurry video and filling a notebook with scribbles, I finally understood split, merge, and the magic of YES + NO = $1. Then I turned it into code.
I still remember the exact moment prediction markets clicked for me. I was staring at a screenshot of an orderbook with two numbers: “yes: $0.20” and “no: $0.81”. My first thought was, that doesn’t even add up to a dollar. Then someone in a video mumbled “market maker places both sides to earn the spread” and the penny dropped.
I’m a Solana developer in training, and I decided to build a fully on‑chain prediction market. Not just a toy, but a real program that mints YES and NO tokens, lets users split and merge collateral, and redeems winnings based on an oracle. In this article, I’ll walk you through everything I learned — from the money mechanics to the Anchor instructions I wrote. This is the guide I wish someone had handed me when I started.
The One Rule That Governs Everything
Forget orderbooks, AMMs, and liquidity for a second. The entire prediction market universe runs on a single invariant:
1 YES token + 1 NO token = $1 of collateral (e.g. USDC). Always.
This isn’t a market price; it’s a guarantee enforced by the smart contract. Why? Because you can always take $1 and split it into a fresh YES and NO pair. And if you hold both, you can always merge them back to get exactly $1. No fees, no slippage — just a 1:1 conversion baked into the code.
This rule is the engine that keeps prices honest. If YES is trading at $0.70 on an exchange, NO must gravitate toward $0.30, because if someone can buy YES for $0.70 and NO for $0.25, they can merge the pair and pocket a profit. Arbitrage bots will do that until the sum is exactly $1.
Once I internalised this, everything else made sense.
Where Does the First Token Come From?
A brand‑new market has zero tokens. Someone needs to create them. That someone is usually a market maker (or an early liquidity provider). But the process itself is open to everyone — it’s the split instruction.
Imagine Bob deposits 100 USDC into the market’s vault. The contract mints 100 YES and 100 NO tokens straight into his wallet. Bob didn’t “buy” these tokens; he manufactured them at the exact 1:1 ratio. He can now sell either side to anyone who wants to bet.
If Bob wants out without taking a directional bet, he can simply merge any leftover YES and NO back into USDC. If he sells all his YES at $0.55 and all his NO at $0.55, his total revenue is $110. Since it cost him $100 to create the pair, he pockets a risk‑free $10 profit — the spread.
This is how market makers earn. They don’t gamble; they run a vending machine that always sells at a slight markup.
A Simple User’s Journey: Alice Bets on Rain
Now take Alice. She thinks it will rain tomorrow. She wants YES tokens. She has two choices.
Option 1: Buy YES directly
Alice sees a sell order on the orderbook: YES for $0.55. She spends $55 and gets 100 YES.
If it rains, each YES is worth $1 → she redeems for $100, profit $45.
If it doesn’t rain, YES is worthless → she loses $55. Simple.
Option 2: Split first, then dump the unwanted side
Alice splits $55, getting 55 YES and 55 NO. She immediately checks the orderbook and finds someone (maybe Charlie, a NO‑bull) willing to buy NO at $0.60. She sells her 55 NO to Charlie for $33.
Now she holds 55 YES, and her net cost was $55 (split) minus $33 (sale) = $22.
That’s an effective price of $0.40 per YES — much cheaper than the direct $0.55. She effectively “bought” YES below the market ask by using the split as a cheap factory and selling the byproduct. She still isn’t a market maker; she just took liquidity from Charlie’s bid.
This is the mental model that finally untangled the spaghetti in my head.
Why On‑Chain? And Why Real SPL Tokens?
In a centralised platform, YES and NO are just database entries. On Solana, we can mint them as proper SPL tokens. That means:
- They can be traded on any DEX like Jupiter or OpenBook.
- They live in your wallet — composability at its finest.
- They can be used as collateral in other DeFi protocols.
I chose to build with Anchor and real SPL tokens. The alternative (internal accounting without tokens) is cheaper in gas but locks liquidity inside the program forever. I wanted my market to be part of the larger Solana DeFi ecosystem.
The Architecture: Accounts and Instructions
Let’s dive into the contract I wrote. The program is a single Anchor module with four instructions (plus an initialisation). Here’s the bird’s‑eye view of the on‑chain accounts.
Core Accounts
- Market account — holds configuration: the collateral mint (USDC), the oracle address, the resolution deadline, and a flag for the winning side.
- Vault account — a PDA that holds the actual USDC collateral. No one can withdraw from it except through the program’s logic.
- Yes mint and No mint — two SPL token mints created when the market is initialised. The program holds the mint authority, allowing it to print tokens during a split and burn them during a merge or redeem.
- User token accounts — standard Associated Token Accounts (ATAs) for YES and NO, created by the user’s wallet just like any other SPL token.
Instruction 1: init_market
This is called once per market. The code:
- Takes the USDC mint address and an oracle address as parameters.
- Derives a PDA for the vault using a seed like
"vault"and the market’s key. - Derives PDAs for the YES and NO mints using seeds like
"yes_mint"and"no_mint". - Creates the mint accounts with the program as the mint authority.
- Initialises the market state.
After this, the market is ready to accept splits.
#[account]
pub struct Market {
pub creator: Pubkey,
pub collateral_mint: Pubkey,
pub yes_mint: Pubkey,
pub no_mint: Pubkey,
pub vault: Pubkey,
pub oracle: Pubkey,
pub resolution_deadline: i64,
pub winning_side: Option<bool>, // None until resolved
pub resolved: bool,
}The vault PDA ensures that the USDC is only moved by the program itself. This is crucial for trustlessness.
Instruction 2: split_tokens
A user calls this with, say, 100 USDC. The instruction:
- Transfers 100 USDC from the user’s token account to the vault PDA via a cross‑program invocation (CPI) to the SPL Token program.
- Mints 100 YES tokens to the user’s YES ATA, and 100 NO tokens to the user’s NO ATA. Both mint CPIs are signed by the program’s mint authority PDA.
The user now holds 100 of each token. The program enforces a 1:1 minting ratio, always. There’s no way to mint just YES or just NO.
Instruction 3: merge_tokens
The user sends back 1 YES and 1 NO (or any equal amount) and gets 1 USDC per pair from the vault. The contract:
- Burns the YES tokens from the user’s ATA.
- Burns the NO tokens from the user’s ATA.
- Transfers the corresponding USDC from the vault back to the user.
This is the undo button for split. It’s what keeps the system fully collateralised. If a market maker needs to exit, they merge before the resolution deadline.
A key security point: the merge instruction must verify that the user is burning an equal quantity of both tokens. If someone could merge 1 YES and 0 NO, the system’s collateral would break.
Instruction 4: redeem_tokens
After the oracle reports the outcome (say, winning_side = true for YES), the winning token becomes a claim on the vault’s USDC. A holder of winning tokens can call redeem_tokens:
- Burn
Nwinning tokens from the user’s account. - Transfer
NUSDC from the vault to the user.
Note that only the winning side can be redeemed. If you hold the losing token, the contract rejects the redemption. The oracle resolution is typically a one‑time call that sets the winning_side and locks the market.
Oracle Integration: The Source of Truth
A prediction market is worthless without a reliable outcome feed. I integrated with a mock oracle for testing, but in production you’d use Pyth or Switchboard. The oracle simply calls a permissioned resolve instruction, passing the winning side as a boolean. Once resolved, no more splits/merges are allowed (to prevent manipulation after the fact).
Designing the oracle access control is critical. Only the pre‑registered oracle address can resolve. If you’re building a public market, you might also add a dispute window.
What I Learned (Beyond the Code)
Building this project taught me more than Solana’s account model. It forced me to think about economic invariants, risk‑free profits, and composability.
- Split/merge is the ultimate arbitrage tool. If the market price of YES + NO ever deviates from $1, the split/merge facility will be hammered by bots until it doesn’t. This is how a fully collateralised market stays pegged to reality without a central bank.
- Real SPL tokens unlock network effects. My YES and NO tokens could be listed on a DEX, used as collateral on a lending protocol, or even DAO‑voted on. That’s the web3 way.
- Market makers are not wizards. They simply use the same split function we all have access to, but they place both buy and sell orders to capture the spread. The protocol designer (me) doesn’t need to be a market maker; I just charge a tiny fee on every split or redeem, and that’s my revenue model.
Want the code? I’ll be open‑sourcing the repo soon. In the meantime, drop a comment if you want me to dive deeper into the Rust CPIs or the frontend. Let’s build the future of on‑chain betting — one split at a time.