Skip to content

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

37 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”₯ RoastArena

Onchain roast battles on Monad Testnet. Create an arena, stake MON, drop your best burn, and let the crowd vote. Losing voters fund the winners β€” prediction market meets roast comedy.

Live at: https://monad-rage.vercel.app/ Β· Contract: 0xff6E651c905cD7a5aE4b8d404173c07dE254D322 on Monad Testnet

DEPLOYED CONTRACT ADDRESS : 0xff6E651c905cD7a5aE4b8d404173c07dE254D322

BUILT BY @duveshp and @sankalp771

How It Works

Phase Duration What happens
OPEN creator-set, 1 min–24 h (default 3 min) Roasters pay roastStake to join & submit their roast
VOTING creator-set, 1 min–24 h (default 4 min) Anyone pays voteStake to vote for their favourite roaster
SETTLED β€” Winners split the roaster pool; voters who backed a winner split the voter pool
CANCELLED β€” Triggered if < 2 roasters joined or nobody voted β€” full refunds issued

The prediction-market twist: Losing voters' stakes flow to the winners' voters. Back the wrong roaster and your stake funds those who backed the right one.


Project Structure

roast-arena/
β”œβ”€β”€ src/
β”‚   └── RoastArena.sol          # Solidity smart contract
β”œβ”€β”€ script/
β”‚   β”œβ”€β”€ Deploy.s.sol            # Foundry deployment script
β”‚   └── Simulate.s.sol          # Full end-to-end simulation (7 scenarios)
β”œβ”€β”€ test/
β”‚   └── RoastArena.t.sol        # Forge test suite (~765 lines)
β”œβ”€β”€ backend/                    # Node.js / Express API
β”‚   β”œβ”€β”€ index.js                # Routes & middleware
β”‚   β”œβ”€β”€ db.js                   # Schema + Turso helpers
β”‚   β”œβ”€β”€ listener.js             # On-chain event listener
β”‚   └── storage.js              # File upload handler (local / cloud-switchable)
β”œβ”€β”€ frontend/                   # Next.js 16 app
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ page.tsx            # Home β€” arena listing + create form
β”‚   β”‚   β”œβ”€β”€ arena/[id]/page.tsx # Arena detail β€” join / vote / claim
β”‚   β”‚   └── profile/[address]/page.tsx
β”‚   β”œβ”€β”€ components/Navbar.tsx
β”‚   └── lib/
β”‚       β”œβ”€β”€ contract.ts         # ABI, addresses, chain config, enums
β”‚       β”œβ”€β”€ useWallet.ts        # Wallet hook (ethers.js)
β”‚       β”œβ”€β”€ api.ts              # Backend API client
β”‚       └── useCountdown.ts
└── foundry.toml

Smart Contract

src/RoastArena.sol β€” Solidity ^0.8.24, deployed with Foundry

State machine

OPEN (0)  ──3 min──▢  VOTING (1)  ──4 min──▢  SETTLED (2)
                                         └──▢  CANCELLED (3)

State is computed lazily from block.timestamp against the stored deadlines β€” no keeper needed.

Key write functions

Function Who can call Description
createRoast(roastStake, voteStake) Anyone Creates arena with default 3/4-minute windows
createRoast(roastStake, voteStake, openDuration, voteDuration) Anyone Creates arena with custom windows (1 min–1 day each)
joinRoast(roastId) Anyone (OPEN phase) Joins as roaster, pays exact roastStake
vote(roastId, candidate) Anyone (VOTING phase) Casts one vote, pays voteStake; no self-votes
settle(roastId) Roasters or voters; anyone 1 h after voting ends Finalises arena, determines winners
claimRoasterReward(roastId) Winners Claim equal share of roaster pool
claimVoterReward(roastId) Voters who backed a winner Claim share of voter pool
claimRefund(roastId) Any participant Refund from CANCELLED arena

Reward math

roasterShare = roasterPool / numWinners

voterPool    = sum of all voteStakes
voterShare   = voterPool / winnerVoterCount
             = voterPool / (numWinners Γ— highestVotes)

Example: 3 voters stake 0.5 ETH each β†’ pool = 1.5 ETH. Alice wins with 2 votes. Two Alice-voters each receive 1.5 / 2 = 0.75 ETH (1.5Γ— their stake). Bob's voter loses.

Security

  • Pull-payment pattern (reentrancy safe) on all claim functions
  • Payouts via call (full gas) β€” smart-contract wallets can claim
  • Custom errors for gas efficiency (RoastNotFound, AlreadyClaimed, NotInWindow, …)
  • exists() modifier guards all view functions
  • Integer division β€” dust stays in contract

Backend

Node.js + Express 5, Turso (SQLite) database

API routes

Method Path Description
GET /health Liveness check
GET /roasts?limit=20 Recent arenas, newest first
GET /roast/:id Single arena metadata
GET /roast/:id/content All roast submissions (with profiles)
POST /roast/:id/content Submit roast text (max 500 chars, one per wallet)
GET /roast/:id/challenge What's being roasted (title, description, media)
POST /roast/:id/challenge Set challenge content
POST /upload Upload image (JPEG/PNG/GIF/WebP, max 10 MB)
GET /profile/:address User profile
POST /profile Upsert profile (username, avatar, bio)
GET /profile/:address/roasts Roast history for an address

Database schema

profiles          (address PK, username, avatar_url, bio, updated_at)
roast_index       (roast_id PK, creator, state, open_until, vote_until, pools, …)
participant_index (roast_id, address β€” composite PK)
roast_content     (id, roast_id, author UNIQUE per roast, content, created_at)
challenge_content (roast_id PK, creator, title, description, media_url, created_at)
listener_state    (key PK, value β€” persists lastPolledBlock across restarts)

Event listener

Polls eth_getLogs every 5 s in 100-block chunks (Monad RPC limit). Persists lastPolledBlock to DB so restarts resume from where they left off. Cold-start lookback: last 200 blocks.

Events handled: RoastCreated Β· ParticipantJoined Β· VoteCast Β· RoastSettled Β· RoastCancelled


Frontend

Next.js 16.1.6 Β· React 19 Β· ethers.js v6 Β· Tailwind CSS 4

Pages

Route Description
/ Arena list with live countdown (Roast / Vote phase labels), create-arena form
/arena/[id] Join, submit roast text, vote, settle, claim rewards; live vote bars
/profile/[address] Edit username/bio/avatar; view roast history

Create-arena form

  • Challenge title (required, max 100 chars) β€” what everyone is roasting
  • Description (optional, max 500 chars)
  • Media: Text only or Image upload (stored via backend, swappable to cloud)
  • Roaster stake & vote stake amounts
  • Transaction fires on-chain β†’ off-chain content is submitted after confirmation

Wallet integration (lib/useWallet.ts)

  • Auto-connects if wallet already approved the site
  • Detects wrong network and offers one-click switch
  • Adds Monad Testnet to MetaMask if not already configured
  • Reacts to account and chain changes

RPC resilience

  • Promise.allSettled for all per-user view calls β€” one node hiccup doesn't break the whole poll
  • getVoteCounts wrapped in its own try-catch (transient CALL_EXCEPTION from QuickNode load balancing on fresh arenas self-heals on the next 4 s poll)
  • Block-timestamp offset correction β€” aligns chain timestamps to real-world clock for accurate countdowns

Local Development

Prerequisites

  • Foundry
  • Node.js β‰₯ 20
  • A Monad Testnet wallet with some MON (faucet)

1. Smart contract

# Build
forge build

# Run tests
forge test -vv

# Run full simulation
forge script script/Simulate.s.sol --fork-url https://testnet-rpc.monad.xyz -vv

# Deploy to Monad Testnet
forge script script/Deploy.s.sol \
  --rpc-url https://testnet-rpc.monad.xyz \
  --private-key $PRIVATE_KEY \
  --broadcast

2. Backend

cd backend
cp .env.example .env   # fill in your values
npm install
node index.js
# β†’ http://localhost:3001

backend/.env

CONTRACT_ADDRESS=0xff6E651c905cD7a5aE4b8d404173c07dE254D322
MONAD_RPC=https://testnet-rpc.monad.xyz
PORT=3001
TURSO_DATABASE_URL=libsql://<your-db>.turso.io
TURSO_AUTH_TOKEN=<your-token>
# Optional: persistent image hosting (local ./uploads fallback if unset)
CLOUDINARY_URL=cloudinary://<api_key>:<api_secret>@<cloud_name>
# Optional: comma-separated CORS allowlist (all origins allowed if unset; no trailing slashes)
ALLOWED_ORIGINS=https://monad-rage.vercel.app,http://localhost:3000

3. Frontend

cd frontend
# create .env.local with the values below
npm install
npm run dev
# β†’ http://localhost:3000

frontend/.env.local

NEXT_PUBLIC_CONTRACT_ADDRESS=0xff6E651c905cD7a5aE4b8d404173c07dE254D322
NEXT_PUBLIC_MONAD_RPC=https://testnet-rpc.monad.xyz
NEXT_PUBLIC_BACKEND_URL=http://localhost:3001
NEXT_PUBLIC_CHAIN_ID=10143

Deployment (Production)

Layer Hosted on
Smart contract Monad Testnet β€” 0xff6E651c905cD7a5aE4b8d404173c07dE254D322
Backend API Render (Node web service) β€” https://monad-rage.onrender.com
Frontend Vercel β€” https://monad-rage.vercel.app
Database Turso (edge SQLite)
File uploads Cloudinary via CLOUDINARY_URL (local uploads/ fallback β€” ephemeral on Render)

Render (backend): root directory backend, build npm install, start npm start, plus the backend/.env variables above (Render sets PORT itself).

Vercel (frontend): root directory frontend, default Next.js build settings, with:

NEXT_PUBLIC_CONTRACT_ADDRESS=0xff6E651c905cD7a5aE4b8d404173c07dE254D322
NEXT_PUBLIC_BACKEND_URL=https://monad-rage.onrender.com

Tech Stack

Layer Technology
Smart contract Solidity ^0.8.24, Foundry
Blockchain Monad Testnet (chain ID 10143)
Backend Node.js, Express 5, ethers.js v6
Database Turso (libSQL / SQLite)
Frontend Next.js 16, React 19, TypeScript
Styling Tailwind CSS 4
Web3 client ethers.js v6, MetaMask
File storage Cloudinary (local fallback)

Simulation Scenarios

script/Simulate.s.sol covers seven end-to-end flows against a live fork:

  1. Happy path β€” Alice wins, winner voters rewarded
  2. Only creator β€” CANCELLED, refunded
  3. No votes β€” CANCELLED, roasters refunded
  4. Three-way tie β€” pools split equally among all three
  5. Prediction market β€” losing voter stakes redistributed to Alice's backers
  6. Roaster also votes β€” roaster pays extra stake to vote
  7. Revert edge cases β€” validates all custom errors fire correctly

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages