Skip to content

Commit

Permalink
Feat/mev/sendPrivateRawTransaction (#95)
Browse files Browse the repository at this point in the history
* feat: ✨ mev: sendPrivateRawTransaction

Signed-off-by: thanhpp <[email protected]>

---------

Signed-off-by: thanhpp <[email protected]>
  • Loading branch information
thanhpp authored Nov 25, 2024
1 parent 8c674f7 commit 1fd89d3
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 41 deletions.
2 changes: 1 addition & 1 deletion pkg/mev/backrun_public_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (b BackrunPublicClient) SendBackrunBundle(
_ []string,
txs ...*types.Transaction,
) (SendBundleResponse, error) {
req := SendBundleRequest{
req := SendRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: ETHSendBundleMethod,
Expand Down
4 changes: 2 additions & 2 deletions pkg/mev/blxr_bundle_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ type BLXRSubmitBundleResponse struct {
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,string,omitempty"`
Result SendBundleResult `json:"result,omitempty"`
Error SendBundleError `json:"error,omitempty"`
Error ErrorResponse `json:"error,omitempty"`
}

func bloxrouteSignFlashbot(key *ecdsa.PrivateKey, p *BLXRSubmitBundleParams) (string, error) {
Expand All @@ -196,7 +196,7 @@ func bloxrouteSignFlashbot(key *ecdsa.PrivateKey, p *BLXRSubmitBundleParams) (st
param.MaxTimestamp = p.MaxTimestamp
param.RevertingTxs = p.RevertingHashes

req := SendBundleRequest{
req := SendRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: ETHSendBundleMethod,
Expand Down
80 changes: 65 additions & 15 deletions pkg/mev/bundle_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,30 @@ import (
// Client https://beaverbuild.org/docs.html; https://rsync-builder.xyz/docs;
// https://docs.flashbots.net/flashbots-auction/advanced/rpc-endpoint#eth_sendbundle
type Client struct {
c *http.Client
endpoint string
flashbotKey *ecdsa.PrivateKey
cancelBySendBundle bool
senderType BundleSenderType
c *http.Client
endpoint string
flashbotKey *ecdsa.PrivateKey
cancelBySendBundle bool
senderType BundleSenderType
enableSendPrivateRaw bool
}

// NewClient set the flashbotKey to nil will skip adding the signature header.
func NewClient(
c *http.Client,
endpoint string,
flashbotKey *ecdsa.PrivateKey,
cancelBySendBundle bool,
senderType BundleSenderType,
cancelBySendBundle bool,
enableSendPrivateRaw bool,
) (*Client, error) {
return &Client{
c: c,
endpoint: endpoint,
flashbotKey: flashbotKey,
cancelBySendBundle: cancelBySendBundle,
senderType: senderType,
c: c,
endpoint: endpoint,
flashbotKey: flashbotKey,
cancelBySendBundle: cancelBySendBundle,
senderType: senderType,
enableSendPrivateRaw: enableSendPrivateRaw,
}, nil
}

Expand Down Expand Up @@ -80,7 +83,7 @@ func (s *Client) CancelBundle(
p := CancelBundleParams{
ReplacementUUID: bundleUUID,
}
req := SendBundleRequest{
req := SendRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: ETHCancelBundleMethod,
Expand All @@ -106,7 +109,7 @@ func (s *Client) CancelBundle(
}

// do
var errResp SendBundleError
var errResp ErrorResponse
switch s.senderType {
case BundleSenderTypeFlashbot:
resp, err := doRequest[FlashbotCancelBundleResponse](s.c, httpReq, headers...)
Expand Down Expand Up @@ -187,7 +190,11 @@ func (s *Client) sendBundle(
blockNumber uint64,
txs ...*types.Transaction,
) (SendBundleResponse, error) {
req := SendBundleRequest{
if !s.enableSendPrivateRaw {
return SendBundleResponse{}, nil
}

req := SendRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: method,
Expand Down Expand Up @@ -233,6 +240,49 @@ func (s *Client) sendBundle(
return resp, nil
}

func (s *Client) SendPrivateRawTransaction(
ctx context.Context,
tx *types.Transaction,
) (SendPrivateRawTransactionResponse, error) {
req := SendRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: ETHSendPrivateRawTransaction,
Params: []any{"0x" + txToRlp(tx)},
}

reqBody, err := json.Marshal(req)
if err != nil {
return SendPrivateRawTransactionResponse{}, fmt.Errorf("marshal json error: %w", err)
}

var headers [][2]string
if s.flashbotKey != nil {
signature, err := requestSignature(s.flashbotKey, reqBody)
if err != nil {
return SendPrivateRawTransactionResponse{}, fmt.Errorf("sign flashbot request error: %w", err)
}
headers = append(headers, [2]string{"X-Flashbots-Signature", signature})
}

httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, s.endpoint, bytes.NewBuffer(reqBody))
if err != nil {
return SendPrivateRawTransactionResponse{}, fmt.Errorf("new http request error: %w", err)
}

resp, err := doRequest[SendPrivateRawTransactionResponse](s.c, httpReq, headers...)
if err != nil {
return SendPrivateRawTransactionResponse{}, err
}

if len(resp.Error.Messange) != 0 {
return SendPrivateRawTransactionResponse{}, fmt.Errorf("response error, code: [%d], message: [%s]",
resp.Error.Code, resp.Error.Messange)
}

return resp, nil
}

func requestSignature(key *ecdsa.PrivateKey, body []byte) (string, error) {
hashed := crypto.Keccak256Hash(body).Hex()
signature, err := crypto.Sign(accounts.TextHash([]byte(hashed)), key)
Expand Down Expand Up @@ -265,7 +315,7 @@ func (b *GetBundleStatsParams) SetBundleHash(bundleHash common.Hash) *GetBundleS
return b
}

type SendBundleRequest struct {
type SendRequest struct {
ID int `json:"id"`
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Expand Down
12 changes: 6 additions & 6 deletions pkg/mev/bundle_sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestSendBundle(t *testing.T) {

uuid := uuid.NewString()
require.NoError(t, err)
sender, err := mev.NewClient(client, endpoint, privateKey, false, mev.BundleSenderTypeFlashbot)
sender, err := mev.NewClient(client, endpoint, privateKey, mev.BundleSenderTypeFlashbot, false, false)
require.NoError(t, err)

resp, err := sender.SendBundle(ctx, &uuid, blockNumber+12, signedTx)
Expand Down Expand Up @@ -101,7 +101,7 @@ func TestCancelBeaver(t *testing.T) {
bundleUUID = uuid.New().String()
)

sender, err := mev.NewClient(client, endpoint, nil, true, mev.BundleSenderTypeBeaver)
sender, err := mev.NewClient(client, endpoint, nil, mev.BundleSenderTypeBeaver, true, false)
require.NoError(t, err)

require.NoError(t, sender.CancelBundle(ctx, bundleUUID))
Expand Down Expand Up @@ -145,8 +145,8 @@ func Test_SimulateBundle(t *testing.T) {
}

client, err := mev.NewClient(http.DefaultClient,
simulationEndpoint, privateKey, false,
mev.BundleSenderTypeFlashbot)
simulationEndpoint, privateKey,
mev.BundleSenderTypeFlashbot, false, false)
require.NoError(t, err)

simulationResponse, err := client.SimulateBundle(context.Background(), uint64(blockNumber), txs...)
Expand Down Expand Up @@ -288,8 +288,8 @@ func TestClient_GetBundleStats(t *testing.T) {
t.Log(res.BundleHash.String(), "bundleHash")

client, err := mev.NewClient(http.DefaultClient,
SimulationEndpoint, fbSigningKey, false,
mev.BundleSenderTypeFlashbot)
SimulationEndpoint, fbSigningKey,
mev.BundleSenderTypeFlashbot, false, false)
require.NoError(t, err)
// Get bundle stats
stats, err := client.GetBundleStats(context.Background(), blockNumber+1, res.BundleHash)
Expand Down
2 changes: 1 addition & 1 deletion pkg/mev/merkle_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (b MerkleClient) SendBackrunBundle(
_ []string,
txs ...*types.Transaction,
) (SendBundleResponse, error) {
req := SendBundleRequest{
req := SendRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: ETHSendBundleMethod,
Expand Down
50 changes: 34 additions & 16 deletions pkg/mev/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"

"github.com/ethereum/go-ethereum"
Expand Down Expand Up @@ -47,6 +48,7 @@ const (
EthCallBundleMethod = "eth_callBundle"
ETHCancelBundleMethod = "eth_cancelBundle"
ETHEstimateGasBundleMethod = "eth_estimateGasBundle"
ETHSendPrivateRawTransaction = "eth_sendPrivateRawTransaction"
MevSendBundleMethod = "mev_sendBundle"
MaxBlockFromTarget = 3
)
Expand Down Expand Up @@ -183,17 +185,24 @@ type SendBundleResponse struct {
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,omitempty"`
Result SendBundleResult `json:"result,omitempty"`
Error SendBundleError `json:"error,omitempty"`
Error ErrorResponse `json:"error,omitempty"`
}

type SendPrivateRawTransactionResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int `json:"id"`
Result string `json:"result"`
Error ErrorResponse `json:"error,omitempty"`
}

type MerkleSendBundleResponse struct {
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,omitempty"`
Result string `json:"result,omitempty"`
Error SendBundleError `json:"error,omitempty"`
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,omitempty"`
Result string `json:"result,omitempty"`
Error ErrorResponse `json:"error,omitempty"`
}

type SendBundleError struct {
type ErrorResponse struct {
Code int `json:"code,omitempty"`
Messange string `json:"message,omitempty"`
}
Expand All @@ -216,8 +225,17 @@ type SendBundleResult struct {
}

func (r *SendBundleResult) UnmarshalJSON(b []byte) error {
if str := string(b); (str == "\"nil\"" || str == "\"null\"") && r != nil {
*r = SendBundleResult{}
if str := string(b); r != nil {
switch {
case (str == "\"nil\"" || str == "\"null\""):
*r = SendBundleResult{}

// handle Blink sendBundle
case strings.HasPrefix(str, "\"0x"):
*r = SendBundleResult{
BundleHash: str,
}
}
return nil
}

Expand All @@ -240,10 +258,10 @@ type SendBundleResults struct {
}

type FlashbotCancelBundleResponse struct {
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,omitempty"`
Result []string `json:"result,omitempty"`
Error SendBundleError `json:"error,omitempty"`
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,omitempty"`
Result []string `json:"result,omitempty"`
Error ErrorResponse `json:"error,omitempty"`
}

func (resp FlashbotCancelBundleResponse) ToSendBundleResponse() SendBundleResponse {
Expand All @@ -260,10 +278,10 @@ func (resp FlashbotCancelBundleResponse) ToSendBundleResponse() SendBundleRespon
}

type TitanCancelBundleResponse struct {
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,omitempty"`
Result int `json:"result,omitempty"`
Error SendBundleError `json:"error,omitempty"`
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,omitempty"`
Result int `json:"result,omitempty"`
Error ErrorResponse `json:"error,omitempty"`
}

func ToCallArg(msg ethereum.CallMsg) interface{} {
Expand Down
1 change: 1 addition & 0 deletions pkg/mev/pkg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func TestUnmarshalSendBundleResponse1(t *testing.T) {
"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"nil\"}",
"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{}}",
"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"bundleHash\": \"0x0\"}}",
"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x1ae69e3198840607d9946c52b0564624ab421d0678402e8696d08f9e5bc93a01\"}",
}

for _, raw := range raws {
Expand Down

0 comments on commit 1fd89d3

Please sign in to comment.