Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 68 additions & 24 deletions playground/test_tx.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package playground

import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/tls"
"encoding/hex"
"fmt"
"io"
"math/big"
"net/http"
"time"
Expand All @@ -16,6 +19,45 @@ import (
"github.com/ethereum/go-ethereum/rpc"
)

// buildernetSigningTransport is an http.RoundTripper that adds the X-BuilderNet-Signature
// header to every request. FlowProxy requires this header for orderflow authentication.
// The signature is: keccak256(body) → format as hex → EIP-191 sign → header.
// Any valid key pair works — it's an identity tag, not access control.
type buildernetSigningTransport struct {
base http.RoundTripper
privateKey *ecdsa.PrivateKey
address common.Address
}

func (t *buildernetSigningTransport) RoundTrip(req *http.Request) (*http.Response, error) {
body, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
req.Body.Close()

// Sign: keccak256(body) → hex string → EIP-191 hash → ECDSA sign
bodyHash := crypto.Keccak256(body)
hashHex := "0x" + hex.EncodeToString(bodyHash)

// EIP-191: "\x19Ethereum Signed Message:\n" + len + message
prefix := fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(hashHex))
msgHash := crypto.Keccak256(append([]byte(prefix), []byte(hashHex)...))

sig, err := crypto.Sign(msgHash, t.privateKey)
if err != nil {
return nil, fmt.Errorf("buildernet signing failed: %w", err)
}
sig[64] += 27 // V: 0/1 → 27/28

req.Header.Set("X-BuilderNet-Signature",
fmt.Sprintf("%s:0x%s", t.address.Hex(), hex.EncodeToString(sig)))

req.Body = io.NopCloser(bytes.NewReader(body))
req.ContentLength = int64(len(body))
return t.base.RoundTrip(req)
}

// TestTxConfig holds configuration for the test transaction
type TestTxConfig struct {
RPCURL string // Target RPC URL for sending transactions (e.g., rbuilder)
Expand Down Expand Up @@ -73,21 +115,36 @@ func SendTestTransaction(ctx context.Context, cfg *TestTxConfig) error {
elRPCURL = cfg.RPCURL
}

// dialRPC connects to an RPC endpoint, optionally skipping TLS verification
// Parse private key (used for both tx signing and BuilderNet header)
privateKey, err := crypto.HexToECDSA(cfg.PrivateKey)
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}
fromAddress := crypto.PubkeyToAddress(privateKey.PublicKey)

// dialRPC connects to an RPC endpoint, adding BuilderNet signature header
// and optionally skipping TLS verification
dialRPC := func(url string) (*ethclient.Client, error) {
var base http.RoundTripper
if cfg.Insecure {
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
rpcClient, err := rpc.DialOptions(ctx, url, rpc.WithHTTPClient(httpClient))
if err != nil {
return nil, err
base = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
return ethclient.NewClient(rpcClient), nil
} else {
base = http.DefaultTransport
}
httpClient := &http.Client{
Transport: &buildernetSigningTransport{
base: base,
privateKey: privateKey,
address: fromAddress,
},
}
rpcClient, err := rpc.DialOptions(ctx, url, rpc.WithHTTPClient(httpClient))
if err != nil {
return nil, err
}
return ethclient.Dial(url)
return ethclient.NewClient(rpcClient), nil
}

// Connect to the EL RPC endpoint (for chain queries)
Expand All @@ -109,19 +166,6 @@ func SendTestTransaction(ctx context.Context, cfg *TestTxConfig) error {
targetClient = elClient
}

// Parse private key
privateKey, err := crypto.HexToECDSA(cfg.PrivateKey)
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("failed to get public key")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

// Get chain ID (from EL)
chainID, err := elClient.ChainID(ctx)
if err != nil {
Expand Down