Bundles
A bundle is an ordered list of signed transactions that land together, in the order given, in one block, or not at all.
Access
The partner panel shows your key, your bundle limit and the bundles you sent, and resets the key.
Send the key in the Authorization header of every request:
Authorization: Bearer <your key>
Without a valid key the bundle methods answer as if they did not exist
(-32601).
eth_sendBundle
eth_sendMevBundle is the same method under another name. Both are served by
every RPC endpoint.
curl -s https://rpc.hades.build \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $HADES_KEY" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendBundle",
"params": [{
"txs": ["0x02f8...", "0x02f8..."],
"maxBlockNumber": <current block + 10>,
"revertingTxHashes": []
}]
}'
{"jsonrpc":"2.0","id":1,"result":"0x8a3b...41c0"}
Replace <current block + 10> with a block number, or omit maxBlockNumber
for current block + 100.
Parameters
One object:
| Field | Type | Required | Description |
|---|---|---|---|
txs |
array of hex strings | yes | Signed raw transactions, in execution order. At most 2 by default, higher on request. |
maxBlockNumber |
number | no | Last block the bundle may land in, as a plain JSON number, not hex. Must be after the current block and at most 1,000 blocks ahead. Defaults to the current block + 100. |
revertingTxHashes |
array of hashes | no | Hashes of transactions in txs that may revert. A revert of any other transaction drops the bundle. |
Result
The bundle hash: keccak256 of the transaction hashes, in order, followed by
maxBlockNumber (or its default) as 8 big-endian bytes. The same
transactions sent with another maxBlockNumber are another bundle.
Limits
| Rule | Value |
|---|---|
| Transactions per bundle | 2 by default, higher on request |
| Average gas price | at least 0.05 gwei |
| Size | 1 MiB of raw transactions |
| Gas | the sum of the gas limits is at most the block gas limit |
The average gas price is sum(gasUsed × effectiveGasPrice) / sum(gasUsed)
over the bundle's transactions, and exactly 0.05 gwei passes. A single
transaction may pay no tip as long as the bundle as a whole meets the floor.
Each transaction must pass the usual transaction pool checks (signature, chain id, intrinsic gas, size up to 128 KiB, a nonce not below the sender's current one). Blob transactions are not accepted.
Errors
Refusals are JSON-RPC errors with code -38000 and a message naming the
cause:
| Message | Cause |
|---|---|
bundle has no transactions |
txs is empty |
bundle has more than N transactions |
more transactions than your limit |
bundle gas exceeds the block gas limit |
the gas limits add up to more than a block |
bundle target block is not after the head |
maxBlockNumber has passed |
bundle target block is more than 1000 blocks ahead |
maxBlockNumber is too far |
revertingTxHashes holds a hash not in txs |
an entry of revertingTxHashes is not one of txs |
bundle already exist |
the same bundle was already sent |
too many pending bundles for this partner |
too many of your bundles are still pending; wait for them to land or expire |
bundle average gas price is below 0.05 gwei |
the bundle pays under the floor |
tx N reverted and is not in revertingTxHashes |
transaction N reverted without being allowed to |
tx N: ... |
transaction N failed to decode or validate |
bundle simulation failed: ... |
the bundle could not be simulated |
bundle pool is full |
temporary: send again later |
bundle simulation unavailable |
temporary: send again later |
bundle simulation interrupted by an interpreter fallback, retry |
temporary: send again |
A string in txs that is not 0x-prefixed or has an odd length, or a hex
maxBlockNumber, is -32602 invalid argument 0: .... Valid hex that is not
a transaction is -38000 tx N: decode: ....
Examples
Each example sends two signed transactions, tx1 and tx2, as one bundle
with the key in the HADES_KEY environment variable.
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
tx1, tx2 := "0x02f8...", "0x02f8..." // signed raw transactions
body, _ := json.Marshal(map[string]any{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendBundle",
"params": []any{map[string]any{
"txs": []string{tx1, tx2}, // no maxBlockNumber: current block + 100
}},
})
req, _ := http.NewRequest("POST", "https://rpc.hades.build", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+os.Getenv("HADES_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var out struct {
Result string `json:"result"`
Error json.RawMessage `json:"error"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
panic(err)
}
fmt.Println(out.Result, string(out.Error))
}
Rust
[dependencies]
reqwest = { version = "0.12", features = ["blocking", "json"] }
serde_json = "1"
use serde_json::{json, Value};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (tx1, tx2) = ("0x02f8...", "0x02f8..."); // signed raw transactions
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendBundle",
"params": [{ "txs": [tx1, tx2] }] // no maxBlockNumber: current block + 100
});
let out: Value = reqwest::blocking::Client::new()
.post("https://rpc.hades.build")
.bearer_auth(std::env::var("HADES_KEY")?)
.json(&body)
.send()?
.json()?;
println!("{out}");
Ok(())
}
Python
import os
import requests
tx1, tx2 = "0x02f8...", "0x02f8..." # signed raw transactions
resp = requests.post(
"https://rpc.hades.build",
headers={"Authorization": f"Bearer {os.environ['HADES_KEY']}"},
json={
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendBundle",
"params": [{"txs": [tx1, tx2]}], # no maxBlockNumber: current block + 100
},
timeout=10,
)
print(resp.json())
JavaScript
Save as bundle.mjs and run with node bundle.mjs (Node.js 18 or later; an
ES module, for the top-level await). Server side only: keep the key out of
browsers.
const [tx1, tx2] = ["0x02f8...", "0x02f8..."]; // signed raw transactions
const resp = await fetch("https://rpc.hades.build", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.HADES_KEY}`,
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_sendBundle",
params: [{ txs: [tx1, tx2] }], // no maxBlockNumber: current block + 100
}),
});
console.log(await resp.json());