The Meta, 06 of 06

The Stack

The Rust stack BLCK is building for Robinhood Chain, and why each piece is there.

The shape of the problem

Strict FCFS at a single sequencer. Blocks about every 100 ms when there is traffic (measured 97.7 ms mean over 621 blocks in a 60 s mainnet sample). No public mempool. A public sequencer feed that carries what the sequencer has ordered, independent of block RPC; whether it runs ahead, and by how much, is unmeasured. A submit endpoint that rejects reads (eth_chainId returns -32601) and exists for eth_sendRawTransaction.

So the loop is: read the feed, decode the message, simulate, craft, sign, send on a warm connection. Every step is on the hot path. That is why the workspace is Rust: no garbage collector pause, no interpreter, one binary per module, and the same crates from the probe up to the executor. Foundry handles the Solidity side.

Built for the sequencer edge, not for FCFS

BLCK calls the discipline sequencer-edge engineering rather than FCFS engineering, and the stack is chosen so that it survives a rule change. Timeboost is live on Arbitrum One and the ArbitrumDAO has voted to move Arbitrum One to Priority Gas Auctions. Nothing equivalent is announced for Robinhood Chain (open question Q3). If a priority auction lands here, this is what changes in the stack:

  • The feed decoder, the probe, and the warm-connection send path stay as they are. Arrival time still orders everything outside the paid lane.
  • blck-sim gains a bid input in the profit check.
  • blck-probe output has to separate paid-lane inclusion from plain inclusion.
  • Nothing in the crate table below is replaced. An auction client is added next to the existing sender.

Today there is no bid. The only ordering input the stack controls is arrival time.

The crates

Versions are the ones pinned in the workspace Cargo.toml.

PieceVersionJob in BLCK
alloy1, full featuresTransaction crafting, signing, typed transaction envelopes, ABI bindings, HTTP and WebSocket JSON-RPC providers, eth_sendRawTransaction to the submit endpoint.
alloy-primitives1Address, the address! macro, and fixed-width integers. blck-common depends on alloy-primitives, serde, and anyhow only, so it stays light.
revmnot yet pinnedLocal EVM. blck-sim runs candidate transactions against state forked from a mainnet RPC before anything is signed. Added at Milestone 3.
tokio1, full featuresThe event loop. One task per stream: feed reader, RPC poller, sender.
tokio-tungstenite0.26, rustls-tls-webpki-rootsThe sequencer feed and any third-party WebSocket RPC.
reqwest0.12, json and rustls-tls onlyHTTP JSON-RPC, and the Robinhood stock-token REST API in blck-gaps.
rustls0.23, ringTLS provider. alloy and the HTTP and WebSocket clients pull different rustls backends, so blck-probe installs ring as the default provider at startup.
base64, hex0.22, 0.4base64 decodes the feed's l2Msg. hex handles key material in keygen and send mode.
clap4, derive and envCLI for every module. Flags fall back to environment variables.
dotenvy0.15Loads .env.
serde, serde_json1Feed frames, probe output, config.
tracing, tracing-subscriber0.1, 0.3Structured logs with RUST_LOG style filters.
futures-util, url, chrono, anyhow, thiserroras pinnedStream combinators, URL parsing, timestamps, error handling.

Release profile: lto = "fat", codegen-units = 1, panic = "abort". Minimum rust-version 1.85. Toolchain channel is stable with clippy and rustfmt; the dev machine runs 1.98.1.

Two practical notes that cost time if you do not know them. The official RPC returns HTTP 403 to the default Python urllib user agent, so send a normal user agent from any client. The curl build on the dev machine has no WebSocket support, so the feed client is the Rust one, not a shell one-liner.

Foundry

Foundry 1.8.3 (forge, cast, anvil). contracts/ is the Foundry project for AtomicArbExecutor and its tests (Milestone 3). cast call is verified working from this machine. forge test --fork-url gives the Solidity tests real mainnet state to run against, reading the live pools without sending anything. The Rust side gets the same thing from blck-sim, where revm forks through RPC without a separate process.

The feed decoder

The feed speaks JSON over WebSocket. A frame looks like this:

{"version":1,"messages":[{"sequenceNumber":N,"message":{"message":{"header":{"kind":3,...},"l2Msg":"<base64>"}}}]}

blockMetadata is null on all 789 captured messages. That is one of four checks (with the receipt field, the RPC namespaces, and a string search of the capture) behind the statement that Timeboost is not enabled.

Decoding. Steps 1 and 2 are observed. Steps 3 to 5 follow Nitro's arbos/parse.go and are not yet validated by BLCK against a live capture.

  1. Track sequenceNumber. A gap means you missed messages. Log it, do not paper over it.
  2. Base64-decode l2Msg into bytes.
  3. The first byte is the L2 message kind. This is a different field from header.kind in the frame; the two are separate enums that happen to share the value 3.
  4. Kind 3 is a batch: a sequence of sub-messages, each with an 8 byte big-endian length prefix and its own kind byte. Recurse. The decoder in toolkit/blck-probe/src/feed.rs hardcodes the 8 byte prefix.
  5. Kind 4 is a signed transaction. The remaining bytes are an EIP-2718 typed transaction envelope, or a legacy RLP transaction. alloy's TxEnvelope decodes either.
  6. Other kinds exist. Log the kind and skip until a module needs it.

Validated against captured frames: blck-probe's decoder is tested against a real feed message, and blck-sniper decodes live launch transactions out of the same framing.

On connect from this machine, 5 messages arrived within about 2 s. The feed hosts CNAME to customer-origin.offchainlabs.com behind Cloudflare, so there is at least one proxy hop in front of the feed origin. Whether that origin is the sequencer host is not known. blck-probe measures feed-versus-RPC delay rather than assuming it. Chainstack publishes a feed decoder for this chain (github.com/chainstacklabs/robinhood-chain-sequencer-feed); compare against it.

Endpoints

Verified live 2026-09-16. The first-party endpoints (official RPC, submit endpoint, feed, explorer), the PublicNode WSS rows, and the L1 contract addresses are constants in toolkit/blck-common/src/chains.rs and addresses.rs. The third-party HTTP RPC list and the stock-token REST URLs are not in the crate yet.

KindMainnet 4663
Official HTTP RPC (rate limited)https://rpc.mainnet.chain.robinhood.com
Sequencer submit endpointhttps://sequencer.mainnet.chain.robinhood.com
Sequencer feedwss://feed.mainnet.chain.robinhood.com
Public WSS RPC (third party)wss://robinhood-rpc.publicnode.com
Other public HTTP RPCrobinhood-rpc.publicnode.com, robinhood.drpc.org, robinhood.rpc.blxrbdn.com, robinhood.api.pocket.network, rpc.ordofi.network (rpc.arrowrpc.com was down)
Explorerhttps://robinhoodchain.blockscout.com
L1 Rollup0x23A19d23e89166adedbDcB432518AB01e4272D94 (Ethereum)
L1 SequencerInbox0xBd0D173EEb87D57A09521c24388a12789F33ba96
L1 Delayed Inbox0x1A07cc4BD17E0118BdB54D70990D2158AbAD7a2D
Stock token RESThttps://api.robinhood.com/rhj/assets and /rhj/prices/{SYMBOL} (bid, ask, isTradingHalt, dailyTradingVolume)

Robinhood publishes no first-party WebSocket RPC. The public WSS rows are PublicNode. Keyed endpoints exist from Alchemy (the docs-recommended provider), QuickNode, Chainstack, dRPC, Blockdaemon, Validation Cloud, and GetBlock. The submit endpoint rejects reads; the official RPC is for reads.

The L1 rows matter for one thing on the searcher side: force inclusion through the Delayed Inbox is 4 days (delaySeconds 345600 from SequencerInbox.maxTimeVariation() on both networks), not the Arbitrum default of about a day. It is meant as a censorship backstop, not a submission path. One caveat: L2BEAT reports an ArbOS 61 transaction-filtering precompile at 0x74 on this chain that can defeat force inclusion. BLCK has not probed it (open question Q4). Do not treat force inclusion as a guaranteed backstop until that is checked.

Environment variables

Everything secret or endpoint-shaped comes from the environment. Copy .env.example to .env and fill it in. .env, .env.*, *.key, and *.pem are gitignored; only .env.example is tracked.

VariableMeaning
BLCK_MAINNET_RPC_URL, BLCK_MAINNET_WS_URL, BLCK_MAINNET_FEED_URLMainnet 4663 endpoints. Defaults match the table above.
BLCK_PRIVATE_KEYSigning key for modules that send (blck-probe send mode, blck-arb live mode). Hex, 0x-prefixed.
BLCK_PROBE_EXTRA_RPCSOptional. Comma-separated label=url pairs for endpoint comparisons in blck-probe.
BLCK_PROBE_REGIONOptional. Free-text region label written into probe output. Runs without it are marked unspecified, and only labeled runs get committed.

BLCK_PRIVATE_KEY is a dedicated key that holds only what you are willing to spend on a run. blck-probe keygen generates one and prints the address. It prints the key only with --show-key and never writes files; you paste it into the gitignored .env yourself, or use scripts/set-key.sh, which prompts with hidden input. Because BLCK targets mainnet, that key spends real ETH: keep the balance small and rotate it when it has been exposed.

Module to stack map

ModuleMilestoneStack piecesStatus
blck-common0alloy-primitives, serde, anyhow. Verified chain constants, endpoints, addresses.Building
blck-probe2tokio, reqwest (RPC round trip), tokio-tungstenite (feed-versus-RPC delay, block cadence via newHeads), alloy (send mode for inclusion delay), base64 for the feed payload, hex for key material, clap, chrono. Will write JSON matching the probe schema. The schema lives at benchmarks/schema/probe-result.schema.json and scripts/check-probe-output.sh checks every committed file against it.Live: all modes have run, nine committed runs
blck-sim3revm against forked state fetched over RPC, alloy for ABI and transaction types, Foundry tests alongside in contracts/. Gas model has to handle ArbGasInfo.getL1BaseFeeEstimate returning 0 on mainnet (open question Q6).Planned
blck-arb4alloy providers and signer, blck-sim for the profit check, AtomicArbExecutor from contracts/, tokio for the quote loop. Paper mode first.Planned
blck-sniper5Feed decoder from blck-probe, alloy event decoding for Pons factories and CurveBuy and CurveSell, blck-sim, submit endpoint on a warm connection.Planned
blck-gaps5reqwest for the Robinhood price API, alloy calls to Chainlink aggregators and to Uniswap v3 pools, StateView, and V4Quoter, chrono for session state. Read-only.Planned

blck-sim, blck-arb, blck-sniper, and blck-gaps all run today, and each carries a README stating what it can do and what it cannot. blck-gaps is read-only and complete for its milestone; the other three are BUILDING, with the missing piece named in each README.

Mainnet only

BLCK targets mainnet, chain 4663. There is no rehearsal chain in the picture, so the safety model is different: every tool that sends refuses unless you pass an explicit command line flag and set an environment variable, and anything untested first runs against a local fork of mainnet state.

A fork gives you the real thing: Uniswap v2, v3, and v4, USDG, the stock tokens, Morpho, and the Pons factories, all at their mainnet addresses and with real liquidity. blck-sim forks through RPC for Rust code; anvil --fork-url and forge test --fork-url do the same for Solidity.

What a fork does not give you: the sequencer. Ordering, feed timing, and inclusion delay can only be measured against the live chain, which is what blck-probe does and why its send mode exists.

So the rehearsal plan is:

  • blck-probe: live. rpc, feed, blocks, and send have all run on mainnet; six runs are committed under benchmarks/data.
  • blck-sim and AtomicArbExecutor: fork tests against mainnet state, no deployment.
  • blck-arb: paper mode against mainnet state, then live only behind the double guard.
  • blck-sniper: read-only watching and dry-run calldata, no sending.
  • blck-gaps: read-only, and it never sends.