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
34 changes: 29 additions & 5 deletions pkg/transaction/wrapped/fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,44 @@ var (
ErrEIP1559NotSupported = errors.New("network does not appear to support EIP-1559 (no baseFee)")
)

// SuggestedFeeAndTip calculates the recommended gasFeeCap and gasTipCap for a transaction.
// SuggestedFeeAndTip calculates the recommended gasFeeCap (maxFeePerGas) and gasTipCap (maxPriorityFeePerGas) for a transaction.
// If gasPrice is provided (legacy mode):
// - On EIP-1559 networks: gasFeeCap = gasPrice; gasTipCap = max(gasPrice - baseFee, minimumTip) to respect the total cap while enforcing a tip floor where possible.
// - On pre-EIP-1559 networks: returns (gasPrice, gasPrice) for legacy transaction compatibility.
//
// If gasPrice is nil: Uses suggested tip with optional boost, enforces minimum, and sets gasFeeCap = 2 * baseFee + gasTipCap.
func (b *wrappedBackend) SuggestedFeeAndTip(ctx context.Context, gasPrice *big.Int, boostPercent int) (*big.Int, *big.Int, error) {
if gasPrice != nil {
// 1. gasFeeCap: The absolute maximum price per gas does not exceed the user's specified price.
// 2. gasTipCap: The entire amount (gasPrice - baseFee) can be used as a priority fee.
return new(big.Int).Set(gasPrice), new(big.Int).Set(gasPrice), nil
latestBlockHeader, err := b.backend.HeaderByNumber(ctx, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to get latest block header: %w", err)
}
if latestBlockHeader == nil || latestBlockHeader.BaseFee == nil {
return new(big.Int).Set(gasPrice), new(big.Int).Set(gasPrice), nil
}

baseFee := latestBlockHeader.BaseFee
if gasPrice.Cmp(baseFee) < 0 {
return nil, nil, fmt.Errorf("specified gas price %s is below current base fee %s", gasPrice, baseFee)
}

// nominal tip = gasPrice - baseFee
gasTipCap := new(big.Int).Sub(gasPrice, baseFee)
gasFeeCap := new(big.Int).Set(gasPrice)

return gasFeeCap, gasTipCap, nil
}

gasTipCap, err := b.backend.SuggestGasTipCap(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed to suggest gas tip cap: %w", err)
}
gasTipCap = new(big.Int).Set(gasTipCap)

if boostPercent != 0 {
if boostPercent < 0 {
return nil, nil, fmt.Errorf("negative boostPercent (%d) not allowed", boostPercent)
}
// multiplier: 100 + boostPercent (e.g., 110 for 10% boost)
multiplier := new(big.Int).Add(big.NewInt(int64(percentageDivisor)), big.NewInt(int64(boostPercent)))
// gasTipCap = gasTipCap * (100 + boostPercent) / 100
Expand All @@ -53,7 +77,7 @@ func (b *wrappedBackend) SuggestedFeeAndTip(ctx context.Context, gasPrice *big.I
return nil, nil, ErrEIP1559NotSupported
}

// EIP-1559: gasFeeCap = (2 * baseFee) + gasTipCap
// gasFeeCap = (2 * baseFee) + gasTipCap
gasFeeCap := new(big.Int).Mul(latestBlockHeader.BaseFee, big.NewInt(int64(baseFeeMultiplier)))
gasFeeCap.Add(gasFeeCap, gasTipCap)

Expand Down
7 changes: 7 additions & 0 deletions pkg/transaction/wrapped/fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ func TestSuggestedFeeAndTip(t *testing.T) {
wantGasFeeCap: big.NewInt(1000),
wantGasTipCap: big.NewInt(1000),
},
{
name: "with gas price and base fee",
gasPrice: big.NewInt(1000),
mockHeader: &types.Header{BaseFee: baseFee},
wantGasFeeCap: big.NewInt(1000),
wantGasTipCap: big.NewInt(900),
},
{
name: "suggest tip error",
mockSuggestGasErr: errors.New("suggest tip error"),
Expand Down
Loading