The solverOpData parameter is a crucial component that contains the transaction data required by your solver contract. This data ensures that your MEV strategy executes precisely and securely within the Atlas ecosystem.
Why is solverOpData Important?
Executes yourMEV strategy on the solver contract
Atlas constructs the call to atlasSolverCall and passes the solverOpData along.
The simplest way to obtain the correct solverOpData is to create a transaction directly to your solver contract (as if Atlas didn't exist) and extract that transaction's data field.
There are two primary approaches to using solverOpData, and the choice depends on your specific implementation:
There are many approaches on how you want to use the solverOpData and they're ultimately up to you.
Potential Approaches:
Approach 1: Direct MEV Logic Implementation in Solver Contract
MEV logic alongside the solver contract which implements the ISolverContract
atlasSolverCall should be calling an internal function
In this method, your solver contract contains all the MEV logic within its functions. The solverOpData is an encoding of a function call within your solver contract, such as solve()
We provide a helper SolverBase which redirects the callData. Your solverOpData should encode your function here solve()
// SPDX-License-Identifier: MITpragmasolidity 0.8.25;import { SafeTransferLib } from"solady/utils/SafeTransferLib.sol";import { Ownable } from"openzeppelin-contracts/contracts/access/Ownable.sol";import { IERC20 } from"openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";import { SolverBase } from"../lib/atlas/src/contracts/solver/SolverBase.sol";/** * @title AtlasDirectSolver * @notice A simple example solver contract that inherits from SolverBase * @dev SolverBase is a helper contract that: * 1. Implements ISolverContract interface required for all solvers * 2. Handles bid payments to Atlas through the payBids modifier * 3. Provides security checks via safetyFirst modifier to ensure: * - Only Atlas can call atlasSolverCall * - Only the owner can initiate solver operations * - Proper reconciliation of funds with Atlas escrow * 4. Manages WETH/ETH conversions as needed for bid payments */contractAtlasDirectSolverisSolverBase, Ownable {boolinternal s_shouldSucceed;constructor(address weth,address atlas) SolverBase(weth, atlas, msg.sender) Ownable(msg.sender) { s_shouldSucceed =true; // should succeed by default, can be set to false }fallback() externalpayable { }receive() externalpayable { }// Called during the SolverOperation phase// This function is called by atlasSolverCall() which forwards the solverOpData calldata// by doing: address(this).call{value: msg.value}(solverOpData)// where solverOpData contains the ABI-encoded call to solve()functionsolve() publicviewonlySelf {// SolverBase automatically handles paying the bid amount to the Execution Environment through// the payBids modifier, as long as this contract has sufficient balance (ETH or WETH) }// ---------------------------------------------------- //// ONLY OWNER //// ---------------------------------------------------- //functionwithdrawETH(address recipient) publiconlyOwner { SafeTransferLib.safeTransferETH(recipient,address(this).balance); }functionwithdrawERC20(address token,address recipient) publiconlyOwner { SafeTransferLib.safeTransfer(token, recipient,IERC20(token).balanceOf(address(this))); }// ---------------------------------------------------- //// MODIFIERS //// ---------------------------------------------------- //// This ensures a function can only be called through atlasSolverCall// which includes security checks to work safely with AtlasmodifieronlySelf() {require(msg.sender ==address(this),"Not called via atlasSolverCall"); _; }}
How It Works:
The solve() function contains the MEV logic.
The atlasSolverCall() function (inherited from SolverBase) calls solve() by forwarding the callData.
solverOpData is simply the ABI-encoded call to solve().
// SPDX-License-Identifier: MITpragmasolidity 0.8.25;import { SafeTransferLib } from"solady/utils/SafeTransferLib.sol";import { ReentrancyGuard } from"solady/utils/ReentrancyGuard.sol";import { Ownable } from"openzeppelin-contracts/contracts/access/Ownable.sol";import { IERC20 } from"openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";import { SolverBase } from"../lib/atlas/src/contracts/solver/SolverBase.sol";/** * @title ProxySolver * @notice An example solver contract that proxies calls to an external contract. * @dev This contract inherits from SolverBase and includes reentrancy guards. * It allows the owner to set an external contract to which MEV solution calls are proxied. * It includes helper functions to manage the external contract and approved EOAs. */contractAtlasProxySolverisSolverBase, Ownable, ReentrancyGuard {addresspayableprivate searcherContract;mapping(address=>bool) internal approvedEOAs;constructor(address weth,address atlas,address_searcherContract )SolverBase(weth, atlas, msg.sender)Ownable(msg.sender) { searcherContract =payable(_searcherContract); }fallback() externalpayable { }receive() externalpayable { }/** * @notice Called during the SolverOperation phase. * This function is called via atlasSolverCall(), which forwards the solverOpData calldata. * @param _searcherCallData The calldata to be forwarded to the external searcher contract. */functionsolve(bytescalldata_searcherCallData) publiconlySelfnonReentrant {// Call the external searcher contract with the provided calldata. (bool success,bytesmemory returnedData) = searcherContract.call(_searcherCallData);// Revert if the call was unsuccessful.require(success,"Searcher call unsuccessful");// SolverBase automatically handles paying the bid amount to the Execution Environment. }// ---------------------------------------------------- //// ONLY OWNER //// ---------------------------------------------------- ///** * @notice Sets the address of the external searcher contract. * @param _searcherContract The address of the new searcher contract. */functionsetSearcherContractAddress(address_searcherContract) publiconlyOwner { searcherContract =payable(_searcherContract); }/** * @notice Withdraws all ETH from the contract to a recipient address. * @param recipient The address to receive the withdrawn ETH. */functionwithdrawETH(address recipient) publiconlyOwner { SafeTransferLib.safeTransferETH(recipient,address(this).balance); }/** * @notice Withdraws all ERC20 tokens of a specific type from the contract to a recipient address. * @param token The address of the ERC20 token contract. * @param recipient The address to receive the withdrawn tokens. */functionwithdrawERC20(address token,address recipient) publiconlyOwner { SafeTransferLib.safeTransfer(token, recipient,IERC20(token).balanceOf(address(this))); }// ---------------------------------------------------- //// MODIFIERS //// ---------------------------------------------------- ///** * @notice Ensures a function can only be called through atlasSolverCall, * which includes security checks to work safely with Atlas. */modifieronlySelf() {require(msg.sender ==address(this),"Not called via atlasSolverCall"); _; }}
How It Works:
The solve(bytes calldata solverOpData) function receives the callData intended for the external contract.
It forwards this callData to the searcherContract using a low-level call.
solverOpData includes the encoded function call for the external contract.
Generating solverOpData:
Suppose your external searcher contract has a function: