Skip to content

Project documentation

aichannode edited this page Jun 1, 2026 · 1 revision

CryptoPets (do-not-stop) — Full Project Documentation

A continuously evolving, multi-chain Web3 "crypto pets" game. Players mint a starter pet (an NFT), then level it up, rename it, breed it with another pet to create offspring, battle other pets, and transfer pets to other wallets — all backed by on-chain state on both Ethereum and Solana.

Live demo: https://cryptopets.vercel.app


1. What this project is

CryptoPets is a full-stack Web3 playground built as a pnpm monorepo. The same game logic is implemented twice — once as Solidity smart contracts for EVM chains and once as a Rust/Anchor program for Solana — and a single React frontend (plus a React Native mobile app) lets the user play on whichever chain their wallet is connected to. A shared TypeScript package abstracts the chain-specific differences behind a common set of hooks and types so the UI doesn't care which chain it's talking to.

The game is intentionally a "do not stop" project: a living codebase used to experiment with the latest Ethereum/Solana tooling and modern React patterns.

Core gameplay loop

Action Description EVM Solana
Create Mint a single random starter pet per wallet createRandom(name) create_starter_pet
Level up Pay a fee to increase a pet's level levelUp(tokenId) (0.001 ETH) level_up (fee in lamports)
Rename Change a pet's name (gated by level on EVM) changeName (level ≥ 2) rename_pet
Breed Combine two pets into a new child pet using verifiable randomness requestCreateFromDNA → VRF callback commit_breedsettle_breed
Battle Two pets fight; winner/loser stats update battle / attack commit_battlesettle_battle
Transfer Send a pet to another wallet ERC-721 transfer transfer_pet

Both implementations use verifiable randomness for fairness:

  • Ethereum: Chainlink VRF v2.5 (asynchronous request/callback).
  • Solana: Switchboard On-Demand VRF with a commit/settle two-step flow.

2. Technology stack

Frontend (web): React 19, TypeScript, Vite, React Router, TanStack Query, Wagmi + Viem (EVM), Solana web3.js + Anchor, Dynamic.xyz (wallet auth / embedded wallets).

Backend: Node.js, Express, TypeScript, JWT, Ethers.js (EVM signature verification), TweetNaCl (Solana signature verification). Runs with tsx hot reload in dev; deployed to Render.

Mobile: React Native + TypeScript (Android + iOS native projects included).

Marketing website: Next.js (App Router) + TypeScript, deployed to Vercel.

Shared core: TypeScript library (@shared/core) consumed by frontend, mobile, and website.

Smart contracts:

  • Ethereum: Solidity 0.8.24, Hardhat, Hardhat Ignition, OpenZeppelin ERC-721, Chainlink VRF v2.5.
  • Solana: Rust, Anchor framework, Switchboard On-Demand VRF, Docker-based local validator.

Tooling: pnpm 9.15.9 workspaces, Husky pre-commit hooks, ESLint, Prettier, concurrently for orchestrating multi-service dev, GitHub Actions for CI/CD, Vercel + Render for hosting.


3. Monorepo layout

do-not-stop/
├── package.json              # Root scripts that orchestrate all packages
├── pnpm-workspace.yaml       # Workspace package list
├── README.md                 # High-level overview
├── DEVELOPMENT.md            # Setup & command reference
├── PROJECT_DOCUMENTATION.md  # (this file)
│
├── frontend/                 # React 19 + Vite — the playable web app
├── website/                  # Next.js marketing/landing site
├── backend/                  # Express + TypeScript auth API
├── mobile/                   # React Native app (Android + iOS)
├── shared/                   # @shared/core — cross-platform hooks, utils, types
│
├── contracts/
│   ├── ethereum/             # Hardhat + Solidity (CryptoPets ERC-721)
│   └── solana/               # Anchor + Rust (cryptopets program) + Docker validator
│
├── .github/workflows/        # CI: frontend.yml, website.yml
├── .husky/                   # Git pre-commit hook
└── .devcontainer/            # Dev container definition

The pnpm workspace (pnpm-workspace.yaml) declares these packages: frontend, website, backend, mobile, shared, contracts/ethereum, contracts/solana/cryptopets.


4. Root orchestration (package.json)

The root package.json is the control panel for the whole repo. It contains no app code — only scripts that delegate into each package with pnpm --prefix and run multiple services together with concurrently.

Script Purpose
install:all Install dependencies in every package
dev:fe / dev:web / dev:be / dev:mobile Start a single app's dev server
eth:node Start the Hardhat local EVM node
eth:deploy Deploy EVM contracts and inject the address into the frontend
eth:vrf:watch Watch for and fulfill local Chainlink VRF mock requests (needed for breeding)
sol:docker Start the Solana validator in Docker
sol:inject-ngrok Wire up an ngrok tunnel for Solana local dev
fe:eth:local Full EVM stack: Hardhat node + deploy + VRF watcher + backend + frontend
mobile:eth:local Same as above but with the mobile app instead of web
fe:sol:local Full Solana stack: backend + frontend + Solana docker/ngrok
dev:be:fe / dev:be:mobile Backend + frontend / backend + mobile together
compile / test Compile / test EVM contracts
build Build contracts, backend, frontend, and website
lint / lint:fix Lint/fix across frontend, shared, website, mobile
prepare Install Husky git hooks

Note: DEVELOPMENT.md references some convenience aliases (pnpm dev, pnpm dev:full, etc.). The authoritative list of working scripts is the root package.json table above; the fe:eth:local / fe:sol:local targets are the canonical "start everything" commands today.

pnpm override pins valtio to 2.1.8 across the workspace.


5. Smart contracts — Ethereum (contracts/ethereum)

A Hardhat project implementing the game as a set of composed Solidity contracts.

Architecture

CryptoPets is the public-facing ERC-721 NFT contract (collection name CryptoPets, symbol PETS). It acts as a facade that composes four helper contracts, deploying them in its constructor and authorizing them to mutate shared state:

CryptoPets (ERC-721 + VRFConsumerBaseV2Plus)
├── Inventory   # Pet data store (the Pet struct, stats, level, DNA, name)
├── Battle      # Battle/attack logic, win/loss tracking, cooldowns
├── Breeding    # Breeding rules + DNA combination
└── Utils       # Random DNA generation, rarity calculation
Source file Responsibility
src/CryptoPets.sol ERC-721 ownership, public API, VRF integration, fee handling
src/Inventory.sol Canonical pet records and stat mutations (caller-authorized)
src/Battle.sol Combat resolution and battle stats
src/Breeding.sol Breed eligibility checks and VRF-driven child creation
src/Utils.sol DNA + rarity helpers
src/LocalCryptoPetsDeployer.sol Helper for local deployment wiring

Key constants & rules (from CryptoPets.sol)

  • LEVEL_UP_FEE = 0.001 ether — exact fee required by levelUp.
  • NAME_CHANGE_LEVEL = 2 — minimum level to rename a pet (changeName).
  • DNA_CHANGE_LEVEL = 20 — minimum level to change DNA (changeDna).
  • One starter per wallet: createRandom reverts if ownerPetCount > 0.
  • tokenURI points metadata at https://api.cryptopets.io/metadata/{id}.
  • withdraw() lets the owner collect accumulated fees.

Breeding via Chainlink VRF (asynchronous)

  1. requestCreateFromDNA(petId1, petId2, name) validates eligibility, marks both parents as having a pending breed, and requests random words from the VRF coordinator. Emits BreedRandomnessRequested.
  2. The coordinator later calls back into fulfillRandomWords, which combines parent DNA with the random word, mints the child to the owner, clears the pending flags, and emits BreedFulfilled.

VRF parameters: callback gas limit 500,000, 3 confirmations, 1 random word. Subscription ID, key hash, coordinator address, and native-vs-LINK payment are constructor-injected (immutable).

Local dev: the VRF coordinator is mocked. You must run pnpm vrf:watch (or the root pnpm eth:vrf:watch, included in fe:eth:local) so a watcher script fulfills BreedRandomnessRequested events against the mock.

Configuration & tooling

  • Solidity: 0.8.24, optimizer enabled.
  • Networks: hardhat, localhost (chain ID 31337, RPC :8545), sepolia.
  • Plugins: Hardhat Toolbox with Viem.
  • Deployment: Hardhat Ignition modules in ignition/modules/ (CryptoPets.ts, CryptoPetsLive.ts). Local deployed addresses land in ignition/deployments/chain-31337/deployed_addresses.json and are auto-injected into frontend/.env.local.
  • Scripts: deploy.ts, verify.ts, networks.ts, sync-abi.js (keeps the frontend ABI in sync), vrf-fulfill-watcher.ts.
  • Tests: test/CryptoPets.test.ts.
  • See ARCHITECTURE_COMPARISON.md for the rationale behind the composed-contract design.

6. Smart contracts — Solana (contracts/solana)

An Anchor program (cryptopets) re-implementing the same game for Solana. Program ID: 78AXV46ks5oFoJHkukvbsfZTJixdj2MeStzuC6thiUry.

Program entrypoints (programs/cryptopets/src/lib.rs)

Instruction Purpose
initialize(level_up_fee_lamports) Create the global state PDA (admin + config)
create_starter_pet(name, dna, rarity) Mint a player's starter pet
level_up() Pay the lamport fee to level up
rename_pet(name) Rename a pet
transfer_pet() Transfer ownership
pause() / unpause() Admin emergency switch
commit_battle(randomness_account) / settle_battle() Two-step battle with VRF
commit_breed(randomness_account, name) / settle_breed() Two-step breeding with VRF

Each instruction lives in its own file under programs/cryptopets/src/instructions/. Supporting modules: state.rs (accounts), errors.rs, rarity.rs, util.rs.

On-chain accounts (state.rs)

  • GlobalState (PDA seed global-state) — admin pubkey, level_up_fee_lamports, next_pet_id counter, paused flag.
  • PlayerProfile (seed player-profile) — per-wallet pet_count and starter_created flag (enforces one starter per player).
  • PetAccount (seed pet) — id, owner, dna, rarity, level, ready_time, win_count, loss_count, and a fixed 32-byte name buffer. Helpers: is_ready, trigger_cooldown (5-second BATTLE_COOLDOWN_SECONDS).
  • BreedRequest (seed breed-request) — pending breed: parents, child id, Switchboard randomness account, commit slot, and the chosen name. Closed on settle.
  • BattleRequest (seed battle-request) — pending battle: attacker/defender owners and pet ids, randomness account, commit slot. Closed on settle.

Battle randomness uses ATTACK_VICTORY_PROBABILITY = 70 (70% victory chance for the attacker model).

VRF flow (commit/settle)

Unlike the EVM async callback, Solana uses an explicit two-transaction pattern: commit_* records a pending request bound to a Switchboard randomness account and the current slot; once randomness resolves, settle_* reads it and applies the outcome (creates the child / decides the battle), then closes the request account.

Local environment

  • Validator runs in Docker (docker-compose.yml, image tchambard/solana-test-validator:latest): RPC :8899, WS :8900, metrics :9900, quiet logging.
  • scripts/inject-ngrok.ts exposes the local validator through an ngrok tunnel (used by fe:sol:local) so wallet providers can reach it.
  • Build/test with Anchor (anchor build / anchor test); integration tests in tests/cryptopets.ts.

7. Shared core (shared@shared/core)

A framework-agnostic TypeScript library that hides chain differences behind a single API. Consumed by the frontend, mobile app, and (partly) the website. This is the heart of the "write once, play on any chain" design.

Public surface (src/index.ts)

  • Auth store: setSolanaAuthSigner, getSolanaAuthSigner, subscribeSolanaAuth, getSolanaAuthAddress — manage the active Solana signing identity for backend auth.
  • API client: createAuthApiClient, setStorageAdapter, getStorageAdapter — talk to the backend auth API with a pluggable token-storage adapter (so web uses localStorage, mobile uses async storage, etc.).
  • Contexts (React providers):
    • AuthProvider / useAuth — login state and JWT lifecycle.
    • ApiClientProvider / useApiClient.
    • SolanaAnchorProvider / useSolanaAnchor — Anchor program + signing wallet.
    • PetsConfigProvider / usePetsConfig — injects EVM contract config.
  • Types: Pet, PetChain ('evm' | 'solana'), PetAction (create | levelUp | rename | battle | breed | transfer).
  • queryClient — a shared TanStack Query client.

Unified Pet model (src/types/pet.ts)

interface Pet {
  id: string;
  chain: 'evm' | 'solana';
  name: string;
  dna: bigint;
  level: number;
  rarity: number;
  winCount: number;
  lossCount: number;
  readyAt: number;
}

EVM and Solana raw structures are normalized into this shape by utils/pets/mapEvmPet.ts and utils/pets/mapSolanaPet.ts.

Cross-chain hooks (src/hooks)

Action hooks expose one API regardless of chain; internally they dispatch to the EVM or Solana implementation based on the active chain:

  • useActiveChain — which chain is currently selected.
  • usePetList — the connected wallet's pets (normalized).
  • useCreatePet, useLevelUpPet, useRenamePet, useBattlePets, useBreedPets, useTransferPet — the gameplay mutations.
  • useNonce — fetch a backend nonce for signature auth.
  • Chain-specific hook sets under hooks/chains/ethereum (Wagmi-based: usePetsContract, useWatchPetsContract, useUserProfile, useVerifySignature) and hooks/chains/solana (useProgram, usePets, usePetActions, useGlobalState, usePlayerProfile, useSolana).

Utilities (src/utils)

  • ethereum/ — address validation, error parsing, pet card formatting, ready-time math.
  • solana/ — PDA derivation (pdas.ts), program ID, account client, Switchboard VRF transaction builders (battleWithSwitchboardVrf.ts, breedWithSwitchboardVrf.ts, switchboardVrfTx.ts), signature-auth codec, transaction error parsing, number/address helpers.
  • pets/ — chain-agnostic helpers: cosmetics (DNA → appearance), featureSupport (which actions a chain supports), readyPets, error mapping, and the EVM/Solana → Pet mappers.

8. Backend (backend)

A small Express + TypeScript API whose sole job is Web3 authentication: prove a user controls a wallet address, then issue a JWT for authenticated requests. User records are kept in-memory (a database would replace this in production).

Structure

  • src/server.ts — entrypoint, binds to PORT (default 3001).
  • src/app.ts — Express app: CORS (optionally restricted via CORS_ORIGIN), JSON body parsing, route mounting.
  • src/routes/auth.ts — nonce + signature verification + JWT issuance.
  • src/routes/protected.ts — example JWT-gated endpoints.
  • src/routes/health.ts — health check.
  • api/index.ts — serverless entry (Vercel-style) wrapper.

Endpoints

Method & path Description Auth
GET / API info + endpoint map
GET /api/auth/nonce Get a one-time nonce to sign
POST /api/auth/verify Verify {address, signature, nonce}; returns JWT + user
GET /api/protected/profile Current user profile Bearer JWT
GET /api/protected/users All known users Bearer JWT
GET /api/health {status, timestamp, users}

Auth flow

  1. Frontend requests a nonce (GET /api/auth/nonce).
  2. User signs a message containing that nonce in their wallet.
  3. Frontend posts {address, signature, nonce} to /api/auth/verify.
  4. Backend verifies the signature against the address — ethers.js for EVM signatures, TweetNaCl for Solana signatures.
  5. On success it issues a JWT; the frontend stores it and sends it as Authorization: Bearer <token> on protected calls.

Configuration & deployment

  • Env vars: JWT_SECRET (required), PORT (default 3001), CORS_ORIGIN (optional comma-separated allowlist). See backend/env.example.
  • Dev: pnpm dev:be (tsx hot reload). Build: pnpm --filter backend build.
  • Deployed to Render via the repo-root render.yaml blueprint (service do-not-stop-api, health check /api/health). backend/README.md has a full Render setup + troubleshooting guide. backend/vercel.json also supports a serverless deployment.

9. Frontend web app (frontend)

The playable game: React 19 + Vite + TypeScript.

Tech & integration

  • EVM: Wagmi + Viem; ABI in src/chains/ethereum/ethereumAbi.json, config in src/chains/ethereum/wagmi.ts.
  • Solana: src/chains/solana/ — Anchor wallet adapter, auth signer, provider, and useDynamicSolanaWallet.
  • Wallet auth / embedded wallets: Dynamic.xyz, wired in src/contexts/dynamic/.
  • Server state: TanStack Query (shared client from @shared/core).
  • Routing: React Router (src/router/).

Structure

frontend/src/
├── main.tsx, App.tsx          # Bootstrap + root component
├── config.ts                  # API_URL + CONTRACT_ADDRESS env wiring + token storage
├── chains/{ethereum,solana}/  # Per-chain wallet + provider plumbing
├── contexts/dynamic/          # Dynamic.xyz auth provider
├── components/
│   ├── common/                # icon, neon-button, neon-card, neon-modal, transaction-status
│   ├── layout/                # App shell
│   ├── pet/                   # create-pet-modal, pet-gallery, pet-creator, pet-container,
│   │                          #   pet-collection-layout, send-pet-modal, pet-interactions,
│   │                          #   interactions/{battle,breed,level-up,rename,state-card}
│   └── wallet/                # account-dropdown, native-balance, token-balance,
│                              #   network-switcher (ethereum/solana), solana-wallet-trigger
├── hooks/                     # useIsLoggedIn, usePetActionErrorDisplay, useWriteContractErrorState
├── pages/main/                # Main game page
├── router/                    # app-routes + per-interaction routes (battle/breed/level-up/rename)
├── constants/                 # chains (ethereum/solana), tokens, tones, interactionRoutes
├── styles/                    # variables.css, messages.css
└── utils/errorParser.ts

Configuration (src/config.ts)

  • VITE_API_URL — backend base URL (defaults to http://localhost:3001).
  • VITE_CONTRACT_ADDRESS — EVM contract address; required, and auto-injected by the EVM deploy step. The app throws on startup if it's missing.
  • Registers a localStorage-backed token storage adapter and a token-success callback with @shared/core.

Run & deploy

  • Dev: pnpm dev:fe, or full stacks via pnpm fe:eth:local / pnpm fe:sol:local.
  • Build: pnpm build → optimized output. Deployed on Vercel (vercel.json), CI in .github/workflows/frontend.yml.

10. Mobile app (mobile)

React Native + TypeScript app (@react-native-community/cli bootstrap) that reuses @shared/core for game logic and auth.

  • Entry: App.tsx; tests in __tests__/.
  • Native projects: android/ (Kotlin, package com.cryptozombies) and ios/.
  • Display name is CryptoPets, but the Android namespace/Kotlin package stays com.cryptozombies deliberately — see mobile/README.md for the React Native BuildConfig bug (facebook/react-native#52754) that blocks renaming it.
  • Run: pnpm dev:mobile (Metro) then build to Android/iOS, or full local stack via pnpm mobile:eth:local.

11. Marketing website (website)

A separate Next.js (App Router) landing site — not the playable app.

  • src/app/layout.tsx, page.tsx.
  • src/components/landing/ — Hero, Features, HowItWorks, Pets, Stats, Roadmap, Testimonials, Backers, Community, Faq, Cta, Footer.
  • src/components/common/ — NeonButton, NeonCard (mirrors the app's neon theme).
  • src/content/landing.ts — landing page copy/content.
  • src/lib/openApp.ts — deep-links into the playable app using NEXT_PUBLIC_APP_URL.
  • Styles in src/styles/ (globals, landing-page, layout, variables).

Deployment: its own Vercel project with Root Directory set to website (the repo root has no next dependency). CI in .github/workflows/website.yml runs remote Vercel builds from the repo root with rootDirectory: website. Set NEXT_PUBLIC_APP_URL in both the Vercel project and as a GitHub Actions variable. Full guide in website/README.md.


12. End-to-end data flow

                ┌─────────────────────────────────────────────┐
                │  User wallet (MetaMask / Solana / Dynamic)   │
                └───────────────┬─────────────────────────────┘
                                │ sign nonce
        ┌───────────────────────┴───────────────────────┐
        │                                                │
        ▼                                                ▼
┌───────────────┐   nonce/verify (JWT)        ┌──────────────────────┐
│  Frontend /   │ ──────────────────────────▶ │  Backend (Express)   │
│  Mobile app   │ ◀────────────────────────── │  ethers / tweetnacl  │
└──────┬────────┘        JWT                   └──────────────────────┘
       │
       │ via @shared/core hooks (chain-agnostic)
       │
   ┌───┴───────────────┐
   ▼                   ▼
┌──────────────┐   ┌────────────────────────────┐
│ EVM chain    │   │ Solana chain               │
│ CryptoPets   │   │ cryptopets program         │
│ ERC-721      │   │ (PDAs: pet/profile/global) │
│ + Chainlink  │   │ + Switchboard VRF          │
│   VRF        │   │   (commit/settle)          │
└──────────────┘   └────────────────────────────┘

The frontend calls @shared/core hooks (useCreatePet, useBattlePets, …). Each hook checks the active chain and routes to the EVM (Wagmi/Viem) or Solana (Anchor) implementation, then normalizes results into the unified Pet type for the UI. Authentication is chain-agnostic: the backend verifies either an EVM or a Solana signature and returns the same JWT.


13. Local development setup

Prerequisites

  • Node.js 20+
  • pnpm 9.15.9 (the repo pins this via packageManager)
  • For EVM: nothing extra (Hardhat runs in-process)
  • For Solana: Docker + Docker Compose (validator), and optionally Anchor/Solana CLI + Rust to build the program
  • For mobile: React Native toolchain (Android Studio / Xcode)

First run

pnpm install:all        # install every workspace package

# Then pick a stack:
pnpm fe:eth:local       # Hardhat node + deploy + VRF watcher + backend + web frontend
pnpm fe:sol:local       # backend + web frontend + Solana validator (Docker + ngrok)
pnpm mobile:eth:local   # same as fe:eth:local but with the mobile app

Common ports

Service URL
Web frontend http://localhost:5173
Backend API http://localhost:3001
Ethereum RPC (Hardhat) http://localhost:8545 (chain ID 31337)
Solana RPC http://localhost:8899 (WS 8900, metrics 9900)

Environment files

Each package ships an env.example; copy it to the appropriate dotenv file:

  • backend/.envJWT_SECRET, PORT, CORS_ORIGIN.
  • contracts/ethereum/.envSEPOLIA_URL, PRIVATE_KEY (testnet only).
  • frontend/.env.localVITE_API_URL, VITE_CONTRACT_ADDRESS (the latter is auto-injected by the EVM deploy step).
  • contracts/solana/.envANCHOR_PROVIDER_URL, ANCHOR_WALLET.
  • website/.env.localNEXT_PUBLIC_APP_URL.

Breeding on local EVM needs the VRF watcher running. fe:eth:local / mobile:eth:local start it automatically; if you run pieces manually, also run pnpm eth:vrf:watch.


14. Build, test, lint, CI

  • Build all: pnpm build (contracts → backend → frontend → website).
  • EVM tests: pnpm test (Hardhat / CryptoPets.test.ts).
  • Solana tests: anchor test in contracts/solana/cryptopets.
  • Lint: pnpm lint / pnpm lint:fix across frontend, shared, website, mobile.
  • Pre-commit: Husky hook in .husky/pre-commit.
  • CI: GitHub Actions — frontend.yml and website.yml build and deploy to Vercel; the backend deploys to Render via render.yaml.

15. Where to find things (quick reference)

I want to… Look here
Understand the EVM game rules contracts/ethereum/src/CryptoPets.sol (+ Inventory/Battle/Breeding/Utils)
Understand the Solana game rules contracts/solana/cryptopets/programs/cryptopets/src/{lib,state}.rs + instructions/
Add/modify a gameplay action in the UI shared/src/hooks/use*.ts then the per-chain impl under hooks/chains/
Change how a pet is displayed shared/src/utils/pets/ + frontend/src/components/pet/
Touch auth / JWT backend/src/routes/auth.ts + shared/src/contexts/AuthContext.tsx
Add a wallet/chain frontend/src/chains/ + frontend/src/constants/chains/
Edit the landing page website/src/components/landing/ + website/src/content/landing.ts
Adjust dev orchestration root package.json scripts

Generated documentation for the do-not-stop / CryptoPets monorepo. Built with ❤️ by the radcrew team.