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
무엇을 보고 있나
- 네트워크: Ethereum Mainnet — mev-boost PBS(제안자–빌더 분리) 경매가 실제로 도는 곳. 공개 Data API에 실 트래픽이 있어 테스트넷 대신 메인넷을 관측합니다.
- 데이터:
proposer_payload_delivered— relay가 제안자에게 실제로 전달한(= 블록에 들어간) 페이로드. relay당 최근 20건. - Relay 4곳: Flashbots · bloXroute · Agnostic · Ultra Sound — 브라우저 CORS를 피하려 서버(
/api/relay)가 병렬로 프록시하며 지연을 측정합니다. - 참고: 표시 전용 · 키/지갑 불필요. 오픈소스
flashbots/relayscan이 같은 데이터를 다룹니다.
Builder 시장점유율 (최근 0 블록)
delivered 블록을 builder_pubkey로 묶어 센 것 — 어느 빌더가 블록을 많이 이겼나.
데이터 없음.
입찰가(value) 분포
제안자에게 지급된 페이로드 가치(ETH) — PBS 경매에서 블록이 얼마에 팔렸나.
Relay별 지연 · 상태
Ethereum Mainnet relay별 응답 시간(서버→relay) — 어느 relay가 빠른가.
| Relay | 호스트 | 상태 | 지연 | 블록 |
|---|
최근 delivered 블록
Ethereum Mainnet에서 최근 블록에 실제로 포함된 페이로드 (슬롯 내림차순).
데이터 없음.
기술 노트
각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.
목적: 이더리움 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")