Flappy Robin docs

What it is

Flappy Robin is a one-button flying game. Its engine is a WebAssembly module stored as contract bytecode on Robinhood Chain (chain 4663). This site only ships the host: it reads the engine from the chain, checks every byte, then runs it.

The candles you fly through are real blocks, read live while you play.

Play

FlapSpace, Arrow Up, W, Enter, click or tap
PauseEscape or P. Leaving the tab pauses too.
SoundM, or the speaker button
Best scoreKept in this browser only

What lives on chain

One ROM contract and its chunks. The ROM is born sealed in a single transaction: its constructor stores every chunk, computes the Merkle root and body hash on chain, and it has no owner. Nothing can be added or replaced later.

ROM contract
Root
Body hash
Raw hash
Chunks
Bytes on chain
Bundle bytes
  • Chunks are data contracts whose code is 0x00 followed by up to 24,575 bytes. The ROM contract creates them itself.
  • The body is a gzipped CROM v1 bundle with two files: flappy-robin.wasm and README.txt.
  • The read surface matches ChainRom, so ChainRom-style readers can load it too.

How your browser checks it

Every step must pass before anything runs. The expected root, body hash and raw hash are pinned in this site's code, so an RPC can fail to deliver the game but cannot swap it.

  1. eth_chainId must be 4663.
  2. The header comes in one batch. sealed_() must be true, and root(), bodyHash() and rawHash() must equal the pins.
  3. pointers() lists the chunks, then one eth_getCode batch reads them. Each must start with 0x00.
  4. The Merkle root is rebuilt from the chunks and must equal the pinned root.
  5. The chunks are joined. Their keccak256 must equal the body hash, their length bodyBytes().
  6. The body is inflated. Its keccak256 must equal the raw hash, its length rawBytes().
  7. The first .wasm in the bundle is compiled. It may import only present, set_palette, poll_event and now_ms.

No JavaScript is ever loaded from chain. The engine runs in the WebAssembly sandbox and talks to the page only through those four imports and its own exports.

Merkle rule: leaf = keccak256(chunk), parent = keccak256(left ‖ right), an odd last node moves up unpaired. With one chunk, the root equals the body hash.

"Check a chunk on chain", behind the "Loaded from Robinhood Chain" button under the game, asks the contract's own verify(i, proof). That answer still travels through your RPC, so the pins above are what actually protect you.

Check it yourself

With Foundry's cast:

ROM=0x…
RPC=https://rpc.mainnet.chain.robinhood.com

cast call $ROM "sealed_()(bool)" --rpc-url $RPC
cast call $ROM "root()(bytes32)" --rpc-url $RPC
cast call $ROM "pointers()(address[])" --rpc-url $RPC

With one chunk, the root is the hash of the chunk's code minus its first byte:

P=$(cast call $ROM "chunks(uint256)(address)" 0 --rpc-url $RPC)
cast keccak $(cast code $P --rpc-url $RPC | sed 's/^0x00/0x/')

With plain curl (0xebf0c717 is root()):

curl -s $RPC -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"'$ROM'","data":"0xebf0c717"},"latest"]}'

The full check, from a checkout of the source (Node 18 or newer):

node tools/verify-rom.mjs --rpc $RPC --rom $ROM

Live data

SourceWhatHow often
rpc.mainnet.chain.robinhood.comLatest block: number, hash, gas used, transactions, base fee500 ms
rpc.mainnet.chain.robinhood.comPons launches, graduations, and curve buys and sells of at least 0.1 in the quote token3 s
api.coinbase.comETH spot price in USD60 s
  • Each candle takes the newest unused block. Its hash sets the gap, its gas used against the previous candle sets the colour. Hitboxes depend only on the gap.
  • With no block newer than 3 s, the next candle is grey, labelled ----, and placed by a local random source.
  • The sky follows New York Stock Exchange hours, read from block timestamps.
  • Launches count only from the Pons factory 0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e. Trades count only from curves whose factory() is that address.
  • Token symbols are chosen by strangers. They are shown as plain text, reduced to letters, digits and emoji, and cut to 10 characters.
  • Requests run one at a time, back off up to 30 s on errors, and stop while the tab is hidden. After two failures in a row they move to rpc.ordofi.network. This site sets no cookies and runs no analytics.

Contract reference

FlappyRom, Solidity 0.8.30. Every function is read-only.

SelectorFunctionReturns
06fdde03name()string, "Flappy Robin"
26d111f5note()string
8da5cb5bowner()address, always zero
ebf0c717root()bytes32, computed in the constructor
ce57b4a0bodyHash()bytes32, keccak256 of all chunks, computed
e2332df3rawHash()bytes32, keccak256 of the bundle, set by the publisher
1ac27abcbodyBytes()uint32, computed
1cb56a14rawBytes()uint32, set by the publisher
bf5390b9sealed_()bool, always true
f91f0937chunkCount()uint256
5a2bc22dchunks(uint256 i)address of chunk i
ed2e5a97read(uint256 i)bytes, chunk i without its 0x00
a79a2a28verify(uint256 i, bytes32[] proof)bool, chunk i is under the root
71267571pointers()address[], every chunk
8c72c54epublisher()address that deployed the ROM

Events: Wrote(uint256 indexed index, address pointer, uint256 length) once per chunk, then Sealed(bytes32 root, uint256 chunkCount, uint32 rawBytes) once.

Engine interface

The engine imports exactly ChainRom's four screen functions from module env, so it also boots on ChainRom's reader, keyboard only and silent.

ImportSignatureMeaning
present(ptr, w, h, format, stride) → i32Show a 144×256 indexed frame
set_palette(ptr, count) → i32RGB triples, 8-bit
poll_event() → i32(type << 24) | code, 0 when empty
now_ms() → f64Milliseconds, monotonic

Event types: 1 key down, 2 key up, 3 press (pointer or touch), 4 pause. Key codes: Arrow Up 1, Arrow Down 2, Arrow Left 3, Arrow Right 4, Control 5, Space 6, Enter 7, Escape 8, Alt 9, Shift 10, Tab 11. This page also sends W as 1 and P as 8.

ExportSignatureMeaning
memory, frameframe()Once per animation frame. Fixed 120 Hz steps inside
feed_hash_ptr, feed_block(number, gas_used, tx_count, timestamp)Push a block; its 32-byte hash goes to the pointer first
msg_ptr, push_msg(len, tone)Ticker message, up to 48 ASCII bytes
set_seed(hi, lo)Seed for the offline candle generator
set_best, set_flags(n), (bits)Restore best score; bit 0 reduced motion, bit 1 muted
score, best, state() → i32State 0 ready, 1 playing, 2 paused, 3 dying, 4 game over
ui_take() → i32Bits since last call: start, scored, died, new best, paused, resumed, ready
sfx_take, sfx_rate, sfx_count() → i32Sound bits since last call, sample rate 22,050, number of sounds
sfx_ptr, sfx_len(id) → i32Sounds synthesized by the engine: 16-bit mono PCM, length in samples

Cost

Publish transaction
Gas used
Gas price
Fee
Published

Contract code costs L2 gas only on Robinhood Chain today, about 218 gas per stored byte.

Token

Only trust the address shown on this site.

Disclaimer

Fan-made game. Not affiliated with, endorsed by, or officially connected with Robinhood Markets, Inc.

Chain activity shown in the game is raw public data, not advice. The game and this site are provided as is.