> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beaconcha.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Investigate a Block

> Audit the winning MEV bid, later alternatives, and complete relay bid ladder for an Ethereum block

## Overview

Block-level MEV analysis answers two different questions:

1. **What was the outcome?** The block timing endpoint summarizes the selected bid and the best alternatives that arrived later.
2. **What did the proposer have available?** The bid endpoint returns every recorded relay bid in arrival order for research and auditing.

<Note>
  **API Endpoints:** This guide uses [`/api/v2/ethereum/block/mev-timing`](/api-reference/ethereum/block/mev-timing) and [`/api/v2/ethereum/block/mev-bids`](/api-reference/ethereum/block/mev-bids).
</Note>

<Info>
  The timing summary is available to all API plans. The full bid ladder requires a Scale or Enterprise plan.
</Info>

<Warning>
  The bid ladder is collected from offchain relay APIs and streams. Relay-reported arrival times, identities, and bids cannot be independently verified from onchain data, and missing or inconsistent relay observations can make the recorded ladder incomplete. Verify onchain fields such as the included block hash separately.
</Warning>

***

## Step 1: Fetch the Timing Outcome

Resolve a block by execution-layer block number, or use the `latest` or `finalized` view. A recent `latest` block will usually return `pending` because per-slot MEV aggregates lag the chain head by about 4 hours.

```bash theme={null}
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/block/mev-timing \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "chain": "mainnet",
  "block": {
    "number": 25378998
  }
}
'
```

An identified relay winner produces a response like this:

```json theme={null}
{
  "data": {
    "status": "timing_games",
    "winning_bid": {
      "value": "118000000000000000",
      "slot_offset_ms": 2460,
      "relays": ["example-relay"],
      "builder_pubkey": null
    },
    "first_better_bid": {
      "value": "121000000000000000",
      "slot_offset_ms": 2518,
      "status": "timing_games"
    },
    "best_later_bid": {
      "value": "139000000000000000",
      "slot_offset_ms": 2784,
      "status": "aggressive_tg"
    },
    "missed_value": "21000000000000000",
    "relay_count": 8,
    "bid_count": 436
  }
}
```

This is an illustrative response. Bid values, relay names, counts, and builder keys depend on the selected block.

***

## Interpret the Outcome

See the [block timing API reference](/api-reference/ethereum/block/mev-timing). In this workflow, compare the included winner with the earliest better bid and the best later bid to distinguish the minimum additional wait from the maximum later opportunity observed.

<Warning>
  `missed_value` is hindsight, not guaranteed revenue. The proposer could not know which future bid would be best, and waiting for it would have increased propagation and orphan risk.
</Warning>

***

For non-classified outcomes, follow the status-specific behavior in the API reference. Retry provisional results, and do not reinterpret unknown coverage as a non-MEV outcome.

***

## Step 2: Fetch the Bid Ladder

The full bid ladder is ordered by arrival time and can contain many thousands of rows. Filter to the timing window you need, request up to 200 bids per page, and follow `paging.next_cursor` until it is empty.

```bash theme={null}
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/block/mev-bids \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "chain": "mainnet",
  "block": {
    "number": 25378998
  },
  "slot_offset_ms_range": {
    "min": 2400,
    "max": 4000
  },
  "page_size": 200
}
'
```

See the [block MEV bids API reference](/api-reference/ethereum/block/mev-bids).

***

## Audit the Alternatives with Python

This script fetches the summary, retrieves every later bid, and independently checks the earliest better and best later values. It keeps wei as integers until formatting the final ETH amounts.

```python theme={null}
import os
from decimal import Decimal

import requests

BASE_URL = "https://beaconcha.in/api/v2/ethereum"
BLOCK_NUMBER = 25378998
HEADERS = {
    "Authorization": f"Bearer {os.environ['BEACONCHAIN_API_KEY']}",
    "Content-Type": "application/json",
}


def post(path, payload):
    response = requests.post(
        f"{BASE_URL}/{path}", headers=HEADERS, json=payload, timeout=30
    )
    response.raise_for_status()
    return response.json()


selector = {"chain": "mainnet", "block": {"number": BLOCK_NUMBER}}
summary = post("block/mev-timing", selector)["data"]
winner = summary["winning_bid"]

if winner is None:
    raise SystemExit(f"No identified winner: {summary['status']}")

cursor = ""
bids = []

while True:
    payload = {
        **selector,
        "slot_offset_ms_range": {
            "min": winner["slot_offset_ms"],
            "max": 4000,
        },
        "page_size": 200,
    }
    if cursor:
        payload["cursor"] = cursor

    page = post("block/mev-bids", payload)
    if not page["ready"]:
        raise SystemExit("Bid data is still processing; retry later")

    bids.extend(page["data"])
    cursor = page.get("paging", {}).get("next_cursor", "")
    if not cursor:
        break

later_bids = [
    bid
    for bid in bids
    if bid["slot_offset_ms"] is not None
    and bid["slot_offset_ms"] > winner["slot_offset_ms"]
]
winning_value = int(winner["value"])
better_bids = [bid for bid in later_bids if int(bid["value"]) > winning_value]

first_better = min(better_bids, key=lambda bid: bid["slot_offset_ms"], default=None)
best_later = max(later_bids, key=lambda bid: int(bid["value"]), default=None)


def eth(wei):
    return Decimal(wei) / Decimal(10**18)


print("status:", summary["status"])
print("winner:", eth(winner["value"]), "ETH at", winner["slot_offset_ms"], "ms")
print("first better:", first_better)
print("best later:", best_later)
print("missed value:", eth(summary["missed_value"]), "ETH")
```

***

## Audit the Alternatives with JavaScript

This Node.js 18+ example uses `BigInt` so bid values remain exact.

```javascript theme={null}
const baseUrl = 'https://beaconcha.in/api/v2/ethereum'
const blockNumber = 25378998
const headers = {
  Authorization: `Bearer ${process.env.BEACONCHAIN_API_KEY}`,
  'Content-Type': 'application/json',
}

async function post(path, payload) {
  const response = await fetch(`${baseUrl}/${path}`, {
    method: 'POST',
    headers,
    body: JSON.stringify(payload),
  })

  if (!response.ok) {
    throw new Error(`${response.status} ${await response.text()}`)
  }

  return response.json()
}

const selector = { chain: 'mainnet', block: { number: blockNumber } }
const { data: summary } = await post('block/mev-timing', selector)
const winner = summary.winning_bid

if (!winner) {
  throw new Error(`No identified winner: ${summary.status}`)
}

let cursor = ''
const bids = []

do {
  const payload = {
    ...selector,
    slot_offset_ms_range: {
      min: winner.slot_offset_ms,
      max: 4000,
    },
    page_size: 200,
    ...(cursor && { cursor }),
  }
  const page = await post('block/mev-bids', payload)

  if (!page.ready) {
    throw new Error('Bid data is still processing; retry later')
  }

  bids.push(...page.data)
  cursor = page.paging?.next_cursor ?? ''
} while (cursor)

const laterBids = bids.filter(bid =>
  bid.slot_offset_ms !== null
  && bid.slot_offset_ms > winner.slot_offset_ms,
)
const winningValue = BigInt(winner.value)
const betterBids = laterBids.filter(bid => BigInt(bid.value) > winningValue)
const firstBetter = betterBids.reduce((first, bid) =>
  !first || bid.slot_offset_ms < first.slot_offset_ms ? bid : first,
null)
const bestLater = laterBids.reduce((best, bid) =>
  !best || BigInt(bid.value) > BigInt(best.value) ? bid : best,
null)

console.log({
  status: summary.status,
  winner,
  first_better: firstBetter,
  best_later: bestLater,
  missed_value_wei: summary.missed_value,
})
```

<Note>
  Relay observations can contain duplicate economic bids from different sources or relays. The timing summary uses beaconcha.in's canonical aggregation. Treat the raw ladder as offchain observational audit data, not as a complete or independently verifiable record and not as a drop-in replacement for the summary fields.
</Note>

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Start with the Summary" icon="list-check">
    Use `block/mev-timing` first. Fetch the full ladder only when the outcome needs deeper investigation.
  </Card>

  <Card title="Filter Before Paging" icon="filter">
    Restrict `slot_offset_ms_range` to the relevant interval to avoid downloading an unnecessarily large ladder.
  </Card>

  <Card title="Preserve Wei Exactly" icon="coins">
    Parse values with Python integers, `Decimal`, JavaScript `BigInt`, or another arbitrary-precision type.
  </Card>

  <Card title="Respect Readiness" icon="clock-rotate-left">
    Retry `pending` summaries and `ready: false` ladders after the roughly four-hour processing lag.
  </Card>
</CardGroup>

***

## Related Resources

* [MEV Analysis Introduction](/use-cases/mev-analysis-introduction) — Endpoint selection, timing bands, and freshness
* [Compare MEV Timing](/use-cases/compare-mev-timing) — Find aggregate outliers before drilling into blocks
* [MEV Timing Games](/mev-timing-games/introduction) — Winning, first better, and best later bids explained
* [API Pagination](/api/pagination) — Cursor handling for long bid ladders
