An hourly pari-mutuel prediction market on the WorldLand ecosystem.
WorldLand was built around ECCPoW — error-correction-code proof-of-work — and after the Rockies upgrade the energy and hardware cost of mining a single WLC is, by most operators' accounting, well above the current market price of the listed WL token. WorldBet is one community attempt to give holders a more interesting on-chain use of WL than just sitting on it: a transparent, no-house, hourly market on whether WL/USD, BTC/USD, or ETH/USD will move up or down. It is meant to be played casually, not as an investment vehicle.
The design is intentionally pari-mutuel — every payout is funded by other players' bets, the contract holds zero liquidity risk, and the 3% protocol fee is split between a community prize pool, a referrer rebate, and a permissionless burn. These are tunable parameters of an open-source contract; there is no house edge on top of the fee.
Disclaimer. WorldBet is software. Outcomes are pari-mutuel and determined by other participants' bets and a public oracle. Nothing in this repository is financial advice, an offer to sell, or a solicitation. Authors and contributors disclaim liability for any decision made on the basis of using this code. Where you live, prediction markets and on-chain wagering may be regulated or prohibited; check before participating.
Where to deploy. Primary target is BNB Smart Chain (BSC, chainId 56) because the WL BEP-20 token (
0x8aaB31fbc69C92fa53f600910Cf0f215531F8239) is listed on KuCoin / Gate / MEXC / HTX and pairs on PancakeSwap there. WorldLand Seoul mainnet (chainId 103) is also wired and is the natural deployment target once a bridged WL exists there. Until then, treat Seoul as a forward-compatible target rather than the launch chain.
- Markets: WL/USD, BTC/USD, ETH/USD
- Rounds: 1 hour each, rolling — every UTC hour a new round opens for betting; the prior hour's bets go live once the lock price posts; the hour before that settles on the close price.
- Stake: WL (ERC-20 / BEP-20). Users approve the contract once, then bet by amount.
- Payout: pari-mutuel — winners get back their stake plus their pro-rata slice of the loser pool. Contract has zero LP risk.
- Fees (3% total):
- 1.0% → weekly leaderboard prize pool (
prizePool, owner-distributed off events) - 0.3% → sticky referrer rebate (claimable, falls back to burn if no referrer)
- 1.7% →
pendingBurn, anyone can callburn()which transfers to0x...dEaD(WL has no publicburn(); transfer to the dead address removes circulating supply, totalSupply unchanged)
- 1.0% → weekly leaderboard prize pool (
- Oracle: 2-of-3 EIP-712 multisig. Bots fetch median spot from KuCoin / Gate / MEXC / HTX at every UTC hour, sign, and post on-chain.
- Refund cases (round status 4): one-sided pool, tie (lock == close), or oracle fails to post within a 30-minute grace window.
- Anti-whale:
setMaxBetPerRound(asset, cap)— opt-in per-asset cap on a single user's bet in one round.
worldbet/
├── contracts/
│ ├── PriceOracle.sol M-of-N EIP-712 signed price feed
│ ├── WorldBet.sol multi-asset pari-mutuel rounds + fee split + burn
│ └── MockWL.sol testnet-only stand-in for WL (open faucet)
├── scripts/
│ ├── deploy.js deploy Oracle + WorldBet, register WL/BTC/ETH
│ ├── deploy-mock-wl.js testnet-only: deploy MockWL + seed deployer
│ ├── mint-tWL.js testnet-only: faucet helper
│ └── check-cex.js verify WL/BTC/ETH listings before deploy
├── oracle/index.js CEX sampler + median + EIP-712 sign + post
├── keeper/index.js permissionless lockRound / settleRound driver
├── test/ Foundry fuzz + stateful invariant tests
├── frontend/ Next.js + wagmi/viem (cards, burn counter, ref link)
├── hardhat.config.js bsc (56) + bscTestnet (97) + seoul (103) + gwangju (10395)
├── foundry.toml Solidity 0.8.24, fuzz=1000 / invariant=100×50
└── README.md
Run this first before mainnet to dry-run the full pipeline. WL is not listed on testnet, so we deploy a MockWL (open faucet, tWL symbol) and point WorldBet at it.
npm install
# 0. Get testnet BNB for gas. Faucet: https://testnet.bnbchain.org/faucet-smart
# (paste your deployer address; ~0.5 BNB is plenty for the whole flow.)
# 1. Set deployer + signers
export DEPLOYER_KEY=0x... # funded testnet BNB
export ORACLE_SIGNERS=0xAAA...,0xBBB...,0xCCC...
export ORACLE_THRESHOLD=2
# 2. Deploy MockWL (10M tWL minted to deployer for seed)
npx hardhat run scripts/deploy-mock-wl.js --network bscTestnet
# -> writes mock-wl.json with the address
# 3. Deploy oracle + WorldBet (auto-loads mock-wl.json on testnet)
npx hardhat run scripts/deploy.js --network bscTestnet
# 4. Faucet helper — mint tWL to a tester (or yourself)
RECIPIENT=0xTESTER AMOUNT=50000 \
npx hardhat run scripts/mint-tWL.js --network bscTestnet
# 5. Run oracle bot + keeper
RPC_URL=https://data-seed-prebsc-1-s1.binance.org:8545 \
ORACLE_ADDR=$(jq -r .oracle deployments.json) \
SIGNER_KEYS=0xkey1,0xkey2 \
node oracle/index.js &
RPC_URL=https://data-seed-prebsc-1-s1.binance.org:8545 \
WORLDBET_ADDR=$(jq -r .worldbet deployments.json) \
KEEPER_KEY=0x... \
node keeper/index.js &
# 6. Frontend (set NEXT_PUBLIC_DEFAULT_CHAIN_ID=97 + addresses from deployments.json)
cd frontend && npm install && npm run dev
# 7. Verify on BSCScan testnet (optional but useful for debugging)
npx hardhat verify --network bscTestnet $(jq -r .oracle deployments.json) \
$(jq -r .deployer deployments.json) "[$(jq -r '.signers | join(\",\")' deployments.json)]" 2
npx hardhat verify --network bscTestnet $(jq -r .worldbet deployments.json) \
$(jq -r .oracle deployments.json) $(jq -r .wl deployments.json) $(jq -r .deployer deployments.json)Run for 24h on testnet to verify oracle posts hourly, keeper drives lock/settle, payouts are correct, and the frontend works end-to-end. Then proceed to mainnet.
npm install
# 0. Verify CEX listings. Each market needs >= 2 healthy venues for
# the median to work. Fix symbols / drop markets if any is short.
npm run check:cex # WL, BTC, ETH
# 1. Keys + addresses
export DEPLOYER_KEY=0x... # funded BNB on BSC
export ORACLE_SIGNERS=0xAAA...,0xBBB...,0xCCC...
export ORACLE_THRESHOLD=2
# WL_ADDRESS defaults to BSC mainnet 0x8aaB31fb... in scripts/deploy.js;
# override with WL_ADDRESS=0x... only for testnet/other chains.
# 2. Compile + deploy
npx hardhat compile
npx hardhat run scripts/deploy.js --network bsc # mainnet
# or:
# npx hardhat run scripts/deploy.js --network bscTestnet # WL_ADDRESS requireddeployments.json is written next to the package with the WL address, oracle/worldbet addresses, signer set, and registered asset keys.
WL is real money. Cap individual bets tightly for the first 24 hours so a bad oracle post or undiscovered bug only burns bounded WL. The owner can loosen them after monitoring.
# from a hardhat console on the deployer key:
npx hardhat console --network bsc
> const wb = await ethers.getContractAt("WorldBet", "0x...");
> const cap = ethers.parseEther("100"); // 100 WL per user per round
> for (const k of ["WL/USD","BTC/USD","ETH/USD"]) await wb.setMaxBetPerRound(ethers.id(k), cap);- No emergency pause in v1. For a critical bug the owner can
setMaxBetPerRound(asset, 1)to block new bets per asset; in-flight rounds still settle on whatever oracle posts. A realpause()+emergencyRefund()is the first follow-up if BSC TVL grows beyond the contained-loss threshold. - External audit recommended before significant volume. Self-checked with 32 unit/fuzz tests + 5 stateful invariants × 5000 calls.
- Oracle key distribution. Combined mode (one host, all keys) is fine for low-stakes launch. Distribute keys across 3 hosts (peer-signature sidecar mode) before TVL grows.
Combined mode (single host, holds 2 of 3 keys):
export RPC_URL=https://bsc-dataseed.binance.org
export ORACLE_ADDR=0x... # from deployments.json
export SIGNER_KEYS=0xkey1,0xkey2 # >= threshold
node oracle/index.jsDistributed mode (one key per host, peers exchange signatures over HTTP):
# host A
SIGNER_KEYS=0xkeyA SIDECAR=1 PEER_PORT=8787 \
PEER_SIG_URLS=http://hostB:8787,http://hostC:8787 \
node oracle/index.js
# host B / C — same shape, different keysThe leader (any host) submits to chain once >= threshold valid signatures are gathered.
cd frontend
cp .env.example .env.local
# Edit .env.local: set NEXT_PUBLIC_WORLDBET_ADDRESS to the deployed address.
# NEXT_PUBLIC_WL_TOKEN_ADDRESS defaults to BSC mainnet WL.
# NEXT_PUBLIC_DEFAULT_CHAIN_ID defaults to 56 (BSC).
npm install
npm run devUI shows the user's WL balance, asset cards with pool ratios + lock countdown, the burn counter, and a referral link panel. Betting requires an approve of WL to the contract first (one-time per allowance amount); the UI prompts when allowance < bet amount. Bets and burn are user-triggered; no admin keys are loaded.
For round N opened at hour T:
| When | Who | Action |
|---|---|---|
T … T+1h |
users | place UP/DOWN bets via UI |
around T+1h |
bot | post oracle price for hourId = T/3600 + 1 (= round N's lock) |
| any | any | call lockRound(asset, N) once lock price is on chain |
around T+2h |
bot | post oracle price for hourId = T/3600 + 2 (= round N's close) |
| any | any | call settleRound(asset, N) |
| any | user | claim(asset, N) to collect winnings or refund |
| any | any | burn() flushes pendingBurn to 0x...dEaD |
The included keeper/ does this automatically: it polls every 30s, calls lockRound once the oracle has posted, and settleRound once closeTime passes. Lock failures while the oracle is still pending are silently retried; after the 30-min grace, the contract auto-refunds.
export RPC_URL=https://seoul.worldland.foundation
export WORLDBET_ADDR=0x...
export KEEPER_KEY=0x... # any funded WL key (no privileges needed)
node keeper/index.js
# or: npm run keeperRun it on the same host as the oracle bot or independently — both are stateless and idempotent.
-
npm run check:cexreturns >= 2 venues for WL/USD; otherwise drop the WL/USD market or fixoracle/index.jssymbol map -
forge testgreen (32 unit/fuzz + 5 invariants) - BSC testnet (chainId 97) dry-run end-to-end (deploy + bet + lock + settle + claim + burn)
- BSC mainnet deploy with
ORACLE_THRESHOLD=2, 3 distinct signer addresses - Set
setMaxBetPerRoundto a conservative cap (e.g. 100 WL) on every asset before announcing - Oracle bot + keeper bot running; manually verify the first hourly post lands on chain
- Verify contracts on BSCScan (
npx hardhat verify --network bsc <addr> ...args) - Dashboard / explorer link to the contract pinned in the community channel
- First 24h: monitor for one-sided rounds, oracle misses, gas anomalies
- Add
pause()+emergencyRefund(asset, id)admin functions; redo fuzz/invariants - External audit ($5–10k) — single contract
- 3 signer keys on 3 separate hosts (not combined-mode); peer signature sidecar with value-convergence (oracle bot leader proposes the median, peers sign that exact value)
- Lock/settle keeper redundancy (>= 2 instances)
- Initial liquidity / market-making bot to seed the first hours of each asset's pool
- Referral link landing page + Discord/Telegram bot
- Twitter thread template with the burn counter snapshot
- Weekly leaderboard cron: read
BetPlaced/Claimedevents, rank by net WL, calldistributePrize(...) - Loosen
setMaxBetPerRoundafter telemetry stabilizes
WorldBet is wired to deploy on WorldLand Seoul (chainId 103) once the WL → WLC bridge ships and a wrapped WL ERC-20 exists on Seoul. Steps then:
- Deploy WL ERC-20 on Seoul (or use the bridge's wrapped form).
WL_ADDRESS=0x... npx hardhat run scripts/deploy.js --network seoul- Same operator + frontend stack; the dApp logic is unchanged.
The Seoul deployment is a forward-compatible mirror, not a substitute for the BSC launch.
- Initial deployment: BSC (chainId 56), where WL is BEP-20-listed. WorldLand Seoul mirror once a bridged WL ERC-20 exists there.
- Oracle: 2-of-3 EIP-712 multisig with median CEX pricing (no Chainlink/Pyth dependency).
- Markets: WL, BTC, ETH offered side by side so players have multiple options.
- Pari-mutuel (not order-book or AMM): zero protocol risk, payouts capped to the pool, no house edge beyond the disclosed 3% fee.
- Burn destination:
0x...dEaD(WL has no publicburn(); transfer to the dead address is the standard ERC-20 substitute), permissionless trigger so anyone can flush. - Stake currency: WL ERC-20 (vanilla, no fee-on-transfer, 18 decimals); user approves once, then bets by amount.
pause()+emergencyRefundadmin functions (add before TVL grows).- Distributed oracle value-convergence (currently combined mode; peer-sidecar mode needs a leader-proposes / peers-sign protocol so signatures align).
- Production signer-relay infra (HSM, signature sidecar hardening, key rotation).
- Front-end deployment (Vercel) + analytics + leaderboard cron.