A full-featured prediction market (Polymarket / Kalshi style) deployed on Arc Testnet (Circle's USDC-native L1), with test-USDC betting and a built-in optimistic dispute-resolution system โ no paid oracle required.
๐ Live app: wager-arc.vercel.app ๐ฆ This repo: full-stack app (smart contract + Next.js frontend) ๐งฉ Standalone contract: the resolution engine is also published on its own as a reusable building block for other Arc builders โ see Standalone contract repo below.
| Contract address | 0x74Cb0cdc0b7608f65C777a46f58CF4cE6ad46C7f |
| USDC (Circle, Arc Testnet) | 0x3600000000000000000000000000000000000000 |
| Chain ID | 5042002 |
- โ Create a market: anyone can post a question + an end date
- โ Bet Yes / No in test USDC (parimutuel system)
- โ Two-phase optimistic resolution: creator proposes โ 24h dispute window โ auto-finalize, or owner arbitration if disputed โ see How resolution works below
- โ Low protocol fee: 0.5% on winnings only, owner-configurable, capped at 5%
- โ Discovery: on-chain categories, search, sort (popular / recent / ending soon), category filters
- โ Profile page: identicon, on-chain stats (bets, win rate, net P&L, markets created), activity badge, bet/market history
- โ Browser notifications: follow a market and get alerted when it's closing soon, disputed, or resolved (client-side only โ see limitation below)
- โ Dark / light mode, fully responsive
- โ 10 languages (en/fr/es/ar/pt/de/zh/ja/hi/ru), including right-to-left layout for Arabic
- โ Claim winnings, per-market history and volume (on-chain events)
predictionmarket/
โโโ contracts/ # Smart contracts (Hardhat + Solidity + OpenZeppelin)
โ โโโ contracts/
โ โ โโโ PredictionMarket.sol # Main contract (parimutuel + optimistic resolution)
โ โ โโโ MockUSDC.sol # Test USDC (fallback if no faucet)
โ โโโ scripts/deploy.js # Deployment script
โ โโโ test/ # Tests (34 tests, all passing)
โโโ web/ # Next.js 14 frontend (App Router)
โโโ app/ # Pages: home, /market/[id], /my-markets, /profile
โโโ components/ # Cards, modals, bet panel, resolution panel, logo, etc.
โโโ messages/ # i18n translations (10 locales)
โโโ lib/ # wagmi config, Arc chain, contract ABI, categories, follow/notifications
Stack: Solidity 0.8.24 ยท OpenZeppelin 5 ยท Hardhat ยท Next.js 14 ยท TypeScript ยท Tailwind ยท wagmi + viem + RainbowKit ยท next-intl ยท next-themes
In MetaMask โ Networks โ Add network manually:
| Field | Value |
|---|---|
| Network Name | Arc Testnet |
| RPC URL | https://rpc.testnet.arc.network |
| Chain ID | 5042002 |
| Currency Symbol | USDC |
| Block Explorer | https://arcscan.io |
๐ก Arc quirk: the native currency โ the one that pays gas โ is USDC, not ETH. You don't need any ETH on this network at all.
- Go to faucet.circle.com, pick Arc Testnet, paste your address. The USDC you receive covers both gas and bets.
- The USDC contract address on Arc Testnet is
0x3600000000000000000000000000000000000000(verified on Circle's official page โ already pre-filled in the project's.envfiles).
๐ก Faucet not working? Leave
USDC_ADDRESSempty in step 4: the deploy script will deploy a MockUSDC instead, with afaucet()function you can call to mint yourself 1000 test USDC.
cd contracts && npm install
cd ../web && npm installcd contracts
cp .env.example .envOpen contracts/.env and fill in:
PRIVATE_KEY=your_metamask_private_key # MetaMask โ Account details โ Export private key
ARC_RPC_URL=https://rpc.testnet.arc.network
USDC_ADDRESS=0x3600000000000000000000000000000000000000 # official USDC on Arc Testnet (or empty โ MockUSDC)
TREASURY_ADDRESS= # optional: fee treasury address (defaults to the deployer)๐ NEVER use a private key from a wallet holding real funds. Use a wallet dedicated to testnets. The
.envfile is git-ignored. โฝ Reminder: the deployer wallet pays gas in native USDC (from the Circle faucet), not ETH.
# Run the tests locally (recommended)
npx hardhat test # 34 passing
# Deploy to Arc Testnet
npm run deploy:arcThe script prints, at the end:
NEXT_PUBLIC_MARKET_ADDRESS=0x...
NEXT_PUBLIC_USDC_ADDRESS=0x...
cd ../web
cp .env.example .env.localPaste the two addresses printed by the deploy script into web/.env.local.
npm run devOpen http://localhost:3000, connect MetaMask (Arc Testnet network), and:
- Create a market ("Open a position" button), optionally picking a category
- Bet: pick Yes/No, enter an amount โ Approve then Bet (2 transactions โ normal for an ERC-20)
- After the end date, the creator proposes a resolution โ see the next section for what happens after that
- Once finalized, winners claim their payout
All "Yes" bets go into one pool, all "No" bets into another.
payout = your stake + (your stake / winning pool) ร losing pool โ protocol fee
The protocol fee (0.5% by default) applies only to the winnings portion, never to your refunded stake โ see Protocol fee below.
Example: Yes pool = 200 USDC (100 of it yours), No pool = 400 USDC. The outcome is Yes โ you get 100 + (100/200) ร 400 = 300 USDC, minus a 0.5% fee on the 200 USDC of winnings (i.e. minus 1 USDC โ 299 USDC net).
Special case: if nobody bet on the winning side, everyone is refunded their exact stake โ fee-free.
Instead of trusting the market creator blindly, resolving a market goes through up to four steps:
market end date reached
โ
โผ
โโโโโโโโโโโโโ
โ Open โ betting allowed
โโโโโโโฌโโโโโโ
โ creator proposes an outcome
โ (or the contract owner, after a 7-day grace
โ period, if the creator stayed inactive)
โผ
โโโโโโโโโโโโโ
โโโโโโโโโโโค Proposed โ 24h dispute window running
โ โโโโโโโฌโโโโโโ
no dispute โ โ anyone can dispute
before the โ โ (locks a 5 USDC bond)
window โ โผ
closes โ โโโโโโโโโโโโโ
โ โ Disputed โ awaiting the contract owner's ruling
โ โโโโโโโฌโโโโโโ
โ โโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โ owner rules 30 days pass with
โ (bond refunded no ruling at all
โ or forfeited) โ
โ โ anyone can trigger a
anyone finalizes โ neutral full refund
โ โ (bond returned, no fees,
โผ โผ nobody favored)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Finalized โ
โ winners claim their payout โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Propose. After the end date, the market's creator proposes an outcome. If the creator never shows up, the contract owner can propose on their behalf once a 7-day grace period has passed, so a market can't be stuck forever.
- Dispute window (24h). Proposing an outcome does not finalize it โ it opens a 24-hour challenge window. Anyone can dispute it during that window by locking a 5 USDC bond.
- Finalize or arbitrate.
- No dispute โ anyone can finalize once the window closes; the proposed outcome becomes final.
- Disputed โ the contract owner rules. If the disputer was right, their bond is refunded; if not, it's forfeited to the protocol treasury.
- Anti-stuck-funds safety net. If a disputed market is never ruled on (owner unavailable, lost keys, etc.), anyone can trigger a fully neutral refund after 30 days: everyone gets their exact stake back, the disputer gets their bond back, no fees, no side favored.
This reduces reliance on a single honest creator, without needing a paid external oracle. See Known limitations for what this does not protect against.
A small fee (0.5% by default, capped at 5%) is taken at claim time, only on the winnings portion โ never on a refunded stake, and never at all on a full refund. The rate is owner-configurable, and accrued fees are withdrawable to a treasury address.
On any market page, click "Follow this market". If you allow browser notifications when prompted, you'll get an alert when a followed market is closing soon, gets disputed, or is finalized.
โ ๏ธ Limitation: this is a client-side-only mechanism (Web Notifications API), not a real push/server notification system. It only fires while the Wager tab is open (even in the background) โ closing the tab means you won't get notified, since there's no backend to deliver alerts when you're gone.
The optimistic-resolution engine used here has been extracted into its own standalone, forkable repo, meant as a reusable building block for other Arc projects (prediction markets, conditional-payout escrows, anything that needs "did X happen?" without a paid oracle):
โ github.com/0xassou/arc-optimistic-prediction-market
It contains the same contract (generalized, no Wager-specific naming), the same 34 tests, and a self-contained README aimed at developers who've never heard of Wager โ including a full function reference and integration guide. This predictionmarket repo is where that contract actually gets used, wired up to a full frontend.
SafeERC20+ReentrancyGuard(OpenZeppelin), checks-effects-interactions pattern throughout- Custom errors, no permanently stuck funds (refund paths for an empty winning pool, and for an unruled dispute after 30 days)
- Optimistic dispute window with a bonded challenge mechanism โ see How resolution works
- The dispute system reduces, but does not eliminate, centralized trust. A dishonest market creator can be overridden by disputing their proposal โ but the contract owner remains the final arbiter for any disputed market, exactly like a real oracle committee of one. This is not a decentralized oracle (no UMA-style token-weighted voting, no Reality.eth-style crowd escalation) โ it's a lightweight, free alternative that trades some decentralization for simplicity and zero external cost.
- If you need protection against a dishonest owner too (not just a dishonest creator), consider deploying with a multisig as the owner, or putting
adminResolvebehind a timelock โ see the standalone contract repo's README for details. - This is a testnet MVP: not audited, not intended for mainnet or real funds as-is.
| Where | Command | Effect |
|---|---|---|
contracts/ |
npx hardhat test |
Runs the 34 tests |
contracts/ |
npm run compile |
Compiles the contracts |
contracts/ |
npm run deploy:arc |
Deploys to Arc Testnet |
web/ |
npm run dev |
Dev server (localhost:3000) |
web/ |
npm run build |
Production build |