MEV Bundles API
The MEV Bundles API is currently in development. These endpoints are not yet available. This documentation describes the planned API design for reference.
Want early access? Contact hello@axol.io to join the beta waitlist.
Submit and simulate MEV bundles through Flashbots and other relays. The MEV API will provide bundle management, simulation, and private transaction submission for atomic execution.
Key Features
- Bundle submission - Submit to Flashbots and other relays
- Bundle simulation - Test bundles before submission
- Private transactions - Flashbots Protect for sandwich protection
- Multi-relay support - Flashbots, MEV-Boost, BloxRoute, Eden
- Status tracking - Monitor bundle inclusion
Supported Relays
| Relay | Chains | Notes |
|---|---|---|
| Flashbots | ethereum | Primary relay |
| MEV-Boost | ethereum | Builder network |
| BloxRoute | ethereum | BDN network |
| Eden | ethereum | Priority network |
| Merkle | gnosis | Gnosis chain |
Planned Endpoints
All endpoints below are planned designs and not yet implemented.
Submit Bundle
POST /v1/mev/{chain}/bundle/submit
Submit a bundle of transactions for atomic inclusion.
Request
{
"transactions": [
{
"signed_tx": "0x02f8...",
"can_revert": false
},
{
"signed_tx": "0x02f8...",
"can_revert": true
}
],
"target_block": null,
"min_timestamp": null,
"max_timestamp": null,
"revert_on_fail": true
}
Parameters
| Field | Type | Description |
|---|---|---|
transactions | array | Ordered list of signed transactions |
transactions[].signed_tx | string | Signed transaction hex |
transactions[].can_revert | boolean | Whether this tx can revert |
target_block | int | Target block number (null for next) |
min_timestamp | int | Minimum block timestamp |
max_timestamp | int | Maximum block timestamp |
revert_on_fail | boolean | Cancel if any tx fails |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
relay | string | Target relay (flashbots, mev-boost, etc.) |
simulate | boolean | Simulate before submitting |
Response
{
"bundle_hash": "0xabc...",
"relay": "flashbots",
"target_block": 19500001,
"transactions": [
{"hash": "0x123...", "index": 0},
{"hash": "0x456...", "index": 1}
],
"simulation": {
"success": true,
"coinbase_diff": "50000000000000000",
"gas_used": 250000,
"state_changes": [...]
},
"submitted_at": "2026-01-15T10:30:00Z"
}
Simulate Bundle
POST /v1/mev/{chain}/bundle/simulate
Simulate a bundle without submitting.
Request
{
"transactions": [
{"signed_tx": "0x02f8...", "can_revert": false}
],
"block_number": null,
"state_block_number": null
}
Response
{
"success": true,
"simulation_block": 19500000,
"results": [
{
"tx_hash": "0x123...",
"success": true,
"gas_used": 150000,
"gas_price": 25000000000,
"coinbase_transfer": "25000000000000000",
"logs": [...],
"state_changes": [
{
"address": "0xabc...",
"key": "0x...",
"before": "0x...",
"after": "0x..."
}
]
}
],
"total_gas_used": 150000,
"coinbase_diff": "25000000000000000",
"coinbase_diff_eth": 0.025
}
Get Bundle Status
GET /v1/mev/{chain}/bundle/{bundle_hash}
Check the status of a submitted bundle.
Response
{
"bundle_hash": "0xabc...",
"status": "included",
"target_block": 19500001,
"included_block": 19500001,
"transactions": [
{
"hash": "0x123...",
"status": "included",
"block_number": 19500001,
"gas_used": 150000
}
],
"relay": "flashbots",
"submitted_at": "2026-01-15T10:30:00Z",
"included_at": "2026-01-15T10:30:12Z"
}
Status Values
| Status | Description |
|---|---|
pending | Submitted, waiting for inclusion |
included | Successfully included in block |
failed | Bundle failed (reverted or not included) |
expired | Target block passed without inclusion |
cancelled | Bundle was cancelled |
Cancel Bundle
DELETE /v1/mev/{chain}/bundle/{bundle_hash}
Attempt to cancel a pending bundle.
{
"bundle_hash": "0xabc...",
"cancelled": true,
"message": "Bundle cancellation submitted"
}
Send Private Transaction
POST /v1/mev/{chain}/private
Submit a single transaction via Flashbots Protect (private mempool).
Request
{
"signed_tx": "0x02f8...",
"max_block_number": null,
"hints": ["calldata", "logs"]
}
Parameters
| Field | Type | Description |
|---|---|---|
signed_tx | string | Signed transaction hex |
max_block_number | int | Max block for inclusion (null = 25 blocks) |
hints | array | Data to share with builders (none, calldata, logs, all) |
Response
{
"tx_hash": "0x123...",
"status": "pending",
"max_block": 19500025,
"submitted_at": "2026-01-15T10:30:00Z"
}
Code Examples
Python - Bundle Submission
import asyncio
import aiohttp
from web3 import Web3
from eth_account import Account
class MEVClient:
def __init__(self, api_key: str, private_key: str, chain: str = "ethereum"):
self.api_key = api_key
self.chain = chain
self.base_url = "https://api.axol.io/api/v1"
self.account = Account.from_key(private_key)
self.w3 = Web3()
async def simulate_bundle(self, signed_txs: list[str]) -> dict:
"""Simulate a bundle."""
async with aiohttp.ClientSession() as session:
url = f"{self.base_url}/mev/{self.chain}/bundle/simulate"
payload = {
"transactions": [
{"signed_tx": tx, "can_revert": False}
for tx in signed_txs
]
}
async with session.post(
url,
headers={"X-API-Key": self.api_key},
json=payload
) as resp:
return await resp.json()
async def submit_bundle(
self,
signed_txs: list[str],
target_block: int = None,
relay: str = "flashbots"
) -> dict:
"""Submit a bundle."""
async with aiohttp.ClientSession() as session:
url = f"{self.base_url}/mev/{self.chain}/bundle/submit"
payload = {
"transactions": [
{"signed_tx": tx, "can_revert": False}
for tx in signed_txs
],
"target_block": target_block,
"revert_on_fail": True
}
async with session.post(
url,
headers={"X-API-Key": self.api_key},
json=payload,
params={"relay": relay, "simulate": True}
) as resp:
return await resp.json()
async def get_bundle_status(self, bundle_hash: str) -> dict:
"""Check bundle status."""
async with aiohttp.ClientSession() as session:
url = f"{self.base_url}/mev/{self.chain}/bundle/{bundle_hash}"
async with session.get(
url,
headers={"X-API-Key": self.api_key}
) as resp:
return await resp.json()
async def send_private(self, signed_tx: str) -> dict:
"""Send via Flashbots Protect."""
async with aiohttp.ClientSession() as session:
url = f"{self.base_url}/mev/{self.chain}/private"
payload = {
"signed_tx": signed_tx,
"hints": ["calldata"]
}
async with session.post(
url,
headers={"X-API-Key": self.api_key},
json=payload
) as resp:
return await resp.json()
# Usage
async def main():
client = MEVClient(
api_key=os.getenv("AXOL_API_KEY"),
private_key=os.getenv("PRIVATE_KEY")
)
# Build transactions
tx1 = {...} # Your first transaction
tx2 = {...} # Your second transaction
signed_tx1 = client.account.sign_transaction(tx1).rawTransaction.hex()
signed_tx2 = client.account.sign_transaction(tx2).rawTransaction.hex()
# Simulate first
sim = await client.simulate_bundle([signed_tx1, signed_tx2])
print(f"Simulation success: {sim['success']}")
print(f"Expected profit: {sim['coinbase_diff_eth']} ETH")
if sim['success']:
# Submit bundle
result = await client.submit_bundle([signed_tx1, signed_tx2])
print(f"Bundle hash: {result['bundle_hash']}")
# Wait and check status
await asyncio.sleep(15)
status = await client.get_bundle_status(result['bundle_hash'])
print(f"Status: {status['status']}")
asyncio.run(main())
TypeScript - Arbitrage Bundle
import { ethers } from 'ethers';
interface BundleTransaction {
signed_tx: string;
can_revert: boolean;
}
interface SimulationResult {
success: boolean;
coinbase_diff_eth: number;
total_gas_used: number;
}
class ArbitrageBot {
private apiKey: string;
private wallet: ethers.Wallet;
private baseUrl: string;
constructor(apiKey: string, privateKey: string) {
this.apiKey = apiKey;
this.wallet = new ethers.Wallet(privateKey);
this.baseUrl = 'https://api.axol.io/api/v1';
}
async simulateArbitrage(
buyTx: ethers.TransactionRequest,
sellTx: ethers.TransactionRequest
): Promise<SimulationResult> {
const signedBuy = await this.wallet.signTransaction(buyTx);
const signedSell = await this.wallet.signTransaction(sellTx);
const response = await fetch(`${this.baseUrl}/mev/ethereum/bundle/simulate`, {
method: 'POST',
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
transactions: [
{ signed_tx: signedBuy, can_revert: false },
{ signed_tx: signedSell, can_revert: false }
]
})
});
return response.json();
}
async executeArbitrage(
buyTx: ethers.TransactionRequest,
sellTx: ethers.TransactionRequest,
minProfitEth: number
): Promise<string | null> {
// Simulate first
const sim = await this.simulateArbitrage(buyTx, sellTx);
if (!sim.success) {
console.log('Simulation failed');
return null;
}
if (sim.coinbase_diff_eth < minProfitEth) {
console.log(`Profit ${sim.coinbase_diff_eth} ETH below minimum`);
return null;
}
// Sign and submit
const signedBuy = await this.wallet.signTransaction(buyTx);
const signedSell = await this.wallet.signTransaction(sellTx);
const response = await fetch(`${this.baseUrl}/mev/ethereum/bundle/submit`, {
method: 'POST',
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
transactions: [
{ signed_tx: signedBuy, can_revert: false },
{ signed_tx: signedSell, can_revert: false }
],
revert_on_fail: true
}),
params: { relay: 'flashbots', simulate: 'true' }
});
const result = await response.json();
return result.bundle_hash;
}
}
Best Practices
1. Always Simulate First
Never submit without simulating:
sim = await client.simulate_bundle(txs)
if not sim['success']:
print(f"Simulation failed: {sim.get('error')}")
return
if sim['coinbase_diff_eth'] < MIN_PROFIT:
print("Not profitable")
return
# Now submit
result = await client.submit_bundle(txs)
2. Use Appropriate Relay
Different relays have different characteristics:
| Relay | Best For |
|---|---|
| Flashbots | Standard MEV, privacy |
| MEV-Boost | Maximum builder coverage |
| BloxRoute | Low latency |
| Eden | Priority for stakers |
3. Handle Competition
Other searchers are competing for the same opportunities:
# Include competitive priority fee
tx['maxPriorityFeePerGas'] = web3.to_wei(5, 'gwei')
# Or include coinbase transfer for higher priority
coinbase_payment = web3.to_wei(0.01, 'ether')
4. Track Bundle Status
Don't assume inclusion - verify:
for i in range(10): # Check for 10 blocks
status = await client.get_bundle_status(bundle_hash)
if status['status'] == 'included':
print(f"Included in block {status['included_block']}")
break
elif status['status'] in ('failed', 'expired'):
print(f"Bundle failed: {status['status']}")
break
await asyncio.sleep(12) # One block
5. Use Private Transactions for Protection
For non-MEV transactions, use Flashbots Protect:
# Avoid sandwich attacks on your swap
result = await client.send_private(signed_swap_tx)
Rate Limits
| Tier | Simulations/min | Submissions/min |
|---|---|---|
| Free | 10 | 5 |
| Pro | 100 | 50 |
| Enterprise | 1000 | 500 |
See Also
- Gas Oracle - Gas prices for bundle pricing
- Mempool Streaming - Find opportunities
- Gateway - Direct node access