Building a Simple Prediction Market dApp with thirdweb

In blockchain, prediction markets let users bet on future outcomes, with immutability ensuring transparency. These markets have gained popularity through platforms like Polymarket, especially around the 2024 US presidential election. In this tutorial, we'll build a simple prediction market dApp from scratch using thirdweb.
Follow along with our video tutorial:
What Are Prediction Markets?
Prediction markets are platforms where users can purchase shares in the potential outcomes of future events. Each prediction market includes:
- Expiration date: When the market closes and no more shares can be purchased
- Question: The event being predicted (e.g., "Will Bitcoin hit $100k by tomorrow?")
- Choices: The possible outcomes users can bet on (e.g., "Yes" or "No")
Our project follows a fixed model, where:
- Each share costs exactly 1 token
- Buyers of the winning outcome split the shares from the losing side
For example, if users buy 12 shares of Option A and 6 shares of Option B, and Option A is correct, each share of Option A receives 0.5 additional shares from Option B's pool.
More complex prediction markets like Polymarket use an order book model, similar to stock trading, where prices fluctuate based on market demand.
Building Our Prediction Market Smart Contract
We'll create a simple Solidity smart contract that handles:
- Creating markets with questions and options
- Purchasing shares of outcomes
- Resolving markets when the outcome is known
- Claiming winnings for users who bet on the correct outcome
Key Components of Our Contract
// Contract structure with key market data
enum MarketOutcome { Unresolved, OptionA, OptionB }
struct Market {
string question;
string optionA;
string optionB;
uint256 endTime;
MarketOutcome outcome;
uint256 totalOptionAShares;
uint256 totalOptionBShares;
bool resolved;
mapping(address => uint256) optionABalances;
mapping(address => uint256) optionBBalances;
mapping(address => bool) hasClaimed;
}
The contract uses a custom ERC20 token for purchases, which we'll deploy separately. Key functions include:
- createMarket: Only the owner can create new markets
- buyShares: Anyone can purchase shares if the market hasn't expired
- resolveMarket: The owner resolves markets after expiration
- claimWinnings: Users claim their rewards if they bet on the correct outcome
Building the Web Interface
We'll create a Next.js application with thirdweb's SDK to interact with our smart contract, featuring:
- A dashboard to display markets filtered by status (active, pending, resolved)
- Market cards showing the current distribution of shares
- UI for purchasing shares
- Functionality for claiming rewards from resolved markets
Setting Up thirdweb Integration
First, we configure thirdweb in our Next.js app:
// src/app/client.ts
import { createThirdwebClient } from "thirdweb";
export const client = createThirdwebClient({
clientId: process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID as string,
});
We also set up contract wrappers to easily interact with our smart contracts:
// src/constants/contract.ts
export const contractAddress = "0x124D803F8BC43cE1081110a08ADd1cABc5c83a3f";
export const tokenAddress = "0x4D9604603527322F44c318FB984ED9b5A9Ce9f71";
export const contract = getContract({
client: client,
chain: baseSepolia,
address: contractAddress
});
Implementing User Authentication
We integrate thirdweb's ConnectButton with smart account abstraction and gas sponsorship, enabling users to sign in with familiar Web2 methods:
<ConnectButton
client={client}
theme={lightTheme()}
chain={baseSepolia}
connectButton={{
label: 'Sign In',
}}
wallets={[
inAppWallet(),
]}
accountAbstraction={{
chain: baseSepolia,
sponsorGas: true,
}}
/>
This approach eliminates the complexity of wallets for new users by:
- Letting them sign in with email, passkeys, or social accounts
- Creating smart wallets automatically
- Sponsoring gas fees so users don't need ETH to participate
Building the Market Card Component
The MarketCard component is central to our UI, showing:
- The market question and expiration date
- Current distribution of shares
- Interface for purchasing shares
- User's current shares and potential winnings
We use thirdweb's hooks to fetch data from our smart contract:
// Fetching market data
const { data: marketData } = useReadContract({
contract,
method: "function getMarketInfo(uint256 _marketId) view returns (string question, string optionA, string optionB, uint256 endTime, uint8 outcome, uint256 totalOptionAShares, uint256 totalOptionBShares, bool resolved)",
params: [BigInt(index)]
});
Implementing Share Purchase Flow
Our share purchase flow includes:
- Selecting an option
- Entering the number of shares to buy
- Approving token spending (if needed)
- Confirming the purchase
// Handle share purchase transaction
const handleConfirm = async () => {
setIsConfirming(true);
try {
const tx = await prepareContractCall({
contract,
method: "function buyShares(uint256 _marketId, bool _isOptionA, uint256 _amount)",
params: [BigInt(marketId), selectedOption === 'A', BigInt(toWei(amount.toString()))]
});
await mutateTransaction(tx);
// Show success toast
toast({
title: "Purchase Successful!",
description: `You bought ${amount} ${selectedOption === 'A' ? market.optionA : market.optionB} shares`,
})
handleCancel();
} catch (error) {
console.error(error);
} finally {
setIsConfirming(false);
}
};
Adding Token Claim Functionality
To simplify the demo, we implement a "Claim Tokens" button that mints prediction tokens to the user's wallet via thirdweb Engine:
// Handle token claim API call
const handleClaimTokens = async () => {
setIsClaimLoading(true);
try {
const resp = await fetch("/api/claimToken", {
method: "POST",
body: JSON.stringify({ address: account?.address }),
});
if (!resp.ok) {
throw new Error('Failed to claim tokens');
}
toast({
title: "Tokens Claimed!",
description: "Your tokens have been successfully claimed.",
});
} catch (error) {
console.error(error);
} finally {
setIsClaimLoading(false);
}
};
Final Application Features
Our completed prediction market dApp includes:
- User Authentication:
- Web2-style login with smart wallets
- No gas fees for users (sponsored transactions)
- Market Dashboard:
- Active markets that are still accepting bets
- Pending markets awaiting resolution
- Resolved markets where users can claim rewards
- Market Interaction:
- Purchasing shares of outcomes
- Viewing current market distribution
- Claiming rewards from winning bets
- Token Management:
- Minting demo tokens via thirdweb Engine
- Approving token spending for purchases
Conclusion
This simple prediction market demonstrates how to combine smart contracts with a user-friendly web interface using thirdweb. The application handles market creation, share purchasing, market resolution, and reward distribution - all core features of a prediction market.
By using account abstraction and gas sponsorship, we've removed significant barriers to entry for new users, allowing them to focus on participating in the prediction markets rather than managing wallets and gas fees.
You can explore the full source code and build your own prediction market by checking out the GitHub repository.
The project serves as a foundation that can be extended with more advanced features like multi-option markets, variable pricing models, or integration with real-world data oracles for automated market resolution.