Skip to content

ntxinh/web3-gamefi

Repository files navigation

card-gamefi

Web3 GameFi platform on Ethereum Sepolia. Two games:

  • E-Card — asymmetric card game (Emperor vs Citizens). Players stake ETH; on-chain escrow holds funds; server oracle settles on game end.
  • Rock-Paper-Scissors — best-of-3. Same stake/settle mechanic.

Both support a free "Play vs Bot" mode that requires no wallet. Weekly ELO-based leaderboard rewards distributed via Merkle airdrop (ERC-20 RewardToken).

Tech stack: Next.js 16 (App Router), Drizzle ORM + Supabase Postgres, viem/wagmi, Foundry, Tailwind CSS.


Prerequisites

Tool Version Purpose
mise any Installs node/pnpm/foundry
MetaMask any Browser wallet (for staked play)
Supabase account Hosted Postgres database
Sepolia ETH ~0.05 ETH Gas + stake testing

Environment setup

cp .env.example .env.local

Fill in .env.local:

DATABASE_URL="postgres://USER:PASS@HOST:6543/postgres?sslmode=require"
NEXT_PUBLIC_CHAIN_ID=11155111
NEXT_PUBLIC_RPC_URL="https://sepolia.infura.io/v3/KEY"
ORACLE_PRIVATE_KEY="0x..."              # server wallet; NEVER expose publicly
NEXT_PUBLIC_ESCROW_ADDRESS="0x..."      # filled after deploy
NEXT_PUBLIC_REWARD_TOKEN_ADDRESS="0x..." # filled after deploy
NEXT_PUBLIC_DISTRIBUTOR_ADDRESS="0x..."  # filled after deploy
CRON_SECRET="long-random-string"        # guards /api/cron/* endpoints

The ORACLE_PRIVATE_KEY wallet must have Sepolia ETH to pay gas for settle transactions. Keep it server-side only — it has full power to settle any match.

Also set FEE_RECIPIENT (any address) when deploying contracts (see below).


Install toolchain

mise install

Installs Node 22, pnpm 9, and Foundry (forge/cast/anvil) as specified in mise.toml.


Contract tests

cd contracts
forge test

Deploy contracts to Sepolia

cd contracts
forge script script/Deploy.s.sol \
  --rpc-url $NEXT_PUBLIC_RPC_URL \
  --broadcast \
  --private-key $ORACLE_PRIVATE_KEY

The script reads ORACLE_PRIVATE_KEY and FEE_RECIPIENT from the environment. Copy the three printed addresses into .env.local:

NEXT_PUBLIC_ESCROW_ADDRESS=<ESCROW printed by script>
NEXT_PUBLIC_REWARD_TOKEN_ADDRESS=<TOKEN printed by script>
NEXT_PUBLIC_DISTRIBUTOR_ADDRESS=<DISTRIBUTOR printed by script>

Post-deploy: fund the MerkleDistributor

MerkleDistributor does not mint tokens — it distributes tokens already held in its balance. After deploy, manually mint RewardToken to the distributor address:

cast send $NEXT_PUBLIC_REWARD_TOKEN_ADDRESS \
  "mint(address,uint256)" \
  $NEXT_PUBLIC_DISTRIBUTOR_ADDRESS \
  <amount_in_wei> \
  --rpc-url $NEXT_PUBLIC_RPC_URL \
  --private-key $ORACLE_PRIVATE_KEY

RewardToken is Ownable; the deployer wallet (oracle key) is the owner and can mint.


Database

Push the Drizzle schema to Supabase (requires DATABASE_URL in .env.local):

pnpm drizzle-kit push

Run dev server

pnpm dev

Open http://localhost:3000.


Gameplay flows

Staked match (wallet A vs wallet B)

  1. Connect MetaMask (Sepolia network).
  2. Wallet A: go to /play/ecard or /play/rps, click Create Match, set stake amount → MetaMask prompts to send ETH to the escrow.
  3. Wallet B: open the same match URL, click Join Match → MetaMask prompts to match the stake.
  4. Both players submit moves each round via the board UI (no on-chain tx per move — moves are server-authoritative).
  5. On game end, the server oracle automatically calls settleMatch() on-chain, paying the winner 2×stake − 10% fee.

Bot play (free, no wallet needed)

Click Play vs Bot (free) on the game page. No MetaMask required, no ETH at stake. Bot moves are generated server-side.


Weekly rewards

The cron job runs every Monday at 00:00 UTC (configured in vercel.json). It computes the top-10 ELO players per game, builds a Merkle tree, stores claims in the DB, and pushes the root on-chain.

Trigger manually:

curl -X POST http://localhost:3000/api/cron/rewards \
  -H "x-cron-secret: $CRON_SECRET"

Claim rewards — navigate to /leaderboard/rps?week=<n> or /leaderboard/ecard?week=<n>. Eligible wallets see a Claim button that calls MerkleDistributor.claim() via wagmi.


Known MVP gaps

Oracle key trust — The ORACLE_PRIVATE_KEY server wallet can settle any match on-chain. There is no on-chain commit-reveal or ZK proof. Sepolia-only; not production-safe.

No frontend/engine unit tests — Only Foundry contract tests exist (forge test). The Next.js app and game engine have no automated tests.

Orphan open DB rows — If a user's createMatch transaction fails or is abandoned after the DB row is inserted, the row stays in status=open indefinitely. Prune manually:

DELETE FROM matches WHERE status = 'open' AND created_at < now() - interval '1 hour';

Score label perspective — The board shows "You / Opp" scores from the creator's perspective. A player joining as opponent sees the labels reversed.

Per-turn forfeit timeout — If a player stops responding mid-game, forfeit must be triggered manually (or via cron):

curl -X POST http://localhost:3000/api/matches/<id>/settle \
  -H "x-cron-secret: $CRON_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"winner":"<other_player_address>"}'

After 6 hours of inactivity the on-chain refundMatch() function becomes callable by anyone, returning each player's stake.

Vercel cron vs POST endpointvercel.json schedules a GET request to /api/cron/rewards, but the route only handles POST with x-cron-secret. Use a Vercel cron proxy function or trigger manually via curl.


Verification checklist

  • forge test passes (all contract tests green)
  • .env.local has all six required variables filled
  • pnpm drizzle-kit push succeeds (tables visible in Supabase)
  • pnpm dev starts without errors
  • MetaMask connects on Sepolia (chain ID 11155111)
  • Create match → join → play → auto-settle completes (check DB status=settled, on-chain tx visible on Sepolia Etherscan)
  • Bot game completes without wallet
  • Manual cron curl returns { week, roots } JSON
  • Leaderboard page shows claim button for rewarded wallet

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors