Skip to content

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

287 Commits

Folders and files

Repository files navigation

Base Mint — NFT Battle Arena

A cross-collection NFT battle game built on Base. Any NFT, any collection — pick your fighter and enter the arena.

🏆 Built for Base Batches 003 Student Track

Preview

🎮 Battle Arena

The flagship feature — a real-time turn-based battle system where NFTs from different collections fight each other.

  • Universal stat normalization — every NFT (Base Invaders, BaseHeads 404, BaseMoods, VoidPFPs, etc.) is converted into a universal stat format (HP, ATK, DEF, SPD, CRIT, Dodge, Lifesteal, Regen)
  • Passive abilities — Ghost Step, Iron Wall, Drain, Berserker, Regen Burst — each with cooldowns and trigger conditions
  • Animated combat — particle effects, floating damage numbers, crit bursts, dodge ghosts, screen shake, slash trails, cinematic round splashes
  • AI opponents — challenge AI-controlled fighters with configurable win rates
  • Anti-cheat snapshots — stat snapshots with SHA-256 hashing to prevent drift abuse
  • Challenge board — post challenges, accept fights, collection-themed card UI

📦 Platform Features

  • NFT Minting — mint from curated Base collections with auto-discovery
  • Bandwidth-smart OpenSea gallery — lazy display previews while browsing, original-resolution images only after opening an item, and opt-in animation playback
  • Rich NFT details — OpenSea rarity rank, estimated value, ownership, traits, trust flags, and marketplace links
  • Reliable mint analytics — browser-persistent outbox, local/OpenSea historical reconciliation, receipt-derived token IDs and rich live-feed cards; no dedicated or always-on server required
  • Points & Gamification — streaks, status badges, global leaderboards
  • Growth & Distribution — automated social sharing, replay-to-play conversion, featured battle highlights
  • Analytics — retention cohorts, conversion funnels, wallet insights
  • Social Sharing — Farcaster cast composing, share cards, viral loops
  • Farcaster Mini App — native integration with Farcaster frames

🛠️ Tech Stack

Layer Technology
Frontend Vite, Vanilla JS, Tailwind CSS
Web3 Reown AppKit, Wagmi, Viem, SIWE
Blockchain Base (Mainnet)
Backend Vercel Serverless Functions
Database Vercel KV (Redis)
Hashing Web Crypto API (SHA-256)

🚀 Quick Start

Prerequisites

  • Node.js v18+
  • WalletConnect Project ID (Reown)
  • Vercel KV (Redis) for backend features

Install & Run

# Install dependencies
npm install

# Run frontend only
npm run dev

# Run full app (frontend + serverless functions)
npm run dev:full

Environment Variables

Create .env in the project root.

Anything prefixed VITE_ is compiled into the client bundle and is public. Never put a secret behind a VITE_ name — the CI build fails if a server-only key shows up in dist/.

# ── Client (public — inlined into the bundle) ──
VITE_WALLETCONNECT_PROJECT_ID=your_reown_project_id
VITE_DEFAULT_CHAIN=base
VITE_BASE_RPC_URL=                 # must be safe to expose (domain-restricted)
VITE_ADMIN_WALLETS=0x123...,0x456...   # only reveals the admin UI; the API enforces auth

# ── Server (secret) ──
JWT_SECRET=your_jwt_secret         # required: auth is disabled without it
OPENSEA_API_KEY=                   # used by /api/nfts, never reaches the browser
RPC_URL=                           # server-side reads (NFT ownership verification)
ADMIN_WALLETS=0x123...,0x456...    # the list the API actually trusts
GEMINI_API_KEY=                    # optional: AI share-post generation

# ── Storage (Upstash Redis / Vercel KV) ──
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
# Legacy Vercel KV names are also accepted:
KV_REST_API_URL=
KV_REST_API_TOKEN=

# ── Optional switches ──
# ALLOWED_ORIGINS=https://preview.example.com   # extra CORS origins
# STRICT_BATTLE_OWNERSHIP=true                  # reject unverifiable fighters
# ALLOW_INSECURE_ADMIN=true                     # local-only admin auth bypass

There is no dedicated or always-on server process. Shared analytics use the existing Vercel serverless /api functions and Upstash REST storage. The browser outbox works without OpenSea; OPENSEA_API_KEY additionally enables one-year account mint discovery.

Testing

npm test          # battle integrity, analytics, KV, proxy allowlist, CSP guards
npm run build     # production build

The CI workflow lives in ci/github-actions-ci.yml (copy it to .github/workflows/ci.yml to enable — see ci/README.md). It runs both commands on every push and pull request, and additionally fails if an inline script/handler or a server secret reaches the build output.

Maintenance scripts

npm run analytics:migrate         # dry run: reconcile battle counters in KV
npm run analytics:migrate:apply   # apply it
npm run kv:cleanup                # dry run: remove stale/legacy KV keys

🏗️ Architecture

src/
├── components/game/       # Battle UI components
│   ├── ChallengeBoard.js  # Challenge listing with collection-themed cards
│   ├── MatchPreviewModal.js # VS split-screen with stat comparison
│   └── NFTSelectorModal.js  # Fighter picker with stat previews
├── lib/battle/            # Battle engine core
│   ├── balanceConfig.js   # Centralized stat caps, passives, tuning
│   ├── collectionProfiles.js # Collection definitions & trait mappings
│   ├── metadataNormalizer.js # Universal stat normalization
│   └── snapshot.js        # Browser-safe SHA-256 stat snapshots
├── lib/game/
│   ├── engine.js          # Turn-based combat engine with passive resolution
│   ├── arenaRenderer.js   # Animated battle renderer (particles, effects)
│   └── matchmaking.js     # Challenge KV store (V2 schema)
├── pages/                 # UI pages (home, mint, analytics, battle)
└── utils/                 # Shared utilities (DOM, social, router)

🎯 Supported Collections

Collection Role Passive Archetype
Base Invaders Fighter Ghost Step Speed / Dodge
BaseHeads 404 Fighter Berserker Aggro DPS
BaseMoods Fighter Regen Burst Balanced / Healer
Void PFPs Fighter Ghost Step Glass Cannon
Quantum Quills Fighter Drain Sustain DPS
Base Fortunes Fighter Iron Wall Tank
Neon Runes Item Buff — V2 Modifier
Neon Shapes Item Buff — V2 Modifier
ByteBeats Item Buff — V2 Modifier
Mini Worlds Environment — V2 Modifier

📸 Adding Collections

Collections are auto-discovered from collections/*.js:

# Sync collection index
npm run collections:sync

Auto-sync runs on npm run dev, npm run build, and npm run dev:full.

🧩 API Routes

The Vercel Hobby plan allows 12 Serverless Functions per deployment, so related endpoints are grouped behind one function using an ?action= router (helpers live in api/_lib/, which Vercel does not count):

Function Actions
/api/admin overview, user, collection, cohort, daily, retention, reconcile, csp, export, cleanup_profile
/api/auth nonce, verify, logout
/api/battle challenge, fight, history, replay, record
/api/share share page (GET), generate-post (POST)
/api/track, /api/leaderboard, /api/user, /api/nfts, /api/csp-report single purpose

api/**/*.test.js is excluded via .vercelignore — without that, each test file would be deployed as its own function. A test enforces the budget so the "No more than 12 Serverless Functions" deploy error cannot come back.

🔒 Security Model

Concern How it is handled
Arena results Wins/points only count for a battle the server produced and stored: PvP is simulated server-side, AI battles are re-simulated from the seed and rejected on mismatch, and each battle counts once per wallet
Fighter stats Every client-supplied stat is clamped to the balance envelope before simulation
NFT ownership Verified on-chain (ownerOf/balanceOf) when posting a challenge, defending a fight and recording an AI battle, with an OpenSea inventory fallback
Mint analytics A mint is counted only when the configured collection emitted an ERC-721/1155 mint from the zero address to the wallet; transaction hashes are idempotent, and failed browser writes remain in a local outbox for retry
API keys Held server-side; the browser talks to /api/nfts instead of OpenSea directly
NFT media bandwidth Cards use OpenSea display_image_url; original_image_url is fetched only after the user opens an NFT, while animation media requires an explicit play action
Auth SIWE/SIWF with single-use nonces and short-lived JWTs; admin routes need an allowlisted wallet
XSS Third-party NFT metadata is escaped, URLs sanitised, and a CSP without script-src 'unsafe-inline' is enforced

Full findings and fixes: FULL_APP_AUDIT.md and ANALYTICS_AUDIT.md.

🔗 Links


⚙️ How the Battle Engine Works

NFT Metadata → Normalizer → Universal Stats → Combat Engine → Animated Renderer
  1. Normalization — Raw NFT traits (Faction, Mood, Body, etc.) are parsed through collection-specific traitsMap rules defined in collectionProfiles.js, producing a universal stat block (HP, ATK, DEF, SPD, CRIT, Dodge, Lifesteal, Regen).

  2. Stat Clamping — All stats are bounded by centralized caps and floors from balanceConfig.js to prevent broken builds. E.g., HP max 300, CRIT max 75%, ATK min 3.

  3. Passive Resolution — Each fighter gets a passive ability based on their collection (with trait-based overrides). Passives fire automatically during combat with cooldown tracking.

  4. Turn-Based Combat — Higher SPD goes first. Each turn: regen → passive triggers → attack roll (crit/dodge checks) → lifesteal → damage application. Max 50 rounds.

  5. AI Rigging — AI battles use a simulation loop (up to 25 seeds) to find a random timeline that matches the configured win rate (default 60%), making fights feel fair while keeping AI competitive.

  6. Snapshot Anti-Cheat — Fighter stats are hashed (SHA-256) when a challenge is posted. Before a fight starts, the hash is re-verified to ensure no stat drift for mutable collections.

🎭 Passive Abilities

Passive Trigger Effect Cooldown
Ghost Step On Defend +25% dodge for 1 turn 2 turns
Iron Wall On Defend -30% incoming damage for 1 turn 3 turns
Drain On Attack Leech 20% of damage dealt as HP 2 turns
Berserker Below 30% HP +40% ATK, -10% DEF Always active
Regen Burst Turn Start Heal 8% of max HP 3 turns

🗺️ Roadmap

✅ Phase 1 — Unified Arena MVP

  • Cross-collection stat normalization (12+ collections)
  • Turn-based auto-combat engine
  • 5 passive abilities with cooldown system
  • Cinematic battle renderer (particles, damage numbers, screen shake)
  • Challenge board with AI opponents
  • VS screen with stat comparison
  • NFT fighter selector with stat previews

✅ Phase 2 — V2 Advanced & PvP (Current)

  • Multi-NFT loadouts (Fighter + Item + Arena modifiers)
  • Wallet inventory parsing for Team Synergies
  • SIWE (Sign-In with Ethereum) JWT authentication
  • PvP match resolution via server-side APIs (Vercel KV)
  • Deterministic seeded PRNG for shareable battle replays
  • Snapshot anti-cheat system (Server verified)
  • V2 Analytics tracking (battle_loadout_built, battle_started_v2, battle_result_v2)
  • Auth cleanup on wallet disconnect (clearBattleAuth)
  • Live balance configuration fetching via CDN (with bundled fallback)
  • Spectator Mode — shareable battle replay URLs
  • Strict JWT event authentication & Rate-limiting (Hardened Pipeline)
  • Split Analytics Dashboard (Arena vs NFT views)
  • Dedicated Battle Points Leaderboard

🔜 Phase 3 — Multiplayer & Social

  • Real-time PvP matchmaking with WebSocket
  • Battle replays — shareable animated GIFs
  • On-chain battle result logging (Base smart contract)
  • Add more Base NFT collections to the battle roster
  • Collection search by name and contract address

🔮 Phase 4 — Platform Expansion

  • Multi-NFT team battles (3v3 with synergy bonuses)
  • More game modes (tournament brackets, seasons, wagered battles)
  • Marketplace tab — browse, buy, sell, make offers via OpenSea API
  • Token-gated features and rewards
  • Cross-chain collection support (Ethereum → Base bridge)
  • Mobile-optimized battle experience

💡 Future Vision

Base Mint evolves into a full NFT gaming platform on Base — where any NFT from any collection has utility through games, trading, and social features. The battle arena is the first module; marketplace and additional game modes follow.

🤝 Contributing

  1. Fork the repo
  2. Create a feature branch (git checkout -b feature/awesome)
  3. Commit changes (git commit -m 'Add awesome feature')
  4. Push and open a PR

📄 License

MIT

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages