Full Example
Full working code to submit bundle
This documentation is geared for the new PFL Auction
system and submission of pfl_addSearcherBundle
before we officially upgraded our servers will likely fail
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,
}
interface PflBundle {
id: number;
jsonrpc: string;
method: string;
params: string[];
}
const provider = new JsonRpcProvider("https://polygon.llamarpc.com");
// PK are foundry default PKs
const userSigner = new Wallet("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", provider);
const solverSigner = new Wallet("59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", provider);
const dappControl = new Contract(dappControlAddr, PFLControlAbi, provider);
const httpFastlaneEndpoint = "https://polygon-rpc.fastlane.xyz";
const opportunityIsLegacyTx = false;
// helper function to get the http connection
const getHttpConnection = (endpoint: string): AxiosInstance => {
return axios.create({
baseURL: endpoint,
timeout: 10000
});
}
// helper function to submit the bundle to the fastlane endpoint
async function submitHttpBundle(bundle: PflBundle): Promise<void> {
const conn = getHttpConnection(httpFastlaneEndpoint);
try {
const resp = await conn.post('/', bundle);
if (resp.data.error) {
console.error(`Error submitting bundle ${bundle.id}`, resp.data);
} else {
console.log(`Response received for bundle ${bundle.id}`, resp.data);
}
} catch (error) {
console.error(`Error submitting bundle ${bundle.id}`, error);
}
}
const createOpportunityRawTx = async (
userSigner: Wallet,
bidAmount: bigint,
toAddress: string,
legacyTx: boolean,
maxFeePerGas: bigint | null,
maxPriorityFeePerGas: bigint | null
) => {
if (!maxFeePerGas) {
throw new Error("maxFeePerGas is required");
}
const txData = await userSigner.populateTransaction({
to: toAddress,
gasLimit: 100000,
...(legacyTx
? { gasPrice: maxFeePerGas }
: {
maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas || maxFeePerGas
}),
value: bidAmount
});
return await userSigner.signTransaction(txData);
}
// 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 solverSigner.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: solverSigner.address, // solverSigner 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;
}
// helper function to generate the pfl bundle
const generatePflBundle = (solverOp: SolverOperation, opportunityRawTx: string, bundleId: number): PflBundle => {
return {
id: bundleId,
jsonrpc: "2.0",
method: "pfl_addSearcherBundle",
params: [`${opportunityRawTx}`, `${JSON.stringify(solverOp.toStruct())}`]
}
}
// main function to submit the bundle to the fastlane endpoint
async function main() {
const gasPrice = await provider.getFeeData();
//match opportunity tx transaction type
console.log("gasPrice", gasPrice);
const maxFeePerGas = opportunityIsLegacyTx ? gasPrice.gasPrice : gasPrice.maxFeePerGas;
const maxPriorityFeePerGas = opportunityIsLegacyTx ? gasPrice.gasPrice : gasPrice.maxPriorityFeePerGas;
if (!maxFeePerGas || !maxPriorityFeePerGas) {
throw new Error("Failed to get gas price data");
}
const solverContract = "0x0000000000000000000000000000000000000000";
const bidAmount = BigInt(10000000000000000); // 0.01 POL
const opportunityRawTx = await createOpportunityRawTx(userSigner, bidAmount, solverContract, opportunityIsLegacyTx, maxFeePerGas, maxPriorityFeePerGas);
console.log("opportunityRawTx", opportunityRawTx);
// Ensure the values passed to getBackrunUserOpHash are BigInt
const userOpHash = await dappControl.getBackrunUserOpHash(
keccak256(opportunityRawTx),
maxFeePerGas,
maxPriorityFeePerGas,
dAppOpSignerAddr
);
console.log("userOpHash", userOpHash);
// Generate the solver operation using the userOpHash and bidAmount
const solverOp = await generateSolverOperation(userOpHash, bidAmount, maxFeePerGas, maxPriorityFeePerGas);
const pflBundle = generatePflBundle(solverOp, opportunityRawTx, 1);
// Submit the bundle to the fastlane endpoint
await submitHttpBundle(pflBundle);
}
main().catch(console.error);
Nodejs Version
In this example we backrun a transfer to ourselves
Also don't forget to check Helpers.
Last updated