데모

PBS 소비자 트랙

Flashbots 인프라를 소비하는 두 트랙: C2 서처(번들 제출, Sepolia) · C4 관찰자(relay Data API, 메인넷). 설계: docs/features/xyz-demo.md

↓ 기술 노트 보기 (목적·동작 방식)

🛰️ C2 서처 — 번들 제출 (Sepolia)

번들(순서 정해진 tx 묶음)을 Flashbots Sepolia relay(relay-sepolia.flashbots.net)에 제출합니다. 먼저 eth_callBundle로 시뮬레이션 후 eth_sendBundle로 전송. 테스트넷 전용 · 서명은 서버의 ADMIN_KEY로만 이뤄지며 브라우저에 노출되지 않습니다. 로그인 필요.

네트워크:
💡 값 추천 (maxFee · priority · blocks)
  • maxFee (gwei): 상한값이라 실제로 다 내진 않아요. Sepolia는 base fee가 작아 25–50이면 충분 (기본 30).
  • priority (gwei): 빌더에게 주는 실제 팁. Sepolia는 2–5면 충분 — 크게 올려도 포함이 보장되진 않아요.
  • blocks: Sepolia에선 이게 가장 큰 지렛대 — 빌더 참여가 드물어 더 많은 슬롯을 노려야 확률이 올라요 (최대 25).
  • 규칙: maxFee ≥ 2 × baseFee + priority (너무 낮으면 팁이 잘립니다).

🛰️ PBS Relay 관찰자 (C4)

공개 relay Data API 폴링 — 키 불필요, 20초 자동 갱신. 누가 블록을 만들었나(builder 점유율)·입찰가 분포·relay별 지연을 집계. 설계: docs/features/xyz-demo.md

무엇을 보고 있나

관측 블록(고유)
0
빌더 수
0
입찰가 중앙값
응답 relay
0/0

Builder 시장점유율 (최근 0 블록)

delivered 블록을 builder_pubkey로 묶어 센 것 — 어느 빌더가 블록을 많이 이겼나.

데이터 없음.

입찰가(value) 분포

제안자에게 지급된 페이로드 가치(ETH) — PBS 경매에서 블록이 얼마에 팔렸나.

최소
중앙값
평균
최대

Relay별 지연 · 상태

Ethereum Mainnet relay별 응답 시간(서버→relay) — 어느 relay가 빠른가.

Relay호스트상태지연블록

최근 delivered 블록

Ethereum Mainnet에서 최근 블록에 실제로 포함된 페이로드 (슬롯 내림차순).

데이터 없음.

기술 노트

각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.

PBS (서처 / 릴레이)라이브

목적: 이더리움 PBS(Proposer-Builder Separation) 시장의 양쪽 측면 — 서처로서 MEV 스타일 번들을 제출하는 것과, 실제 mev-boost 릴레이 경매 데이터를 관찰하는 것 — 을 검증인이나 블록 빌더를 직접 운영하지 않고도 보여줍니다.

동작 방식: 서처 경로는 서버에 보관된 키로 EIP-1559 트랜잭션을 서명하고, SDK 없이 직접 Flashbots의 X-Flashbots-Signature 인증 헤더를(일회용 평판 서명자로) 구성합니다. 제출 전에 eth_callBundle로 시뮬레이션해 revert를 먼저 걸러내고, 포함률을 높이기 위해 여러 블록 빌더 릴레이에 eth_sendBundle을 동시에 전송합니다. 별도 폴러가 트랜잭션 영수증을 확인해 번들이 실제로 포함됐는지 판단합니다. 관찰자 경로는 공개 메인넷 릴레이 4곳의 Data API에 서버에서 동시 요청을 보내고, block_hash로 중복을 제거한 뒤 빌더 점유율과 입찰가 분포를 집계해 20초마다 갱신합니다.

관련 코드:
"""PBS (searcher / relay) PoC — a simplified proposer-builder-separation auction.
Illustrates the core mechanism: searchers submit bundles with a bid, a relay picks the
highest bid to hand to the proposer, simulating each bundle first to reject reverts.
"""

import random
from dataclasses import dataclass


@dataclass
class Bundle:
    searcher: str
    bid_wei: int
    txs: list[str]
    reverts: bool = False


def simulate(bundle: Bundle) -> bool:
    """Mimics eth_callBundle: would this bundle revert if included?"""
    return not bundle.reverts


class Relay:
    def __init__(self):
        self.mempool: list[Bundle] = []

    def submit(self, bundle: Bundle) -> None:
        self.mempool.append(bundle)

    def run_auction(self) -> Bundle | None:
        valid = [b for b in self.mempool if simulate(b)]
        rejected = [b for b in self.mempool if not simulate(b)]
        for b in rejected:
            print(f"  reject {b.searcher}: bundle reverts, dropped before relay sees a bid")
        if not valid:
            return None
        winner = max(valid, key=lambda b: b.bid_wei)
        return winner


if __name__ == "__main__":
    random.seed(7)
    relay = Relay()
    relay.submit(Bundle("searcher-A", bid_wei=12_000_000_000_000, txs=["swap(WETH,USDC)"]))
    relay.submit(Bundle("searcher-B", bid_wei=18_500_000_000_000, txs=["arb(pool1,pool2)"]))
    relay.submit(Bundle("searcher-C", bid_wei=25_000_000_000_000, txs=["liquidate(user42)"], reverts=True))
    relay.submit(Bundle("searcher-D", bid_wei=16_000_000_000_000, txs=["sandwich(tx99)"]))

    print("bundles submitted to relay:")
    for b in relay.mempool:
        print(f"  {b.searcher}: bid={b.bid_wei / 1e18:.6f} ETH  txs={b.txs}")

    print("\nrunning auction (simulate, then pick highest bid among non-reverting bundles):")
    winner = relay.run_auction()
    print(f"\nwinning bundle: {winner.searcher} at {winner.bid_wei / 1e18:.6f} ETH -> forwarded to proposer")

docs/code/pocs/pbs.py