Grudge Studio

BACKEND
MASTER PLAN

Architecture · Best Practices · Deployment Roadmap · Web3 Integration

v1.0 — March 2026
⚙️ Game Studio Backend Best Practices

Production patterns Grudge Studio should follow for a scalable, secure, game-grade backend.

ℹ️Principle: Treat your backend like a game server — deterministic, atomic, auditable. Every state change (currency, inventory, ownership) must be logged and reversible.
API Design

Versioned REST + tRPC

All game APIs should be versioned (/api/v1/). Use tRPC for type-safe internal calls. Never expose internal IDs — use UUIDs. Rate-limit every endpoint per user, not just per IP.

Data Integrity

Atomic Transactions Always

Any operation that touches GBux, inventory, or NFT state must be wrapped in a DB transaction. Never do partial updates. Use optimistic locking to prevent race conditions on inventory.

Auth Pattern

JWT + Server Sessions

Short-lived JWTs (15min) with refresh tokens stored in httpOnly cookies. Never store tokens in localStorage. Verify wallet signatures server-side with nonce-based challenges.

Serverless Caution

Know Your Vercel Limits

Vercel functions have 10s/60s execution limits. Long-running jobs (NFT minting, batch rewards) must use queues (Inngest, BullMQ) or Vercel Cron. Never block the main thread for blockchain calls.

Observability

Log Everything Game-Critical

Log every GBux transaction, every NFT mint, every character state change with timestamps, user IDs, and before/after values. Use structured JSON logs. Tools: Axiom, Datadog, or Logtail.

Security

Never Trust the Client

All game logic validation must happen server-side. Players cannot grant themselves GBux, items, or characters. Input sanitize everything. Use Zod schemas at every API boundary.


🗄️ Recommended Tech Stack
LayerRecommendedWhyPriority
FrameworkNext.js 14+ (App Router)Server Components, API Routes, Edge Functions — unified stackCore
DatabaseSupabase (Postgres)Row-level security, realtime, auth built in, works on VercelCore
ORMPrisma or DrizzleType-safe queries, migration management, game schema evolutionCore
AuthSupabase Auth + NextAuthEmail, wallet, OAuth — covers all player auth typesCore
Queue / JobsInngestServerless-native, retry logic, great for NFT mint queuesImportant
CacheUpstash RedisRate limiting, session cache, leaderboards — serverless RedisImportant
NFT / Web3Crossmint APICustodial wallets, minting, no-code NFT for playersImportant
File StorageSupabase Storage / Cloudflare R2Character assets, island maps, NFT metadata imagesImportant
MonitoringAxiom + Vercel AnalyticsStructured logging, error tracking, game analyticsNice to have
TestingVitest + PlaywrightUnit tests for game logic, E2E for critical flowsNice to have
🏗️ System Architecture

How all Grudge Studio systems connect — from player to blockchain.

CLIENT LAYER
Web App (Next.js)
Mobile (React Native)
Game Client
↓
API LAYER — Vercel Edge / Serverless Functions
/api/auth
/api/characters
/api/gbux
/api/islands
/api/nfts
↓
DATA LAYER
Supabase Postgres
Upstash Redis
Supabase Storage
↓
EXTERNAL SERVICES
Crossmint Wallets
Crossmint Mint API
Stripe / Payments
Inngest Jobs
↓
BLOCKCHAIN LAYER
Solana (cNFTs)
Polygon (EVM alt)

DATABASE SCHEMA — KEY TABLES
-- Core player account CREATE TABLE players ( id UUID PRIMARY KEY, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE, wallet_id TEXT, -- Crossmint custodial gbux_balance BIGINT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW() ); -- Characters owned by players CREATE TABLE characters ( id UUID PRIMARY KEY, player_id UUID REFERENCES players(id), name TEXT NOT NULL, class TEXT NOT NULL, nft_mint_id TEXT, -- on-chain address metadata JSONB, is_minted BOOLEAN DEFAULT FALSE ); -- GBux transaction ledger CREATE TABLE gbux_transactions ( id UUID PRIMARY KEY, player_id UUID REFERENCES players(id), amount BIGINT NOT NULL, -- positive or negative reason TEXT NOT NULL, ref_id UUID, -- item, quest, purchase created_at TIMESTAMPTZ DEFAULT NOW() ); -- Islands CREATE TABLE islands ( id UUID PRIMARY KEY, owner_id UUID REFERENCES players(id), name TEXT, nft_mint_id TEXT, tier INT DEFAULT 1, data JSONB );
API ROUTE PATTERN
// app/api/gbux/transfer/route.ts import { auth } from '@/lib/auth' import { db } from '@/lib/db' import { z } from 'zod' const Schema = z.object({ toPlayerId: z.string().uuid(), amount: z.number().int().positive().max(10000), reason: z.string().min(1).max(100) }) export async function POST(req) { const session = await auth() if (!session) return unauthorized() const body = Schema.parse(await req.json()) // Atomic transaction — never partial await db.transaction(async (tx) => { await tx.debitPlayer(session.id, body.amount) await tx.creditPlayer(body.toPlayerId, body.amount) await tx.logTransaction({ ...body }) }) }
✅Always: validate input with Zod → check auth → atomic DB operation → return minimal response. This is the pattern for every game state mutation.
👤 Accounts & Authentication

Player account system supporting email, social, and wallet-based auth.

Step 1

Email / Password Auth

Set up Supabase Auth with email verification. Players get a UUID player ID on signup. Create a players row automatically via database trigger.

Step 2

OAuth (Google/Discord)

Enable OAuth providers in Supabase. Discord is critical for game communities. Link social accounts to the same player ID using a player_accounts join table.

Step 3

Wallet Connect

Allow players to link an external wallet. Use nonce-based signature challenge. Store wallet address in players.external_wallet. Crossmint custodial wallet is auto-created separately.

// lib/auth/wallet-connect.ts — Nonce signature verification export async function verifyWalletSignature(address, signature, nonce) { // 1. Fetch nonce from Redis (expires in 5 min) const storedNonce = await redis.get(`nonce:${address}`) if (!storedNonce || storedNonce !== nonce) throw new Error('Invalid nonce') // 2. Verify signature matches wallet address const recovered = ethers.utils.verifyMessage(nonce, signature) if (recovered.toLowerCase() !== address.toLowerCase()) throw new Error('Bad sig') // 3. Delete nonce (one-time use) await redis.del(`nonce:${address}`) return true }
⚠️Critical: Auto-provision a Crossmint custodial wallet for EVERY new player at account creation. Don't wait for them to request it. Call POST /api/2022-06-09/wallets immediately after player row is inserted.
🧙 Characters & Inventory

Character creation, progression, and optional NFT minting flow.

Character Lifecycle

Create → Play → Mint

Characters exist as DB records first. Players can play without minting. When they choose to mint, the character becomes a cNFT on Solana via Crossmint. The nft_mint_id field gets populated. Future trades happen on-chain.

Metadata Standard

Dynamic NFT Metadata

Store character stats in Supabase. NFT metadata URI points to your API endpoint which returns live stats. This lets characters evolve without re-minting. Use /api/metadata/character/[id] as the token URI.

// Character NFT metadata endpoint — /api/metadata/character/[id]/route.ts export async function GET(req, { params }) { const character = await db.characters.findUnique({ where: { id: params.id } }) // Returns Metaplex-compatible metadata return Response.json({ name: character.name, description: `Grudge Island character — ${character.class}`, image: `${CDN_URL}/characters/${character.id}/avatar.png`, external_url: `https://grudgestudio.com/character/${character.id}`, attributes: [ { trait_type: 'Class', value: character.class }, { trait_type: 'Level', value: character.metadata.level }, { trait_type: 'Island', value: character.metadata.island_name }, ], properties: { category: 'image', creators: [{ address: STUDIO_WALLET }] } }) }
💰 GBux & Server-Side Wallets

Virtual currency system with ledger accounting and on-chain settlement options.

⚠️GBux is real money equivalent. Treat it like a bank ledger. Every change must be logged. Never update gbux_balance directly — always insert a gbux_transactions row and update balance atomically.
Earn

GBux Sources

Quest completion, island activities, daily rewards, referrals, tournament prizes. Each source has a defined max per day to prevent farming exploits.

Spend

GBux Sinks

Character upgrades, island expansion, cosmetic items, NFT minting fees, marketplace transactions. Sinks prevent inflation — design them deliberately.

Convert

On/Off Ramp

Players can purchase GBux via Stripe. Premium GBux (real money) should be tracked separately from earned GBux for regulatory compliance and refund handling.

// lib/gbux.ts — The ONLY way to modify GBux export async function awardGbux(playerId, amount, reason, refId?) { if (amount <= 0) throw new Error('Amount must be positive') return db.transaction(async (tx) => { // Check daily earn limit const todayEarned = await tx.getDailyEarned(playerId) if (todayEarned + amount > DAILY_EARN_LIMIT) throw new Error('Daily limit reached') // Insert transaction record first const txn = await tx.gbuxTransactions.create({ data: { playerId, amount, reason, refId } }) // Update balance atomically await tx.players.update({ where: { id: playerId }, data: { gbuxBalance: { increment: amount } } }) return txn }) } export async function spendGbux(playerId, amount, reason, refId?) { return db.transaction(async (tx) => { const player = await tx.players.findUnique({ where: { id: playerId } }) if (player.gbuxBalance < amount) throw new Error('Insufficient GBux') await tx.gbuxTransactions.create({ data: { playerId, amount: -amount, reason, refId } }) await tx.players.update({ where: { id: playerId }, data: { gbuxBalance: { decrement: amount } } }) }) }
CROSSMINT SERVER-SIDE WALLETS — AUTO-PROVISION FLOW
// lib/wallets.ts — Create custodial wallet for new player export async function provisionPlayerWallet(playerId, email) { const res = await fetch('https://staging.crossmint.com/api/2022-06-09/wallets', { method: 'POST', headers: { 'X-API-KEY': process.env.CROSSMINT_SERVER_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'solana-custodial-wallet', linkedUser: `email:${email}` }) }) const { walletAddress } = await res.json() // Store wallet address on player record await db.players.update({ where: { id: playerId }, data: { walletId: walletAddress } }) return walletAddress }
⛓️ Crossmint & cNFT Minting

Compressed NFTs on Solana for characters and islands — cheap, fast, scalable.

Why Compressed NFTs

cNFT vs Standard NFT

Compressed NFTs use Solana's state compression. Minting cost drops from ~$2 per NFT to ~$0.0005. Perfect for game items, characters, and islands at scale. Crossmint abstracts all of this.

Minting Strategy

Lazy Minting Pattern

Don't mint on character creation. Mint only when the player requests it or reaches a milestone. Use an Inngest background job so minting failures don't block the player.

// lib/nft/mint-character.ts — Via Crossmint Headless Minting export async function mintCharacterNFT(characterId) { const character = await db.characters.findUnique({ where: { id: characterId }, include: { player: true } }) if (character.isMinted) throw new Error('Already minted') // Mint compressed NFT to player's custodial wallet const res = await fetch(`https://www.crossmint.com/api/2022-06-09/collections/${COLLECTION_ID}/nfts`, { method: 'POST', headers: { 'X-API-KEY': process.env.CROSSMINT_SERVER_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ recipient: `solana:${character.player.walletId}`, metadata: { name: character.name, image: `${CDN_URL}/characters/${characterId}/avatar.png`, description: `Grudge Island — ${character.class}`, attributes: buildAttributes(character) }, compressed: true // cNFT flag }) }) const { id: mintJobId } = await res.json() // Poll for completion via webhook or Inngest await db.characters.update({ where: { id: characterId }, data: { mintJobId, isMinted: false } // isMinted set true on webhook }) }
CROSSMINT WEBHOOK — CONFIRM MINT SUCCESS
// app/api/webhooks/crossmint/route.ts export async function POST(req) { const event = await req.json() if (event.type === 'nft.minted') { await db.characters.update({ where: { mintJobId: event.data.mintId }, data: { isMinted: true, nftMintId: event.data.onChain.mintAddress } }) // Notify player via WebSocket or push notification } }
🗺️ Phased Roadmap

Ordered steps to get all systems operational. Do these in sequence.

01

Foundation — Auth & Accounts

Get the player account system fully operational before everything else. This is the bedrock all other systems depend on.

Supabase project setup Email auth + OAuth (Discord/Google) Player DB schema deployed Crossmint wallet auto-provision on signup Session management (JWT + httpOnly cookies) Player profile API
DO NOW
02

GBux Ledger System

Implement the virtual currency engine with full transaction logging. No earning or spending until this is bulletproof.

gbux_transactions table awardGbux / spendGbux atomic functions Daily earn limits + anti-cheat rules Balance read API Transaction history API
DO NOW
03

Characters — Off-Chain First

Characters as full DB entities with all game data. No blockchain yet — get the gameplay loop working first.

Character creation API Character progression / stats Character inventory system Character ↔ Island binding Dynamic metadata endpoint
NEXT
04

Islands — Off-Chain First

Island creation, ownership, and gameplay data. Islands are the core game world — get them stable before NFT-ing them.

Island creation + ownership Island tier system GBux earn mechanics on islands Island → Character activity
NEXT
05

Crossmint Collection Setup

Create the on-chain collection on Crossmint. Set royalties, set studio wallet as creator. Test mint pipeline on Devnet.

Create Character Collection (Crossmint dashboard) Create Island Collection Set royalty % and creator wallet Devnet test mint → verify metadata Webhook endpoint for mint events
PHASE 2
06

NFT Minting — Characters & Islands

Enable the mint flow for players. Characters become cNFTs. Islands become cNFTs. All via Crossmint headless API with Inngest job queue.

Inngest mint job setup Character mint API endpoint Island mint API endpoint Mint status polling + webhook confirm GBux cost to mint Player mint history UI
PHASE 2
07

Marketplace & Trading

Enable players to trade characters and islands. GBux marketplace for items. NFT marketplace integration for on-chain trades.

GBux item marketplace Character/Island listing NFT transfer via Crossmint Royalty enforcement on resales
PHASE 3
✅ Pre-Launch Checklist

Everything that must be true before opening to players.

SECURITY
  • 🔒
    Env vars in Vercel — no secrets in code. CROSSMINT_SERVER_KEY, DB_URL, etc.
  • 🔒
    Supabase RLS enabled — row-level security on ALL game tables
  • 🔒
    API rate limiting — Upstash Redis rate limiter on every endpoint
  • 🔒
    Input validation — Zod schema on every POST/PATCH body
  • 🔒
    Webhook verification — validate Crossmint webhook signatures
DATA INTEGRITY
  • 🗄️
    DB migrations tracked — Prisma or Drizzle migration history in git
  • 🗄️
    Backups configured — Supabase daily backups enabled
  • 🗄️
    GBux ledger tested — concurrent spend/earn stress tested
  • 🗄️
    Atomic transactions — no GBux update without a transaction log entry
WEB3 READINESS
  • ⛓️
    Crossmint staging tested — full mint → webhook → DB update flow verified
  • ⛓️
    Wallet auto-provision — every new account gets a wallet immediately
  • ⛓️
    Collection IDs stored — Character and Island collection addresses in env vars
  • ⛓️
    Metadata endpoint live — /api/metadata/character/[id] returns valid JSON
OPERATIONS
  • 📊
    Error monitoring — Vercel logs + Axiom or Sentry configured
  • 📊
    Inngest dashboard — job queue visible, failed jobs alerting
  • 📊
    Vercel preview deploys — all PRs get a preview URL
  • 📊
    Staging environment — separate Crossmint staging + Supabase project
0