SVR Searcher Onboarding: Ethereum Mainnet

This guide explains how to identify and bid on Chainlink SVR liquidation opportunities on Ethereum Mainnet through Flashbots MEV-Share. SVR Feeds support a private transmission channel for oracle price updates, enabling searchers to backrun new prices with liquidation transactions and compete for profit.

For Base, Arbitrum, and BNB Chain, see the Atlas searcher onboarding guide.

Traditional MEV searching depends on spotting oracle price updates in the public mempool. With SVR, new price reports are shared in a private mempool via Flashbots MEV-Share. Searchers place bids to include liquidation transactions directly after the price update in the same block. The highest bidder gets included, capturing the liquidation bonus while sharing part of the winning bid with the DeFi protocol (e.g., Aave) and the Chainlink Network.

Example Application: Aave Liquidations

Aave is a lending protocol where collateralized positions must remain above a threshold called their health factor. When an oracle price update reveals a borrower is undercollateralized, the position becomes eligible for liquidation. The searcher liquidates the position in exchange for a liquidation bonus reward. With Chainlink SVR, a searcher's liquidation transaction can be bundled with the new SVR price update through Flashbots.

For more background on how Aave liquidations work, see Aave Documentation. To understand how Chainlink SVR adds a structured auction for these Aave liquidation opportunities, read the following sections.

Price Update, Auction, and Liquidation Execution Flow

How to Participate as a Searcher

1. High-Level Steps for Searchers

  1. Monitor and Identify SVR Price Updates: Chainlink SVR sends oracle reports through Flashbots Protect (MEV-Share). Your searcher node must watch private transactions, not just the public mempool. It must also filter by these SVR data feed update events, and determine which feed address the update is for.

  2. Bundle and Bid

    • Detect an eligible liquidation (e.g., an undercollateralized Aave position) triggered by the new price update.
    • Submit your liquidation transaction in the same bundle as the price update, placing a bid to entice block builders.
  3. Execute Liquidation: If your bundle is selected, your liquidation happens immediately after the fresh price arrives onchain, capturing the liquidation bonus. Based on the results of the auction, a portion of the MEV is recaptured and split between Aave and the Chainlink Network, while the rest goes to you.

2. Listen for Events on MEV Share

Chainlink SVR uses forwarder contracts to route feed updates. You typically see one transaction per event, which calls the function forward(address to, bytes callData) with the function selector 6fadcf72. This method, in turn, calls the SVR aggregator's transmitSecondary() function with the new price data.

  1. Forwarder Contracts: Each Node Operator Proxy has a unique forwarder contract. That means that feed update transactions may come from different forwarder addresses depending on which proxy sends the transaction.

  2. Function Selector: In the transaction's txs array, look for the selector 6fadcf72 (forward(...)).

  3. callData: The callData includes the parameters for transmitSecondary() on the aggregator contract. You can decode this if you need to extract the updated price when the onchain event is not emitted (details in the section below).

Below are code examples for setting up a listener that monitors the MEV-Share event stream for SVR feed updates. These examples show how to connect to the stream, filter for relevant transactions with the 6fadcf72 function selector, and process incoming events:

import MevShareClient, { IPendingBundle, IPendingTransaction } from "@flashbots/mev-share-client"

const mevShareClient = MevShareClient.useEthereumMainnet(authSigner)

const txHandler = mevShareClient.on("transaction", async (tx: IPendingTransaction) => {
  if (tx.functionSelector === "6fadc72") {
    // Do something with the pending tx here.
  }
})

// call before your program terminates:
txHandler.close()
bundleHandler.close()

3. Decode callData for forward and transmitSecondary

Once you've identified a potential SVR feed update transaction, you'll need to decode its payload. This step allows you to extract:

  • The feed address: This is required to determine which SVR data feed is being updated.
  • The new price data: This is essential for determining if profitable liquidation opportunities exist.

The decoding process involves understanding two function calls:

  1. First, the transaction calls the forward function:

    forward(address to, bytes callData)
    
  2. Inside the callData parameter is the encoded call to transmitSecondary, which contains the actual price data:

    transmitSecondary(
        bytes32[3] calldata reportContext,
        bytes calldata report,
        bytes32[] calldata rs,
        bytes32[] calldata ss,
        bytes32 rawVs
    )
    

    The structure of report is:

    struct Report {
      uint32 observationsTimestamp;
      bytes32 observers;
      int192[] observations;
      int192 juelsPerFeeCoin;
    }
    

Below is an example of an ABI definition for the forward and transmitSecondary function interfaces to help you decode the function calls:

[
  {
    "type": "function",
    "name": "forward",
    "inputs": [
      { "name": "to", "type": "address" },
      { "name": "callData", "type": "bytes" }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "transmitSecondary",
    "inputs": [
      { "name": "reportContext", "type": "bytes32[3]", "internalType": "bytes32[3]" },
      { "name": "report", "type": "bytes", "internalType": "bytes" },
      { "name": "rs", "type": "bytes32[]", "internalType": "bytes32[]" },
      { "name": "ss", "type": "bytes32[]", "internalType": "bytes32[]" },
      { "name": "rawVs", "type": "bytes32", "internalType": "bytes32" }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  }
]

The following code samples demonstrate the complete decoding process, including extracting the median price from the nested report structure. These implementations show how to:

  1. Decode the forward function call
  2. Extract and decode the transmitSecondary function data
  3. Parse the report bytes using the Report struct
  4. Access the median observation (the updated price)
const decodeInterface = new ethers.Interface(EXAMPLE_ABI)

function decodeForwarderCall(callData: string) {
  const decodedForward = decodeInterface.decodeFunctionData("forward", callData)
  return { to: decodedForward[0], callData: decodedForward[1] }
}

function decodeTransmitSecondary(callData: string) {
  try {
    const decodedTransmitSecondary = decodeInterface.decodeFunctionData("transmitSecondary", callData)
    return { report: decodedTransmitSecondary[1] }
  } catch (error) {
    console.error("not transmit secondary call", error)
    return {}
  }
}

function decodeReport(reportRaw: string) {
  const decodedReport = ethers.AbiCoder.defaultAbiCoder().decode(["uint32", "bytes32", "int192[]", "int192"], reportRaw)

  return { observations: decodedReport[2] }
}

function updatedPrice(observations: bigint[]) {
  const medianObservation = observations[Math.floor(observations.length / 2)]
  return medianObservation
}

async function processTransaction(pendingTx: IPendingTransaction) {
  if (pendingTx.functionSelector !== CHAINLINK_PRICE_FEED_FUNCTION_SELECTOR) {
    return null
  }

  const { to, callData } = decodeForwarderCall(pendingTx.callData)
  const { report } = decodeTransmitSecondary(callData)
  if (report) {
    const { observations } = decodeReport(report)
    const newPrice = updatedPrice(observations)
  }

  // calculate health factors for affected users ...
}

4. Detect SVR-enabled Feeds

When processing forward calls, verify that the to address from the code sample above (the destination of the forward call) matches one of these feed addresses. This tells you which SVR data feed the price update is for:

FeedSVR FeedAave Dedicated SVR Feed
AAVE/USDNot available0xcd07B31D85756098334eDdC92DE755dEae8FE62f
BTC/USD0x6f3F8d82694d52E6B6171A7b26A88c9554e7999b0xdc715c751f1cc129A6b47fEDC87D9918a4580502
COMP/USD0x458138Fc0D67027E9A6778ef40a6ffC318c69061Not available
CRCLon/USD (Ondo API)0x965750914f5bB1c9Da8dbf5587970FEDAc1534C4Not available
ETH/USD0xad88fc1A810379Ef4EFbF2D97EdE57e306178e5a0x7c7FdFCa295a787ded12Bb5c1A49A8D2cC20E3F8
LINK/USD0x4F3EBf190f8889734424aE71Ac0B00e1A8013f3C0x64c67984A458513C6BAb23a815916B1b1075cf3a
QQQon-USD (Calculated)0x320E22c489e4bb634aC1aa5822543014A6fbB292Not available
QQQon/USD (Ondo API)0xe660B4DC23430BdF2eC30b961fcAf6CCac8276a3Not available
SPYon-USD (Calculated)0x2053257478bA1FeDF7F99dEF0C412006753aC9BfNot available
SPYon/USD (Ondo API)0x9dDb5fBA9A737860c7ccEd0D9177Af56AB16c183Not available
TSLAon-USD (Calculated)0x9F6B06e826d3DF391285c695749F8f921F6972D9Not available
TSLAon/USD (Ondo API)0x95dC7c293ad1706C80bCde068B609CA61B3FF78CNot available
USDC/USD0x7d06199061Da586dAFc5D18fd1AeeAf18ae7593b0xe13fafe4FB769e0f4a1cB69D35D21EF99188EFf7
USDT/USD0x757EB2AF32c76621FEAE483c6458C04ba19906Ba0x9df238BE059572d7211F1a1a5fEe609F979AAD2d

5. Calculate Updated Price

After successfully decoding the transaction data, you need to extract the median price from the observations array inside the decoded report:

report.observations[report.observations.length / 2]

This formula retrieves the median value from the sorted array of price observations. The middle element represents the median price. This price is the value committed to the blockchain that protocols will use for determining liquidation eligibility.

To use this price effectively, convert it to the appropriate decimal representation for the asset pair.

6. Bidding With Flashbots MEV-Share

Once you've detected an SVR update and identified a profitable liquidation opportunity, you need to construct and submit a bundle to Flashbots. Consider these key aspects when preparing your submission:

  • Bundle Components: Your bundle must contain two elements in this exact order:

    1. The original oracle update transaction (referenced by its hash)
    2. Your liquidation transaction (fully signed transaction bytes)
  • Backrun Position: MEV-Share only permits backruns, meaning your liquidation transaction must come after the target transaction. This order is enforced by the bundle structure.

  • Bidding Strategy: Block builders typically select the highest-paying bundle. Your bid must be competitive while leaving room for profit. The bundle's economic value comes from:

    • The gas price of your transaction
    • Any explicit payment you include to the builder

    How to submit your bid: Within your backrun transaction itself, send ETH to block.coinbase (the block builder's address). The higher the payment sent to block.coinbase, the more competitive your bundle becomes. Example:

    // Within your liquidation transaction, send payment to block.coinbase
    payable(block.coinbase).transfer(bidAmount);
    

    You pay your full bid amount to block.coinbase. The revenue split between Chainlink and the DeFi protocol occurs as part of the SVR system.

  • Inclusion Parameters: Specify the target block range for your bundle to be considered

    • Typically target the next block plus a small range for redundancy
    • A narrow block range can increase chances of inclusion but risks missing execution
  • Authentication: All Flashbots bundles require cryptographic signing with your wallet

Below are code examples demonstrating how to construct and submit bundles to the Flashbots relay:

async function sendBackrunBundle(pendingTx: IPendingTransaction) {
  const currentBlock = await provider.getBlockNumber()
  const signedBackrunTx = await buildDummyBackrunTx(pendingTx)
  const bundleParams: BundleParams = {
    inclusion: {
      block: currentBlock + 1,
      maxBlock: currentBlock + TARGET_BUNDLE_BLOCK_DELAY,
    },
    body: [{ hash: pendingTx.hash }, { tx: signedBackrunTx, canRevert: false }],
  }

  const sendBundleResult = await mevShareClient.sendBundle(bundleParams)
  return sendBundleResult
}

async function buildDummyBackrunTx(pendingTx: IPendingTransaction) {
  const backrunTx = { data: {}, maxFeePerGas: 22000, maxPriorityFeePerGas: 22000 }
  const signedBackrunTx = await executorWallet.signTransaction(backrunTx)
  return signedBackrunTx
}

For gas management, simulations, and other advanced usage, see the official Flashbots documentation.

7. Considerations

  • Competition: Multiple searchers might detect the same liquidation. The best bid typically wins.
  • Profit-Sharing: A portion of liquidation MEV is recaptured and redirected to the integrating DeFi protocol and the Chainlink Network.
  • Gas Efficiency: Bundles that are too costly might erode the profitability of the proposed transaction. Optimize carefully.
  • Non-SVR forward calls: There are other events on the MEV-Share event stream that have the same forward function signature. Be sure to filter for the ones that call transmitSecondary on the listed SVR feeds.

8. Next Steps for Searchers

  1. Set Up Your Searcher Node

    • Integrate with Flashbots MEV-Share to read private transactions.
    • Filter for calls matching function selector 6fadcf72 directed at the correct contract address.
  2. Test Your Bundles

    • Construct atomic bundles combining the price update and your liquidation transaction.
    • Try local simulations or testnet deployments where available.
  3. Stay Updated

    • Keep track of any updates to the SVR aggregator address, function signatures, or Flashbots MEV-Share changes.
    • Monitor for changes in liquidation parameters (e.g., new assets, different collateral thresholds).

What's next

Get the latest Chainlink content straight to your inbox.