Stake convictions with friends. Settled by World ID-verified humans or AI agents on World Chain.

Joust is a social staking game built as a World Mini App. Create a pool about anything ("Will it rain tomorrow?", "Who wins the match?"), stake ETH on your answer, and share the link with friends to challenge them. When the pool closes, an arbiter decides the outcome and winners get paid.
What makes Joust different is who settles the outcome. Every pool is decided by either a World ID Orb-verified human or an AI agent registered in World's AgentBook. Never by luck or algorithms.
Trust is enforced through the Honor Score system. After every settlement, participants rate the arbiter's fairness. Each vote is backed by a World ID Orb verification, guaranteeing one person equals one vote. Over time, arbiters build a public reputation score that is impossible to game through sybil accounts. This makes Joust's trust layer fundamentally dependent on World ID.
World ID 4.0 verification badges (Orb, Device, Unverified) appear across the entire app on pool creators, arbiters, stakers, and the leaderboard. Only Orb-verified humans can serve as arbiters or cast honor votes. Proof validation runs server-side via the v4 verify endpoint with RP signatures from a managed signing key.
The app integrates seven MiniKit 2.0 commands: walletAuth (SIWE sign-in), sendTransaction (pool creation, staking, settlement, refunds via calldata with userOp polling), share, shareContacts, sendHapticFeedback, requestPermission, and closeMiniApp. All on-chain activity runs through a UUPS-upgradeable JoustArena contract on World Chain mainnet with a 2x-capped winner payout model.
AI arbiters use AgentKit with registered agent wallets that auto-accept arbiter roles and settle pools by analyzing the pool question and options. This gives users a choice: trust a verified human's judgment or an AI's analysis.
Stack: MiniKit 2.0, IDKit 4.x (World ID 4.0), AgentKit, World Chain, Foundry/Solidity, Next.js 15, React 19, Tailwind v4, wagmi/viem, Prisma/Postgres.
The smart contracts are built with Foundry using pure standard EVM Solidity, deployed to World Chain mainnet via a UUPS proxy (OpenZeppelin upgradeable) so we can ship fixes without redeploying state. Solmate handles ERC20 interactions, Solady handles safe ETH and token transfers.
The core market engine is parimutuel. Every stake flows into a shared pool regardless of side. When the arbiter declares a winner, the pool distributes proportionally: if you staked 10% of the winning side's total, you receive 10% of the winnings. Winners are hard-capped at 2x their original stake, which is critical for making the game feel fair in lopsided markets. The excess from that cap doesn't disappear; it gets refunded proportionally to the losing side, so losers in a heavily skewed pool still get some of their stake back. Fees come out of the losers' excess first (house fee at 3.99 bps, arbiter fee capped at 2% per pool), meaning winners only pay fees when losers can't cover them. If everyone picks the same outcome, all participants get their stake back minus proportional fees. Full refunds (arbiter or public refund after expiry + 1 day) return 100% of original stakes with zero fees. The contract supports N-way outcomes (not just binary yes/no), so you can create pools like "Who wins: Team A, Team B, or Draw?" with contiguous joust types from 1 to N.
What makes this social and not just another DeFi primitive is the full stack around it. MiniKit 2.0's shareContacts lets you pick friends from your World App contacts and invite them into a pool. The share command lets you post results after settlement. sendHapticFeedback gives tactile feedback when your stake confirms or when you win. These aren't bolted-on features; the entire UX is designed around challenging people you know and proving you were right.
The trust layer runs on World ID 4.0 through IDKit 4.x. When a user verifies, our backend generates an RP signature using the managed signing key, IDKit creates the proof request, and the user completes verification inside World App. The proof is forwarded server-side to POST /v4/verify/{rp_id} for validation. We store nullifiers in Postgres (Neon, via Prisma) per action, which powers the Honor Score system: after every settlement, participants rate the arbiter fair or unfair. Each World ID gets exactly one vote per pool per arbiter, enforced by nullifier uniqueness. Over time this builds a sybil-resistant reputation graph where arbiter trust is earned from verified unique humans, not sockpuppet accounts.
The AI arbiter integration uses AgentKit. We register an agent wallet in AgentBook tied to a real human's World ID. Pools can choose an AI arbiter at creation, which auto-accepts the on-chain arbiter role and settles pools by analyzing the question and outcome options. The agent wallet is a standard EOA signing transactions server-side. This creates a genuine choice for pool creators: trust a verified human's subjective judgment, or an AI's deterministic analysis. Both are accountable through the same Honor Score system.
The frontend is Next.js 15 (Turbopack), React 19, Tailwind v4, managed with pnpm. MiniKit 2.0 walletAuth handles SIWE sign-in. All on-chain writes go through MiniKit sendTransaction with viem's encodeFunctionData producing raw calldata. World App returns a userOpHash (not a tx hash), so we built a polling layer against the World Developer API (/api/v2/minikit/userop/{hash}) that resolves the final transaction hash before recording it in the database. This dual-state model (contract as source of truth for funds, Postgres for metadata) lets us show rich pool data without on-chain reads on every page load.
One notable discovery: World App wallets hold WETH internally, but our contract accepts native ETH via msg.value. MiniKit's sendTransaction handles the unwrapping transparently, so we didn't need to modify the contract's ETH handling. We also had to hex-encode ETH values (0x prefix) for the v2 calldata format, which differs from the v1 ABI-driven approach.

