A 36-element pixel cellular automaton that runs entirely in your browser at 60 frames per second, where you can paint sand into water and drop fire on plants. When you make something good, mint the canvas as an on-chain ERC-721, with the simulation running locally and the chain holding the receipt.
Falling-sand games are good. Owning the result of a falling-sand game is new.
Powder Toy and Sandspiel taught half a generation what cellular automata can do in a browser. Both are masterpieces of single-developer iteration. Both are also fundamentally tab-bound: you paint, you watch, the tab closes, the canvas is gone. There's no mechanism to keep what you discovered.
EtherSand keeps the part that's fun (a fast local simulation) and adds one new layer: ownership. Every element you can paint with is an ERC-1155 token in your wallet. Every snapshot you mint is an ERC-721 with the canvas data stored on chain in RLE. Even the global weather - driven by Uniswap v4 swap activity - is real on-chain state every player reads.
It's not the chain doing the simulation. The chain doesn't know what you're painting and doesn't care. The chain does what the chain is good at: it remembers who owns what, and it makes the rules immutable.
No template reuse. No "well, fire just burns wood" shorthand. Each of the 36 element types has a hand-written updateFn(c, x, y, read, write) that explicitly defines its motion, lifetime, temperature behaviour, and every neighbour interaction.
Common elements are free at registration. Uncommon, Rare, and Legendary drop from mystery packs paid in $SAND. Tap any tile to paint it. The canvas runs the same engine the full game uses.
Tier filter: 12 common · 10 uncommon · 8 rare · 6 legendary
The engine is 700 lines of pure JavaScript. No framework, no state library, no ECS. Each rule is a pure function that reads from the previous tick's grid and writes to the next. Race-free by construction.
// Lava - id 13. Falls slow. Reacts on contact. function lavaRule(c, x, y) { // neighbour interactions first for (const [dx, dy] of NEIGH4) { const n = read(x+dx, y+dy) & ID; if (n === WATER) { setW(x, y, pack(STONE, 0, 200)); setW(x+dx, y+dy, pack(STEAM, 0, 220)); return; } if (n === SAND) setW(x+dx, y+dy, pack(GLASS, 0, 200)); if (n === ICE || n === SNOW) setW(x+dx, y+dy, pack(WATER, 0, 200)); if (isFlammable(n)) setW(x+dx, y+dy, pack(FIRE, 0, 220)); } // motion: 50% chance to fall, slowing the cascade if (rand2() === 0) return; if (tryFall(c, x, y)) return; }
Lava reacts on contact. When a Lava cell finds adjacent Water, both transmute: Lava → Stone, Water → Steam. Sand on contact becomes Glass. Flammables ignite. The rule runs ~once per millisecond per cell, deterministically.
A custom Uniswap v4 hook intercepts every swap on the SAND/ETH pool. It overrides LP fees per market mood, takes 0.25% of swap volume to the treasury, and writes a global WeatherState that every connected sandbox renders. Big sells trigger storms. Big buys trigger golden rain.
User trades SAND/ETH on the canonical pool. Hook intercepts beforeSwap and afterSwap.
Reads price vs 60-bucket TWAP. Classifies as Storm / Cloudy / Calm / Clear / Sunny / Heatwave. Sets dynamic LP fee. Detects whale moves.
Hook is the only writer of WeatherState. Every connected client polls it every 30s. All sandboxes render the same storm.
Acid rain in every sandbox
Snow drifts down
Aurora, no physics change
Standard physics, no event
Golden rain falls
Ambient temp +20
Pack opens burn 50% of cost. Creation mints burn 50% of cost. Hook fees route 5% to permanent burn. Most distribution paths shrink the float; the only inflationary path is yield from hook fees, capped by the hard cap of 100M.
The deployer holds the entire 32M initial supply. Use is restricted by social contract: seed liquidity on the SAND/ETH pool, fund the first-week airdrop, retain a small partnership reserve. No team allocation. No vesting. No insiders.
The launch is intentionally minimal. Future versions add depth without changing the contract surface - every contract is immutable, so every expansion is a new deployment alongside.
36 fully-implemented elements. ERC-1155 elements + ERC-721 creations + $SAND token. Mystery pack opener. Uniswap v4 hook driving global weather. Treasury auto-distribution.
Persistent local canvas slots. Named save-states. Shareable canvas URLs that other players can open and continue painting from.
384 × 216 grid for capable hardware. Web Worker integration to keep 60FPS at the larger size. New ambient particle effects.
On-chain shared canvases any registered player can paint to. 24-hour rotating snapshot auto-mints to the most-active painters of that day.
Combine elements via on-chain recipes. Sand + Heat + Pressure → Glass. Recipe NFTs grant a small yield share to discoverers.
Quarterly themed events with limited-edition legendary elements. Each season's elements fork off from the stable core; old seasons stay playable.
Once pool depth and creator base are healthy, deployer renounces remaining helper authority. Treasury becomes the sole point of central coordination, governed entirely by its immutable distribution policy.
keccak256(block.prevrandao, msg.sender, packCounter). packCounter is a global incrementing nonce so opening twice in the same block gives different seeds. block.prevrandao is constant within a block, so reverting and replaying inside one block produces the identical roll - you can't fish for better outcomes. The only way to "reroll" is to wait for a new block, which is just fresh randomness.CREATE2 salt that satisfies that constraint at deploy time.WeatherState into SandCore. All clients pick up the new weather within 30 seconds.Open the page. Paint. Discover what 36 hand-written rules do when they touch each other.
OPEN SANDBOX ▸