Skip to content

Source integration branch

Orderflow Provider Integration

This guide is for orderflow providers (OFPs) that route private transactions into TOOL, either as a plain bundle through eth_sendBundle or through the two-step quote flow (ofp_sendUnsignedBundle + ofp_sendSignedBundle), and optionally receive solutions on their flow through a callback.

Value Proposition

OFPs send private order flow into TOOL and let searchers on every node compete to add value around it, without running a searching strategy of their own. A callback is optional: with one, the OFP receives only solutions built purely on its own flow and decides itself what to do with them; with the builders allow-list, it controls which external builders may receive its bundles. Your callback never receives another source's transactions, and no other callback receives yours.

This guide explains what the shared Delivery Rules mean for an OFP. Error strings are listed in Errors.

Choosing a Flow

Plain bundle: eth_sendBundle Quote flow: ofp_sendUnsignedBundle + ofp_sendSignedBundle
Use when You already have signed transactions Your user must not sign until you know the outcome of a backrun
Calls One Two, to the same node, plus a callback endpoint
Callback Optional Required for the unsigned call, optional for the signed call

Node access. Either run your own node (you control uptime; solutions are found by every node in the network, so you need no searching strategy of your own; your FlashbotsKey signs your outbound callbacks), or use an endpoint of the TOOL team or a third party and get its signing address from them. The node must run with callback mode enabled.

Fields shared by all calls

Field Notes
txs Raw signed transactions; for the unsigned call, eth_call-style objects (Quote Flow)
blockNumber "0x0", "pending", "latest" or the node's exact next block number. Anything else: state not ready (see Errors)
callbackUrl http or https URL with a host, at most 512 characters
builders Optional, see The builders Field
minTimestamp, maxTimestamp, revertingTxHashes eth_sendBundle only. Rejected by the unsigned call, ignored by the signed call

Blob transactions are not supported anywhere.

The builders Field

Accepted by eth_sendBundle and ofp_sendSignedBundle. It lists the external builders allowed to receive bundles built from your order flow.

callbackUrl builders Your bundle itself goes to Solutions on it go to
no omitted or null all builders all builders
no ["titan"] titan titan
no [] nowhere nowhere, except through relay merging (Merged Solutions)
yes omitted or null nowhere your callback
yes ["titan"] nowhere your callback and titan
yes ["*"] nowhere your callback and all builders
  • "*" selects every registered builder. Names are case-insensitive, surrounding whitespace is ignored, duplicates are removed.
  • Unknown names are dropped silently. If none of the names is known, the call fails with unknown builders: ....
  • Send [] literally. A client that drops empty arrays (Go's omitempty, for example) sends no field and gets the default.
  • Registered builders on mainnet: flashbots, beaverbuild.org, buildernet, titan, btcs, bobthebuilder, eureka, bombora, quasar, ethgas, ultrasound.
  • Some builders forward order flow to other parties. builders cannot control that.

Plain Bundle

See eth_sendBundle and SendBundleArgs.

curl -X POST http://<your-node-rpc>:8545 -H "Content-Type: application/json" -d '{
  "jsonrpc": "2.0", "id": 1, "method": "eth_sendBundle",
  "params": [{
    "txs": ["0x02f8b0...", "0x02f8a3..."],
    "blockNumber": "0x0",
    "callbackUrl": "https://your-ofp-endpoint.example.com/tool-callback",
    "builders": ["titan"]
  }]
}'

The response {"bundleHash": "0x..."} confirms only that the bundle was accepted. Where it goes is the table in The builders Field. Forwarding to builders happens in the background; builder failures are never reported to you.

Callbacks. Your bundle is gossiped and not pinned to the node you called. Whichever node's searcher builds a solution on it sends the callback from that node, signed with that node's key, in the format of Step 6. Expect zero, one or several callbacks per submission.

eth_sendRawTransaction is the same path in miniature: the transaction also becomes a bundle with all builders and no callback, so it is forwarded to all builders right away.

Quote Flow

See ofp_sendUnsignedBundle, ofp_sendSignedBundle and the Quote Callback Contract.

sequenceDiagram
    participant O as OFP
    participant N as TOOL node (origin)
    participant S as Searchers (any node)
    participant B as Builders
    O->>N: 1. ofp_sendUnsignedBundle (txs, callbackUrl)
    N-->>O: bundleHash
    N->>S: unsigned commitment (gossip)
    S->>N: solution on the unsigned commitment
    N->>O: 4. quote callback (one per candidate)
    O->>N: 5. ofp_sendSignedBundle (signed txs, unsignedCmtId, optional callbackUrl, builders)
    N->>O: 6. final callback (signed bundle), if callbackUrl set
    N->>B: signed bundle, in the background
    N-->>O: bundleHash of the signed commitment

Steps 2 and 3 (searchers find and attach solutions) are internal.

Step 1. Request a quote: ofp_sendUnsignedBundle

Request schema: UnsignedTxArgs.

{
  "txs": [{
    "from": "0x1111111111111111111111111111111111111111",
    "to": "0x2222222222222222222222222222222222222222",
    "value": "0x2386f26fc10000",
    "gas": "0x5208",
    "maxFeePerGas": "0x77359400",
    "maxPriorityFeePerGas": "0x3b9aca00",
    "nonce": "0x0",
    "chainId": "0x0"
  }],
  "blockNumber": "0x0",
  "callbackUrl": "https://your-ofp-endpoint.example.com/tool-quote"
}
  • txs are call-style objects, not signed transactions. from must be a real, funded account. nonce is required and is not filled from chain state. to is omitted for contract creation. Legacy (gasPrice) or EIP-1559 pricing, data/input, accessList, chainId and authorizationList are supported.
  • callbackUrl is required. builders is ignored here (pass it in step 5). revertingTxHashes is rejected.
  • Rate limit: 100 requests per second.
  • The response bundleHash is not what you pass in step 5.

Step 4. Receive quotes

TOOL POSTs each candidate solution to the step 1 callbackUrl. Quotes come only from the node that accepted step 1, because only it holds your unsigned transaction bodies. The X-Flashbots-Signature header is empty: do not require a signature on quotes.

{
  "commitment": {
    "id": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "blockTime": 1753000000,
    "blockParent": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
    "subslot": 0,
    "expirySubslot": 15,
    "txsSize": 340,
    "execGasUsed": 145000,
    "stateGasUsed": 0,
    "maxExecGasLimit": 30000000,
    "maxStateGasLimit": 0,
    "fees": "210000000000000",
    "accesses": {},
    "callbackUrl": "0x5f0e...<hash of your callback URL>",
    "extBuilders": ["flashbots", "titan"],
    "unsignedTxs": ["0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
    "stateDiff": {
      "accounts": {
        "0x1111111111111111111111111111111111111111": { "balance": "-3120000000000000", "nonce": 1 },
        "0x2222222222222222222222222222222222222222": { "balance": "3000000000000000" }
      }
    }
  },
  "messages": [{
    "from": "0x1111111111111111111111111111111111111111",
    "to": "0x2222222222222222222222222222222222222222",
    "value": "0x2386f26fc10000",
    "gasLimit": "0x5208",
    "gasPrice": "0x77359400",
    "gasFeeCap": "0x77359400",
    "gasTipCap": "0x3b9aca00",
    "data": "0x",
    "blobGasFeeCap": "0x0"
  }]
}

What matters in a quote:

  • id: pass it as unsignedCmtId in step 5 for the candidate you choose.
  • unsignedTxs: hashes of your own transactions, identical for every candidate of one submission. Use it to correlate quotes. callbackUrl cannot be used for that: it is a hash of the URL, not the URL.
  • stateDiff.accounts: a map keyed by address. balance is a signed decimal string. An absent address means no change.
  • extBuilders: the builders that may receive the final bundle of this candidate. Your step 5 builders is limited to it. Omitted when empty.
  • messages: the resolved execution arguments of every transaction in the candidate, in order (yours, then the searcher's).

Choosing a candidate. Compare stateDiff.accounts[target].balance for your target address across the candidates and take the highest. Do it within the block: candidates stop being resolvable when the chain head moves.

Things to plan for

  • Zero quotes is normal. Use a timeout.
  • A quote can arrive later than its solution appeared: if the searcher's transactions have not reached your node yet, the quote is sent when they do.
  • stateDiff can be null (and logs absent) for a very large candidate: one that is 400 KB or more, or that needs more gas than one subslot offers. You cannot rank such a candidate by balance.
  • A quote never contains another source's transactions (Merged Solutions).

Your endpoint must answer within 5 seconds with HTTP status below 300 and a JSON body without an error field. There is no retry.

Step 5. Finalize: ofp_sendSignedBundle

Request schema: SignedBundleArgs.

Have your user sign transactions with the quoted parameters and submit them to the same node as step 1:

{
  "txs": ["0x02f8b0..."],
  "blockNumber": "0x0",
  "callbackUrl": "https://your-ofp-endpoint.example.com/tool-final",
  "builders": ["titan", "flashbots"],
  "unsignedCmtId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}
  • txs: the signed versions of the step 1 transactions, in the same order.
  • unsignedCmtId: the id of the chosen quote.
  • callbackUrl: optional and independent of the step 1 URL. Without it you get no final callback.
  • builders: as in The builders Field, then limited to the candidate's extBuilders.

The signed bundle always joins the TOOL network (pool and gossip), so relay merging can include it and searchers can build on it.

What TOOL checks. It re-executes your signed transactions with the solver's and compares the resulting state diff with the quoted one. Any change of the outcome fails with signed bundle state diff mismatch. Differences that leave the state untouched (a higher gas limit with the same gas used) are accepted.

The call can wait. If a transaction of the chosen candidate has not reached the node yet, the call waits up to 1 second for the missing transaction, or until the block is sealed if that comes first. A shorter client timeout ends the call first.

Do not retry after a callback error

The node validates the bundle, stores it, gossips it and only then delivers it. The final callback is sent before the RPC response, so your endpoint must not wait for that response. If your callback fails, the call returns the error, but the bundle is already in the network and builders may already have it. A retry is rejected as already known.

Step 6. Final callback

See the Callback Receiver Contract.

POST <step 5 callbackUrl>
Content-Type: application/json
Accept: application/json
X-Flashbots-Signature: <address>:<hex signature>
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_sendBundle",
  "params": [{
    "txs": ["0x02f8b0...", "0x02f8a3..."],
    "blockNumber": "0x152dd21",
    "minTimestamp": 1753000000,
    "maxTimestamp": 1753000000
  }]
}

txs holds every transaction in order (yours, then the solver's), ready to broadcast. revertingTxHashes appears only when it is not empty. builders is never included. The same body goes to the selected builders.

Step 7. Follow-up callbacks

Searchers on any node may build further solutions on your signed bundle. A solution that only adds the searcher's transactions inherits your step 5 callback and is delivered there in the step 6 format, from the node that built it and signed with that node's key. A solution that merges your bundle with another source is not delivered to you.

Callback Endpoint

  • Acknowledge fast: 5 seconds, HTTP status below 300, JSON without error. No retries.
  • Expect zero, one or many calls per submission, from different nodes with different keys.
  • Deduplicate by transaction hashes.
  • Do not block on the RPC response of ofp_sendSignedBundle.
  • Verify signed callbacks (the standard Flashbots scheme):
  • Split X-Flashbots-Signature on : into signerAddress and signatureHex.
  • bodyHashHex = the hex string (0x..., 66 characters) of keccak256(rawBody).
  • digest = keccak256("\x19Ethereum Signed Message:\n66" + bodyHashHex).
  • ecrecover(digest, signatureHex) must equal signerAddress and a TOOL signing address you trust (for solutions built by other nodes, that node's address).
  • Quotes carry an empty signature header. So do signed callbacks from a node without a FlashbotsKey.
  • Treat the callback URL as visible to node operators: it appears as a hash in JSON but in clear text in node logs.

Merged Solutions

A searcher can build on your bundle in two ways:

  1. Add its own transactions, or combine bundles that all have your callback. You get the solution.
  2. Merge your bundle with another source's bundle, or with one that has no callback. No callback gets this solution. It goes only to the builders allowed by all merged bundles, or to the external relay on relay merging nodes.

What follows for you:

  • Your callback never receives another source's transactions, and no other callback receives yours.
  • A callback-only bundle (callback, no builders) can be merged only on relay merging nodes. To let it take part in merged solutions everywhere, send builders with it; merged solutions then go to those builders and your callback is not notified.
  • A bundle with no callback and "builders": [] is delivered to nobody by ordinary nodes; only relay merging uses it.
  • In the quote flow, a merge with another source always fails: nothing can be delivered until you have signed.

For errors see Errors; for worked cases see Delivery by Participant (Orderflow provider view).

Attribution

TOOL attributes inclusion to the source commitment it sees. If an OFP sends flow directly to TOOL, the OFP can be the visible source. If an OFP sends flow to a builder and the builder submits a merged bundle to TOOL, the builder is the visible source for that merged commitment unless a later attribution model is agreed.

Authentication and TEE Verification

Send an X-Flashbots-Signature header with each request to a TOOL node, as described in Conventions.

Outbound callbacks are signed by the delivering TOOL node's FlashbotsKey (quotes are unsigned); see Callback Endpoint. TEE verification is optional from the integrator's side: OFPs that require end-to-end privacy assurance can verify TOOL attestation or RA-TLS before sending private flow.

Privacy-sensitive deployments

Use attestation verification before sending private flow when the integration requires proof that traffic is terminating inside the expected TOOL environment. Use RA-TLS when the transport itself must be bound to that attested service.