A backrun engine for block builders that acts as a 1inch Fusion resolver. At the end of each block-building iteration, it evaluates live Fusion orders against the final AMM state and finds profitable fill routes using Propellerheads' Fynd solver. When a route is profitable after gas, it produces a settlement transaction that your builder can append to the block.
The engine subscribes to DEX state via Tycho and continuously watches the 1inch Fusion orderbook. As your builder constructs a block it streams executed transactions to the engine, which tracks pending AMM state in real time (currently Uniswap V3 only; see Pending state below). At IterationComplete it runs the solver against the updated state and publishes a candidate if one is found.
Two types define the integration boundary, in crates/builder-types/src/lib.rs: BuildEvent goes in, BackrunCandidate comes out. Both are serde-enabled, so they cross process or network boundaries without extra work.
EOA. The backrunner submits transactions through a Propellerheads-operated resolver contract. Your integration needs a dedicated EOA whose address you send to us for whitelisting. Transactions in BackrunCandidate are pre-built but unsigned — your builder signs them with that EOA before inclusion.
Tycho API key. The engine subscribes to live DEX state via Tycho. Provision an API key at t.me/fynd_portal_bot. Pass it as BackrunnerConfig::tycho_api_key.
EOA whitelisting requires coordination with Propellerheads before your integration can go live.
Add backrunner as a Rust dependency. Construct a Backrunner, create a tokio channel pair, and spawn run() alongside your builder loop. Your builder writes BuildEvents to the sender; the backrunner publishes BackrunCandidates to the watch channel.
use backrunner::{Backrunner, BackrunnerConfig};
use builder_types::{BackrunCandidate, BuildEvent};
use tokio::sync::{mpsc, watch};
use std::time::Duration;
let config = BackrunnerConfig {
chain: "ethereum".to_string(),
tycho_url: "app.propellerheads.xyz".to_string(),
tycho_api_key: Some("your-api-key".to_string()),
rpc_url: "https://your-rpc".to_string(),
protocols: vec![
"uniswap_v2", "uniswap_v3", "uniswap_v4",
"sushiswap_v2", "pancakeswap_v2", "pancakeswap_v3",
"vm:curve", "fluid_v1", "ekubo_v2", "ekubo_v3",
].into_iter().map(str::to_owned).collect(),
resolver_address: "0x2B658151310A7793E88E9038b927d5B25EC6915e".parse()?,
chain_id: 1,
min_tvl: 10.0,
slippage: 0.005,
ready_timeout: Duration::from_secs(30),
orderbook_interval: Duration::from_secs(12),
};
let backrunner = Backrunner::build(config).await?;
let (event_tx, event_rx) = mpsc::channel::<BuildEvent>(1024);
let (candidate_tx, candidate_rx) = watch::channel(None::<BackrunCandidate>);
tokio::spawn(backrunner.run(event_rx, candidate_tx));Pending state. The engine uses pending AMM state (mid-block pool state derived from transactions seen so far) when evaluating routes. Currently only Uniswap V3 supports pending state; all other protocols fall back to confirmed state. Support for additional protocols is in progress.
For each block-building iteration your builder should emit:
// Start of iteration
event_tx.send(BuildEvent::IterationStart { uuid, block }).await?;
// After each transaction executes
event_tx.send(BuildEvent::TxExecuted { uuid, tx }).await?;
// When iteration finishes
event_tx.send(BuildEvent::IterationComplete { uuid, state }).await?;
// or, if aborted:
event_tx.send(BuildEvent::IterationAborted { uuid }).await?;The backrunner binary exposes the same engine over a message queue transport. Compile it and deploy it as a sidecar; send BuildEvent JSON to its input queue and consume BackrunCandidate JSON from its output queue.
Queue wiring is not yet complete. See the TODOs in crates/backrunner/src/main.rs. The binary compiles and the engine runs; only the transport layer is missing.
A BackrunCandidate contains one or more unsigned EIP-1559 transactions (BackrunTx). Each carries an expected_profit_wei and expected_gas estimate. Your builder decides whether to include them:
if let Some(candidate) = candidate_rx.borrow().as_ref() {
for backrun_tx in &candidate.txs {
if backrun_tx.expected_profit_wei > min_profit_threshold {
let signed = your_eoa.sign_transaction(&backrun_tx.tx).await?;
block.include(signed);
}
}
}Candidates are keyed by uuid matching the originating IterationStart event. Stale candidates (from a previous iteration) should be discarded.
The smoke binary runs the full pipeline against live mainnet data without requiring a deployed resolver contract. It subscribes to Tycho, waits for the initial market snapshot, polls the 1inch Fusion orderbook, and issues one synthetic builder iteration per Ethereum block. For each iteration it attempts to build a candidate and validates it via eth_call using a bytecode state override.
Required env vars:
| Variable | Description |
|---|---|
TYCHO_URL |
Tycho WebSocket host (e.g. tycho-beta.propellerheads.xyz) |
TYCHO_API_KEY |
Your Tycho API key |
ETH_RPC_URL |
Ethereum JSON-RPC endpoint |
Optional env vars:
| Variable | Default | Description |
|---|---|---|
CHAIN_ID |
1 |
1inch Fusion chain ID |
RESOLVER_ADDRESS |
virtual address | Use a real deployed resolver; omit to inject bytecode via state override |
RUST_LOG |
— | Log filter (e.g. warn,backrunner=debug) |
Run:
TYCHO_URL=tycho-beta.propellerheads.xyz \
TYCHO_API_KEY=your-key \
ETH_RPC_URL=https://your-rpc \
RUST_LOG=warn,backrunner=debug \
cargo run --bin smokeExpected output: The binary logs market data ready once the initial Tycho snapshot loads (up to ~10 minutes on first run), then orderbook has N live Fusion orders. After that it logs one line per block — either no candidate, empty candidate, or candidate found with an eth_call result. swap output below auction price, skipping log lines are normal: they mean the Dutch auction hasn't decayed to a profitable price yet.
crates/builder-types/src/lib.rs: every type that crosses the boundarycrates/backrunner/src/lib.rs: the engine itself, including a full config referencecrates/backrunner/src/main.rs: the out-of-process entry point
The engine uses fynd-core for route-finding and tycho-simulation for market data. Both are published crates with public source.