-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: thanhpp <[email protected]>
- Loading branch information
Showing
3 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package mev | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"crypto/ecdsa" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"strconv" | ||
|
||
"github.com/ethereum/go-ethereum/accounts" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
) | ||
|
||
const ( | ||
SendBundleID = 1 | ||
SendBundleBloxrouteMethod = "blxr_simulate_bundle" | ||
SendBundleMethod = "eth_sendBundle" | ||
) | ||
|
||
func SendBundle( // nolint: cyclop | ||
ctx context.Context, c *http.Client, endpoint string, req SendBundleRequest, options ...SendBundleOptions, | ||
) (SendBundleResponse, error) { | ||
var opts sendBundleOpts | ||
for _, fn := range options { | ||
if fn == nil { | ||
continue | ||
} | ||
fn(&opts) | ||
} | ||
|
||
reqBody, err := json.Marshal(req) | ||
if err != nil { | ||
return SendBundleResponse{}, fmt.Errorf("marshal json error: %w", err) | ||
} | ||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(reqBody)) | ||
if err != nil { | ||
return SendBundleResponse{}, fmt.Errorf("new http request error: %w", err) | ||
} | ||
if opts.flashbotSignKey != nil { | ||
if err := signFlashbotRequest(opts.flashbotSignKey, httpReq, reqBody); err != nil { | ||
return SendBundleResponse{}, fmt.Errorf("sign flashbot request error: %w", err) | ||
} | ||
} | ||
httpReq.Header.Add("Content-Type", "application/json") | ||
httpReq.Header.Add("Accept", "application/json") | ||
|
||
httpResp, err := c.Do(httpReq) | ||
if err != nil { | ||
return SendBundleResponse{}, fmt.Errorf("do request error: %w", err) | ||
} | ||
defer httpResp.Body.Close() | ||
|
||
respBody, err := io.ReadAll(httpResp.Body) | ||
if err != nil { | ||
return SendBundleResponse{}, fmt.Errorf("read response error: %w", err) | ||
} | ||
|
||
if httpResp.StatusCode != http.StatusOK { | ||
return SendBundleResponse{}, fmt.Errorf("not OK status, status: [%d], data: [%s]", | ||
httpResp.StatusCode, string(respBody)) | ||
} | ||
|
||
var resp SendBundleResponse | ||
if err := json.Unmarshal(respBody, &resp); err != nil { | ||
return SendBundleResponse{}, fmt.Errorf("unmarshal response error: %w, data: [%s]", err, string(respBody)) | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
func signFlashbotRequest(key *ecdsa.PrivateKey, request *http.Request, body []byte) error { | ||
hashed := crypto.Keccak256Hash(body).Hex() | ||
signature, err := crypto.Sign(accounts.TextHash([]byte(hashed)), key) | ||
if err != nil { | ||
return fmt.Errorf("sign crypto error: %w", err) | ||
} | ||
|
||
request.Header.Add( | ||
"X-Flashbots-Signature", | ||
fmt.Sprintf("%s:%s", crypto.PubkeyToAddress(key.PublicKey), hexutil.Encode(signature))) | ||
|
||
return nil | ||
} | ||
|
||
type sendBundleOpts struct { | ||
flashbotSignKey *ecdsa.PrivateKey | ||
} | ||
|
||
type SendBundleOptions func(*sendBundleOpts) | ||
|
||
func WithFlashbotSignature(key *ecdsa.PrivateKey) SendBundleOptions { | ||
return func(sbo *sendBundleOpts) { | ||
if key == nil { | ||
return | ||
} | ||
|
||
sbo.flashbotSignKey = key | ||
} | ||
} | ||
|
||
type SendBundleRequest struct { | ||
Method string `json:"method,omitempty"` | ||
ID string `json:"id,omitempty"` | ||
Params SendBundleParams `json:"params,omitempty"` | ||
} | ||
|
||
func (r *SendBundleRequest) SetMethod(method string) *SendBundleRequest { | ||
r.Method = method | ||
return r | ||
} | ||
|
||
func (r *SendBundleRequest) SetID(id int) *SendBundleRequest { | ||
r.ID = strconv.Itoa(id) | ||
return r | ||
} | ||
|
||
func (r *SendBundleRequest) SetTransactions(txs ...*types.Transaction) *SendBundleRequest { | ||
transactions := make([]string, 0, len(txs)) | ||
for _, tx := range txs { | ||
transactions = append(transactions, fmt.Sprintf("0x%s", txToRlp(tx))) | ||
} | ||
|
||
r.Params.Transaction = transactions | ||
|
||
return r | ||
} | ||
|
||
func (r *SendBundleRequest) SetBlockNumber(block uint64) *SendBundleRequest { | ||
r.Params.BlockNumber = fmt.Sprintf("0x%x", block) | ||
|
||
return r | ||
} | ||
|
||
type SendBundleParams struct { | ||
Transaction []string `json:"transaction,omitempty"` | ||
BlockNumber string `json:"block_number,omitempty"` | ||
StateBlockNumber string `json:"state_block_number,omitempty"` | ||
Timestamp int `json:"timestamp,omitempty"` | ||
BlockchainNetwork string `json:"blockchain_network,omitempty"` | ||
} | ||
|
||
type SendBundleResponse struct { | ||
Jsonrpc string `json:"jsonrpc,omitempty"` | ||
ID string `json:"id,omitempty"` | ||
Result SendBundleResult `json:"result,omitempty"` | ||
} | ||
|
||
type SendBundleResult struct { | ||
BundleGasPrice string `json:"bundleGasPrice,omitempty"` | ||
BundleHash string `json:"bundleHash,omitempty"` | ||
CoinbaseDiff string `json:"coinbaseDiff,omitempty"` | ||
EthSentToCoinbase string `json:"ethSentToCoinbase,omitempty"` | ||
GasFees string `json:"gasFees,omitempty"` | ||
Results []SendBundleResults `json:"results,omitempty"` | ||
StateBlockNumber int `json:"stateBlockNumber,omitempty"` | ||
TotalGasUsed int `json:"totalGasUsed,omitempty"` | ||
} | ||
|
||
type SendBundleResults struct { | ||
GasUsed int `json:"gasUsed,omitempty"` | ||
TxHash string `json:"txHash,omitempty"` | ||
Value string `json:"value,omitempty"` | ||
} | ||
|
||
func txToRlp(tx *types.Transaction) string { | ||
var buff bytes.Buffer | ||
_ = tx.EncodeRLP(&buff) | ||
return fmt.Sprintf("%x", buff.Bytes()) | ||
} |