@@ -6,10 +6,12 @@ import (
6
6
"encoding/json"
7
7
"fmt"
8
8
"io"
9
+ "io/ioutil"
9
10
"log"
10
11
"math/big"
11
12
"net/http"
12
13
"os"
14
+ "sync"
13
15
"time"
14
16
15
17
"github.com/ethereum/go-ethereum/accounts"
@@ -730,3 +732,157 @@ func (rpc *FlashbotsRPC) FlashbotsCancelPrivateTransaction(privKey *ecdsa.Privat
730
732
err = json .Unmarshal (rawMsg , & cancelled )
731
733
return cancelled , err
732
734
}
735
+
736
+ type BuilderBroadcastRPC struct {
737
+ urls []string
738
+ client httpClient
739
+ log logger
740
+ Debug bool
741
+ Headers map [string ]string // Additional headers to send with the request
742
+ Timeout time.Duration
743
+ }
744
+
745
+ // NewBuilderBroadcastRPC create broadcaster rpc client with given url
746
+ func NewBuilderBroadcastRPC (urls []string , options ... func (rpc * BuilderBroadcastRPC )) * BuilderBroadcastRPC {
747
+ rpc := & BuilderBroadcastRPC {
748
+ urls : urls ,
749
+ log : log .New (os .Stderr , "" , log .LstdFlags ),
750
+ Headers : make (map [string ]string ),
751
+ Timeout : 30 * time .Second ,
752
+ }
753
+ for _ , option := range options {
754
+ option (rpc )
755
+ }
756
+ rpc .client = & http.Client {
757
+ Timeout : rpc .Timeout ,
758
+ }
759
+ return rpc
760
+ }
761
+
762
+ // https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint/#eth_sendbundle
763
+ func (broadcaster * BuilderBroadcastRPC ) BroadcastBundle (privKey * ecdsa.PrivateKey , param FlashbotsSendBundleRequest ) []BuilderBroadcastResponse {
764
+ requestResponses := broadcaster .broadcastRequest ("eth_sendBundle" , privKey , param )
765
+
766
+ responses := []BuilderBroadcastResponse {}
767
+
768
+ for _ , requestResponse := range requestResponses {
769
+ if requestResponse .Err != nil {
770
+ responses = append (responses , BuilderBroadcastResponse {Err : requestResponse .Err })
771
+ }
772
+ fbResponse := FlashbotsSendBundleResponse {}
773
+ err := json .Unmarshal (requestResponse .Msg , & fbResponse )
774
+ responses = append (responses , BuilderBroadcastResponse {BundleResponse : fbResponse , Err : err })
775
+ }
776
+
777
+ return responses
778
+ }
779
+
780
+ type broadcastRequestResponse struct {
781
+ Msg json.RawMessage
782
+ Err error
783
+ }
784
+
785
+ func (broadcaster * BuilderBroadcastRPC ) broadcastRequest (method string , privKey * ecdsa.PrivateKey , params ... interface {}) []broadcastRequestResponse {
786
+ request := rpcRequest {
787
+ ID : 1 ,
788
+ JSONRPC : "2.0" ,
789
+ Method : method ,
790
+ Params : params ,
791
+ }
792
+
793
+ body , err := json .Marshal (request )
794
+ if err != nil {
795
+ responseArr := []broadcastRequestResponse {{Msg : nil , Err : err }}
796
+ return responseArr
797
+ }
798
+
799
+ hashedBody := crypto .Keccak256Hash ([]byte (body )).Hex ()
800
+ sig , err := crypto .Sign (accounts .TextHash ([]byte (hashedBody )), privKey )
801
+ if err != nil {
802
+ responseArr := []broadcastRequestResponse {{Msg : nil , Err : err }}
803
+ return responseArr
804
+ }
805
+
806
+ signature := crypto .PubkeyToAddress (privKey .PublicKey ).Hex () + ":" + hexutil .Encode (sig )
807
+
808
+ var wg sync.WaitGroup
809
+ responseCh := make (chan []byte )
810
+
811
+ // Iterate over the URLs and send requests concurrently
812
+ for _ , url := range broadcaster .urls {
813
+ wg .Add (1 )
814
+
815
+ go func (url string ) {
816
+ defer wg .Done ()
817
+
818
+ // Create a new HTTP GET request
819
+ req , err := http .NewRequest ("POST" , url , bytes .NewBuffer (body ))
820
+ if err != nil {
821
+ return
822
+ }
823
+
824
+ req .Header .Add ("Content-Type" , "application/json" )
825
+ req .Header .Add ("Accept" , "application/json" )
826
+ req .Header .Add ("X-Flashbots-Signature" , signature )
827
+ for k , v := range broadcaster .Headers {
828
+ req .Header .Add (k , v )
829
+ }
830
+
831
+ response , err := broadcaster .client .Do (req )
832
+ if response != nil {
833
+ defer response .Body .Close ()
834
+ }
835
+ if err != nil {
836
+ return
837
+ }
838
+
839
+ // Read the response body
840
+ body , err := ioutil .ReadAll (response .Body )
841
+ if err != nil {
842
+ return
843
+ }
844
+
845
+ // Send the response body through the channel
846
+ responseCh <- body
847
+
848
+ if broadcaster .Debug {
849
+ broadcaster .log .Println (fmt .Sprintf ("%s\n Request: %s\n Signature: %s\n Response: %s\n " , method , body , signature , string (body )))
850
+ }
851
+
852
+ }(url )
853
+ }
854
+
855
+ go func () {
856
+ wg .Wait ()
857
+ close (responseCh )
858
+ }()
859
+
860
+ responses := []broadcastRequestResponse {}
861
+ for data := range responseCh {
862
+ // On error, response looks like this instead of JSON-RPC: {"error":"block param must be a hex int"}
863
+ errorResp := new (RelayErrorResponse )
864
+ if err := json .Unmarshal (data , errorResp ); err == nil && errorResp .Error != "" {
865
+ // relay returned an error
866
+ responseArr := []broadcastRequestResponse {{Msg : nil , Err : fmt .Errorf ("%w: %s" , ErrRelayErrorResponse , errorResp .Error )}}
867
+ return responseArr
868
+ }
869
+
870
+ resp := new (rpcResponse )
871
+ if err := json .Unmarshal (data , resp ); err != nil {
872
+ responseArr := []broadcastRequestResponse {{Msg : nil , Err : err }}
873
+ return responseArr
874
+ }
875
+
876
+ if resp .Error != nil {
877
+ responseArr := []broadcastRequestResponse {{Msg : nil , Err : fmt .Errorf ("%w: %s" , ErrRelayErrorResponse , (* resp ).Error .Message )}}
878
+ return responseArr
879
+ }
880
+
881
+ responses = append (responses , broadcastRequestResponse {
882
+ Msg : resp .Result ,
883
+ Err : nil ,
884
+ })
885
+ }
886
+
887
+ return responses
888
+ }
0 commit comments