Skip to content

Wire the Beasts NFT to the permissionless species registry - #20

Closed
loothero wants to merge 4 commits into
feat/beast-registryfrom
feat/nft-registry-integration
Closed

Wire the Beasts NFT to the permissionless species registry#20
loothero wants to merge 4 commits into
feat/beast-registryfrom
feat/nft-registry-integration

Conversation

@loothero

@loothero loothero commented Aug 1, 2026

Copy link
Copy Markdown
Member

Stacked on #19 (feat/beast-registry), not main — per the decision to keep the registry off main until the whole stack has been exercised on Sepolia.

What this does

Community species (76+) resolve minter, traits, name, art, and stats through the registry. Genesis species (1–75) keep every existing baked-in path unchanged.

Area Change
mint Per-species minter authorization. Genesis → dungeon_address; community → registry.get_minter(id). A zero minter reads as paused, not "matches a zero caller".
Provenance mint_provenance + emit_species_metadata_update, both registry-only. Uses erc721.mint, not safe_mintsafe_mint calls back into the recipient, handing a contract artist a reentry point into the registry while its definition is written but next_id hasn't settled.
token_uri Routes name, art, and stats by species range. beast_svg / metadata_generator now take a pre-fetched ByteArray instead of a dispatcher, so one code path serves both species ranges.
Art validation New art_validation.cairo. A custom provider is arbitrary artist code whose output is embedded verbatim inside a single-quoted src='...' SVG attribute — so render-time it must clear an allowlisted image media type (png/gif/webp/svg+xml) plus a strict base64 body. No quote, angle bracket, or whitespace survives. This was the PR-4 commitment made in response to the Codex HIGH on #19.
Stats New stats_cache.cairo (3×u64 packed into one felt). Community species read a cache filled by permissionless refresh_stats; token_uri never calls the artist-nominated source. Starknet can't catch a failed external call, so a live read would let any artist permanently brick rendering for their whole species. refresh_stats rejects no-op refreshes so it isn't a free ERC-4906 spam faucet.
Fan-out fix The Genesis Beast holds rank 0 and lives outside beast_species_lists — a list-only walk left the artist's own token permanently stale after every art change. Now emitted explicitly. Fan-out is capped at FAN_OUT_LIMIT with the overflow bookmarked.

Not foreclosing burn_and_mint

Migration to V3 is intended to be a player-initiated burn-and-mint, deferred to PR 6. This PR keeps that open: mint validation lives in reusable helpers (assert_can_mint, prepare_mint_with_traits) rather than inlined in mint; minted starts empty except the 75 genesis affix slots; and species 1–75 resolve without any registry entry. Registry backfill of 1–75 is explicitly dropped from scope — documented in the design doc as still possible later, since no read path asserts a lower bound on a stored key.

Tests

158 passed, 0 failed (was 116 on #19). 21 are new end-to-end tests in registry_integration_tests.cairo — the first place the real NFT and real registry are wired together and driven through register → provenance mint → species mint → render.

Notable coverage: the global dungeon address cannot mint a community species; a paused (zero-minter) species rejects mints; an unregistered species reverts; a provider returning AAAA'AAA or a text/html payload is rejected at render; fan-out event counts verify the genesis token is included both before and after mints.

scarb fmt --check --workspace clean.

Deploy note

The two contracts wire after the fact via write-once setters (registry.set_nft_address, nft.set_registry_address) because each needs the other's address. A stack missing either accepts no community species — the intended fail-closed state. Deployment sequence updated in the design doc.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings August 1, 2026 02:07
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

GPT Code Review

[CRITICAL] src/lib.cairo:715 - token_uri and animation_url call an artist-controlled art provider live. The provider can revert, omit the entrypoint, or return an unbounded value; validation occurs only after the call and cannot preserve rendering.
Impact: An artist can brick metadata for every token in their species, permanently if the custom provider is locked.
Fix: Never call custom providers from render paths. Fetch, size-limit, validate, and cache art during a separate refresh flow, retaining the last valid value; alternatively restrict rendering to trusted stored-art providers.

[MEDIUM] src/lib.cairo:743 - The fan-out emits the Genesis token and then up to FAN_OUT_LIMIT ranked tokens, producing 651 ERC-4906 events despite the documented 650-event cap.
Impact: Maximum-size refresh transactions exceed the intended gas bound and may revert if the limit was chosen against the block budget.
Fix: Subtract the Genesis event from the ranked-token allowance and bookmark the first un-emitted rank.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Integrates the Beasts NFT contract with the permissionless species registry for community species (76+), while keeping the existing genesis species (1–75) behavior unchanged. This wires per-species mint authorization, routes token_uri resolution through registry-backed name/art/stats for community species, and adds safety boundaries for untrusted provider output.

Changes:

  • Adds registry wiring (set_registry_address) and updates mint authorization to resolve traits/minter via the registry for community species.
  • Introduces render-time art validation for untrusted community art providers and routes metadata generation through pre-fetched name/art inputs.
  • Adds a community-species stats cache with permissionless refresh, plus end-to-end registry↔NFT integration tests.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/stats_cache.cairo Adds packed cached-stats storage type for community species.
src/registry_integration_tests.cairo Adds end-to-end tests wiring real registry + real NFT, including art validation + stats refresh + fan-out.
src/minting_coordinator.cairo Refactors mint preparation to accept pre-resolved (tier, type) traits for registry/genesis paths.
src/mint_tests.cairo Updates mint failure expectations for community species when registry is not wired.
src/metadata_generator.cairo Switches metadata generation to accept resolved species name + pre-fetched image URI (no dispatcher).
src/lib.cairo Wires registry into the NFT, adds stats refresh/cache, updates token-uri routing + fan-out behavior.
src/interfaces.cairo Extends the public interface with registry wiring and cached-stats APIs.
src/beast_svg.cairo Takes a pre-fetched image URI rather than calling a provider dispatcher from inside the renderer.
src/beast_manager.cairo Introduces genesis boundary constant, trait validation, and helpers for registry-supplied traits.
src/art_validation.cairo Adds strict allowlist + base64 validation for untrusted provider-returned image data URIs.
docs/community-beasts-design.md Updates migration/backfill/deployment sequencing documentation to match the new wiring model.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib.cairo
Comment on lines +795 to +798
let num_deaths = death_mountain_dispatcher
.get_collectable_count(
death_mountain_dispatcher.contract_address, beast_hash,
);
Comment thread src/lib.cairo Outdated
Comment thread src/lib.cairo
Comment on lines +402 to +409
fn get_cached_stats(self: @ContractState, token_id: u256) -> BeastLiveStats {
let cached = self.cached_stats.entry(token_id).read();
BeastLiveStats {
adventurers_killed: cached.adventurers_killed,
last_killed_by: cached.last_killed_by,
last_killed_timestamp: cached.last_killed_timestamp,
}
}
loothero and others added 4 commits July 31, 2026 21:22
Community species (76+) now resolve everything they need through the
registry, while genesis species (1-75) keep their existing baked-in paths.

- mint: per-species minter authorization. Genesis species answer to the
  single dungeon address; every community species names its own minter,
  and a zero minter reads as paused rather than matching a zero caller.
- mint_provenance / emit_species_metadata_update: registry-only
  entrypoints. Provenance uses erc721.mint, not safe_mint, so a contract
  artist cannot reenter the registry mid-registration.
- token_uri: routes name, art, and stats by species range. Art from a
  custom provider is validated at render time (allowlisted image media
  type + strict base64) because that provider is arbitrary artist code
  embedded verbatim into a single-quoted SVG attribute.
- stats: community species read a cache filled by a permissionless
  refresh_stats, never a live call. Starknet cannot catch a failed
  external call, so a live read would let any artist brick rendering for
  their whole species.
- fan-out: the Genesis Beast holds rank 0 and lives outside
  beast_species_lists, so a list-only walk left the artist's own token
  permanently stale after every art change. It is now emitted explicitly.

Species resolution is split so nothing forecloses the deferred
burn_and_mint migration: validation lives in reusable helpers, and the
genesis tables stay reachable without any registry entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Without the gate, mock_art_provider and mock_stats_feed compile into the
production artifact set — deployable contracts that exist only to serve
tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses, class hashes, wiring transactions, and the verification run
that exercised register -> provenance mint -> species mint -> render on
a live network.

deployments/ is gitignored, so the record lives in docs/ where the
addresses are actually shareable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three end-to-end tests over the deployed pair rather than a mock: an
ordinary ERC721 transfer of the creator token hands the species to the
buyer, the seller loses admin with it, and enumeration plus a local decode
recovers which species a wallet controls with no registry reads.

That last one is why the two changes belong together — owner enumeration
gives a client the token list, and the derived role makes the token list
the answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@loothero
loothero force-pushed the feat/nft-registry-integration branch from 0a9e422 to e7cfd75 Compare August 1, 2026 04:24
@loothero loothero closed this Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants