Skip to content

raunit-dev/OutComeMarketContract

Repository files navigation

Solana Prediction Market

A decentralized binary prediction market built on Solana using the Anchor framework.

License: ISC Anchor Version Solana

Overview

This prediction market protocol enables users to create binary markets, trade outcome tokens, and settle markets based on real-world events. Built using the Conditional Token Framework (CTF) model similar to Polymarket.

How It Works

1. Market Creation → 2. Token Splitting → 3. Trading → 4. Settlement → 5. Redemption
  • Deposit Collateral: Users deposit collateral (e.g., USDC) to mint equal amounts of Outcome A and Outcome B tokens
  • Trade Outcomes: Outcome tokens can be traded on secondary markets based on event probability
  • Market Settlement: Authority settles the market by declaring the winning outcome
  • Claim Rewards: Winners redeem their tokens 1:1 for collateral
  • Void Markets: If voided, users redeem matched pairs of both tokens for collateral

Features

Feature Description
Binary Markets Create markets with two possible outcomes (Yes/No, A/B)
Token Splitting Convert collateral into equal outcome tokens (1 USDC → 1 YES + 1 NO)
Token Merging Burn matched pairs to reclaim collateral before settlement
Flexible Settlement Resolve to Outcome A, Outcome B, or Neither (void)
Security First Built with Anchor's security features and comprehensive error handling
PDA Architecture Secure account derivation using program-derived addresses

📁 Project Structure

sol-prediction-market/
├── programs/
│   └── prediction_market/
│       └── src/
│           ├── lib.rs              # Program entrypoint
│           ├── error.rs            # Custom error definitions
│           ├── state/
│           │   ├── mod.rs          # State module exports
│           │   └── market.rs       # Market account structure
│           └── instructions/
│               ├── mod.rs          # Instruction module exports
│               ├── initialize_market.rs
│               ├── split_token.rs
│               ├── merge_token.rs
│               ├── set_winner.rs
│               └── claim_rewards.rs
├── tests/                          # Integration tests
├── Anchor.toml                     # Anchor configuration
└── package.json                    # Dependencies

Program Architecture

State Accounts

Market Account
pub struct Market {
    pub authority: Pubkey,              // Market creator/admin
    pub market_id: u32,                 // Unique market identifier
    pub settlement_deadline: i64,       // Unix timestamp for settlement
    pub outcome_a_mint: Pubkey,         // Outcome A token mint
    pub outcome_b_mint: Pubkey,         // Outcome B token mint
    pub collateral_mint: Pubkey,        // Collateral token mint (e.g., USDC)
    pub collateral_vault: Pubkey,       // Vault holding collateral
    pub is_settled: bool,               // Settlement status
    pub winning_outcome: Option<WinningOutcome>, // Final outcome
    pub total_collateral_locked: u64,   // Total collateral in vault
    pub bump: u8,                       // PDA bump seed
}
Winning Outcomes
pub enum WinningOutcome {
    OutcomeA,  // Outcome A wins
    OutcomeB,  // Outcome B wins
    Neither,   // Market voided (both sides refunded)
}

📝 Instructions

1. Initialize Market

Creates a new prediction market with two outcome tokens.

Parameters:

  • market_id: Unique identifier for the market
  • settlement_deadline: Unix timestamp after which the market can be settled

Accounts:

  • authority: Market creator (signer, payer)
  • collateral_mint: Token to use as collateral
  • market: Market PDA
  • collateral_vault: Token account to hold collateral
  • outcome_a_mint: Mint for Outcome A tokens
  • outcome_b_mint: Mint for Outcome B tokens

Example:

await program.methods
  .initializeMarket(
    1, // market_id
    new anchor.BN(Date.now() / 1000 + 30 * 24 * 60 * 60) // 30 days from now
  )
  .accounts({
    authority: wallet.publicKey,
    collateralMint: usdcMint,
  })
  .rpc();
2. Split Tokens

Convert collateral into equal amounts of both outcome tokens.

Parameters:

  • amount: Amount of collateral to split

Logic:

  • Transfers collateral from user to vault
  • Mints equal amounts of Outcome A and Outcome B tokens to user

Example:

await program.methods
  .splitTokens(new anchor.BN(1_000_000)) // 1 USDC (6 decimals)
  .accountsPartial({
    user: user.publicKey,
    userCollateral: userUsdcAccount,
    collateralVault: vaultPda,
    outcomeAMint: outcomeAMint,
    outcomeBMint: outcomeBMint,
    userOutcomeA: userOutcomeAAccount,
    userOutcomeB: userOutcomeBAccount,
    market: marketPda,
  })
  .signers([user])
  .rpc();
3. Merge Tokens

Burn matched pairs of outcome tokens to reclaim collateral (before settlement).

Parameters:

  • amount_a: Amount of Outcome A tokens to burn
  • amount_b: Amount of Outcome B tokens to burn

Logic:

  • Burns the minimum of amount_a and amount_b from both token accounts
  • Returns equivalent collateral to user

Example:

await program.methods
  .mergeTokens(
    new anchor.BN(500_000), // 0.5 Outcome A
    new anchor.BN(500_000)  // 0.5 Outcome B
  )
  .accountsPartial({
    user: user.publicKey,
    userCollateral: userUsdcAccount,
    collateralVault: vaultPda,
    collateralMint: usdcMint,
    outcomeAMint: outcomeAMint,
    outcomeBMint: outcomeBMint,
    userOutcomeA: userOutcomeAAccount,
    userOutcomeB: userOutcomeBAccount,
    market: marketPda,
  })
  .signers([user])
  .rpc();
4. Set Winning Side

Settles the market by declaring the winning outcome (admin only).

Parameters:

  • admin_outcome: The winning outcome (OutcomeA, OutcomeB, or Neither)

Example:

await program.methods
  .setWinningSide({ outcomeA: {} })
  .accountsPartial({
    authority: authority.publicKey,
    market: marketPda,
  })
  .signers([authority])
  .rpc();
5. Claim Rewards

Redeem winning tokens for collateral after settlement.

Parameters:

  • amount_a: Amount of Outcome A tokens to redeem
  • amount_b: Amount of Outcome B tokens to redeem

Redemption Logic:

  • Outcome A wins: Redeem Outcome A tokens 1:1 for collateral
  • Outcome B wins: Redeem Outcome B tokens 1:1 for collateral
  • Neither wins: Redeem matched pairs of both tokens 1:1 for collateral

Example:

// If Outcome A won
await program.methods
  .claimRewards(
    new anchor.BN(1_000_000), // 1 Outcome A token
    new anchor.BN(0)
  )
  .accountsPartial({
    user: user.publicKey,
    userCollateral: userUsdcAccount,
    collateralVault: vaultPda,
    collateralMint: usdcMint,
    outcomeAMint: outcomeAMint,
    outcomeBMint: outcomeBMint,
    userOutcomeA: userOutcomeAAccount,
    userOutcomeB: userOutcomeBAccount,
    market: marketPda,
  })
  .signers([user])
  .rpc();

⚠️ Error Codes

Code Name Description
6000 InvalidSettlementDeadline Settlement deadline must be in the future
6001 MarketAlreadySettled Cannot perform action on settled market
6002 MarketExpired Market has passed settlement deadline
6003 InvalidAmount Amount must be greater than zero
6004 MathOverflow Arithmetic overflow occurred
6005 MathUnderflow Arithmetic underflow occurred
6006 MarketNotSettled Market must be settled before claiming
6007 NoRedeemableTokens No matched pairs available for redemption
6008 NoWinningTokens User has no winning tokens to claim

🔑 PDA Seeds

Account Seeds Example
Market ["market", market_id.to_le_bytes()] findProgramAddressSync([Buffer.from("market"), marketIdBuffer])
Collateral Vault ["vault", market_id.to_le_bytes()] findProgramAddressSync([Buffer.from("vault"), marketIdBuffer])
Outcome A Mint ["outcome_a", market_id.to_le_bytes()] findProgramAddressSync([Buffer.from("outcome_a"), marketIdBuffer])
Outcome B Mint ["outcome_b", market_id.to_le_bytes()] findProgramAddressSync([Buffer.from("outcome_b"), marketIdBuffer])

🚀 Getting Started

Prerequisites

Tool Version Purpose
Rust 1.70+ Solana program development
Solana CLI 1.18+ Blockchain interaction
Anchor 0.31.1 Framework for Solana programs
Node.js 16+ Running tests and scripts
Yarn Latest Package management

Installation

# 1. Clone the repository
git clone https://github.com/yourusername/sol-prediction-market.git
cd sol-prediction-market

# 2. Install dependencies
yarn install

# 3. Build the program
anchor build

# 4. Get the program ID
solana address -k target/deploy/prediction_market-keypair.json

# 5. Update the program ID in:
#    - Anchor.toml
#    - programs/prediction_market/src/lib.rs (in declare_id!())

# 6. Rebuild after updating program ID
anchor build

🧪 Testing

# Start a local validator (in a separate terminal)
solana-test-validator

# Run the test suite
anchor test --skip-local-validator

# Or run tests with a fresh validator
anchor test

Test Coverage

✅ Market initialization ✅ Token splitting (collateral → outcome tokens) ✅ Token merging (outcome tokens → collateral) ✅ Market settlement (all three outcomes) ✅ Reward claiming for Outcome A winners ✅ Reward claiming for Outcome B winners ✅ Reward claiming for voided markets (Neither outcome)

🌐 Deployment

Devnet Deployment

# 1. Configure Solana CLI for devnet
solana config set --url devnet

# 2. Airdrop SOL for deployment
solana airdrop 2

# 3. Deploy the program
anchor deploy

# 4. Update Anchor.toml cluster setting to "devnet"

Mainnet Deployment

⚠️ Warning: Ensure thorough testing and security audits before mainnet deployment.

# 1. Configure for mainnet
solana config set --url mainnet-beta

# 2. Deploy (ensure you have sufficient SOL)
anchor deploy

💡 Usage Examples

Creating a Market
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PredictionMarket } from "../target/types/prediction_market";

const provider = anchor.AnchorProvider.env();
const program = anchor.workspace.predictionMarket as Program<PredictionMarket>;

// Create market with 30-day settlement
const marketId = 1;
const settlementDeadline = Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60);

await program.methods
  .initializeMarket(marketId, new anchor.BN(settlementDeadline))
  .accounts({
    authority: provider.wallet.publicKey,
    collateralMint: usdcMintAddress,
  })
  .rpc();
Complete Trading Flow
// 1. User deposits 100 USDC and gets 100 YES + 100 NO tokens
await program.methods
  .splitTokens(new anchor.BN(100_000_000))
  .accountsPartial({ /* accounts */ })
  .rpc();

// 2. User trades tokens on secondary market (not included in this contract)

// 3. Market settles - admin declares winner
await program.methods
  .setWinningSide({ outcomeA: {} })
  .accountsPartial({ /* accounts */ })
  .rpc();

// 4. Winner claims 1:1 collateral for winning tokens
await program.methods
  .claimRewards(new anchor.BN(100_000_000), new anchor.BN(0))
  .accountsPartial({ /* accounts */ })
  .rpc();

🔒 Security Considerations

Feature Implementation
Authority Control Only the market authority can settle markets
Timestamp Validation Settlement deadline must be in the future
Overflow Protection All math operations use checked arithmetic
Market State Actions are restricted based on settlement status
Token Validation All token accounts are validated against market PDAs
PDA Security All accounts derived using secure PDAs

🚧 Limitations & Future Improvements

Current Limitations

  • ⚠️ Manual settlement by authority (no oracle integration)
  • ⚠️ Binary markets only (two outcomes)
  • ⚠️ No automated market maker (AMM) for trading
  • ⚠️ No fees or revenue mechanism

Potential Enhancements

  • Oracle Integration: Automatic settlement using Pyth/Switchboard
  • Multi-Outcome Markets: Support for more than 2 options
  • Built-in AMM: Trading outcome tokens without external DEX
  • Fee Mechanism: Creator fees and protocol revenue
  • Market Creation Fees: Economic spam prevention
  • Permissionless Markets: Allow anyone to create markets
  • Dispute Resolution: Appeal system for contested outcomes
  • TWAP Oracles: Time-weighted average price integration
  • Cross-chain Support: Multi-chain collateral options

📄 License

This project is licensed under the ISC License.

🤝 Contributing

Contributions are welcome! Here's how you can help:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

🙏 Acknowledgments

  • Built with Anchor Framework
  • Inspired by Polymarket and Gnosis Conditional Tokens
  • Uses Solana's SPL Token program
  • Community feedback and contributions

📞 Contact & Support


⚠️ Disclaimer

This is experimental software. Use at your own risk. Not audited for production use.

Made with ❤️ for the Solana ecosystem

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published