Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
98d9a98
feat: render generated pet art in the web and mobile clients
heyradcode Aug 4, 2026
90db55f
fix(shared): stop the EVM mint stranding at the settle phase
heyradcode Aug 4, 2026
e86d216
feat(frontend): fill the pet card's upper panel with generated art
heyradcode Aug 4, 2026
7db757d
fix(frontend): let the card art actually reach the frame
heyradcode Aug 4, 2026
7299cda
fix(frontend): stop pet cards overflowing a squeezed gallery
heyradcode Aug 4, 2026
f588035
fix(frontend): let the pet card size to its content
heyradcode Aug 4, 2026
95424a9
feat(frontend): reveal the new pet's art in the create dialog
heyradcode Aug 5, 2026
b045482
feat(frontend): let the sidebar be pinned open
heyradcode Aug 5, 2026
00be39f
fix(frontend): let the create dialog close once the pet has settled
heyradcode Aug 5, 2026
9d67899
feat(frontend): replace the favicon with a CryptoPets mark
heyradcode Aug 5, 2026
c476b43
chore(frontend): rename the favicon to favicon.svg
heyradcode Aug 5, 2026
5e47b8c
feat(frontend): make the pet the combatant card
heyradcode Aug 5, 2026
7eead0b
feat(frontend): give breeding parents the battle page's card
heyradcode Aug 5, 2026
b051d75
refactor: move deployable services into services/
heyradcode Aug 5, 2026
d7bd94e
refactor: move backend back to the repo root
heyradcode Aug 5, 2026
b322a5c
fix(image-generator): repoint the digitPair parity test at protocol
heyradcode Aug 5, 2026
e52c476
fix: repair broken doc links and two disarmed test guards
heyradcode Aug 5, 2026
53a1377
ci: run the checks that existed but nothing invoked
heyradcode Aug 5, 2026
cd59bcc
ci: lint the five packages nothing linted, and lint the backend at all
heyradcode Aug 5, 2026
00f8c2a
ci: lint contracts/ethereum with solhint, security rules only
heyradcode Aug 5, 2026
b85861f
docs: correct the cross-chain adapter boundary
heyradcode Aug 5, 2026
7125fe1
refactor(shared): group the 36 loose hooks into feature directories
heyradcode Aug 5, 2026
5119da2
fix(shared): let a failed settleBreed be retried
heyradcode Aug 5, 2026
c76f1b0
refactor(shared): lift the EVM entropy settle flow out of the pet hooks
heyradcode Aug 5, 2026
e0884a0
style(frontend): strip UTF-8 BOMs from five component files
heyradcode Aug 5, 2026
d00c20d
refactor(frontend): move Layout.tsx into a kebab-case directory
heyradcode Aug 5, 2026
9683854
docs: write down the frontend panel-controller rule
heyradcode Aug 5, 2026
166128f
test(shared): cover the expiry formatter and the Solana PDA seeds
heyradcode Aug 5, 2026
855e4b1
test(shared): cover the Solana account client and pin the memcmp offsets
heyradcode Aug 5, 2026
bbb4b07
feat(dev): run backend, frontend, image-generator and indexer-go from…
heyradcode Aug 5, 2026
b1f0e8b
feat(frontend): drop the name-theme cards from the rename panel
heyradcode Aug 5, 2026
35f71ec
fix(ci): run static-checks on Node 22
heyradcode Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .github/workflows/image-generator.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
name: Image generator

# The service is deliberately not a pnpm workspace member (see
# image-generator/README.md), so the root `pnpm lint` / `pnpm test` aggregates and
# the Coverage workflow do not reach it. Without this workflow nothing runs its
# services/image-generator/README.md), so the root `pnpm lint` / `pnpm test` aggregates
# and the Coverage workflow do not reach it. Without this workflow nothing runs its
# suite at all.
# The path filter must also list the files `src/solanaLayout.test.ts` reads, not just
# this package. That suite pins the PetAccount byte layout against the Anchor IDL and
# `pet.rs`, so a Solana account change breaks it while touching nothing under
# `image-generator/`. Filtered on this package alone the suite simply does not run, which
# `services/image-generator/`. Filtered on this package alone the suite simply does not run, which
# is how `open_to_challenges` was removed from the program while the decoder kept its
# byte — every field after it misaligned, and a pet rendered as a different pet.
#
Expand All @@ -17,17 +17,17 @@ on:
pull_request:
branches: [main]
paths:
- 'image-generator/**'
- 'services/image-generator/**'
- '.github/workflows/image-generator.yml'
- 'contracts/solana/cryptopets/programs/cryptopets/src/state/**'
- 'indexer-go/internal/solana/idl/**'
- 'services/indexer-go/internal/solana/idl/**'
push:
branches: [main]
paths:
- 'image-generator/**'
- 'services/image-generator/**'
- '.github/workflows/image-generator.yml'
- 'contracts/solana/cryptopets/programs/cryptopets/src/state/**'
- 'indexer-go/internal/solana/idl/**'
- 'services/indexer-go/internal/solana/idl/**'

permissions:
contents: read
Expand All @@ -38,7 +38,7 @@ concurrency:

defaults:
run:
working-directory: image-generator
working-directory: services/image-generator

jobs:
check:
Expand All @@ -55,7 +55,7 @@ jobs:
node-version: 22
cache: pnpm
# Its own lockfile, not the monorepo root's.
cache-dependency-path: image-generator/pnpm-lock.yaml
cache-dependency-path: services/image-generator/pnpm-lock.yaml

# --ignore-workspace, or pnpm walks up to the monorepo root and installs
# that instead, leaving this package with no node_modules.
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/mobile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Mobile

# `mobile` is the one package with a test script that no workflow ran. Root `pnpm lint`
# covers its ESLint, and `pnpm build` skips it entirely (React Native builds through the
# `android`/`ios` scripts), so nothing executed its jest suite. That suite is a single
# boot smoke test, but it is the only mechanical check that App.tsx's imports still
# resolve and its provider tree renders, which is exactly what a rename in `shared` or a
# moved module breaks silently here while every other package stays green.

on:
pull_request:
branches: [main]
push:
branches: [main]

permissions:
contents: read

concurrency:
group: mobile-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

# Deliberately not path-filtered on `mobile/**`. The failure this catches comes
# from changing `shared` or a module mobile imports, not from touching mobile
# itself, so a filter would skip precisely the run that matters. See the
# image-generator workflow for what path filtering has already cost here.
- name: Mobile tests
run: pnpm --filter mobile test
12 changes: 6 additions & 6 deletions .github/workflows/parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name: Combat parity
# neither:
#
# protocol tests/combat/goldenVectors.test.ts (the canonical TS engine)
# indexer-go internal/combat/combat_golden_test.go (the independent Go port)
# services/indexer-go internal/combat/combat_golden_test.go (the independent Go port)
# contracts/ethereum test/XpFormula.test.ts (the XP fixture)
#
# §F's circuit breaker only has value while the TS and Go ports are independent and both
Expand Down Expand Up @@ -84,19 +84,19 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version-file: indexer-go/go.mod
cache-dependency-path: indexer-go/go.sum
go-version-file: services/indexer-go/go.mod
cache-dependency-path: services/indexer-go/go.sum

- name: Vet
working-directory: indexer-go
working-directory: services/indexer-go
run: go vet ./...

# Unit tests only. The Postgres-backed tests are gated on TEST_DATABASE_URL and
# truncate tables, so they are deliberately not given one here.
- name: Test
working-directory: indexer-go
working-directory: services/indexer-go
run: go test ./...

- name: Build
working-directory: indexer-go
working-directory: services/indexer-go
run: go build -o /dev/null ./cmd/indexer
67 changes: 67 additions & 0 deletions .github/workflows/static-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Static checks

# The two whole-repo checks that nothing ran.
#
# Lint: the root `pnpm lint` aggregate ran in no workflow. `protocol` and `verifier`
# are linted by their own workflows, and `image-generator` by its own, but `frontend`,
# `backend`, `shared`, `website`, and `mobile` were linted only by hand. That includes
# frontend's custom CSS-naming check (`lint:css`), which AGENTS.md lists under
# Enforcement.
#
# Build: the only thing that typechecks `frontend`, `backend`, and `website`. None of
# the three has a `typecheck` script — their type check *is* the build (`tsc -b &&
# vite build`, `tsc`, `next build`) — and vitest does not typecheck, since esbuild
# strips types without checking them. A type error on a path no test executes passed
# every existing workflow and surfaced at deploy.
#
# One job rather than two: both need the same install, which dominates the runtime.
# `if: always()` on the build step so a lint failure still reports the build result.
#
# Deliberately not path-filtered. These break from a change in `shared` or `protocol`
# landing on a consumer that was not touched, so filtering on the consumer's own paths
# would skip the run that matters.

on:
pull_request:
branches: [main]
push:
branches: [main]

permissions:
contents: read

concurrency:
group: static-checks-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
# 22, not the 20 most workflows use: `pnpm build` starts with the contracts
# compile, and Hardhat 3 needs >= 22.10. It calls `.flatMap` on the iterator
# from `Map.values()`, an Iterator Helpers method absent before Node 22, so
# `compile` dies with a TypeError rather than a version check. Same reason
# parity.yml pins 22.
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

# frontend (eslint + CSS naming), backend, protocol, verifier, shared,
# website, mobile. Not contracts/ethereum, which has no lint script.
- name: Lint
run: pnpm lint

# Root aggregate: contracts compile, then backend, frontend, and website.
# Runs unattended (no secrets), about a minute and a half.
- name: Build
if: always()
run: pnpm build
8 changes: 8 additions & 0 deletions .github/workflows/verifier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ jobs:
if: always()
run: pnpm --filter @cryptopets/verifier lint

# Separate from the tests: vitest strips types rather than checking them, so a
# type error in a path the suite does not execute reaches a third party's
# checkout instead of CI. `protocol` is typechecked in the parity workflow for
# the same reason; this package is consumed as raw TS too.
- name: Verifier typecheck
if: always()
run: pnpm --filter @cryptopets/verifier typecheck

# The committed corpus, run through the actual CLI rather than the library, so the
# thing a third party would run is the thing CI proves still works. No network
# access: the ruleset these battles were fought under is pinned in the checkout.
Expand Down
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ dist
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

# Hardhat files (scoped — a bare `cache/` also hid indexer-go/internal/cache/)
# Hardhat files (scoped — a bare `cache/` also hid services/indexer-go/internal/cache/)
contracts/**/cache/
contracts/**/artifacts/
typechain-types/
Expand All @@ -156,9 +156,6 @@ coverage.json
.openzeppelin/
deployments/

# EVM subgraph generated files (regenerated by pnpm configure)
backend/indexing/evm/subgraph/src/addresses.ts

# Hardhat local network
.openzeppelin/unknown-*.json

Expand Down
18 changes: 9 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@ Root coordination contract for AI and human contributors in this repo. Detailed

## Scope

- Applies to the whole monorepo: `frontend/`, `backend/`, `mobile/`, `website/`, `shared/`, `contracts/ethereum/`, `contracts/solana/`, `indexer-go/`, `proto/`, `image-generator/`.
- Applies to the whole monorepo: `frontend/`, `backend/`, `mobile/`, `website/`, `shared/`, `contracts/ethereum/`, `contracts/solana/`, `services/indexer-go/`, `proto/`, `services/image-generator/`.
- No nested `AGENTS.md` files exist yet. If one is added under a package, it may tighten rules for that subtree but must not relax the rules here.

Normative language: `MUST`/`MUST NOT` are mandatory. `SHOULD`/`SHOULD NOT` are expected by default; deviations should be explained in the PR. `MAY` is optional.

## Non-Negotiables

- `MUST NOT` change Solana's frozen combat port (`game/battle_sim.rs`, `game/xp.rs`). It has no caller left in the program, but its golden-vector tests are what still prove `contracts/test-vectors/{battle,xp}.json` describe what actually settled on that chain. A bug found there is fixed forward in the live ports below, under a new `rulesetVersion`, never by patching the frozen one. **The Solidity port is gone**: `CombatSim.sol` was deleted once it had no on-chain caller, which also removed `battle.json`'s Solidity generator and validator. `battle.json` itself is unchanged and still gates the live ports.
- `MUST` keep the two **live** combat ports in step with each other and with the golden vectors: `protocol/src/combat/` (the canonical engine, re-exported from `shared/src/utils/combat` for existing importers) and `indexer-go/internal/combat/` (the independent verifier). Changing one without the other re-breaks the circuit breaker in §F, whose whole value is that the two were written to disagree if either drifts. This covers XP and level progression too (`protocol/src/combat/xp.ts`, validated against `contracts/test-vectors/xp.json`), so an XP or decay change is a both-ports change. `indexer-go/internal/combat/xp.go` still covers the formula and the decay but not level-up.
- `MUST` keep the two **live** combat ports in step with each other and with the golden vectors: `protocol/src/combat/` (the canonical engine, re-exported from `shared/src/utils/combat` for existing importers) and `services/indexer-go/internal/combat/` (the independent verifier). Changing one without the other re-breaks the circuit breaker in §F, whose whole value is that the two were written to disagree if either drifts. This covers XP and level progression too (`protocol/src/combat/xp.ts`, validated against `contracts/test-vectors/xp.json`), so an XP or decay change is a both-ports change. `services/indexer-go/internal/combat/xp.go` still covers the formula and the decay but not level-up.
- `MUST NOT` edit `contracts/test-vectors/{battle,xp}.json` to make a failing test pass — this holds more strongly now, not less. The vectors are the only mechanical link left between the frozen ports and the live ones. A live port that fails them has drifted away from the rules real battles were settled under.
- `MUST NOT` assume the `ChainAdapter` interface (`shared/src/hooks/adapters/`) covers more than pet-action mutations and reads. It is a real, shared interface (`useEvmAdapter`/`useSolanaAdapter` both implement it) and every public pet-action hook consumes it chain-blind, but the low-level chain wiring in `frontend/src/chains/{ethereum,solana}/`, the async battle/breed VRF flows, and the combat simulator remain intentionally separate per chain. See CLAUDE.md's cross-chain interfaces section for the exact boundary.
- `MUST` match the license of the package being edited when adding new files: `contracts/ethereum`, `contracts/solana`, `indexer-go`, `proto`, `protocol`, and `verifier` are MIT; everything else, `image-generator` included, is PolyForm Noncommercial 1.0.0 (root `LICENSE`). See the table in `README.md`. `protocol` is MIT on purpose (third parties have to be able to replay signed battle receipts), so it `MUST NOT` import from a PolyForm package; a test in that package enforces it. `verifier` is MIT for the same reason and depends on nothing but `protocol`.
- `MUST NOT` assume the root `pnpm lint` / `pnpm test` cover `image-generator`, and `MUST NOT` verify it with `pnpm --filter image-generator <script>`. It is not a pnpm workspace member, so that command prints `No projects matched the filters` **and exits 0**: it reports success having run nothing. Run its scripts from `image-generator/` instead. It keeps its own lockfile, installs with `pnpm install --ignore-workspace`, and is checked by its own CI workflow.
- `MUST NOT` assume the `ChainAdapter` interface (`shared/src/hooks/adapters/`) covers more than pet-action mutations and reads. It is a real, shared interface (`useEvmAdapter`/`useSolanaAdapter` both implement it) and every public pet-action hook consumes it for the mutation, but the low-level chain wiring in `frontend/src/chains/{ethereum,solana}/`, the async breed/mint randomness flows, and the combat simulator remain intentionally separate per chain. `useCreatePet` and `useBreedPets` are only chain-blind on the action: both carry the EVM settle lifecycle inline behind `isEvm` guards. See CLAUDE.md's cross-chain interfaces section for the exact boundary.
- `MUST` match the license of the package being edited when adding new files: `contracts/ethereum`, `contracts/solana`, `services/indexer-go`, `proto`, `protocol`, and `verifier` are MIT; everything else, `services/image-generator` included, is PolyForm Noncommercial 1.0.0 (root `LICENSE`). See the table in `README.md`. `protocol` is MIT on purpose (third parties have to be able to replay signed battle receipts), so it `MUST NOT` import from a PolyForm package; a test in that package enforces it. `verifier` is MIT for the same reason and depends on nothing but `protocol`.
- `MUST NOT` assume the root `pnpm lint` / `pnpm test` cover `image-generator`, and `MUST NOT` verify it with `pnpm --filter image-generator <script>`. It is not a pnpm workspace member, so that command prints `No projects matched the filters` **and exits 0**: it reports success having run nothing. Run its scripts from `services/image-generator/` instead. It keeps its own lockfile, installs with `pnpm install --ignore-workspace`, and is checked by its own CI workflow.
- `MUST NOT` treat the v1 contract gaps documented in `contracts/plan-contract-upgrade.md` (no battle authorization, the `changeDna` cheat, client-supplied Solana starter-pet DNA) as bugs to silently patch. They are the known baseline the v2 rewrite is designed around.
- `MUST` run the smallest scoped lint/test/build command for the package you touched (see Command Baseline below), not a full monorepo run, unless the change is broad.
- `SHOULD NOT` trust `DEVELOPMENT.md`, `contracts/ethereum/README.md`, or the root `eth:deploy` / `eth:vrf:watch` scripts at face value. Several reference commands removed in a past refactor; see CLAUDE.md's Commands section for what is actually current.

## Command Baseline

- Install: `pnpm install` (root), or `pnpm install:all` (root + frontend + website + backend + mobile + contracts/ethereum)
- Dev: `pnpm dev` (backend + frontend), or `pnpm dev:fe` / `pnpm dev:be` / `pnpm dev:mobile` / `pnpm dev:web` individually, or `pnpm fe:eth:local` / `pnpm fe:sol:local` for a full local chain + backend + frontend stack
- Lint: `pnpm lint` (covers frontend, shared, website, mobile only, not backend or contracts/ethereum)
- Dev: `pnpm dev` (backend + frontend + image-generator + indexer-go; no `--kill-others-on-fail`, so an unconfigured optional service dies alone), or `pnpm dev:fe` / `pnpm dev:be` / `pnpm dev:art` / `pnpm dev:idx` / `pnpm dev:mobile` / `pnpm dev:web` individually, or `pnpm fe:eth:local` / `pnpm fe:sol:local` for a full local chain + backend + frontend stack
- Lint: `pnpm lint` (covers frontend, backend, shared, protocol, verifier, website, mobile, contracts/ethereum)
- Test: `pnpm test` (equals `contracts/ethereum` test only; per-package test commands are in CLAUDE.md)
- Build: `pnpm build`

Expand All @@ -34,10 +34,10 @@ Full per-package lint/test/build matrix and single-test syntax: see [CLAUDE.md](
## Where To Look

- Behavioral guidelines and full architecture: [CLAUDE.md](./CLAUDE.md)
- Data flow and component map: [CLAUDE.md](./CLAUDE.md#architecture) (`docs/architecture.md` is referenced in places but does not exist)
- Data flow and component map: [CLAUDE.md](./CLAUDE.md#architecture) (there is no `docs/architecture.md`)
- Test suite conventions: [docs/testing.md](./docs/testing.md)
- Backend API surface: [backend/API.md](./backend/API.md)
- Indexer internals: [indexer-go/README.md](./indexer-go/README.md)
- Indexer internals: [services/indexer-go/README.md](./services/indexer-go/README.md)
- Contract v1-to-v2 migration plan: [contracts/plan-contract-upgrade.md](./contracts/plan-contract-upgrade.md)
- Contribution workflow: [CONTRIBUTING.md](./CONTRIBUTING.md)
- Reporting vulnerabilities: [SECURITY.md](./SECURITY.md)
Expand Down
Loading
Loading