Fatecast
A decentralized Ethereum-based prediction market using AI agents, Pyth Oracle, and PYUSD.
Problem Statement
Fatecast is a decentralized social prediction market platform built on Ethereum that enables users to bet on future events using cryptocurrency. It’s a sophisticated Web3 application that combines smart contracts, AI automation, and real-world data feeds.What It DoesAt its core, Fatecast allows users to:• Create prediction markets for YES/NO questions about future events (e.g., “Will BTC reach $100,000 by November 1, 2025?”)• Place bets using PYUSD (PayPal USD stablecoin) on their predictions• Automatically resolve events using real-world data from Pyth Oracle• Claim winnings when their predictions are correctKey ComponentsSmart Contract (Solidity) - Handles bets, funds, and event resolution on EthereumASI Agent (TypeScript) - AI bot that automatically creates and resolves prediction eventsFrontend (Next.js) - Web interface for users to browse and betPyth Oracle - Provides real-world crypto price data for trustless resolutionFarcaster Integration - Social sharing featuresUse CasesCrypto Price Predictions: Bet on whether Bitcoin, Ethereum, or Solana will reach certain price targetsTime-based Events: Create markets with specific deadlines for resolutionDecentralized Decision Markets: Leverage crowd wisdom for predictionsSocial Betting: Share predictions via Farcaster with the community
Solution
Core ArchitectureI. Smart Contract Layer (Solidity) The heart of Fatecast is the PredictionMarket.sol contract built with Foundry and Solidity 0.8.20. Key technical decisions:Technology Stack:OpenZeppelin Contracts for battle-tested security primitives:Ownable for access controlSafeERC20 for secure token transfersReentrancyGuard to prevent reentrancy attacksPausable for emergency circuit breaker functionalityContract Design Pattern:struct Event { uint256 id; string question; bytes32 pythFeedId; // Links to Pyth oracle int64 targetPrice; // Price threshold for resolution uint256 deadline; uint256 totalYes; // Pool tracking uint256 totalNo; uint256 totalPool; bool resolved; bool outcome; address creator; uint256 createdAt; }Clever Implementation Details:Dual Authorization Pattern: The contract uses onlyOwnerOrAgent modifier, allowing both the contract owner and an authorized ASI agent to create events - enabling trustless automation while maintaining emergency admin control.Oracle Integration: The _getPythPrice() function uses low-level staticcall to fetch prices from Pyth, with manual assembly for data extraction:(bool success, bytes memory data) = pythOracle.staticcall( abi.encodeWithSignature("getPrice(bytes32)", feedId) ); // Assembly parsing for MockPyth compatibility assembly { price := mload(add(data, 32)) }Active Event Tracking: Uses a dynamic array activeEventIds[] that's pruned on resolution to optimize gas for event queries - a pragmatic approach to on-chain data management.Proportional Payout System: Winners receive (userBet / winningPool) * totalPool, ensuring fair distribution without requiring complex AMM mathematics.II. ASI Agent Backend (TypeScript/Node.js) The automation layer is built with TypeScript and ethers.js v6, featuring a sophisticated event lifecycle manager:Architecture Pattern:asi-agent/ ├── src/ │ ├── index.ts # Main orchestrator with cron scheduling │ ├── eventCreator.ts # Event generation logic │ ├── eventResolver.ts # Resolution monitoring │ └── utils/ │ ├── contracts.ts # Contract instances & ABIs │ ├── pyth.ts # Pyth API integration │ └── logger.ts # Winston logging setupNotable Technical Implementations:Pyth Hermes API Integration: Instead of on-chain price updates (expensive), the agent uses Pyth's REST API:export async function fetchPythPrice(feedId: string): Promise<PythPrice | null> { const url = `${config.pythHermesUrl}/api/latest_price_feeds?ids[]=${feedId}`; const response = await axios.get<PythPrice[]>(url); // Price formatting with exponential notation const price = parseInt(priceData.price.price); const expo = priceData.price.expo; return price * Math.pow(10, expo); }Retry Logic with Exponential Backoff: Robust error handling for oracle price fetches:for (let i = 0; i < maxRetries; i++) { try { const priceData = await fetchPythPrice(feedId); if (priceData) return formatPythPrice(priceData); } catch (error) { await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } }Continuous Monitoring with Cron: Event creation every hour, resolution checks every 5 minutes:cron.schedule(cronSchedule, async () => { await createPredictionEvent(); }); startResolutionMonitor(config.eventResolutionInterval);Daily Rate Limiting: Prevents spam with in-memory counter reset:let eventsCreatedToday = 0; let lastResetDate = new Date().toDateString(); // Resets automatically on date change Frontend (Next.js 16 + React 19)III. Modern React stack with Web3 integration:Integration Highlights:RainbowKit for wallet connection UXWagmi hooks for contract interactionsFarcaster MiniApp SDK for social prediction sharingBlockscout SDK for contract verification linksParticularly Hacky/Notable SolutionsMock vs. Real Pyth Oracle Compatibility The contract's _getPythPrice() function uses assembly to handle both MockPyth (for testing) and real Pyth oracle responses - a pragmatic hack avoiding complex interface implementations.Int64 Price Conversion Pyth uses int64 with 8 decimal precision, requiring careful conversion:export function toPythInt64(price: number): bigint { return BigInt(Math.floor(price * 1e8)); }Event Array Pruning Rather than using mappings exclusively, the contract maintains an array of active events and manually removes resolved ones - trading gas on resolution for cheaper queries:function _removeFromActiveEvents(uint256 eventId) internal { for (uint256 i = 0; i < activeEventIds.length; i++) { if (activeEventIds[i] == eventId) { activeEventIds[i] = activeEventIds[activeEventIds.length - 1]; activeEventIds.pop(); break; } } }PYUSD Integration on Sepolia Uses PayPal's PYUSD stablecoin on Sepolia testnet (0x...) with ERC20 standard interface - innovative choice for a stablecoin prediction market.Deployment Artifacts as Configuration The agent reads deployment addresses from JSON files rather than hardcoding:const deploymentFile = vm.readFile("deployments/deployment-11155111.json"); const marketAddress = vm.parseJsonAddress(deploymentFile, ".contracts.PredictionMarket");
Hackathon
ETHOnline 2025
2025
Contributors
- psychemist
27 contributions
- vercel[bot]
2 contributions