Complete guide for using the Zolymarket FHEVM prediction market platform as an end user.
- Getting Started
- Home Page Navigation
- Browsing Markets
- Understanding Bet Types
- Placing Bets
- My Dashboard
- Payout System
- User Positions & History
- Privacy & FHEVM
- FAQ & Troubleshooting
Step 1: Visit the platform homepage
- The platform requires a Web3 wallet (MetaMask, WalletConnect, etc.)
Step 2: Click "Connect Wallet" button
- Located in the top navigation bar
- Select your preferred wallet provider
- Approve the connection request in your wallet
Step 3: Wait for FHEVM initialization
- After connecting, the platform initializes FHEVM encryption (~1-2 seconds)
- You'll see a notification: "🔐 Privacy encryption ready"
- This enables private betting with encrypted amounts
Supported Networks:
- Zama Devnet
- Local Hardhat network (for testing)
First-time Setup:
1. Connect wallet → MetaMask popup appears
2. Approve connection → Wallet connected
3. FHEVM initializes → Encryption ready
4. Start browsing markets! → You're ready to bet
The home page offers 4 main filters:
| Filter | Icon | Description | Sorting Logic |
|---|---|---|---|
| Trending | 📈 TrendingUp | Most popular markets | By volume (highest first) |
| New | ⚡ Zap | Recently created markets | By creation date (newest first) |
| Ending Soon | ⏰ Clock | Markets closing within 24 hours | By end time (soonest first) |
| Bookmarked | ⭐ Star | Your saved markets | Your bookmarked bets only |
Location: frontend/src/pages/Home.jsx:30-55
How it works:
// Filters are applied client-side after fetching all bets from blockchain
- Trending: sorted by totalVolume (descending)
- New: sorted by contractId (descending - higher ID = newer)
- Ending Soon: filtered where endTime <= (now + 24 hours)
- Bookmarked: filtered using localStorage bookmark IDsBelow the filters, you'll see category tabs (e.g., Sports, Politics, Entertainment).
How to use:
- Click on a category tab
- The bet grid filters to show only bets in that category
- The active filter switches to "Category Markets"
- Click again to deselect and view all markets
Data Source: Categories are fetched from the MongoDB backend API (categoryAPI.getAll())
Location: frontend/src/components/home/CategoryTabs.jsx
Located in the header, the search bar allows you to search markets by:
- Bet title
- Bet description
How it works:
- Type keywords in the search bar
- Results filter in real-time
- A blue banner shows: "Searching for: [your query]"
- Click "Clear" to remove the search filter
Location: frontend/src/components/home/BetGrid.jsx:93-99
// Search logic (case-insensitive)
filtered = filtered.filter(bet =>
bet.title?.toLowerCase().includes(query) ||
bet.description?.toLowerCase().includes(query)
);Each market displays as a card with:
- Image: Market thumbnail (if uploaded by admin)
- Title: Market question
- Description: Brief description (first 2 lines)
- Category Badge: Category name with icon
- End Time: Time remaining or "Ended"
- Volume: Total betting volume (if decrypted)
Location: frontend/src/components/home/BetCard.jsx
Dropdown menu allows sorting by:
| Sort Option | Description |
|---|---|
| Newest First | Recently created markets (high contractId → low) |
| Oldest First | Oldest markets (low contractId → high) |
| Highest Volume | Most popular markets by total bets |
| Lowest Volume | Least popular markets |
| Ending Soonest | Markets closing first |
| Ending Latest | Markets closing last |
Location: frontend/src/components/home/BetGrid.jsx:269-280
- 30 markets per page
- Navigation: Previous/Next buttons + page numbers
- Shows: "Showing 1-30 of 150 markets"
- Clicking a page scrolls to top automatically
Location: frontend/src/components/home/BetGrid.jsx:298-356
How to bookmark:
- Click the star icon on any bet card
- The bet is saved to your bookmarks (stored in browser localStorage)
- Access all bookmarks via "Bookmarked" filter tab
Data Storage: localStorage.setItem('bookmarkedBets', JSON.stringify([...betIds]))
Location: frontend/src/components/home/BetGrid.jsx:149-162
The platform supports 3 bet types:
Description: Two-outcome markets (e.g., Yes/No, Win/Lose)
Example:
"Will Bitcoin reach $100,000 by end of 2025?"
- Option 1: Yes
- Option 2: No
How it works:
- Select one option (Yes or No)
- Enter bet amount
- If your option wins, you receive a share of the total pool
Description: Markets with 3+ exclusive outcomes
Example:
"Who will win the 2025 NBA Championship?"
- Option 1: Lakers
- Option 2: Celtics
- Option 3: Warriors
- Option 4: Nuggets
How it works:
- Select one option from multiple choices
- Only ONE option can win
- Winners share the entire pool
Description: Multiple propositions, each with Yes/No outcomes. Each proposition is resolved independently.
Example:
"2025 Tech Predictions"
- Proposition 1: "Apple releases VR headset" → YES or NO
- Proposition 2: "Tesla stock hits $300" → YES or NO
- Proposition 3: "ChatGPT reaches 1B users" → YES or NO
How it works:
- Each proposition has independent Yes/No outcomes
- You can bet on multiple propositions
- Each proposition is resolved separately
- Winners are determined per proposition
Location: frontend/src/components/bet/BetDetail.jsx:1111
Visual Identification:
betType === 0 ? 'Binary'
betType === 1 ? 'Multiple'
betType === 2 ? 'Nested'1. Navigate to Bet Details
- Click on any bet card from the home page
- You'll see the full bet details page
2. Connect Wallet (if not connected)
- Click "Connect Wallet" button
- Approve wallet connection
- Wait for FHEVM encryption to initialize
3. Check Your Balance
- Your USDC balance is displayed at the top
- Click "Decrypt Balance" to reveal your encrypted balance (first time only)
- Balance is cached for future use
Location: frontend/src/components/bet/BetDetail.jsx:688-714
4. Select Your Option
For Binary/Multiple Bets:
- Click on the option card you want to bet on
- The card highlights in blue
For Nested Bets:
- Click on a proposition card
- Select either "YES" or "NO" button
5. Enter Bet Amount
- Type the amount in USDC (e.g., 10, 25, 100)
- Minimum bet: displayed on the page (e.g., $1)
- Maximum bet: displayed on the page (e.g., $10,000)
6. Review Potential Returns
The platform calculates your potential profit using Parimutuel odds:
Formula:
potentialReturn = (yourAmount / newWinnerPool) × (totalPool - liquidity) - yourAmount
Example Calculation:
Current state:
- Total pool: $1,000
- Winning option pool: $400
- Liquidity: $100
You bet: $50
After your bet:
- New total pool: $1,050
- New winning option pool: $450
- Distributable pool: $1,050 - $100 = $950
Your estimated payout:
= ($50 / $450) × $950
= $105.56
Your potential profit:
= $105.56 - $50 = $55.56
Location: frontend/src/components/bet/BetDetail.jsx:116-161
Note: Potential returns are estimates and may change based on other bets placed before resolution.
7. Place Bet
- Click the green "Place Bet" button
- FHEVM encrypts your bet amount (keeps it private on-chain)
- Approve the transaction in your wallet
- Wait for transaction confirmation (~5-15 seconds)
8. Success Confirmation
- A success modal appears with transaction details
- Your bet is added to transaction cache
- Balance is optimistically updated (decremented)
- You can view your position in the "My Positions" tab
Location: frontend/src/components/bet/BetDetail.jsx:1149-1243
When you place a bet, the following happens behind the scenes:
// 1. Encrypt bet amount using FHEVM
const encryptedAmount = await fhevmInstance.encrypt64(amountInMicroUSDC);
// 2. Create encrypted input for smart contract
const encryptedInput = fhevmInstance.createEncryptedInput(contractAddress, account);
encryptedInput.add64(amountInMicroUSDC);
const encryptedData = encryptedInput.encrypt();
// 3. Submit encrypted bet to blockchain
const tx = await contract.placeBet(
betId,
optionIndex,
encryptedData.handles[0],
encryptedData.inputProof
);
// 4. Your bet amount remains encrypted on-chain ✅
// Only you can decrypt it (or admin via relayer for resolution)Location: frontend/src/lib/fhe.js:81-150
Privacy Guarantee:
- Your bet amount is never visible on-chain to other users
- Only you can decrypt your position
- Admins can only decrypt via relayer callback for payout calculation
Access your dashboard by clicking "Dashboard" in the navigation bar.
The dashboard has 3 tabs:
Shows: All bets you've placed that are still ongoing
Displayed Information:
- Bet title & description
- Time remaining (e.g., "5d 12h left")
- Bet type (Binary, Multiple, Nested)
- Status badge: "Active" (green)
Actions:
- Click on any bet to view details
Location: frontend/src/pages/Dashboard.jsx:400-416
Logic:
// A bet is "active" if:
bet.isResolved === false
&& bet.endTime > Date.now()
&& bet.isActive === trueShows: Bets that have ended but are not yet resolved, or bets you lost
Displayed Information:
- Bet title & description
- Status badge: "Ended" (yellow) or "Resolved" (blue)
- Win/Loss indicator (if resolved)
- Red "Lost" badge if you lost
- No badge if awaiting resolution
Actions:
- View bet details
- Wait for admin to resolve the bet
Location: frontend/src/pages/Dashboard.jsx:418-434
Logic:
// A bet is "ended" if:
(bet.endTime <= Date.now() && !bet.isResolved)
|| (bet.isResolved && userWon === false)Shows: Resolved bets where you WON and can claim your winnings
Displayed Information:
- Bet title & description
- Status badge: "Resolved" (blue)
- Win indicator: Green "You Won!" with trophy icon
- Claim status: "Claimed" badge (if already claimed)
Actions:
- View bet details
- Claim your winnings (see Payout System section)
Location: frontend/src/pages/Dashboard.jsx:436-452
Logic:
// A bet is "claimable" if:
bet.isResolved === true
&& userWon === true (checked via MongoDB)
&& bet.hasClaimed === falseTop of the dashboard shows 3 stat cards:
| Stat | Description |
|---|---|
| Active Bets | Number of ongoing bets you've placed |
| Ended Bets | Number of bets awaiting resolution or lost |
| Claimable | Number of won bets ready to claim |
Location: frontend/src/pages/Dashboard.jsx:324-354
The payout system has 2 steps: Request Payout → Claim Winnings
When: After a bet is resolved and you won
What it does:
- Triggers the relayer callback to decrypt your encrypted position
- Relayer calculates your exact payout amount
- Payout amount is stored on-chain (unencrypted)
How to request:
- Go to the bet details page of a resolved bet
- You'll see a yellow button: "Request Payout"
- Click the button
- Approve the transaction in your wallet
- Wait 1-2 minutes for relayer to process
Location: frontend/src/components/bet/BetDetail.jsx:214-247
What happens behind the scenes:
// 1. Submit payout request on-chain
await payoutContract.requestPayout(betId);
// 2. Relayer callback endpoint is triggered (off-chain)
POST http://localhost:5001/api/callback/relayer
{
"betId": 123,
"user": "0xYourAddress"
}
// 3. Relayer decrypts your encrypted bet amount
const decryptedAmount = await gateway.decrypt(encryptedHandle);
// 4. Relayer calculates your payout using parimutuel formula
const payout = (userAmount / winnerPoolTotal) × (totalPool - liquidity);
// 5. Relayer submits payout amount to contract
await payoutContract.fulfillPayout(betId, user, payoutAmount);
// 6. Your payout is now ready to claim!Processing Time: ~1-2 minutes (depends on relayer callback speed)
Location (Backend): backend/routes/callback.js:60-120
When: After payout request is processed
What it does:
- Withdraws your winnings from the smart contract to your wallet
- Updates your USDC balance
How to claim:
- After requesting payout, refresh the page
- The button changes to green: "Claim $XXX.XX"
- Click the "Claim" button
- Approve the transaction in your wallet
- Your winnings are transferred to your wallet ✅
Location: frontend/src/components/bet/BetDetail.jsx:249-292
Transaction Details:
// 1. Submit claim transaction
const tx = await payoutContract.claimPayout(betId);
await tx.wait();
// 2. Smart contract transfers USDC to your address
// 3. Your balance is updated (cached optimistically)| Status | Description | Available Actions |
|---|---|---|
| Not Requested | You haven't requested payout yet | Click "Request Payout" |
| Requested | Payout requested, waiting for relayer | Wait 1-2 minutes, refresh page |
| Processed | Payout calculated and ready | Click "Claim $XX.XX" |
| Claimed | You already claimed your winnings | None (badge shows "Claimed") |
| Lost | You did not win this bet | None (badge shows "Lost") |
Location: frontend/src/components/bet/BetDetail.jsx:164-212
MongoDB Check for Losers:
// Backend checks MongoDB to see if user won or lost
GET /api/user-positions/:userAddress/:betId
Response:
{
"success": true,
"hasPosition": true,
"isResolved": true,
"isWinner": false // ← User lost, hide payout button
}Location (Backend): backend/routes/userPositions.js
Each bet details page has 2 tabs: My Positions and Order History
Shows: Your current positions aggregated from all bets placed on this market
Data Source: Client-side cache (UserTransactionsCacheInstance)
Displayed Information:
| Column | Description |
|---|---|
| Option | The option you bet on (e.g., "Yes", "Option 1") |
| Total Amount | Sum of all bets on this option |
| Shares | Estimated shares you hold |
| Current Price | Current market probability (0-100%) |
| Unrealized P&L | Estimated profit/loss (not finalized until resolution) |
For Nested Bets:
- Each proposition shows separately
- YES and NO outcomes are listed individually
Example:
Option: "Will Bitcoin reach $100K?" (YES)
Total Amount: $150.00
Shares: 180
Current Price: 65%
Unrealized P&L: +$25.00 (estimated)
Location: frontend/src/components/bet/BetDetail.jsx:337-429
Position Aggregation Logic:
// Aggregate all transactions for the same option
cachedTransactions.forEach(tx => {
const key = `${tx.optionIndex}-${tx.outcome}`;
positionsMap[key].totalAmount += tx.amount;
positionsMap[key].totalShares += tx.amount / priceAtBet;
});Privacy Note:
- Positions are stored locally in browser cache
- NOT fetched from blockchain (preserves privacy)
- Clearing browser data will remove position history
Shows: Chronological list of all your bets on this market
Displayed Information:
| Column | Description |
|---|---|
| Time | When you placed the bet (e.g., "2 hours ago") |
| Option | The option you bet on |
| Amount | Bet amount (revealed or encrypted) |
| Status | Revealed or Encrypted |
| Transaction | Blockchain transaction hash (click to view on explorer) |
Example:
Time: 3 hours ago
Option: "Yes" (YES)
Amount: $50.00 (Revealed)
Transaction: 0xabc123...def456 ↗
Location: frontend/src/components/bet/BetDetail.jsx:294-335
Data Source:
// Fetched from UserTransactionsCache (localStorage)
const cachedTransactions = UserTransactionsCacheInstance.getTransactions(account, betId);Why "Encrypted" vs "Revealed"?
- Encrypted: Bet just placed, amount is encrypted on-chain
- Revealed: After decryption (either by you or relayer), amount is visible
FHEVM (Fully Homomorphic Encryption Virtual Machine) enables private smart contracts on blockchain.
Key Benefits:
- Your bet amounts are encrypted on-chain
- Other users cannot see how much you bet
- Smart contracts can compute on encrypted data without decrypting it
- Only you (and authorized relayers) can decrypt your data
Location: frontend/src/hooks/useFHEVM.js
- You enter bet amount (e.g., $50)
- FHEVM encrypts the amount before sending to blockchain
- Encrypted value is stored on-chain:
0x7f8e9a3b...(gibberish to others) - Only you hold the decryption key
- You can decrypt your own positions anytime
- Click "Decrypt" button on My Positions tab
- FHEVM uses your wallet signature to decrypt
- Decrypted values are cached locally (browser)
- When bet resolves, admin uses relayer callback
- Relayer decrypts ALL user positions (via gateway)
- Calculates payouts using parimutuel formula
- Stores final payout amounts on-chain (unencrypted)
Privacy Trade-off:
- During betting: fully private (encrypted amounts)
- After resolution: payouts are public (needed for claiming)
When you connect your wallet:
// 1. Initialize FHEVM instance
const instance = await initializeFHE();
// 2. Create FHE client
const { publicKey, privateKey } = instance.generateKeypair();
// 3. Generate EIP-712 signature (proves you own the wallet)
const signature = await provider.send('eth_signTypedData_v4', [account, eip712]);
// 4. Store instance for encryption/decryption
setFhevmInstance(instance);
// 5. Ready to place encrypted bets! ✅Location: frontend/src/lib/fhe.js:23-79
Initialization Time: ~1-2 seconds
Notification: "🔐 Privacy encryption ready"
Q: Do I need cryptocurrency to use the platform? A: Yes, you need USDC (stablecoin) on the supported network. You also need native tokens (e.g., ZAMA) for gas fees.
Q: Can other users see how much I bet? A: No! Your bet amounts are encrypted using FHEVM. Only you can decrypt them. After resolution, your payout amount becomes public.
Q: How are odds calculated? A: The platform uses Parimutuel odds (pool-based). Your payout depends on:
- Total pool size
- How much is bet on the winning option
- Your share of the winning pool
Q: When can I claim my winnings? A: After the bet is resolved AND you've requested payout (processed by relayer). The process takes ~1-2 minutes.
Q: What happens if I lose? A: Your bet amount goes into the total pool and is distributed to winners. You cannot claim anything, and the "Request Payout" button will not appear.
Q: Can I cancel a bet after placing it? A: No. All bets are final once the transaction is confirmed on-chain.
Solution:
- Wait 2-3 seconds after connecting wallet
- Refresh the page
- Ensure you're on a supported network (Zama Devnet)
- Check browser console for errors
Solution:
- Click "Decrypt Balance" to reveal your actual balance
- Ensure you have enough USDC for the bet
- Reserve some USDC for gas fees (~$0.50-$1.00)
- If needed, fund your wallet with USDC
Possible Reasons:
- Bet is not yet resolved → Wait for admin to resolve
- You lost the bet → MongoDB marked you as loser (no payout)
- You already requested payout → Refresh the page to see claim button
- Relayer is processing → Wait 1-2 minutes and refresh
Solution:
- Check bet status (should say "Resolved")
- Check your position in "My Positions" tab
- If you lost, you won't see the button (this is expected)
Solution:
- Wait 2-3 minutes (relayer may be slow)
- Refresh the page
- Check backend logs:
backend/routes/callback.js - Ensure relayer service is running
- Contact support if stuck for >5 minutes
Possible Reasons:
- Insufficient gas fees
- Bet already ended
- Bet amount below minimum or above maximum
- Contract paused by admin
Solution:
- Check error message in wallet
- Ensure you have native tokens for gas
- Verify bet is still active
- Check min/max bet amounts on bet details page
Solution:
- Wait 10-15 seconds for transaction confirmation
- Click "Decrypt Balance" button to refresh
- Check transaction on block explorer
- Clear browser cache and refresh
Solution:
- Positions are stored in browser cache (localStorage)
- If you cleared browser data, positions are lost (but still on-chain)
- Check "Order History" tab to see your bets
- Your actual winnings are safe on-chain (claimable after resolution)
Note: Losing local cache only affects viewing, not claiming payouts!
The Zolymarket FHEVM platform provides a private, decentralized prediction market using advanced encryption technology.
Key Features:
- ✅ Private Betting: Bet amounts encrypted with FHEVM
- ✅ Parimutuel Odds: Fair, pool-based payout system
- ✅ Multiple Bet Types: Binary, Multiple Choice, Nested
- ✅ Easy Claiming: Request payout → Claim winnings (2 steps)
- ✅ Dashboard Tracking: View active, ended, and claimable bets
- ✅ Transparent Resolution: Decentralized, admin-resolved outcomes
Quick Start Checklist:
- ✓ Connect wallet
- ✓ Wait for FHEVM initialization
- ✓ Decrypt balance (first time)
- ✓ Browse markets and select a bet
- ✓ Place encrypted bet
- ✓ Track in Dashboard
- ✓ Claim winnings after resolution
For more technical details, see:
- Admin Guide:
ADMIN_GUIDE.md - FHEVM Integration:
FHEVM_INTEGRATION.md - Smart Contracts:
hardhat/contracts/
Happy predicting! 🎯