Add MEV-Share client module (closes #34)#63
Conversation
mev_share.MevShareClient wraps flashbots.Relay with the MEV-Share searcher surface, mirroring mev-share-client-ts: - sendTransaction: delegates to Relay.sendPrivateTransaction - Event stream: PendingEvent (transaction/bundle) parsed from the SSE hint stream via sse_transport; blocking on(callback) loop - simulateBundle (mev_simBundle) with SimBundleOpts/SimBundleResult - getEventHistory (GET /api/v1/history) with query params flashbots.Relay.authenticatedRequest and buildMevSendBundleParams are now pub for reuse. 19 unit tests cover event parsing, params builders (exact-JSON), result parsers, and history queries; a refAllDecls test forces semantic analysis of the untested network paths so lazily compiled API breakage cannot hide (lesson from the 0.16 migration). Unverified wire details are marked TODO in code: simBundle timeout encoding, sim-result error field name, nested bundleLogs (only txLogs parsed). New docs page docs/content/docs/mev-share.mdx with a backrun-loop sketch; docs build passes (20/20 pages).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR introduces a complete MEV-Share client for Zig that mirrors the TypeScript reference implementation. The module enables private transaction backrunning and bundle simulation on Ethereum via MEV-Share's SSE event stream and JSON-RPC APIs. Supporting changes expose relay authentication and bundle parameter helpers from ChangesMEV-Share Client Implementation & Documentation
Sequence Diagram(s)sequenceDiagram
participant User
participant MevShareClient
participant Relay
participant MevShare_SSE
participant MevShare_API
User->>MevShareClient: initMainnet(auth_key)
MevShareClient->>MevShareClient: init relay + http client
User->>MevShareClient: sendTransaction(opts)
MevShareClient->>Relay: authenticatedRequest(eth_sendPrivateTransaction)
Relay->>MevShare_API: POST /eth/sendPrivateTransaction
MevShare_API-->>Relay: tx hash
Relay-->>MevShareClient: hash
MevShareClient-->>User: [32]u8
User->>MevShareClient: on(callback)
MevShareClient->>MevShare_SSE: subscribe event stream
MevShare_SSE-->>MevShareClient: data: {...hash, txs, logs...}
MevShareClient->>MevShareClient: parseEventData
MevShareClient->>User: callback(PendingEvent)
User->>MevShareClient: process event, return
MevShareClient->>MevShareClient: freePendingEvent
User->>MevShareClient: simulateBundle(opts, sim_opts)
MevShareClient->>MevShareClient: buildSimBundleParams
MevShareClient->>Relay: authenticatedRequest(mev_simBundle)
Relay->>MevShare_API: POST /mev/simBundle
MevShare_API-->>Relay: {success, mev_gas_price, profit...}
Relay-->>MevShareClient: result json
MevShareClient->>MevShareClient: parseSimBundleResult
MevShareClient-->>User: SimBundleResult
User->>MevShareClient: getEventHistory(params)
MevShareClient->>MevShareClient: buildHistoryQuery
MevShareClient->>MevShare_API: GET /api/v1/history?...
MevShare_API-->>MevShareClient: [{block, timestamp, hint}, ...]
MevShareClient->>MevShareClient: parseEventHistoryResponse
MevShareClient-->>User: []EventHistoryEntry
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/mev_share.zig (1)
28-33: ⚡ Quick winAdd the missing Sepolia default alongside the mainnet helpers.
Line 31 already exports
sepolia_stream_url, but callers still have to hardcode the Sepolia relay URL because onlymainnet_relay_urlandinitMainnet()exist. That leaves the “network defaults for mainnet and Sepolia” objective only half exposed and leaks a literal into the docs example.Suggested API addition
pub const mainnet_stream_url = "https://mev-share.flashbots.net"; /// MEV-Share SSE event stream + history API (Sepolia). pub const sepolia_stream_url = "https://mev-share-sepolia.flashbots.net"; /// Flashbots relay JSON-RPC endpoint (mainnet). pub const mainnet_relay_url = "https://relay.flashbots.net"; +/// Flashbots relay JSON-RPC endpoint (Sepolia). +pub const sepolia_relay_url = "https://relay-sepolia.flashbots.net"; @@ /// Convenience constructor for Ethereum mainnet. pub fn initMainnet(allocator: std.mem.Allocator, auth_key: [32]u8) MevShareClient { return init(allocator, mainnet_relay_url, mainnet_stream_url, auth_key); } + + /// Convenience constructor for Ethereum Sepolia. + pub fn initSepolia(allocator: std.mem.Allocator, auth_key: [32]u8) MevShareClient { + return init(allocator, sepolia_relay_url, sepolia_stream_url, auth_key); + }Also applies to: 369-372
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/mev_share.zig` around lines 28 - 33, Add a Sepolia relay constant and a Sepolia initializer to mirror the existing mainnet helpers: define sepolia_relay_url (matching the relay endpoint used for Sepolia) alongside mainnet_relay_url and add an initSepolia() function (parallel to initMainnet()) that wires sepolia_stream_url and sepolia_relay_url into the same setup flow; then update the network-defaults usage sites (the code around the other defaults, e.g., the block referenced at 369-372) to use initSepolia() or sepolia_relay_url instead of a hardcoded Sepolia URL so callers no longer need to embed literals.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/mev_share.zig`:
- Around line 259-262: The SimBundle wire-shape must serialize
SimBundleOpts.timeout as an unquoted JSON number under simOptions and must parse
failure text from result.error when success==false; update the code that builds
the outbound JSON to put the timeout value (the field timeout: ?u64 in the
SimBundleOpts/struct) inside the simOptions object and emit it as a numeric JSON
value (not a quoted string), and update the response handling logic that
consumes the simulation result to check result.success and, when false, read
result.error (a string) and propagate that text as the failure message; locate
and change the JSON construction/serialization code that references timeout and
the response-parsing code that inspects result.success/result.error to implement
these behaviors.
---
Nitpick comments:
In `@src/mev_share.zig`:
- Around line 28-33: Add a Sepolia relay constant and a Sepolia initializer to
mirror the existing mainnet helpers: define sepolia_relay_url (matching the
relay endpoint used for Sepolia) alongside mainnet_relay_url and add an
initSepolia() function (parallel to initMainnet()) that wires sepolia_stream_url
and sepolia_relay_url into the same setup flow; then update the network-defaults
usage sites (the code around the other defaults, e.g., the block referenced at
369-372) to use initSepolia() or sepolia_relay_url instead of a hardcoded
Sepolia URL so callers no longer need to embed literals.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b23e5e54-c974-46bc-8368-85092e31b8b2
📒 Files selected for processing (6)
CHANGELOG.mddocs/content/docs/meta.jsondocs/content/docs/mev-share.mdxsrc/flashbots.zigsrc/mev_share.zigsrc/root.zig
| /// Simulation timeout in seconds. Default: 5. | ||
| /// TODO: the docs do not specify the wire encoding; sent as a plain | ||
| /// JSON number (seconds), matching the documented default ("5"). | ||
| timeout: ?u64 = null, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Flashbots mev_simBundle timeout encoding response error field name mev-share-client-ts official docs
💡 Result:
The "mev_simBundle" RPC method includes a "timeout" field within its "simOptions" object, which specifies the duration for the simulation in seconds, with a default value of 5 seconds [1][2][3]. In the context of the Flashbots mev-share-client-ts library, simulation is performed using the "simulateBundle" function [4][5]. If you are encountering an encoding error or timeout issue related to a field name, it is likely due to an inconsistency between the expected JSON-RPC schema and the parameters being passed. Based on the official Flashbots specifications for "mev_simBundle", the "simOptions" object is structured as follows [1][3]: { parentBlock?: number | string, blockNumber?: number, coinbase?: string, timestamp?: number, gasLimit?: number, baseFee?: bigint, timeout?: number // Default is 5 seconds } Troubleshooting steps: 1. Verify Field Names: Ensure you are nesting "timeout" inside the "simOptions" object rather than passing it at the top level of the "mev_simBundle" parameters [1][3]. 2. Check Data Types: The "timeout" field expects a number (in seconds) [1][2][3]. If your client is attempting to encode this as a string or another incompatible type, it will trigger an encoding error. 3. Review Library Version: Ensure your "mev-share-client-ts" library is up to date, as the "simOptions" interface is explicitly defined in the library to handle these fields correctly [5][6]. If you are seeing an error specifically mentioning "field name" during encoding, it often indicates that the library's TypeScript interfaces are not matching the structure expected by the MEV-Share Node [1][3]. Always cross-reference your request object against the schema provided in the official Flashbots documentation [1][3].
Citations:
- 1: https://github.com/flashbots/flashbots-docs/blob/main/docs/specs/mev-share/_mev_simBundle.mdx
- 2: https://docs.flashbots.net/flashbots-mev-share/release-notes/2023-06
- 3: https://docs.flashbots.net/flashbots-auction/advanced/rpc-endpoint
- 4: https://docs.flashbots.net/flashbots-mev-share/searchers/debugging
- 5: https://github.com/flashbots/mev-share-client-ts/blob/main/README.md
- 6: https://docs.flashbots.net/flashbots-mev-share/searchers/getting-started
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/mev_share.zig"
echo "== Lines around 250-290 =="
nl -ba "$FILE" | sed -n '240,290p'
echo
echo "== Lines around 540-590 =="
nl -ba "$FILE" | sed -n '540,590p'
echo
echo "== Lines around 620-670 =="
nl -ba "$FILE" | sed -n '620,670p'
echo
echo "== Search for mev_simBundle / simulateBundle / error parsing =="
rg -n "mev_simBundle|simulateBundle|simOptions|timeout|\"error\"|error\b|fail|failure" "$FILE"Repository: StrobeLabs/eth.zig
Length of output: 150
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/mev_share.zig"
echo "== Lines around 250-290 =="
awk 'NR>=240 && NR<=290 {printf "%6d %s\n", NR, $0}' "$FILE"
echo
echo "== Lines around 540-590 =="
awk 'NR>=540 && NR<=590 {printf "%6d %s\n", NR, $0}' "$FILE"
echo
echo "== Lines around 620-670 =="
awk 'NR>=620 && NR<=670 {printf "%6d %s\n", NR, $0}' "$FILE"
echo
echo "== Search for mev_simBundle / simulateBundle / simOptions / timeout / \"error\" parsing =="
rg -n "mev_simBundle|simulateBundle|simOptions|timeout|\"error\"|error\b|failure\b|fail\b" "$FILE" || trueRepository: StrobeLabs/eth.zig
Length of output: 13489
🌐 Web query:
Flashbots mev_simBundle response result { success error } field name
💡 Result:
The Flashbots mev_simBundle RPC method returns a JSON object containing a success boolean field, which indicates whether the simulation of the bundle was successful [1][2]. If the simulation fails, the response may include an error string field explaining the failure [2]. According to the official Flashbots MEV-Share client implementations, the simulation response object typically includes the following fields [2]: - success: (boolean) Whether the simulation succeeded [1][2]. - error: (string, optional) An error message if the simulation failed [2]. - stateBlock: (string) The block number used for the simulation, represented as a hex-encoded string [1][2]. - mevGasPrice: (string) The effective MEV gas price [1][2]. - profit: (string) The expected profit in wei [1][2]. - refundableValue: (string) The value that would be refunded [1][2]. - gasUsed: (string) The total gas used by the bundle [1][2]. - logs: (array, optional) Transaction logs resulting from the simulation [1][2]. These fields are commonly accessed by client libraries to verify bundle execution and economic viability before submission [1][3][4].
Citations:
- 1: https://github.com/harpy-wings/flashbot
- 2: https://github.com/flashbots/mev-share-client-ts/blob/main/src/api/interfaces.ts
- 3: https://github.com/flashbots/mev-share-client-ts/blob/main/README.md
- 4: https://context7.com/flashbots/flashbots-docs/llms.txt
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/mev_share.zig"
echo "== Lines around build params for mev_simBundle (500-560) =="
awk 'NR>=480 && NR<=590 {printf "%6d %s\n", NR, $0}' "$FILE"
echo
echo "== Lines around simulateBundle() usage (360-460) =="
awk 'NR>=350 && NR<=480 {printf "%6d %s\n", NR, $0}' "$FILE"
echo
echo "== Tests around timeout + parseSimBundleResult error (880-1030) =="
awk 'NR>=880 && NR<=1035 {printf "%6d %s\n", NR, $0}' "$FILE"Repository: StrobeLabs/eth.zig
Length of output: 18501
mev_simBundle: timeout and failure error wire fields align with spec/ts
SimBundleOpts.timeoutis nested undersimOptionsand serialized as an unquoted JSON number of seconds (unit tests assert"timeout":10), matching the Flashbotsmev_simBundlesimOptions.timeoutdefinition.- Failure text parsing from
result.error(string) whensuccess=falsematches mev-share-client-ts’s response shape and is covered by the unit test using{"success":false,"error":"execution reverted"}.
Remaining wire-shape TODOs to review are outside these two fields (e.g., unparsed nested bundleLogs).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/mev_share.zig` around lines 259 - 262, The SimBundle wire-shape must
serialize SimBundleOpts.timeout as an unquoted JSON number under simOptions and
must parse failure text from result.error when success==false; update the code
that builds the outbound JSON to put the timeout value (the field timeout: ?u64
in the SimBundleOpts/struct) inside the simOptions object and emit it as a
numeric JSON value (not a quoted string), and update the response handling logic
that consumes the simulation result to check result.success and, when false,
read result.error (a string) and propagate that text as the failure message;
locate and change the JSON construction/serialization code that references
timeout and the response-parsing code that inspects result.success/result.error
to implement these behaviors.
What
mev_share.MevShareClient— the MEV-Share searcher/user surface from #34, mirroringmev-share-client-ts:eth_sendPrivateTransaction(delegates to the Relay support from Add eth_sendPrivateTransaction support (closes #40) #62)on(callback)drivessse_transportover the hint stream;parseEventDatais a pure function producingPendingEvent(transaction/bundle) with hash, logs, calldata, selector, to, valuemev_simBundlewithSimBundleOpts(parentBlock, blockNumber, coinbase, timestamp, gasLimit, baseFee, timeout) and parsedSimBundleResult(profit, mevGasPrice, refundableValue, gasUsed, logs)GET /api/v1/historywith block/timestamp/limit/offset filtersinitMainnetconvenience (relay.flashbots.net + mev-share.flashbots.net); free helpers for all heap-owning resultsTests
19 unit tests (event fixtures incl. malformed, exact-JSON params assertions, result parsers, history queries) plus a
refAllDeclsRecursivetest that forces semantic analysis of the network paths — no more lazily-hidden API breakage (the failure mode behind #55/#60). Wire shapes verified against the Flashbots docs; three unverifiable details are marked TODO in code.Docs
New guide
docs/content/docs/mev-share.mdx(first flashbots/MEV docs page) with a backrun-loop sketch; registered in nav; docs build passes (20/20 pages).Verification
zig build/test/fmt --checkon 0.16.0 andbuild teston 0.17.0-dev: pass. CodeRabbit reviewed: its one finding on this diff claimedWriter.buffered()doesn't exist in 0.16 — it does (lib/std/Io/Writer.zig:155), and the new refAllDecls test proves the path compiles.Closes #34
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation