(@@) Immortal Fruit FlySourceMint a fly

Contracts & addresses

Everything is on BNB Smart Chain (chain ID 56). Sources are verified on BscScan and on Sourcify as exact matches.

ContractAddress
FlyRegistryV3, the organism: ERC-721 “Immortal Fruit Flies” (FLYS), identity, brain state, memory, lineage, history; life paid in BNB0x69DA3239B69c0B7C9C063F105c4DDf008FFb8F53 · Element
FlyCore, every fly's on-chain compass core (155 FlyWire neurons in the EVM, keyed by token id; pebbles anchor here), redeployed against v30x77F6066B2ab12072DCEFb7D9DB998944C4ec28C2
FlyRegistry v2, the previous registry: the same flies until the migration; their record up to then is on it (history)0x0eeB0A675720306Ef6f426Bd8560c1288848f813
Arena body (streams the live fly)0x47005543c06246124480D196a275327325695BEd
DOOM body0x642ebC7fD62a24406d8A86885F0131472E641c86
$FLY token0x23791aa3b031659b593cf141a2bc76b0ad657777
FlyWorld, fly #1's arena before the registry (read-only now)0xD730E65Bdc1cBd40f720a36EeD71e2028Bf20EB4 · source
FlyArcade, fly #1's DOOM session 5 (read-only now)0x3dE4fe3535dd9E1CC17b6718B985593e3E463279
FlyBrain v2, the on-chain compass core (live)0xee80f8cB5309C572343c38b5D717283BBBb517c5 · source
Circuit table v2 (SSTORE2 data contract)0x2eE3C5168CD3F60E87693716E660470011EA9C7e
FlyBrain v1 (first fly, retired)0x32D28e97b50f5978eb51d7608492CC7221b01f63 · source
Keeper / deployer0xA2eD0B7da1C7b5ee1af819CA281F4894B1700e27
Registry v3. FlyRegistryV3 at 0x69DA3239B69c0B7C9C063F105c4DDf008FFb8F53 replaced the v2 registry on 17 September 2026. What changed: life is paid in BNB, nothing is burned as metabolism. feed(id, seconds) and resurrect(id, seconds) are payable: a feed costs lifeCost(seconds) = seconds × lifeWeiPerSecond (today 0.01 BNB per day of life), a resurrection resurrectWei (0.002 BNB) plus the life it wakes with; the BNB goes to the treasury, and the curator adjusts both prices as BNB moves (LifePriceSet). The keeper (the operator) feeds for free, so the operator can keep flies alive. Minting still burns 1 $FLY and breeding 5,000; the LifeFund that fed v2 with $FLY is retired. Migration: every fly of v2 was re-created on v3 with the same id, the same owner and its whole record (brain state, memory, lineage, energy, generation, deaths), one Migrated(id, to, previousRegistry) event each, after which closeMigration() ends it for ever; bodies re-registered and were re-assigned. The old registry stays on-chain as history: its record of every fly up to the migration is still readable there, and previousRegistry() on v3 points at it.
v1 → v2. The first fly dropped pending synaptic input at the end of each tick, so a bump could not survive between ticks. v2 keeps it in storage, has a Yul inner loop (about 2.3× cheaper), and parameters calibrated across several trajectories. v1 stays on-chain as the first organism; its lineage is a fossil.

FlyRegistry interface

One contract is the permanent layer. A fly is a token; everything about it lives here or is anchored here. Bodies (an arena, a game, a robot) register, get assigned a fly, accept it, and are then the only address that can write its brain state, and only forward.

struct Fly { bytes32 connectome; uint32 model; uint32 generation; uint32 deaths; uint256 parentA; uint256 parentB;
             bytes32 stateRoot; bytes32 memoryRoot; string stateURI; uint64 brainStep; uint64 energy;
             uint64 bornBlock; uint64 lastCommitBlock; address body; address pendingBody; bool alive; }

function mint(string name) returns (uint256 id);               // anyone; burns MINT_PRICE (1 FLY); fresh genesis brain; max 10,000
function feed(uint256 id, uint64 seconds_) payable;            // anyone; msg.value >= lifeCost(seconds_) in BNB; nothing burned; the keeper pays nothing
function resurrect(uint256 id, uint64 seconds_) payable;       // anyone; only when dead; msg.value >= resurrectWei + lifeCost(seconds_); generation + 1
function breed(uint256 a, uint256 b, bytes32 childMemoryRoot, string name) returns (uint256 id); // owner of both, both alive; burns 5,000
function lifeCost(uint64 seconds_) view returns (uint256);     // seconds_ × lifeWeiPerSecond
function lifeWeiPerSecond() view returns (uint256); function resurrectWei() view returns (uint256); function keeper() view returns (address);
function previousRegistry() view returns (address); function migrationOpen() view returns (bool);
function registerBody(string name, string uri);                // anyone; a body announces itself (the arena puts its live-stream URL here)
function assign(uint256 id, address body);                     // the owner, or the body currently running it
function accept(uint256 id); function release(uint256 id);     // the body
function commit(uint256 id, bytes32 stateRoot, bytes32 memoryRoot, string stateURI, string metadataURI, uint64 brainStep, uint64 energy, bytes32 historyRoot); // the body; step must advance
function interaction(uint256 id, bytes32 kind, string data);   // the body: ate, jumped, doom decision, met #n …
function died(uint256 id, bytes32 stateRoot, bytes32 memoryRoot, string stateURI, string metadataURI, uint64 brainStep, string cause); // the body
function attest(uint256 id, uint64 brainStep, bytes32 stateRoot); // anyone who replayed it
function setMetadata(uint256 id, string uri);                   // curator, only while no body runs the fly (portraits)

event Minted(uint256 indexed id, address indexed to, string name, uint256 parentA, uint256 parentB);
event Fed(uint256 indexed id, address indexed by, uint64 seconds_, uint256 paidWei);
event Resurrected(uint256 indexed id, address indexed by, uint32 generation, uint64 energy, uint256 paidWei);
event LifePriceSet(uint256 lifeWeiPerSecond, uint256 resurrectWei);
event Migrated(uint256 indexed id, address indexed to, address previousRegistry);
event Assigned / Accepted / Released (uint256 indexed id, address indexed body …);
event Commit(uint256 indexed id, address indexed body, bytes32 stateRoot, bytes32 memoryRoot, string stateURI, uint64 brainStep, uint64 energy, bytes32 historyRoot);
event Interaction(uint256 indexed id, address indexed body, bytes32 indexed kind, string data);
event Died(uint256 indexed id, address indexed body, uint32 generation, bytes32 stateRoot, bytes32 memoryRoot, string stateURI, uint64 brainStep, string cause);
event Attested(uint256 indexed id, address indexed attestor, uint64 brainStep, bytes32 stateRoot, bool matches);

Rules the contract enforces: a fly has at most one body; only that body commits, and only forward; a dead fly cannot be committed and cannot be transferred or sold until resurrected; breeding needs two living flies; supply stops at 10,000. Snapshots are pinned to IPFS and named by their sha256, which is the stateRoot, so any body can fetch, verify and continue a fly. ERC-2981 royalty: 2.5% to the operator on secondary sales. Nothing is upgradeable; the curator can only set dormant flies' portraits, the collection's branding, the BNB price of life and the keeper.

Life in BNB

Feeding is the organism's metabolism, and on v3 it is paid in BNB to the registry itself: anyone calls feed(id, seconds) with at least lifeCost(seconds) for any fly, and the fly's energy rises by that many seconds at once (its body applies it at the next poll, as food). Waking a dead fly is resurrect(id, seconds) with resurrectWei + lifeCost(seconds). Nothing is burned; every wei goes to the treasury, and the site shows the price it read before it pays. The keeper address pays nothing, so the operator can keep flies alive out of its own pocket; every Fed and Resurrected event carries what was paid.

PricelifeWeiPerSecond = 115,740,740,740 wei: 0.01 BNB ≈ 24 h of life; the curator can change it (setLifePrice, event LifePriceSet), and every page reads the live price
ResurrectionresurrectWei = 0.002 BNB, plus at least 60 s of life
Minimumanything that buys at least one second (TooLittle otherwise); the site suggests 0.001 BNB or more
Keeperthe operator; feeds for free while a fly runs in a body (a fly that is not running does not age)

Constants of the registry

MINT_PRICE1 FLY, burned
BREED_PRICE5,000 FLY, burned
lifeWeiPerSecond0.01 BNB per day of life (adjustable)
resurrectWei0.002 BNB (+ at least 60 s of life, adjustable)
GENESIS_ENERGY3,600 s
MAX_SUPPLY10,000
CONNECTOMEsha256 of the FlyWire 783 connectome build (see brain/identity.json)
MODEL2 (Shiu et al. 2024 parameters, brain/sim.py)

$FLY

A BEP-20 with 1,000,000,000 supply, 18 decimals and a 1% transfer tax; ownership renounced; EIP-2612 permit. It has no burn function, so the fly “burns” by transferring to 0x000000000000000000000000000000000000dEaD. FlyBrain.totalBurned() counts what the fly has eaten.

FlyWorld interface (historical)

function placeFood(int32 x, int32 y, uint256 amount) external returns (uint256 id); // burns $FLY; 1 $FLY = 1 s of life, min 60
function resurrect(uint256 extraFood) external;                                      // only when dead; burns 50,000 $FLY + extraFood
function checkpoint(uint64 step, uint64 ageMs, bytes32 stateHash, int32 x, int32 y, uint64 energy, uint64 spikes, string snapshotURI); // operator
function reportDeath(uint64 ageMs, bytes32 stateHash, string snapshotURI);                                                             // operator
function foods(uint256) view returns (address by, int32 x, int32 y, uint64 seconds_, uint64 blockNumber);
function alive() view returns (bool); function generation() view returns (uint32); function lastHash() view returns (bytes32);

event FoodPlaced(uint256 indexed id, address indexed by, int32 x, int32 y, uint64 seconds_, uint256 tokensBurned);
event Checkpoint(uint64 step, uint64 ageMs, bytes32 stateHash, int32 x, int32 y, uint64 energy, uint64 spikes, uint32 generation, string snapshotURI);
event Died(uint32 indexed generation, uint64 ageMs, bytes32 stateHash, string snapshotURI);
event Resurrected(uint32 indexed generation, address indexed by, uint256 tokensBurned, uint64 energy);

The operator (the deployer address) can only post attestations about the off-chain simulation; it cannot move anyone's tokens or change prices. The arena is 240 × 240 body lengths; coordinates outside ±120 revert. Each checkpoint's snapshotURI points to a downloadable snapshot of every membrane potential and synaptic current, and also tells the website where the live stream is.

FlyBrain interface (the on-chain core)

function tick(uint16 steps) external;                       // anyone, gas only
function feed(uint256 amount) external;                     // burns $FLY, +energy
function stimulate(uint8 channel, uint8 param, uint8 strength, uint16 steps) external;
function resurrect(uint256 extraFood) external;             // only when dead
// permit variants: feedWithPermit, stimulateWithPermit, resurrectWithPermit

function brainState() external view returns (int16[] v, int8[] bias, uint16[16] headingHist,
    int32[] pendingInput, uint64 step, uint64 energy, bool alive, uint32 generation,
    int64 posX, int64 posY, int32 headX, int32 headY);
function activeStimulus() external view returns (uint8 channel, uint8 param, uint16 strength, uint64 untilStep, bool active);
function lineage(uint256 i) external view returns (uint64 bornBlock, uint64 diedBlock, uint64 steps, uint64 spikes, bytes32 brainStateHash);
function neuron(uint256 i) external view returns (uint64 rootId, uint8 cellType, uint8 wedge, uint8 side, uint16 outDegree);
function synapsesOf(uint256 i) external view returns (uint8[] post, uint8[] weight);
function circuitData() external view returns (bytes);        // the raw table
function brainStateHash() external view returns (bytes32);

event Ticked(address indexed by, uint64 fromStep, uint16 steps, uint32 spikes, int32 headX, int32 headY, int64 posX, int64 posY, uint64 energyLeft);
event Fed(address indexed by, uint256 tokensBurned, uint64 energyAdded, uint64 energy);
event Stimulated(address indexed by, uint8 channel, uint8 param, uint16 strength, uint64 untilStep, uint256 tokensBurned);
event Died(uint32 indexed generation, uint64 bornBlock, uint64 diedBlock, uint64 lifeSteps, uint64 lifeSpikes, bytes32 brainStateHash);
event Resurrected(uint32 indexed generation, address indexed by, uint256 tokensBurned, uint64 energy);

Channels: 1 cue (param = wedge 0–15), 2 turn left, 3 turn right, 4 shock. Strength 1–255; price is STIM_PRICE × strength. A stimulus lasts STIM_TTL = 64 steps.

Constants of the live fly

TOKENS_PER_STEP1 FLY
STIM_PRICE100 FLY per unit strength
RESURRECT_PRICE100,000 FLY
MAX_STEPS64 per tick
Genesis energy1,000,000 steps

No owner

FlyBrain has no owner, no admin function, no upgrade path, no pause. Every parameter is an immutable. The only way to change the fly is to deploy a new one.

Build and test

git clone https://github.com/MidTermDev/immortal-fruit-fly
cd immortal-fruit-fly/contracts
forge install foundry-rs/forge-std OpenZeppelin/openzeppelin-contracts@v5.1.0 --no-git
forge test -vv        # 28 tests incl. the mainnet replay and the immortality loop (die in the arena, wake in DOOM)