Search projects, hackathons, sponsors and tech

    ← All projects

    Horus

    Fully on Chain Prediction Markets (no order book)

    About

    The problem it solves Prediction markets today are either centralized (the operator picks the outcome) or limited to assets on a single chain. Horus solves both problems: **Multi-chain price markets for any token.** Users can bet on whether *any* token with Uniswap liquidity — on Ethereum, Base, or any supported chain — will go up in 10 minutes. The market contract lives on Ethereum Sepolia, but the price oracle reads from tokens on other chains via the Uniswap API. This means you can create a prediction market for a Base memecoin while settling in USDC on Sepolia. **Auditable oracle with a path to trustlessness.** The admin submits the price, but *cannot fabricate it*. Every market stores its oracle metadata on-chain — the API endpoint, source chain, pool address, and token address. Anyone can independently query the same Uniswap API endpoint and verify the submitted price matches. This metadata is also the exact input specification for a future zkTLS verifier, which would make resolution fully permissionless and provably correct without any admin involvement. **What users can do:** - **Create markets** — anyone with ≥10 USDC can create a 10-minute prediction market for any token (permissionless on the contract level) - **Trade** — buy YES/NO tokens with USDC. Prices reflect crowd probability via a constant-product AMM (CPMM) - **Provide liquidity** — earn 2% swap fees by adding USDC to the pool - **Claim winnings** — after resolution, winning token holders redeem 1:1 for USDC - **Mint/Redeem** — always mint 1 YES + 1 NO for 1 USDC, or redeem the pair back. Fully collateralized at all times. **Why it matters:** Short-duration prediction markets on memecoins are a natural fit — high volatility, high engagement, fast feedback loops. Horus makes this possible across chains with an auditable oracle, moving the trust model from "trust the operator" to "verify the data source yourself."

    How it's made

    Challenges we ran into **Decoupling the oracle from the market chain.** V1 and V2 of the contract read the price directly from a Uniswap V3 pool on the same chain (`slot0().tick`). This meant markets could only exist for tokens with pools on the deployment chain. Moving to a submitted-price model (V3) required rethinking the entire resolution flow — the contract no longer reads the price itself, so we needed to store oracle metadata on-chain to keep the system auditable. Getting the right balance between "fast to build" and "architecturally sound for future zkTLS" took several iterations. **Uniswap API response format inconsistencies.** The Uniswap Trade API returns price data in different formats depending on the routing path — sometimes `quote.output.amount` (raw bigint), sometimes `quote.outputAmount`, sometimes `quote.quoteDecimals` (human-readable float). We had to handle all three variants and normalize to a consistent 18-decimal int256 format for the contract. The API also returns 504 gateway timeouts under load, so we added retry logic with exponential backoff. **Multi-hop price routing.** Many tokens (especially memecoins) don't have a direct USDC pool. The initial implementation failed silently for these tokens. We had to build a two-hop fallback (token → WETH → USDC) using two sequential quote calls, which introduced complexity around decimal handling — WETH uses 18 decimals while USDC uses 6. **USDC approval flow for permissionless creation.** V2's factory pulled USDC from the admin wallet. V3 allows anyone to create a market, so the factory now pulls USDC from `msg.sender`. This required adding an explicit `approve` transaction before `createMarket` — a two-step flow that needed careful handling in both the API (for admin-created markets) and the frontend (for user-created markets). **Frontend crashes from undefined on-chain data.** When the API failed to read on-chain data (e.g. during RPC timeouts), it returned market objects without `yesReserve`, `noReserve`, or price fields. The frontend called `BigInt(undefined)` and crashed. We had to add defensive fallbacks at both the API layer (return zero defaults on failure) and the frontend (guard every BigInt conversion). **Contract version migration.** Upgrading from V2 to V3 ABIs across the entire stack (API routes, chain service, test scripts, frontend config) was error-prone. A single missed ABI reference would cause silent failures where the contract call returned unexpected data. We caught several of these only through the integration test suite.