11package playground
22
33import (
4+ "bytes"
45 "context"
56 "crypto/ecdsa"
67 "crypto/tls"
8+ "encoding/hex"
79 "fmt"
10+ "io"
811 "math/big"
912 "net/http"
1013 "time"
@@ -16,6 +19,45 @@ import (
1619 "github.com/ethereum/go-ethereum/rpc"
1720)
1821
22+ // buildernetSigningTransport is an http.RoundTripper that adds the X-BuilderNet-Signature
23+ // header to every request. FlowProxy requires this header for orderflow authentication.
24+ // The signature is: keccak256(body) → format as hex → EIP-191 sign → header.
25+ // Any valid key pair works — it's an identity tag, not access control.
26+ type buildernetSigningTransport struct {
27+ base http.RoundTripper
28+ privateKey * ecdsa.PrivateKey
29+ address common.Address
30+ }
31+
32+ func (t * buildernetSigningTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
33+ defer req .Body .Close ()
34+ body , err := io .ReadAll (req .Body )
35+ if err != nil {
36+ return nil , err
37+ }
38+
39+ // Sign: keccak256(body) → hex string → EIP-191 hash → ECDSA sign
40+ bodyHash := crypto .Keccak256 (body )
41+ hashHex := "0x" + hex .EncodeToString (bodyHash )
42+
43+ // EIP-191: "\x19Ethereum Signed Message:\n" + len + message
44+ prefix := fmt .Sprintf ("\x19 Ethereum Signed Message:\n %d" , len (hashHex ))
45+ msgHash := crypto .Keccak256 (append ([]byte (prefix ), []byte (hashHex )... ))
46+
47+ sig , err := crypto .Sign (msgHash , t .privateKey )
48+ if err != nil {
49+ return nil , fmt .Errorf ("buildernet signing failed: %w" , err )
50+ }
51+ sig [64 ] += 27 // V: 0/1 → 27/28
52+
53+ req .Header .Set ("X-BuilderNet-Signature" ,
54+ fmt .Sprintf ("%s:0x%s" , t .address .Hex (), hex .EncodeToString (sig )))
55+
56+ req .Body = io .NopCloser (bytes .NewReader (body ))
57+ req .ContentLength = int64 (len (body ))
58+ return t .base .RoundTrip (req )
59+ }
60+
1961// TestTxConfig holds configuration for the test transaction
2062type TestTxConfig struct {
2163 RPCURL string // Target RPC URL for sending transactions (e.g., rbuilder)
@@ -73,21 +115,36 @@ func SendTestTransaction(ctx context.Context, cfg *TestTxConfig) error {
73115 elRPCURL = cfg .RPCURL
74116 }
75117
76- // dialRPC connects to an RPC endpoint, optionally skipping TLS verification
118+ // Parse private key (used for both tx signing and BuilderNet header)
119+ privateKey , err := crypto .HexToECDSA (cfg .PrivateKey )
120+ if err != nil {
121+ return fmt .Errorf ("failed to parse private key: %w" , err )
122+ }
123+ fromAddress := crypto .PubkeyToAddress (privateKey .PublicKey )
124+
125+ // dialRPC connects to an RPC endpoint, adding BuilderNet signature header
126+ // and optionally skipping TLS verification
77127 dialRPC := func (url string ) (* ethclient.Client , error ) {
128+ var base http.RoundTripper
78129 if cfg .Insecure {
79- httpClient := & http.Client {
80- Transport : & http.Transport {
81- TLSClientConfig : & tls.Config {InsecureSkipVerify : true },
82- },
83- }
84- rpcClient , err := rpc .DialOptions (ctx , url , rpc .WithHTTPClient (httpClient ))
85- if err != nil {
86- return nil , err
130+ base = & http.Transport {
131+ TLSClientConfig : & tls.Config {InsecureSkipVerify : true },
87132 }
88- return ethclient .NewClient (rpcClient ), nil
133+ } else {
134+ base = http .DefaultTransport
135+ }
136+ httpClient := & http.Client {
137+ Transport : & buildernetSigningTransport {
138+ base : base ,
139+ privateKey : privateKey ,
140+ address : fromAddress ,
141+ },
142+ }
143+ rpcClient , err := rpc .DialOptions (ctx , url , rpc .WithHTTPClient (httpClient ))
144+ if err != nil {
145+ return nil , err
89146 }
90- return ethclient .Dial ( url )
147+ return ethclient .NewClient ( rpcClient ), nil
91148 }
92149
93150 // Connect to the EL RPC endpoint (for chain queries)
@@ -109,19 +166,6 @@ func SendTestTransaction(ctx context.Context, cfg *TestTxConfig) error {
109166 targetClient = elClient
110167 }
111168
112- // Parse private key
113- privateKey , err := crypto .HexToECDSA (cfg .PrivateKey )
114- if err != nil {
115- return fmt .Errorf ("failed to parse private key: %w" , err )
116- }
117-
118- publicKey := privateKey .Public ()
119- publicKeyECDSA , ok := publicKey .(* ecdsa.PublicKey )
120- if ! ok {
121- return fmt .Errorf ("failed to get public key" )
122- }
123- fromAddress := crypto .PubkeyToAddress (* publicKeyECDSA )
124-
125169 // Get chain ID (from EL)
126170 chainID , err := elClient .ChainID (ctx )
127171 if err != nil {
0 commit comments