diff --git a/pkg/transaction/wrapped/fee.go b/pkg/transaction/wrapped/fee.go index 3b02b3423ec..34fe2cc60d5 100644 --- a/pkg/transaction/wrapped/fee.go +++ b/pkg/transaction/wrapped/fee.go @@ -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 @@ -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) diff --git a/pkg/transaction/wrapped/fee_test.go b/pkg/transaction/wrapped/fee_test.go index 77847ec40d1..41afb69f28f 100644 --- a/pkg/transaction/wrapped/fee_test.go +++ b/pkg/transaction/wrapped/fee_test.go @@ -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"),