-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfunc.go
More file actions
371 lines (333 loc) · 11.4 KB
/
func.go
File metadata and controls
371 lines (333 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
package flashbot
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"math/big"
"math/rand"
"net/http"
"strconv"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"go.opentelemetry.io/otel/codes"
)
// Simulate runs the bundle against the Flashbots Relay to check for reverts.
// This should ALWAYS be called before Broadcast.
// blockNumber: The target block you want to land in.
// stateBlock: The block state to simulate on (usually target - 1).
func (f *flashbot) Simulate(ctx context.Context, bundle *Bundle, targetBlock uint64, opts ...BundleOption) (*SimulateResponse, error) {
ctx, span := f.tracer.Start(ctx, "flashbot.Simulate")
defer span.End()
if len(bundle.Transactions) == 0 {
span.SetStatus(codes.Error, "bundle is empty")
return nil, fmt.Errorf("bundle cannot be empty")
}
// Convert transactions to hex strings
body := make([]mevSendBundleBodyItem, 0, len(bundle.Transactions))
for i, tx := range bundle.Transactions {
bs, err := tx.MarshalBinary()
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to encode transaction: %w", err)
}
txHex := hexutil.Encode(bs)
canRevert := false
if i < len(bundle.CanRevert) {
canRevert = bundle.CanRevert[i]
}
body = append(body, mevSendBundleBodyItem{
Tx: &txHex,
CanRevert: &canRevert,
})
}
// Build the mev_simBundle params
var blockHex string
if targetBlock == 0 {
blockHex = "latest"
} else {
blockHex = "0x" + strconv.FormatUint(targetBlock, 16)
}
params := mevSimBundleParams{
Version: "v0.1",
Inclusion: mevSendBundleInclusion{
Block: blockHex,
},
Body: body,
}
// Apply options
for _, opt := range opts {
err := opt(¶ms)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to apply option: %w", err)
}
}
// Create JSON-RPC request
reqID := rand.Intn(1000000)
reqBody := rpcReq{
JsonRpc: jsonRPCVersion,
Id: reqID,
Method: methodMevSimBundle,
Params: []interface{}{params},
}
httpReq, err := f.newRequest(ctx, &reqBody)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
// Execute request
resp, err := f.client.Do(httpReq)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to execute request: %w", err)
}
// Parse response
var rpcResp struct {
Id int `json:"id"`
Result *MevSimResponse `json:"result,omitempty"`
Error *rpcError `json:"error,omitempty"`
}
bs, err := io.ReadAll(resp.Body)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to read response body: %w", err)
}
err = resp.Body.Close()
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to close response body: %w", err)
}
err = json.Unmarshal(bs, &rpcResp)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if rpcResp.Error != nil {
span.SetStatus(codes.Error, rpcResp.Error.Message)
return rpcResp.Result, fmt.Errorf("RPC error: %s (code: %d)", rpcResp.Error.Message, rpcResp.Error.Code)
}
if rpcResp.Result == nil {
span.SetStatus(codes.Error, "empty result")
return nil, fmt.Errorf("empty result from relay")
}
span.SetStatus(codes.Ok, "simulation completed successfully")
return rpcResp.Result, nil
}
// Broadcast sends the bundle to the configured list of builders (Titan, Beaver, Flashbots, etc.).
// It returns the list of builders that accepted the request.
func (f *flashbot) Broadcast(ctx context.Context, bundle *Bundle, targetBlock uint64, opts ...BundleOption) (*BroadcastResponse, error) {
ctx, span := f.tracer.Start(ctx, "flashbot.Broadcast")
defer span.End()
_, err := f.Simulate(ctx, bundle, targetBlock)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to simulate bundle: %w", err)
}
// Convert transactions to hex strings
body := make([]mevSendBundleBodyItem, 0, len(bundle.Transactions))
for i, tx := range bundle.Transactions {
bs, err := tx.MarshalBinary()
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to encode transaction: %w", err)
}
txHex := hexutil.Encode(bs)
canRevert := false
if i < len(bundle.CanRevert) {
canRevert = bundle.CanRevert[i]
}
body = append(body, mevSendBundleBodyItem{
Tx: &txHex,
CanRevert: &canRevert,
})
}
// Build the mev_simBundle params
var blockHex string
if targetBlock == 0 {
blockHex = "latest"
} else {
blockHex = "0x" + strconv.FormatUint(targetBlock, 16)
}
maxBlock := "0x" + strconv.FormatUint(targetBlock+30, 16)
params := mevSimBundleParams{
Version: "v0.1",
Inclusion: mevSendBundleInclusion{
Block: blockHex,
MaxBlock: &maxBlock,
},
Body: body,
}
// Apply options
for _, opt := range opts {
err := opt(¶ms)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to apply option: %w", err)
}
}
// Create JSON-RPC request
reqID := rand.Intn(1000000)
reqBody := rpcReq{
JsonRpc: jsonRPCVersion,
Id: reqID,
Method: methodMevSendBundle,
Params: []interface{}{params},
}
httpReq, err := f.newRequest(ctx, &reqBody)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
// Execute request
resp, err := f.client.Do(httpReq)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to execute request: %w", err)
}
// Parse response
var rpcResp struct {
Jsonrpc string `json:"jsonrpc"`
ID int `json:"id"`
Result *BroadcastResponse `json:"result,omitempty"`
Error *rpcError `json:"error,omitempty"`
}
bs, err := io.ReadAll(resp.Body)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to read response body: %w", err)
}
fmt.Println(string(bs))
err = resp.Body.Close()
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to close response body: %w", err)
}
err = json.Unmarshal(bs, &rpcResp)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if rpcResp.Error != nil {
span.SetStatus(codes.Error, rpcResp.Error.Message)
return rpcResp.Result, fmt.Errorf("RPC error: %s (code: %d)", rpcResp.Error.Message, rpcResp.Error.Code)
}
if rpcResp.Result == nil {
span.SetStatus(codes.Error, "empty result")
return nil, fmt.Errorf("empty result from relay")
}
span.SetStatus(codes.Ok, "simulation completed successfully")
return rpcResp.Result, nil
}
// SendPrivateTransaction sends a single transaction directly to builders (eth_sendPrivateTransaction).
// Useful for simple transfers where you don't need a full bundle but want frontrunning protection.
func (f *flashbot) SendPrivateTransaction(ctx context.Context, signedTxHex string, maxBlockHeight uint64) error {
return fmt.Errorf("not implemented")
}
// --- Gas & Network Intelligence ---
// GetGasPrice returns the suggested gas price.
// For EIP-1559 chains, this should return the BaseFee + PriorityFee.
// You can implement this to return a "fast" price for aggressive inclusion.
// tip is the current optimal priority fee (bribe) based on network congestion.
// This helps you calculate the `minerBribe` dynamically rather than hardcoding 2 Gwei.
func (f *flashbot) GetGasPrice(ctx context.Context) (gasPrice *big.Int, tip *big.Int, err error) {
ctx, span := f.tracer.Start(ctx, "flashbot.GetGasPrice")
defer span.End()
gasPrice, err = f.ethC.SuggestGasPrice(ctx)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, nil, fmt.Errorf("failed to get gas price: %w", err)
}
tip, err = f.ethC.SuggestGasTipCap(ctx)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, nil, fmt.Errorf("failed to get gas tip: %w", err)
}
span.SetStatus(codes.Ok, "gas tip retrieved successfully")
return gasPrice, tip, nil
}
// EstimateGasBundle calculates the gas units required for the entire bundle.
// This is useful for calculating exactly how much "sponsorship" ETH to send the user.
func (f *flashbot) EstimateGasBundle(ctx context.Context, bundle *Bundle) (uint64, error) {
ctx, span := f.tracer.Start(ctx, "flashbot.EstimateGasBundle")
defer span.End()
totalGas := uint64(0)
for _, tx := range bundle.Transactions {
totalGas += tx.Gas()
}
span.SetStatus(codes.Ok, "gas units calculated successfully")
return totalGas, nil
}
// --- Utilities ---
// GetUserStats checks your signing key's reputation on the relay.
func (f *flashbot) GetUserStats(ctx context.Context, blockNumber *big.Int) (*UserStats, error) {
return nil, fmt.Errorf("not implemented")
}
// GetBundleStats checks the status of a specific bundle on the relay
// EXPERIMENTAL: This is not yet implemented.
func (f *flashbot) GetBundleStats(ctx context.Context, bundleHash string, blockNumber uint64) (*BundleStats, error) {
return nil, fmt.Errorf("not implemented")
}
// INTERNAL METHODS
func (f *flashbot) newRequest(ctx context.Context, req *rpcReq) (*http.Request, error) {
ctx, span := f.tracer.Start(ctx, "flashbot.newRequest")
defer span.End()
// Marshal request body
jsonBody, err := req.ToJson()
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
// Sign the request
signature, err := f.signRequest(jsonBody)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to sign request: %w", err)
}
// Create HTTP request
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, f.relayURL, bytes.NewReader(jsonBody))
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set(headerFlashbotSignature, signature)
return httpReq, nil
}
// signRequest signs the request body using EIP-191 and returns the signature in the format "address:signature"
// The signature is calculated by taking the EIP-191 hash of the json body encoded as UTF-8 bytes.
func (f *flashbot) signRequest(body []byte) (string, error) {
// Create EIP-191 message hash (accounts.TextHash prepends the Ethereum message prefix)
// Apply EIP-191 directly on the JSON body bytes, not on a hash of the body
hashedBody := crypto.Keccak256Hash(body).Hex()
sig, err := crypto.Sign(accounts.TextHash([]byte(hashedBody)), f.pk)
if err != nil {
return "", fmt.Errorf("failed to sign request: %w", err)
}
// Get the address from the private key
address := crypto.PubkeyToAddress(f.pk.PublicKey)
// Return in format "address:signature"
return fmt.Sprintf("%s:%s", address.Hex(), hexutil.Encode(sig)), nil
}