Beasts is a fully onchain NFT collection featuring 75 unique monster species that integrate with the Loot Survivor game ecosystem on Starknet. Each Beast is dynamically generated with unique attributes, names, and artwork—all stored and rendered directly from the blockchain.
The Beasts are a collection of digital-native creatures, born onchain and built for battle. Across 75 species, each Beast combines naming, visual, and combat attributes to balance abundance and scarcity. Beasts carry two sets of traits: visual and combat.
- Visual traits power collecting: Shiny and Animated forms activate pixel-perfect effects. Non-genesis Beasts receive live ranking within their species based on power and health, and those rankings update as new Beasts are minted.
- Combat traits power play: On mint, a Beast includes level and health. Together with its type and tier, this defines a combat profile compatible with the Loot Survivor system that first brought Beasts into the world.
- Live, credibly neutral traits such as Adventurers slain, last Adventurer who defeated a Beast, and timestamp of that defeat enable long-term growth systems without hardcoding game logic.
- Beasts are earned by worthy Adventurers in the dungeons of Loot Survivor using verifiable randomness. Every step is etched onchain for permanent provenance. For collectors, Beasts offer verifiable scarcity and provenance; for players, they unlock endless onchain fun.
- 🎨 Fully Onchain Artwork: Every Beast’s image data and metadata are generated onchain
- 🎮 Born Onchain: Beasts emerge from the dungeons of Loot Survivor
- ⚔️ Battle-Ready: Each Beast is minted with level and health and is compatible with the Loot Survivor combat system
- 🏛️ 75 Unique Species: From mystical Warlocks to fierce Minotaurs, each with distinct visual traits
- 📊 Tiered Rarity System: 5 tiers with visual indicators through border colors and effects
- 🔐 Deterministic Token IDs: A Beast’s ERC721 token ID is the packed representation of its species, name parts, combat stats, and visual flags
- Scarb 2.18.0
- Starknet Foundry 0.60.0
- Starkli for deployment
- Cairo Coverage and lcov for local coverage reports
Tool versions used by CI are pinned in .tool-versions.
- Clone the repository:
git clone https://github.com/Provable-Games/beasts.git
cd beasts- Build contracts:
scarb build- Run tests:
snforge testsrc/
├── lib.cairo # Entry point; exposes modules and ERC721 contract (beasts_nft)
├── beast_definitions.cairo # 75 species definitions and names
├── beast_manager.cairo # Validation and uniqueness hashing
├── minting_coordinator.cairo # Single and batch mint prep
├── beast_ranking.cairo # Per-species live ranking
├── pack.cairo # Attribute packing (id, name parts, level, health, shiny, animated)
├── metadata_generator.cairo # Onchain JSON metadata generation
├── beast_svg.cairo # Dynamic SVG artwork generation
├── beast_images.cairo # Shared image helpers
├── beast_png_*_data.cairo # PNG image data provider contracts
├── beast_gif_*_data.cairo # GIF image data provider contracts
├── encoding.cairo # Encoding helpers
├── utils.cairo # Shared utilities
└── interfaces.cairo # External interfaces (image data providers, systems)
Each Beast is efficiently packed into 116 bits. The packed value is also the ERC721 token_id:
PackableBeast {
id: u64, // 64 bits - species (1–75 genesis, 76+ community)
prefix: u8, // 7 bits - name prefix
suffix: u8, // 5 bits - name suffix
level: u16, // 16 bits - level
health: u16, // 16 bits - health
shiny: u8, // 1 bit - visual trait
animated: u8, // 1 bit - visual trait
tier: u8, // 3 bits - tier (1–5), static per species
beast_type: u8, // 3 bits - type (0 = Magic, 1 = Hunter, 2 = Brute)
}Beasts do not use sequential token IDs. For fresh deployments, every token ID is deterministic:
token_id = encode_token_id(PackableBeast)
The bit layout is:
id
+ prefix * 2^64
+ suffix * 2^71
+ level * 2^76
+ health * 2^92
+ shiny * 2^108
+ animated * 2^109
+ tier * 2^110
+ beast_type * 2^113
This keeps every valid Beast token ID below 2^116, so it fits in u128 (two 64-bit words for indexers and databases; JavaScript clients should use BigInt). The same format is used for genesis and non-genesis Beasts. Tier and type are resolved by the contract at mint time — never caller-supplied — so decoded values are trustworthy after an ERC721 existence check, and power = level * (6 - tier) is a pure function of the token ID.
Because the token ID is the source of the Beast attributes, contract reads such as get_beast(token_id), token_uri(token_id), and ranking comparisons decode the token ID after verifying ERC721 ownership/existence. There is no separate onchain map from token_id to PackableBeast.
Genesis Beasts are minted in the constructor to the owner with prefix = 0, suffix = 0, level = 1, health = 100, shiny = 1, and animated = 1. Genesis Beasts have rank 0 and are entered into the uniqueness map at construction. The (id, 0, 0) affix slot is reserved for the Genesis Beast of each species: non-genesis mints require prefix >= 1 and suffix >= 1, so genesis status is derived directly from the token ID and every species has a maximum supply of exactly 1,243 (69 prefixes × 18 suffixes + 1 genesis). Non-genesis Beast uniqueness is tracked by (beast_id, prefix, suffix), while ranking and metadata refresh state continue to index by packed token ID.
total_supply() is a count of minted NFTs, not the largest token ID.
- 🔮 Magical: Mystical creatures with arcane powers
- 🏹 Hunter: Swift and agile predators
- ⚔️ Brute: Raw strength and physical dominance
- Tier 1: Orange borders (Legendary) - Most powerful beasts
- Tier 2: Purple borders (Epic)
- Tier 3: Blue borders (Rare)
- Tier 4: Green borders (Uncommon)
- Tier 5: White borders (Common) - Entry level beasts
Run the test suite:
# Default local run
snforge test
# Match the CI test command
snforge test --max-n-steps 4294967295
# Generate local coverage
snforge test --coverage
# Summarize coverage locally
lcov --summary coverage/coverage.lcovFor a longer local pass, use snforge test --fuzzer-runs 500 --coverage. CI currently enforces formatting and tests, but not coverage thresholds; aim for >=80% overall coverage when feasible.
- Configure environment variables:
cp .env.example .env
# Edit .env with your configurationRequired in .env (no defaults are assumed):
STARKNET_ACCOUNT,STARKNET_PRIVATE_KEYRPC_URL(e.g., Sepolia or Mainnet endpoint)NAME,SYMBOLOWNER,ROYALTY_RECEIVER,ROYALTY_FRACTION(u128, denominator 10,000)
Optional in .env:
DEATH_MOUNTAIN_ADDRESS(ContractAddress, default0): external systems integration address; set to0to disable.
- Deploy to Starknet:
bash scripts/deploy.shNotes:
- The script declares and deploys the four image data provider contracts, then deploys the core NFT with their addresses passed to the constructor.
- The script fails with a descriptive error if any required
.envvalue is missing. - The deploy script reads
DEATH_MOUNTAIN_ADDRESSand appends it to the constructor.
- Run formatter before committing:
scarb fmt
scarb fmt --check --workspaceThe project uses GitHub Actions for:
- Linting (
scarb fmt --check --workspace) - Contract tests (
snforge test --max-n-steps 4294967295) - Caching/installing coverage tools for local parity; coverage thresholds are not currently enforced in CI
- Beast pixels from the legends at 1337 Skulls