A FastLane Atlas bundle consists of 2 transactions:
The opportunity-creating transaction. (regular opportunity transaction)
Searcher meta transaction which includes:
- callData which will be passed to searcher solver contract
- bidAmount
- atlas specific parameter such as dAppControl contract address (PLF auction address)
The transactions must be hexadecimal encoded (see Relay JSON-RPC API for more information)
1. Generate CallData for the Solver contract function
The first step is to generate and encode the backrun for a particular opportunity transaction
In our example the solve() function
Responsibilities:
perform backrun operation
makes sure the the contract has bidToken in bidAmount quantity (POL)
// 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()
function solve() public view onlySelf {
//do backrun stuff
}
2. Generate SolverOperation to Submit for
The second part will be generating the a EIP-712 signed messages with a atlas specific format
struct SolverOperation {
address from; // Solver address
address to; // Atlas address
uint256 value; // Amount of ETH(POL) required for the solver operation (used in `value` field of the solver call)
uint256 gas; // Gas limit for the solver operation
uint256 maxFeePerGas; // maxFeePerGas matching the opportunity tx
uint256 deadline; // block.number deadline for the solver operation
address solver; // Nested "to" address (used in `to` field of the solver call)
address control; // PFL Auction DAppControl (see documentation)
bytes32 userOpHash; // hash of User's Operation, for verification of user's tx (if not matched, solver wont be
// charged for gas)
address bidToken; // address(0) for ETH(POL)
uint256 bidAmount; // Amount of bidToken that the solver bids
bytes data; // Solver op calldata (used in `data` field of the solver call)
bytes signature; // Solver operation signature signed by SolverOperation.from
}
dAppControl Contract Address
Description: This is the current address of the PFL AuctiondappControl contract, which is responsible for generating the userOpHash needed for the solver operation.
helper contract for Fastlane to submit bundles
entrypoint contract which configures Atlas behavior for PFL auction system
dAppOpSigner Address
Description: This is the address of the dAppOpSigner, which acts as the signer for the bundles dAppOperation
Fastlane PFL system is using this authorizes signer to generate dAppControl operation
AtlasVerification Contract Address
Description: This contract is used for EIP-712 domain verification when signing the solver operation.
atlas Contract Address
Description: Atlas main entryPoint contract metacall will be used to submit PFL-Bundles (handled by FastLane bundler)
Alway verify that the provided example address are up to date. User our Addresses & Endpoints page
Example:
import { OperationBuilder, SolverOperation } from "@fastlane-labs/atlas-sdk";
import axios, { AxiosInstance } from "axios";
import { Contract, Interface, JsonRpcProvider, keccak256, parseEther, TypedDataDomain, Wallet } from "ethers";
const dappControlAddr = "0x3e23e4282FcE0cF42DCd0E9bdf39056434E65C1F"; // current dappControl address (review docs)
const dAppOpSignerAddr = "0x96D501A4C52669283980dc5648EEC6437e2E6346"; // current dAppOpSigner address (review docs)
const atlasVerificationAddr = "0xf31cf8740Dc4438Bb89a56Ee2234Ba9d5595c0E9"; // current atlasVerification address (review docs)
const atlasAddr = '0x4A394bD4Bc2f4309ac0b75c052b242ba3e0f32e0';
const PFLControlAbi = [
{
"inputs": [
{ "internalType": "bytes32", "name": "oppTxHash", "type": "bytes32" },
{ "internalType": "uint256", "name": "oppTxMaxFeePerGas", "type": "uint256" },
{ "internalType": "uint256", "name": "oppTxMaxPriorityFeePerGas", "type": "uint256" },
{ "internalType": "address", "name": "fastLaneSigner", "type": "address" }
],
"name": "getBackrunUserOpHash",
"outputs": [{ "internalType": "bytes32", "name": "userOpHash", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
];
const eip712Domain: TypedDataDomain = {
name: "AtlasVerification",
version: "1.0",
chainId: 137,
verifyingContract: atlasVerificationAddr,
}
// Generate the solver call data for the solver operation
const generateSolverCallData = () => {
const searcherAbi = [
`function solve()`,
];
const iface = new Interface(searcherAbi);
// Grab bytes for solve()
const searcherCallDataBytes = iface.encodeFunctionData("solve");
return searcherCallDataBytes;
}
// helper function to generate the solver signature using eip712Domain
const generateSolverSignature = async (solverOp: SolverOperation) => {
return await signer.signTypedData(eip712Domain, solverOp.toTypedDataTypes(), solverOp.toTypedDataValues());
}
// helper function to generate the solver operation
const generateSolverOperation = async (userOpHash: string, bidAmount: bigint, maxFeePerGas: bigint, maxPriorityFeePerGas: bigint):Promise<SolverOperation> => {
// Generate the solver call data
const solverCallData = generateSolverCallData();
// Generate the solver operation
const solverOp = OperationBuilder.newSolverOperation({
from: solverSigner.address, // solver address
to: atlasAddr, // atlasAddr address
value: BigInt(0), // 0 value
gas: BigInt(500000), // 500,000 gasLimit
maxFeePerGas: maxFeePerGas,
deadline: BigInt(0), // 0 deadline
solver: dAppOpSignerAddr, // dAppOpSigner address
control: dappControlAddr, // dappControl address
userOpHash: userOpHash,
bidToken: "0x0000000000000000000000000000000000000000", // POL
bidAmount: bidAmount,
data: solverCallData,
signature: "0x" // empty signature
});
// Generate the solver signature
const solverSignature = await generateSolverSignature(solverOp);
// Set the solver signature
solverOp.setField("signature", solverSignature);
return solverOp;
}
// some code omitted for this example
// main function to submit the bundle to the fastlane endpoint
async function main() {
const opportunityRawTx = "0x000";
// Get the userOpHash for the opportunity tx calling the PFL dappControl contract
//NOTE: we need to use the same transaction type as the opportunity tx
const userOpHash = await dappControl.getBackrunUserOpHash(
keccak256(opportunityRawTx),
opportunityIsLegacyTx ? maxFeePerGas : maxFeePerGas,
opportunityIsLegacyTx ? maxFeePerGas : maxPriorityFeePerGas,
dAppOpSignerAddr
);
// Generate the solver operation
const solverOp = await generateSolverOperation(userOpHash);
const pflBundle = generatePflBundle(solverOp, opportunityRawTx, 1 );
// Submit the bundle to the fastlane endpoint
await submitHttpBundle(pflBundle);
}
main().catch(console.error);
More details in
from web3 import Web3
pfl_bundle = [
opportunityTx.rawTransaction.hex(),
signedSearcherFlashBidTx.rawTransaction.hex()
]