Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion tools/preconf-rpc/blocktracker/blocktracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (b *blockTracker) Start(ctx context.Context) <-chan struct{} {
}
_ = b.blocks.Add(blockNo, block)
b.latestBlockNo.Store(block.NumberU64())
b.log.Info("New block detected", "number", block.NumberU64(), "hash", block.Hash().Hex())
b.log.Debug("New block detected", "number", block.NumberU64(), "hash", block.Hash().Hex())
b.triggerCheck()
}
}
Expand Down
48 changes: 24 additions & 24 deletions tools/preconf-rpc/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Bidder interface {
}

type Pricer interface {
EstimatePrice(ctx context.Context, txn *types.Transaction) (*pricer.BlockPrice, error)
EstimatePrice(ctx context.Context) (*pricer.BlockPrices, error)
}

type Store interface {
Expand Down Expand Up @@ -136,26 +136,17 @@ func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) {
return json.RawMessage(fmt.Sprintf(`{"timeInSecs": "%d"}`, timeToOptIn)), false, nil
})
server.RegisterHandler("mevcommit_estimateDeposit", func(ctx context.Context, params ...any) (json.RawMessage, bool, error) {
blockPrice, err := h.pricer.EstimatePrice(
ctx,
types.NewTransaction(0, h.depositAddress, big.NewInt(0), 21000, big.NewInt(0), nil),
)
blockPrices, err := h.pricer.EstimatePrice(ctx)
if err != nil {
h.logger.Error("Failed to estimate deposit price", "error", err)
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
"failed to estimate deposit price",
)
}
if blockPrice == nil {
h.logger.Warn("No block price estimated for deposit")
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
"no block price available for deposit",
)
}
cost := getNextBlockPrice(blockPrices)
result := map[string]interface{}{
"bidAmount": blockPrice.BidAmount.String(),
"bidAmount": cost.String(),
"depositAddress": h.depositAddress.Hex(),
}

Expand All @@ -167,26 +158,20 @@ func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) {
"failed to marshal deposit estimate",
)
}
h.logger.Debug("Estimated deposit price", "bidAmount", blockPrice.BidAmount, "depositAddress", h.depositAddress.Hex())
h.logger.Debug("Estimated deposit price", "bidAmount", cost, "depositAddress", h.depositAddress.Hex())
return resultJSON, false, nil
})
server.RegisterHandler("mevcommit_estimateBridge", func(ctx context.Context, params ...any) (json.RawMessage, bool, error) {
blockPrice, err := h.pricer.EstimatePrice(
ctx,
types.NewTransaction(0, h.bridgeAddress, big.NewInt(0), 21000, big.NewInt(0), nil),
)
blockPrices, err := h.pricer.EstimatePrice(ctx)
if err != nil {
h.logger.Error("Failed to estimate bridge price", "error", err)
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
"failed to estimate bridge price",
)
}
if blockPrice == nil {
h.logger.Warn("No block price estimated for bridge")
return nil, true, nil // No price available, proxy
}
bridgeCost := new(big.Int).Mul(blockPrice.BidAmount, big.NewInt(2))
cost := getNextBlockPrice(blockPrices)
bridgeCost := new(big.Int).Mul(cost, big.NewInt(2))
result := map[string]interface{}{
"bidAmount": bridgeCost.String(),
"bridgeAddress": h.bridgeAddress.Hex(),
Expand All @@ -200,11 +185,26 @@ func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) {
"failed to marshal bridge estimate",
)
}
h.logger.Debug("Estimated bridge price", "bidAmount", blockPrice.BidAmount, "bridgeAddress", h.bridgeAddress.Hex())
h.logger.Debug("Estimated bridge price", "bidAmount", bridgeCost, "bridgeAddress", h.bridgeAddress.Hex())
return resultJSON, false, nil
})
}

func getNextBlockPrice(blockPrices *pricer.BlockPrices) *big.Int {
for _, price := range blockPrices.Prices {
if price.BlockNumber == blockPrices.CurrentBlockNumber+1 {
for _, estimate := range price.EstimatedPrices {
if estimate.Confidence == 99 {
priceInWei := estimate.PriorityFeePerGasGwei * 1e9
return new(big.Int).Mul(new(big.Int).SetUint64(uint64(priceInWei)), big.NewInt(21000))
}
}
}
}

return big.NewInt(0) // Return zero if no suitable estimate is found
}

func (h *rpcMethodHandler) handleGetBlockByHash(
ctx context.Context,
params ...any,
Expand Down
9 changes: 9 additions & 0 deletions tools/preconf-rpc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ var (
},
}

optionBlocknativeAPIKey = &cli.StringFlag{
Name: "blocknative-api-key",
Usage: "Blocknative API key for transaction pricing",
EnvVars: []string{"PRECONF_RPC_BLOCKNATIVE_API_KEY"},
Value: "",
}

optionLogFmt = &cli.StringFlag{
Name: "log-fmt",
Usage: "log format to use, options are 'text' or 'json'",
Expand Down Expand Up @@ -246,6 +253,7 @@ func main() {
optionAutoDepositAmount,
optionDepositAddress,
optionBridgeAddress,
optionBlocknativeAPIKey,
},
Action: func(c *cli.Context) error {
logger, err := util.NewLogger(
Expand Down Expand Up @@ -316,6 +324,7 @@ func main() {
Signer: signer,
DepositAddress: common.HexToAddress(c.String(optionDepositAddress.Name)),
BridgeAddress: common.HexToAddress(c.String(optionBridgeAddress.Name)),
PricerAPIKey: c.String(optionBlocknativeAPIKey.Name),
}

s, err := service.New(&config)
Expand Down
65 changes: 29 additions & 36 deletions tools/preconf-rpc/pricer/pricer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,39 @@ import (
"encoding/json"
"errors"
"io"
"math/big"
"net/http"
"time"

"github.com/ethereum/go-ethereum/core/types"
)

var apiURL = "https://api.blocknative.com/gasprices/blockprices?chainid=1"

type blockPrice struct {
CurrentBlockNumber int64 `json:"currentBlockNumber"`
BlockPrices []struct {
BlockNumber int64 `json:"blockNumber"`
EstimatedPrices []struct {
Confidence int `json:"confidence"`
PriorityFeePerGas float64 `json:"maxPriorityFeePerGas"`
}
}
type EstimatedPrice struct {
Confidence int `json:"confidence"`
PriorityFeePerGasGwei float64 `json:"maxPriorityFeePerGas"`
}

type BlockPrice struct {
BlockNumber int64
BidAmount *big.Int
BlockNumber int64 `json:"blockNumber"`
EstimatedPrices []EstimatedPrice `json:"estimatedPrices"`
}

type BlockPrices struct {
MsSinceLastBlock int64 `json:"msSinceLastBlock"`
CurrentBlockNumber int64 `json:"currentBlockNumber"`
Prices []BlockPrice `json:"blockPrices"`
}

type BidPricer struct {
apiKey string
}

type BidPricer struct{}
func NewPricer(apiKey string) *BidPricer {
return &BidPricer{
apiKey: apiKey,
}
}

func (b *BidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (*BlockPrice, error) {
func (b *BidPricer) EstimatePrice(ctx context.Context) (*BlockPrices, error) {
client := &http.Client{
Timeout: 10 * time.Second,
}
Expand All @@ -42,6 +47,10 @@ func (b *BidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (
return nil, err
}

if b.apiKey != "" {
req.Header.Set("Authorization", b.apiKey)
}

resp, err := client.Do(req)
if err != nil {
return nil, err
Expand All @@ -60,30 +69,14 @@ func (b *BidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (
return nil, err
}

var bp blockPrice
if err := json.Unmarshal(respBuf, &bp); err != nil {
bp := new(BlockPrices)
if err := json.Unmarshal(respBuf, bp); err != nil {
return nil, err
}

if len(bp.BlockPrices) == 0 {
if len(bp.Prices) == 0 {
return nil, errors.New("no block prices available")
}

for _, price := range bp.BlockPrices {
if price.BlockNumber == bp.CurrentBlockNumber+1 {
for _, p := range price.EstimatedPrices {
if p.Confidence == 99 { // Assuming we want the 99% confidence price
// Convert the priority fee from Gwei to Wei
// 1 Gwei = 1e9 Wei
priorityFee := p.PriorityFeePerGas * 1e9
bidAmount := big.NewInt(0).Mul(big.NewInt(int64(priorityFee)), big.NewInt(int64(txn.Gas())))
return &BlockPrice{BlockNumber: price.BlockNumber, BidAmount: bidAmount}, nil
}
}
}
}

// If we reach here, it means we didn't find a suitable price.
// This could happen if the API response format changes or if no 99% confidence price is available.
return nil, errors.New("no suitable price found for the next block")
return bp, nil
}
41 changes: 23 additions & 18 deletions tools/preconf-rpc/pricer/pricer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,46 @@ package pricer_test

import (
"context"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/primev/mev-commit/tools/preconf-rpc/pricer"
)

func TestEstimatePrice(t *testing.T) {
t.Parallel()

bp := pricer.BidPricer{}
bp := pricer.NewPricer("")

ctx := context.Background()
txn := types.NewTransaction(
0,
common.HexToAddress("0x1234567890123456789012345678901234567890"),
big.NewInt(1000000000), // 1 Gwei
21000, // gas limit
big.NewInt(1000000000), // gas price
nil, // no data
)

price, err := bp.EstimatePrice(ctx, txn)

prices, err := bp.EstimatePrice(ctx)
if err != nil {
t.Fatalf("failed to estimate price: %v", err)
}

if prices.CurrentBlockNumber == 0 {
t.Error("expected non-zero current block number")
}

if len(prices.Prices) == 0 {
t.Error("expected at least one block price")
}

price := prices.Prices[0]

if price.BlockNumber == 0 {
t.Error("expected non-zero block number in estimated price")
t.Error("expected non-zero block number in price")
}

if len(price.EstimatedPrices) == 0 {
t.Error("expected at least one estimated price")
}

if price.BidAmount.Cmp(big.NewInt(0)) <= 0 {
t.Error("expected estimated price to be greater than zero")
for _, estPrice := range price.EstimatedPrices {
if estPrice.PriorityFeePerGasGwei <= 0 {
t.Errorf("expected positive priority fee per gas, got %f", estPrice.PriorityFeePerGasGwei)
}
}

t.Logf("Estimated price: %s at block %d", price.BidAmount.String(), price.BlockNumber)
t.Logf("Estimated prices: %v", prices)
}
Loading
Loading