Skip to content

Commit 222d0a5

Browse files
fkondejcanercidam
andauthored
feat: add headers required by FlowProxy to test command (#378)
Co-authored-by: Caner Çıdam <canercidam01@gmail.com>
1 parent 10b23bc commit 222d0a5

File tree

1 file changed

+68
-24
lines changed

1 file changed

+68
-24
lines changed

playground/test_tx.go

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package playground
22

33
import (
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("\x19Ethereum 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
2062
type 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

Comments
 (0)