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'somitempty, 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.
builderscannot 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"
}
txsare call-style objects, not signed transactions.frommust be a real, funded account.nonceis required and is not filled from chain state.tois omitted for contract creation. Legacy (gasPrice) or EIP-1559 pricing,data/input,accessList,chainIdandauthorizationListare supported.callbackUrlis required.buildersis ignored here (pass it in step 5).revertingTxHashesis rejected.- Rate limit: 100 requests per second.
- The response
bundleHashis 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 asunsignedCmtIdin 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.callbackUrlcannot be used for that: it is a hash of the URL, not the URL.stateDiff.accounts: a map keyed by address.balanceis a signed decimal string. An absent address means no change.extBuilders: the builders that may receive the final bundle of this candidate. Your step 5buildersis 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.
stateDiffcan benull(andlogsabsent) 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: theidof the chosen quote.callbackUrl: optional and independent of the step 1 URL. Without it you get no final callback.builders: as in ThebuildersField, then limited to the candidate'sextBuilders.
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-Signatureon:intosignerAddressandsignatureHex. bodyHashHex= the hex string (0x..., 66 characters) ofkeccak256(rawBody).digest = keccak256("\x19Ethereum Signed Message:\n66" + bodyHashHex).ecrecover(digest, signatureHex)must equalsignerAddressand 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:
- Add its own transactions, or combine bundles that all have your callback. You get the solution.
- 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, sendbuilderswith 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.