Don't Trust, Verify
Every claim on this site is reproducible from the contracts. Here are the ten that matter most — each backed by the exact code that enforces it.
Contract addresses will appear here after mainnet deploy. Until then, all code shown is verifiable in the public repo and the Sepolia testnet deployment (link added at deploy).
1. The pool cannot be closed
There is no function in the Vault that lets anyone — owner, deployer, anyone — drain or pause the holder pool. Search the source. There is no emergencyWithdraw, no pause, no kill.
The only ETH-out paths are:
function claim(uint256[] calldata tokenIds) external nonReentrant { ... }
function withdrawSettled() external nonReentrant { ... }
function withdrawDeployer() external nonReentrant { ... }
function recoverUnclaimed(address user) external nonReentrant { ... }claim/withdrawSettled— require token ownership (yours only)withdrawDeployer— sends only the 10% deployer cut to the immutable deployer walletrecoverUnclaimed— sweeps a wallet's settled balance back to the pool after 365 days inactivity (does not extract)
No backdoor. No master switch.
2. You cannot claim more than once
The rewardDebt mapping is the lock. After every claim, your debt is set to your current entitlement. The next call computes pending = entitlement − debt = 0.
function claim(uint256[] calldata tokenIds) external nonReentrant {
uint256 accNow = accETHPerShare;
for (uint256 i; i < tokenIds.length; ) {
// ...
uint256 owed = (uint256(share) * accNow) / ACC_PRECISION - rewardDebt[id];
if (owed > 0) {
unclaimed[msg.sender] += owed;
rewardDebt[id] = (uint256(share) * accNow) / ACC_PRECISION;
// ↑ now equals the entitlement; next call yields 0
}
// ...
}
if (amount == 0) revert NothingToClaim();
// ...
}The only way pending becomes non-zero again is for accETHPerShare to rise — which happens only when new royalty arrives.
3. Shares cannot be inflated from nothing
Every updateShare call is symmetric: the change to totalShares exactly matches the change applied to the token. There is no path where a token gains share without totalShares increasing by the same amount.
if (newShare != oldShare) {
unchecked {
if (newShare > oldShare) totalShares += (newShare - oldShare);
else totalShares -= (oldShare - newShare);
}
}
rewardDebt[tokenId] = (uint256(newShare) * accETHPerShare) / ACC_PRECISION;updateShare is restricted to only the Glyphs contract:
modifier onlyGlyphs() {
if (msg.sender != address(glyphs)) revert OnlyGlyphs();
_;
}4. The deployer cannot mint to themselves
mint is the only path to create tokens. It is payable and requires msg.value == MINT_PRICE * quantity. There is no ownerMint, no airdrop, no freeMint.
function mint(uint256 quantity) external payable nonReentrant {
if (!mintOpen) revert MintClosed();
// ...
if (msg.value != MINT_PRICE * quantity) revert WrongPrice();
// ...
_mint(msg.sender, quantity);
vault.receiveMint{value: msg.value}();
}Even the deployer pays the same price.
5. The supply cap is enforced on-chain
uint256 public constant SUPPLY = 10_000;
// ...
if (_totalMinted() + quantity > SUPPLY) revert SupplyExceeded();SUPPLY is a constant. It is part of the deployed bytecode. There is no setter. There is no way to ever mint a 10,001st turtle.
6. The share caps are constants too
uint32 public constant HOLD_CAP = 10_000; // 10.0 shares
uint32 public constant FORGE_CAP = 9_000; // 9.0 shares
uint32 public constant GROWTH_PER_WEEK = 700; // +0.7 / weekThese three numbers define the entire economic system. They are constant. They cannot be changed. Not by the owner. Not by anyone. Ever.
7. The royalty percentage cannot be changed
The 10% royalty and the 90/10 split between holders and deployer are constants — part of the deployed bytecode, identical for every sale, forever. There is no setter, no admin function, no DAO vote that can move these numbers up or down.
uint96 public constant ROYALTY_BPS = 1000; // 10% royalty on every saleuint256 public constant HOLDERS_BPS = 9000; // 90% of royalty → holder pool
uint256 public constant DEPLOYER_BPS = 1000; // 10% of royalty → deployer walletThe royalty function the marketplaces call is hard-wired to these constants:
function royaltyInfo(uint256, uint256 salePrice)
external view override
returns (address receiver, uint256 royaltyAmount)
{
return (address(vault), (salePrice * ROYALTY_BPS) / 10_000);
}The deployer cannot raise the fee to grab more. The community cannot lower it to skip royalties. It is what it is, until the end of Ethereum.
8. The Vault contract address cannot be changed
The link between the NFT contract and the Vault is one-shot. Once setVault is called, the function reverts on every future call:
function setVault(address v) external onlyOwner {
if (address(vault) != address(0)) revert VaultAlreadySet();
if (v == address(0)) revert ZeroAddress();
vault = IVault(v);
emit VaultSet(v);
}Once the Vault is linked, it's permanent. No re-wiring. No rug-swap.
9. Extra metadata: writable only by the turtle's owner, never by an admin
The contract has a per-token storage slot called extraData — a free-form key/value map. It exists so future features (custom names, equipped accessories, achievements, side-game state) can attach metadata to a turtle without ever touching the share, royalty, path, or any economic variable.
The critical guarantee: only the current token owner can write to it.
function setExtraData(uint256 tokenId, bytes32 key, bytes32 value) external {
if (ownerOf(tokenId) != msg.sender) revert NotTokenOwner();
extraData[tokenId][key] = value;
emit ExtraDataSet(tokenId, key, value);
}There is no admin function to write extraData. There is no "approved writer" registry. The deployer cannot inject metadata. A future game contract cannot write either — unless the holder explicitly calls setExtraData themselves.
This is the only way the contract can be "extended" — and it costs the holder nothing in trust. The Renderer can read extraData to compose visuals. Future games can read it for state. But no one but you can write to your turtle's slot.
10. The art contract (Renderer) can be upgraded — but only the art
The Renderer is view only. It composes the SVG and metadata. It does not touch state, shares, royalties, or any economic variable. Upgrading the Renderer can change how a turtle looks. It cannot change what a turtle owns or earns.
Upgrades happen through a public function whose onlyOwner role is held by a TimelockController contract with a 48-hour delay — not a personal wallet. Anyone watching the chain sees the proposed upgrade and has 48 hours to react before it takes effect.
function setRenderer(address r) external onlyOwner {
if (r == address(0)) revert ZeroAddress();
renderer = IRenderer(r);
emit RendererUpdated(r);
}onlyOwner here resolves to the address of the deployed TimelockController. Any call to setRenderer must be scheduled by the timelock and then executed only after the 48h delay expires. There is no path for the deployer to push an instant Renderer swap.
Reading the code yourself
The full source for all three contracts will be linked here once the public repo and Etherscan verification are live. Until then, the code excerpts on this page are the canonical reference.
Tests: 45/45 passing internally — covering mint, share growth, transfer reset, forge, Legendary rules, royalty splits, claim, recovery, and the extraData extension.
→ FAQ