FastLane MEV Auction Handler - Searcher Integration Guide
Overview
The FastLane Auction Handler enables searchers to submit MEV bids on Monad for Top of Block ("TOB") or backrun opportunities.
Core Concepts
Bid Submission Flow
- Searcher bids are transactions that call the
flashExecutionBid()function of the FastLane Auction Handler contract with a bid amount, target block, and execution payload. The transactions are submitted viaeth_sendRawTransaction.
tip
You can submit your transaction on any RPC service or node. The transaction will make its way to validators via RaptorCast just like any other Monad transaction.
- For the targeted block, the auction handler executes the searcher's contract via
fastLaneCall(). - Searcher performs MEV operations
- Searcher repays bid amount to auction handler
- Proceeds distributed to validators
Auction Handler Interface
interface IFastLaneAuctionHandler {
// Entry point function
function flashExecutionBid(
uint256 bidAmount,
bytes32[] calldata txHashes,
uint256 targetBlockNumber,
bool executeOnLoss,
bool payBidOnFail,
address searcherToAddress,
bytes calldata searcherCallData
)
external
payable;
}
Parameters
bidAmount- Amount of native token (e.g., MON) you agree to pay on success (and potentially on failure ifpayBidOnFail = true).txHashes- Array of txHashes that the MEV client will ensure your transaction is executed directly after. Use[bytes32(0)]for ToB.targetBlockNumber- Block number the bid is targeting. Set this to the specific block where you want the bid to compete.executeOnLoss- Iftrue, your transaction will be executed even if you lose the auction.payBidOnFail- Iftrue, the bid will still be paid if the searcher call fails.searcherToAddress- The address the relay will call. Must be your Searcher Direct/Proxy contract.searcherCallData- ABI-encoded call your Searcher executes, e.g.doStuff(...).
Smart Contract Integration
contract MySearcherBot is FastLaneSearcherDirectContract {
constructor() FastLaneSearcherDirectContract() {}
function myMEVFunction(/* params */) public payable returns (bool) {
// Security check
if (msg.sender != address(this)) {
require(approvedEOAs[msg.sender], "SenderEOANotApproved");
}
// MEV logic here
return true; // Return success status
}
}
Required Setup:
- Deploy your searcher contract
- Call
setMFLAuctionAddress(auction_address) - Call
approveFastLaneEOA(your_eoa)for authorized callers
Submitting Bids
Submitting bids or bundles is the same as sending a regular onchain transaction via eth_sendRawTransaction.
You create a normal transaction with the to address set to the auction handler contract and the calldata encoded for flashExecutionBid.
Auction Handler Contract Addresses:
- Monad Testnet:
0x11f34d16BB4B898c3a489B40cD1024d89A313b88 - Monad Mainnet:
0xD32EdF6642D917DbBE7B8BF8e5d6F5df6a9FFF58
Flash Execution Bid
// For atomic backruns
auctionHandler.flashExecutionBid{value: bidAmount}(
bidAmount, // Bid amount in native token
txHashes, // Array of tx hashes to backrun
targetBlockNumber, // Block number this bid should target
false, // executeOnLoss
false, // payBidOnFail
searcherContract, // Your searcher contract
searcherCallData // abi.encodeWithSignature("myMEVFunction(...)", ...)
);
For ToB bids, pass [bytes32(0)] as txHashes. For backruns, ensure targetBlockNumber matches the block where you expect the referenced transactions to land.
Security Requirements
Critical Checks
- Always verify caller: Use
onlyRelayermodifier - Check EOA permissions: Implement
checkFastLaneEOA(_sender) - Use reentrancy guards: Inherit
ReentrancyGuard - Validate balance: Ensure sufficient funds to repay bid
Example Security Pattern
function fastLaneCall(
address _sender,
uint256 _bidAmount,
bytes calldata _searcherCallData
) external payable onlyRelayer nonReentrant returns (bool, bytes memory) {
checkFastLaneEOA(_sender); // Verify caller authorization
(bool success, bytes memory data) = address(this).call(_searcherCallData);
if (!success) return (false, data);
require(address(this).balance >= _bidAmount, "Insufficient funds");
safeTransferETH(MFLAuction, _bidAmount);
return (true, data);
}
Full Example Contract
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;
import { ReentrancyGuard } from "@solady/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
abstract contract FastLaneSearcherDirectContract is ReentrancyGuard {
address public owner;
address payable private MFLAuction;
error WrongPermissions();
error OriginEOANotOwner();
error SearcherCallUnsuccessful(bytes retData);
error SearcherInsufficientFunds(uint256 amountToSend, uint256 currentBalance);
mapping(address => bool) internal approvedEOAs;
constructor() {
owner = msg.sender;
}
// The FastLane Auction contract will call this function
// The `onlyRelayer` modifier makes sure the calls can only come from MFL or will revert
// MFL will pass along the original msg.sender as _sender for the searcher to do additional checks
// Do NOT forget `onlyRelayer` and `checkFastLaneEOA(_sender);` or ANYONE will be able to call your contract with
// arbitrary calldata
function fastLaneCall(
address _sender, // Relay will always set this to msg.sender that called it. Ideally you (owner) or an
// approvedEOA.
uint256 _bidAmount,
bytes calldata _searcherCallData // contains func selector and calldata for your MEV transaction ie:
// abi.encodeWithSignature("doStuff(address,uint256)", 0xF00, 1212);
)
external
payable
onlyRelayer
nonReentrant
returns (bool, bytes memory)
{
// Make sure it's your own EOA that's calling your contract
checkFastLaneEOA(_sender);
// Execute the searcher's intended function
(bool success, bytes memory returnedData) = address(this).call(_searcherCallData);
if (!success) {
// If the call didn't turn out the way you wanted, revert either here or inside your MEV function itself
return (false, returnedData);
}
// Balance check then Repay MFL at the end
require(
(address(this).balance >= _bidAmount),
string(
abi.encodePacked(
"SearcherInsufficientFunds ",
Strings.toString(_bidAmount),
" ",
Strings.toString(address(this).balance)
)
)
);
safeTransferETH(MFLAuction, _bidAmount);
// /!\ Important to return success true or relay will revert.
// In case of success == false, `returnedData` will be used as revert message that can be decoded with
// `.humanizeError()`
return (success, returnedData);
}
// Other functions / modifiers that are necessary for FastLane integration:
// NOTE: you can use your own versions of these, or find alternative ways
// to implement similar safety checks. Please be careful when altering!
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
function setMFLAuctionAddress(address _mflAuction) public {
require(msg.sender == owner, "OriginEOANotOwner");
MFLAuction = payable(_mflAuction);
}
function approveFastLaneEOA(address _eoaAddress) public {
require(msg.sender == owner, "OriginEOANotOwner");
approvedEOAs[_eoaAddress] = true;
}
function revokeFastLaneEOA(address _eoaAddress) public {
require(msg.sender == owner, "OriginEOANotOwner");
approvedEOAs[_eoaAddress] = false;
}
function checkFastLaneEOA(address _eoaAddress) internal view {
require(approvedEOAs[_eoaAddress] || _eoaAddress == owner, "SenderEOANotApproved");
}
function isTrustedForwarder(address _forwarder) public view returns (bool) {
return _forwarder == MFLAuction;
}
// Be aware with a fallback fn that:
// `address(this).call(_searcherCallData);`
// Will hit this if _searcherCallData function is not implemented.
// And success will be true.
fallback() external payable { }
receive() external payable { }
modifier onlyRelayer() {
if (!isTrustedForwarder(msg.sender)) revert("InvalidPermissions");
_;
}
}
contract SearcherContractExample is FastLaneSearcherDirectContract {
// Your own MEV contract / functions here
// NOTE: its security checks must be compatible w/ calls from the FastLane Auction Contract
address public anAddress; // just a var to change for the placeholder MEV function
uint256 public anAmount; // another var to change for the placeholder MEV function
function doStuff(address _anAddress, uint256 _anAmount) public payable returns (bool) {
// NOTE: this function can't be external as the FastLaneCall func will call it internally
if (msg.sender != address(this)) {
// NOTE: msg.sender becomes address(this) if using call from inside contract per above example in
// `fasfastLaneCall`
require(approvedEOAs[msg.sender], "SenderEOANotApproved");
}
// Do MEV stuff here
// placeholder
anAddress = _anAddress;
anAmount = _anAmount;
bool isSuccessful = true;
return isSuccessful;
}
function doFail() public payable {
if (msg.sender != address(this)) {
// NOTE: msg.sender becomes address(this) if using call from inside contract per above example in
// `fasfastLaneCall`
require(approvedEOAs[msg.sender], "SenderEOANotApproved");
}
// Will cause Error(string) of:
// 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000f4641494c5f4f4e5f505552504f53450000000000000000000000000000000000
// to bubble up to the relay contract.
// Use the read function `FastLaneRelay.humanizeError(bytes error)` to get a human readable version of an error
// should your searcher contract fail on a require.
require(false, "FAIL_ON_PURPOSE");
}
}