> For the complete documentation index, see [llms.txt](https://fastlane-labs.gitbook.io/polygon-fastlane/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://fastlane-labs.gitbook.io/polygon-fastlane/searcher-guides/bundles-backruns/bid-submission.md).

# Bid Submission

FastLane Searchers submit their bids and solutions to the FastLane Protocol using `EIP-712` signed messages. This method allows for off-chain data signing and on-chain verification, reducing gas costs and improving efficiency. \
\
The submission to the PFL-Auction will be performed by Bundler EOA's. To establish a trusted a PFL-Auction transaction is constructed of 3 parts here:

* UserOperation.   : (opportunity transaction converted on PFL-Auction system)
* SolverOperation : (generated and signed by searcher)
* dAppOperation   : (generated and signed by dAppSigner to hash userOperation and selected  \
  &#x20;                                  solverOperations)

{% hint style="info" %}
The above definitions are simplified to for more technical details we encourage to read our Atlas documentation&#x20;
{% endhint %}

### Submitting the Signed Message

This will be a two step process

Atlas will call the `atlasSolverCall` on the solver/searcher contracts

```solidity
// Opionanted atlasSolverCall implementation which forwards
// to a internal call using the solverOpData
function atlasSolverCall(
    address solverOpFrom,
    address executionEnvironment,
    address bidToken,
    uint256 bidAmount,
    bytes calldata solverOpData,
    bytes calldata
)
    external
    payable
    virtual
    safetyFirst(executionEnvironment, solverOpFrom)
    payBids(executionEnvironment, bidToken, bidAmount)
{
    (bool success,) = address(this).call{ value: msg.value }(solverOpData);
    if (!success) revert SolverCallUnsuccessful();
}
```

### 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)

```solidity

// 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 {
    // 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)
}

```

### 2.  Generate SolverOperation to Submit for

The second part will be generating the a `EIP-712` signed messages with a atlas specific format

#### EIP-712 like Message Structure

Searchers need to construct and sign a message containing the bid and operation details according to an Atlas format similar to the EIP-712 standard.\
A SolverOperation contains the following important details&#x20;

&#x20;The message includes:

* **Bid Details**: Bid token address, bid amount.
* **Operation Data**: Encoded data for the solver operation.
* **dAppControl:** PFL-Auction specific implementation of the Atlas hooks (pre-solver, post-solver)
* **dAppSigner**: Account which will be signing the dAppControl operation
* **userOpHash**:  hash of User's Operation, for verification
* **deadline**: is provided in block number not timestamp!

```solidity
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**
  * **Address:** [0x3e23e4282FcE0cF42DCd0E9bdf39056434E65C1F](https://polygonscan.com/address/0x3e23e4282FcE0cF42DCd0E9bdf39056434E65C1F)
  * **Description:** This is the current address of the `dAppControl` contract, which is responsible for generating the `userOpHash` needed for the solver operation.
  * PFL-Auctions implementation for Atlas hooks (pre-solver, post-solver) and value distribution
* **dAppOpSigner Address**
  * **Address:** [`0x96D501A4C52669283980dc5648EEC6437e2E6346`](/polygon-fastlane/searcher-guides/addresses-and-endpoints.md)
  * **Description:** This is the address of the `dAppOpSigner`, which acts as the signer for the bundled dAppOperation
* **atlasVerification Contract Address**
  * **Address:** [0xf31cf8740Dc4438Bb89a56Ee2234Ba9d5595c0E9](https://polygonscan.com/address/0xf31cf8740Dc4438Bb89a56Ee2234Ba9d5595c0E9)
  * **Description:** This contract is used for EIP-712 domain verification when signing the solver operation.
* **atlas Contract Address**
  * **Address:** [0x4A394bD4Bc2f4309ac0b75c052b242ba3e0f32e0](https://polygonscan.com/address/0x4A394bD4Bc2f4309ac0b75c052b242ba3e0f32e0)
  * **Description:** Atlas main entryPoint contract `metacall` will be used to submit PFL-Bundles (handled by FastLane bundler)

### Encoding of the Bundle

When encoding the bundle, represent all `BigInt` values as hex strings (prefixed with `0x`), and serialize the entire JSON object as a string.

Example Solver Operation as struct:

```javascript
{
  from: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
  to: '0x4A394bD4Bc2f4309ac0b75c052b242ba3e0f32e0',
  value: '0x0',
  gas: '0x7a120',
  maxFeePerGas: '0x848788e91',
  deadline: '0x0',
  solver: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
  control: '0x3e23e4282FcE0cF42DCd0E9bdf39056434E65C1F',
  userOpHash: '0x31ebaf2b454ec7b665697554b8be6422d39e775befa93768f36a101d4653ae8c',
  bidToken: '0x0000000000000000000000000000000000000000',
  bidAmount: '0x2386f26fc10000',
  data: '0x890d6908',
  signature: '0xe1a0e01491f5a45110ab8d777d7feeec596b8ccdea086383a04af80bacd4c67038c0b9d9d6139e7818dd4069d23fc96bf1cdd63073e37ff44287e6722f5299c21b'
}
```

Example Bundle payload what we expect:

```javascript
  id: 1,
  jsonrpc: '2.0',
  method: 'pfl_addSearcherBundle',
  params: [
    '0x02f8778189820be28506c6b450bc8508052c48a9830186a0940000000000000000000000000000000000000000872386f26fc1000080c001a064d1594494fddeaa9412ed29975cbe6f349da4a5f39448659d9520c78f6588fca06cfea84e288b0e9861ce01f01465389d511c412acb02222c7e00f4299abd0fc1',
    '{"from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","to":"0x4A394bD4Bc2f4309ac0b75c052b242ba3e0f32e0","value":"0x0","gas":"0x7a120","maxFeePerGas":"0x8052c48a9","deadline":"0x0","solver":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","control":"0x3e23e4282FcE0cF42DCd0E9bdf39056434E65C1F","userOpHash":"0xcc1d143ccb5365daf7cb5c8b3fdd3fad51dc8c2db10ee23f1e47f41894a89713","bidToken":"0x0000000000000000000000000000000000000000","bidAmount":"0x2386f26fc10000","data":"0x890d6908","signature":"0xf71884b9b567b2b212194054577fa402a4c5469d751766e3eb8fe3c21056661972e7f482f20b8f461caaa952ab5879e88a45ef9e998bbe2c86a0030dbf253c961b"}'
  ]
}
```

**Example:**

{% tabs %}
{% tab title="Nodejs" %}

```javascript
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;

// Mock values for gas prices for opportunity tx
const maxFeePerGas = BigInt(10000000000000000);
const maxPriorityFeePerGas = BigInt(10000000000000000);

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 doMEV(uint256, string)`,
    ];

    const iface = new Interface(searcherAbi);

    // Grab bytes for doMEV(uint256, string)
    const searcherCallDataBytes = iface.encodeFunctionData("doMEV", [parseEther("1.0"), "hello"]);
    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: solverSigner.address, // 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;
}


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
    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
    
    // self transfer to solver contract
    const opportunityRawTx = await createOpportunityRawTx(userSigner, bidAmount, solverContract, opportunityIsLegacyTx, maxFeePerGas, maxPriorityFeePerGas);
    //match opportunity tx transaction type
    const maxFeePerGas = isLegacyTx ? feeData.gasPrice: feeData.maxFeePerGas;
    const maxPriorityFeePerGas = isLegacyTx ? feeData.gasPrice : feeData.maxPriorityFeePerGas,

    // 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);
```

Please see [Full Example](/polygon-fastlane/searcher-guides/bundles-backruns/full-example.md) section on how to target your own transaction instead of one from a provider.

{% endtab %}

{% tab title="Python" %}

```python
import requests
import json
from web3.middleware import geth_poa_middleware

# Set up the provider
provider_url = "https://polygon.llamarpc.com"
web3 = Web3(HTTPProvider(provider_url))

# Inject PoA middleware if needed
web3.middleware_onion.inject(geth_poa_middleware, layer=0)

def submit_http_bundle(bundle: PflBundle):
    try:
        resp = requests.post(
            httpFastlaneEndpoint,
            json={
                "id": bundle.id,
                "jsonrpc": bundle.jsonrpc,
                "method": bundle.method,
                "params": bundle.params,
            },
            timeout=10,
        )
        data = resp.json()
        if "error" in data:
            print(f"Error submitting bundle {bundle.id}: {data}")
        else:
            print(f"Response received for bundle {bundle.id}: {data}")
    except Exception as e:
        print(f"Error submitting bundle {bundle.id}: {e}")
```

{% endtab %}
{% endtabs %}

For details on how to create a bundle review the [Full Example](/polygon-fastlane/searcher-guides/bundles-backruns/full-example.md)
