mirror of
https://github.com/status-im/status-go.git
synced 2025-02-06 11:56:01 +00:00
feat(wallet)_: added suggested min and max priority fee and current base fee for the path
- `Path` type extended with the following fields: - `SuggestedMinPriorityFee`, suggested min priority fee by the network - `SuggestedMaxPriorityFee`, suggested max priority fee by the network - `CurrentBaseFee`, current network base fee - The following wallet api endpoints marked as deprecated: - `GetSuggestedFees` - `GetTransactionEstimatedTime`
This commit is contained in:
parent
67134d9811
commit
90f4740add
@ -2,7 +2,6 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -13,6 +12,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
mock_client "github.com/status-im/status-go/rpc/chain/mock/client"
|
mock_client "github.com/status-im/status-go/rpc/chain/mock/client"
|
||||||
|
"github.com/status-im/status-go/services/wallet/router/fees"
|
||||||
"github.com/status-im/status-go/services/wallet/wallettypes"
|
"github.com/status-im/status-go/services/wallet/wallettypes"
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
)
|
)
|
||||||
@ -82,9 +82,10 @@ func TestSendTransactionWithSignalTimout(t *testing.T) {
|
|||||||
WalletResponseMaxInterval = 1 * time.Millisecond
|
WalletResponseMaxInterval = 1 * time.Millisecond
|
||||||
|
|
||||||
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
||||||
|
feeHistory := &fees.FeeHistory{}
|
||||||
|
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
|
||||||
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
||||||
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
|
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
|
||||||
mockedChainClient.EXPECT().SuggestGasTipCap(state.ctx).Times(1).Return(big.NewInt(0), errors.New("EIP-1559 is not enabled"))
|
|
||||||
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
||||||
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)
|
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)
|
||||||
|
|
||||||
@ -127,9 +128,10 @@ func TestSendTransactionWithSignalAccepted(t *testing.T) {
|
|||||||
t.Cleanup(signal.ResetMobileSignalHandler)
|
t.Cleanup(signal.ResetMobileSignalHandler)
|
||||||
|
|
||||||
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
||||||
|
feeHistory := &fees.FeeHistory{}
|
||||||
|
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
|
||||||
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
||||||
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
|
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
|
||||||
mockedChainClient.EXPECT().SuggestGasTipCap(state.ctx).Times(1).Return(big.NewInt(0), errors.New("EIP-1559 is not enabled"))
|
|
||||||
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
||||||
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)
|
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)
|
||||||
|
|
||||||
@ -169,9 +171,10 @@ func TestSendTransactionWithSignalRejected(t *testing.T) {
|
|||||||
t.Cleanup(signal.ResetMobileSignalHandler)
|
t.Cleanup(signal.ResetMobileSignalHandler)
|
||||||
|
|
||||||
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
||||||
|
feeHistory := &fees.FeeHistory{}
|
||||||
|
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
|
||||||
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
||||||
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
|
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
|
||||||
mockedChainClient.EXPECT().SuggestGasTipCap(state.ctx).Times(1).Return(big.NewInt(0), errors.New("EIP-1559 is not enabled"))
|
|
||||||
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
||||||
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)
|
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package connector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/status-im/status-go/services/connector/chainutils"
|
"github.com/status-im/status-go/services/connector/chainutils"
|
||||||
"github.com/status-im/status-go/services/connector/commands"
|
"github.com/status-im/status-go/services/connector/commands"
|
||||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
||||||
|
"github.com/status-im/status-go/services/wallet/router/fees"
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -104,9 +104,10 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
|
|||||||
|
|
||||||
// Send transaction
|
// Send transaction
|
||||||
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
||||||
|
feeHistory := &fees.FeeHistory{}
|
||||||
|
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
|
||||||
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
||||||
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
|
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
|
||||||
mockedChainClient.EXPECT().SuggestGasTipCap(state.ctx).Times(1).Return(big.NewInt(0), errors.New("EIP-1559 is not enabled"))
|
|
||||||
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
|
||||||
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)
|
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)
|
||||||
|
|
||||||
|
@ -438,6 +438,7 @@ func (api *API) FetchTokenDetails(ctx context.Context, symbols []string) (map[st
|
|||||||
return api.s.marketManager.FetchTokenDetails(symbols)
|
return api.s.marketManager.FetchTokenDetails(symbols)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @deprecated we should remove it once clients fully switched to wallet router, `GetSuggestedRoutesAsync` should be used instead
|
||||||
func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*fees.SuggestedFeesGwei, error) {
|
func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*fees.SuggestedFeesGwei, error) {
|
||||||
logutils.ZapLogger().Debug("call to GetSuggestedFees")
|
logutils.ZapLogger().Debug("call to GetSuggestedFees")
|
||||||
return api.s.router.GetFeesManager().SuggestedFeesGwei(ctx, chainID)
|
return api.s.router.GetFeesManager().SuggestedFeesGwei(ctx, chainID)
|
||||||
@ -448,6 +449,7 @@ func (api *API) GetEstimatedLatestBlockNumber(ctx context.Context, chainID uint6
|
|||||||
return api.s.blockChainState.GetEstimatedLatestBlockNumber(ctx, chainID)
|
return api.s.blockChainState.GetEstimatedLatestBlockNumber(ctx, chainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @deprecated we should remove it once clients fully switched to wallet router, `GetSuggestedRoutesAsync` should be used instead
|
||||||
func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Float) (fees.TransactionEstimation, error) {
|
func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Float) (fees.TransactionEstimation, error) {
|
||||||
logutils.ZapLogger().Debug("call to getTransactionEstimatedTime")
|
logutils.ZapLogger().Debug("call to getTransactionEstimatedTime")
|
||||||
return api.s.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil
|
return api.s.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil
|
||||||
|
115
services/wallet/router/fees/estimated_time.go
Normal file
115
services/wallet/router/fees/estimated_time.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package fees
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const inclusionThreshold = 0.95
|
||||||
|
|
||||||
|
type TransactionEstimation int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unknown TransactionEstimation = iota
|
||||||
|
LessThanOneMinute
|
||||||
|
LessThanThreeMinutes
|
||||||
|
LessThanFiveMinutes
|
||||||
|
MoreThanFiveMinutes
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *FeeManager) TransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Int) TransactionEstimation {
|
||||||
|
feeHistory, err := f.getFeeHistory(ctx, chainID, 100, "latest", nil)
|
||||||
|
if err != nil {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.estimatedTime(feeHistory, maxFeePerGas)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FeeManager) estimatedTime(feeHistory *FeeHistory, maxFeePerGas *big.Int) TransactionEstimation {
|
||||||
|
fees, err := f.getFeeHistorySorted(feeHistory)
|
||||||
|
if err != nil || len(fees) == 0 {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// pEvent represents the probability of the transaction being included in a block,
|
||||||
|
// we assume this one is static over time, in reality it is not.
|
||||||
|
pEvent := 0.0
|
||||||
|
for idx, fee := range fees {
|
||||||
|
if fee.Cmp(maxFeePerGas) == 1 || idx == len(fees)-1 {
|
||||||
|
pEvent = float64(idx) / float64(len(fees))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probability of next 4 blocks including the transaction (less than 1 minute)
|
||||||
|
// Generalising the formula: P(AUB) = P(A) + P(B) - P(A∩B) for 4 events and in our context P(A) == P(B) == pEvent
|
||||||
|
// The factors are calculated using the combinations formula
|
||||||
|
probability := pEvent*4 - 6*(math.Pow(pEvent, 2)) + 4*(math.Pow(pEvent, 3)) - (math.Pow(pEvent, 4))
|
||||||
|
if probability >= inclusionThreshold {
|
||||||
|
return LessThanOneMinute
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probability of next 12 blocks including the transaction (less than 5 minutes)
|
||||||
|
// Generalising the formula: P(AUB) = P(A) + P(B) - P(A∩B) for 20 events and in our context P(A) == P(B) == pEvent
|
||||||
|
// The factors are calculated using the combinations formula
|
||||||
|
probability = pEvent*12 -
|
||||||
|
66*(math.Pow(pEvent, 2)) +
|
||||||
|
220*(math.Pow(pEvent, 3)) -
|
||||||
|
495*(math.Pow(pEvent, 4)) +
|
||||||
|
792*(math.Pow(pEvent, 5)) -
|
||||||
|
924*(math.Pow(pEvent, 6)) +
|
||||||
|
792*(math.Pow(pEvent, 7)) -
|
||||||
|
495*(math.Pow(pEvent, 8)) +
|
||||||
|
220*(math.Pow(pEvent, 9)) -
|
||||||
|
66*(math.Pow(pEvent, 10)) +
|
||||||
|
12*(math.Pow(pEvent, 11)) -
|
||||||
|
math.Pow(pEvent, 12)
|
||||||
|
if probability >= inclusionThreshold {
|
||||||
|
return LessThanThreeMinutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probability of next 20 blocks including the transaction (less than 5 minutes)
|
||||||
|
// Generalising the formula: P(AUB) = P(A) + P(B) - P(A∩B) for 20 events and in our context P(A) == P(B) == pEvent
|
||||||
|
// The factors are calculated using the combinations formula
|
||||||
|
probability = pEvent*20 -
|
||||||
|
190*(math.Pow(pEvent, 2)) +
|
||||||
|
1140*(math.Pow(pEvent, 3)) -
|
||||||
|
4845*(math.Pow(pEvent, 4)) +
|
||||||
|
15504*(math.Pow(pEvent, 5)) -
|
||||||
|
38760*(math.Pow(pEvent, 6)) +
|
||||||
|
77520*(math.Pow(pEvent, 7)) -
|
||||||
|
125970*(math.Pow(pEvent, 8)) +
|
||||||
|
167960*(math.Pow(pEvent, 9)) -
|
||||||
|
184756*(math.Pow(pEvent, 10)) +
|
||||||
|
167960*(math.Pow(pEvent, 11)) -
|
||||||
|
125970*(math.Pow(pEvent, 12)) +
|
||||||
|
77520*(math.Pow(pEvent, 13)) -
|
||||||
|
38760*(math.Pow(pEvent, 14)) +
|
||||||
|
15504*(math.Pow(pEvent, 15)) -
|
||||||
|
4845*(math.Pow(pEvent, 16)) +
|
||||||
|
1140*(math.Pow(pEvent, 17)) -
|
||||||
|
190*(math.Pow(pEvent, 18)) +
|
||||||
|
20*(math.Pow(pEvent, 19)) -
|
||||||
|
math.Pow(pEvent, 20)
|
||||||
|
if probability >= inclusionThreshold {
|
||||||
|
return LessThanFiveMinutes
|
||||||
|
}
|
||||||
|
|
||||||
|
return MoreThanFiveMinutes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FeeManager) getFeeHistorySorted(feeHistory *FeeHistory) ([]*big.Int, error) {
|
||||||
|
fees := []*big.Int{}
|
||||||
|
for _, fee := range feeHistory.BaseFeePerGas {
|
||||||
|
i := new(big.Int)
|
||||||
|
i.SetString(strings.Replace(fee, "0x", "", 1), 16)
|
||||||
|
fees = append(fees, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(fees, func(i, j int) bool { return fees[i].Cmp(fees[j]) < 0 })
|
||||||
|
return fees, nil
|
||||||
|
}
|
@ -2,16 +2,11 @@ package fees
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
|
|
||||||
"github.com/status-im/status-go/errors"
|
"github.com/status-im/status-go/errors"
|
||||||
"github.com/status-im/status-go/rpc"
|
"github.com/status-im/status-go/rpc"
|
||||||
"github.com/status-im/status-go/rpc/chain"
|
"github.com/status-im/status-go/rpc/chain"
|
||||||
@ -29,6 +24,8 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ErrCustomFeeModeNotAvailableInSuggestedFees = &errors.ErrorResponse{Code: errors.ErrorCode("WRF-001"), Details: "custom fee mode is not available in suggested fees"}
|
ErrCustomFeeModeNotAvailableInSuggestedFees = &errors.ErrorResponse{Code: errors.ErrorCode("WRF-001"), Details: "custom fee mode is not available in suggested fees"}
|
||||||
|
ErrEIP1559IncompaibleChain = &errors.ErrorResponse{Code: errors.ErrorCode("WRF-002"), Details: "EIP-1559 is not supported on this chain"}
|
||||||
|
ErrInvalidRewardData = &errors.ErrorResponse{Code: errors.ErrorCode("WRF-003"), Details: "invalid reward data"}
|
||||||
)
|
)
|
||||||
|
|
||||||
type MaxFeesLevels struct {
|
type MaxFeesLevels struct {
|
||||||
@ -37,13 +34,20 @@ type MaxFeesLevels struct {
|
|||||||
High *hexutil.Big `json:"high"`
|
High *hexutil.Big `json:"high"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MaxPriorityFeesSuggestedBounds struct {
|
||||||
|
Lower *big.Int
|
||||||
|
Upper *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
type SuggestedFees struct {
|
type SuggestedFees struct {
|
||||||
GasPrice *big.Int `json:"gasPrice"`
|
GasPrice *big.Int
|
||||||
BaseFee *big.Int `json:"baseFee"`
|
BaseFee *big.Int
|
||||||
MaxFeesLevels *MaxFeesLevels `json:"maxFeesLevels"`
|
CurrentBaseFee *big.Int // Current network base fee (in ETH WEI)
|
||||||
MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"`
|
MaxFeesLevels *MaxFeesLevels
|
||||||
L1GasFee *big.Float `json:"l1GasFee,omitempty"`
|
MaxPriorityFeePerGas *big.Int
|
||||||
EIP1559Enabled bool `json:"eip1559Enabled"`
|
MaxPriorityFeeSuggestedBounds *MaxPriorityFeesSuggestedBounds
|
||||||
|
L1GasFee *big.Float
|
||||||
|
EIP1559Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
@ -81,58 +85,29 @@ func (s *SuggestedFees) FeeFor(mode GasFeeMode) (*big.Int, error) {
|
|||||||
return s.MaxFeesLevels.FeeFor(mode)
|
return s.MaxFeesLevels.FeeFor(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
const inclusionThreshold = 0.95
|
|
||||||
|
|
||||||
type TransactionEstimation int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Unknown TransactionEstimation = iota
|
|
||||||
LessThanOneMinute
|
|
||||||
LessThanThreeMinutes
|
|
||||||
LessThanFiveMinutes
|
|
||||||
MoreThanFiveMinutes
|
|
||||||
)
|
|
||||||
|
|
||||||
type FeeHistory struct {
|
|
||||||
BaseFeePerGas []string `json:"baseFeePerGas"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeeManager struct {
|
type FeeManager struct {
|
||||||
RPCClient rpc.ClientInterface
|
RPCClient rpc.ClientInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {
|
func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {
|
||||||
backend, err := f.RPCClient.EthClient(chainID)
|
feeHistory, err := f.getFeeHistory(ctx, chainID, 300, "latest", []int{25, 50, 75})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return f.getNonEIP1559SuggestedFees(ctx, chainID)
|
||||||
}
|
}
|
||||||
gasPrice, err := backend.SuggestGasPrice(ctx)
|
|
||||||
|
maxPriorityFeePerGasLowerBound, maxPriorityFeePerGas, maxPriorityFeePerGasUpperBound, baseFee, err := getEIP1559SuggestedFees(feeHistory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return f.getNonEIP1559SuggestedFees(ctx, chainID)
|
||||||
}
|
|
||||||
maxPriorityFeePerGas, err := backend.SuggestGasTipCap(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return &SuggestedFees{
|
|
||||||
GasPrice: gasPrice,
|
|
||||||
BaseFee: big.NewInt(0),
|
|
||||||
MaxPriorityFeePerGas: big.NewInt(0),
|
|
||||||
MaxFeesLevels: &MaxFeesLevels{
|
|
||||||
Low: (*hexutil.Big)(gasPrice),
|
|
||||||
Medium: (*hexutil.Big)(gasPrice),
|
|
||||||
High: (*hexutil.Big)(gasPrice),
|
|
||||||
},
|
|
||||||
EIP1559Enabled: false,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
baseFee, err := f.getBaseFee(ctx, backend)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SuggestedFees{
|
return &SuggestedFees{
|
||||||
GasPrice: gasPrice,
|
|
||||||
BaseFee: baseFee,
|
BaseFee: baseFee,
|
||||||
|
CurrentBaseFee: baseFee,
|
||||||
MaxPriorityFeePerGas: maxPriorityFeePerGas,
|
MaxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||||
|
MaxPriorityFeeSuggestedBounds: &MaxPriorityFeesSuggestedBounds{
|
||||||
|
Lower: maxPriorityFeePerGasLowerBound,
|
||||||
|
Upper: maxPriorityFeePerGasUpperBound,
|
||||||
|
},
|
||||||
MaxFeesLevels: &MaxFeesLevels{
|
MaxFeesLevels: &MaxFeesLevels{
|
||||||
Low: (*hexutil.Big)(new(big.Int).Add(baseFee, maxPriorityFeePerGas)),
|
Low: (*hexutil.Big)(new(big.Int).Add(baseFee, maxPriorityFeePerGas)),
|
||||||
Medium: (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), maxPriorityFeePerGas)),
|
Medium: (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), maxPriorityFeePerGas)),
|
||||||
@ -142,6 +117,9 @@ func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*Sugges
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TODO: remove `SuggestedFeesGwei` once mobile app fully switched to router, this function should not be exposed via api
|
||||||
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
func (f *FeeManager) SuggestedFeesGwei(ctx context.Context, chainID uint64) (*SuggestedFeesGwei, error) {
|
func (f *FeeManager) SuggestedFeesGwei(ctx context.Context, chainID uint64) (*SuggestedFeesGwei, error) {
|
||||||
fees, err := f.SuggestedFees(ctx, chainID)
|
fees, err := f.SuggestedFees(ctx, chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -175,126 +153,3 @@ func (f *FeeManager) getBaseFee(ctx context.Context, client chain.ClientInterfac
|
|||||||
baseFee := misc.CalcBaseFee(config, header)
|
baseFee := misc.CalcBaseFee(config, header)
|
||||||
return baseFee, nil
|
return baseFee, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeeManager) TransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Int) TransactionEstimation {
|
|
||||||
fees, err := f.getFeeHistorySorted(chainID)
|
|
||||||
if err != nil {
|
|
||||||
return Unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
// pEvent represents the probability of the transaction being included in a block,
|
|
||||||
// we assume this one is static over time, in reality it is not.
|
|
||||||
pEvent := 0.0
|
|
||||||
for idx, fee := range fees {
|
|
||||||
if fee.Cmp(maxFeePerGas) == 1 || idx == len(fees)-1 {
|
|
||||||
pEvent = float64(idx) / float64(len(fees))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probability of next 4 blocks including the transaction (less than 1 minute)
|
|
||||||
// Generalising the formula: P(AUB) = P(A) + P(B) - P(A∩B) for 4 events and in our context P(A) == P(B) == pEvent
|
|
||||||
// The factors are calculated using the combinations formula
|
|
||||||
probability := pEvent*4 - 6*(math.Pow(pEvent, 2)) + 4*(math.Pow(pEvent, 3)) - (math.Pow(pEvent, 4))
|
|
||||||
if probability >= inclusionThreshold {
|
|
||||||
return LessThanOneMinute
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probability of next 12 blocks including the transaction (less than 5 minutes)
|
|
||||||
// Generalising the formula: P(AUB) = P(A) + P(B) - P(A∩B) for 20 events and in our context P(A) == P(B) == pEvent
|
|
||||||
// The factors are calculated using the combinations formula
|
|
||||||
probability = pEvent*12 -
|
|
||||||
66*(math.Pow(pEvent, 2)) +
|
|
||||||
220*(math.Pow(pEvent, 3)) -
|
|
||||||
495*(math.Pow(pEvent, 4)) +
|
|
||||||
792*(math.Pow(pEvent, 5)) -
|
|
||||||
924*(math.Pow(pEvent, 6)) +
|
|
||||||
792*(math.Pow(pEvent, 7)) -
|
|
||||||
495*(math.Pow(pEvent, 8)) +
|
|
||||||
220*(math.Pow(pEvent, 9)) -
|
|
||||||
66*(math.Pow(pEvent, 10)) +
|
|
||||||
12*(math.Pow(pEvent, 11)) -
|
|
||||||
math.Pow(pEvent, 12)
|
|
||||||
if probability >= inclusionThreshold {
|
|
||||||
return LessThanThreeMinutes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probability of next 20 blocks including the transaction (less than 5 minutes)
|
|
||||||
// Generalising the formula: P(AUB) = P(A) + P(B) - P(A∩B) for 20 events and in our context P(A) == P(B) == pEvent
|
|
||||||
// The factors are calculated using the combinations formula
|
|
||||||
probability = pEvent*20 -
|
|
||||||
190*(math.Pow(pEvent, 2)) +
|
|
||||||
1140*(math.Pow(pEvent, 3)) -
|
|
||||||
4845*(math.Pow(pEvent, 4)) +
|
|
||||||
15504*(math.Pow(pEvent, 5)) -
|
|
||||||
38760*(math.Pow(pEvent, 6)) +
|
|
||||||
77520*(math.Pow(pEvent, 7)) -
|
|
||||||
125970*(math.Pow(pEvent, 8)) +
|
|
||||||
167960*(math.Pow(pEvent, 9)) -
|
|
||||||
184756*(math.Pow(pEvent, 10)) +
|
|
||||||
167960*(math.Pow(pEvent, 11)) -
|
|
||||||
125970*(math.Pow(pEvent, 12)) +
|
|
||||||
77520*(math.Pow(pEvent, 13)) -
|
|
||||||
38760*(math.Pow(pEvent, 14)) +
|
|
||||||
15504*(math.Pow(pEvent, 15)) -
|
|
||||||
4845*(math.Pow(pEvent, 16)) +
|
|
||||||
1140*(math.Pow(pEvent, 17)) -
|
|
||||||
190*(math.Pow(pEvent, 18)) +
|
|
||||||
20*(math.Pow(pEvent, 19)) -
|
|
||||||
math.Pow(pEvent, 20)
|
|
||||||
if probability >= inclusionThreshold {
|
|
||||||
return LessThanFiveMinutes
|
|
||||||
}
|
|
||||||
|
|
||||||
return MoreThanFiveMinutes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FeeManager) getFeeHistorySorted(chainID uint64) ([]*big.Int, error) {
|
|
||||||
var feeHistory FeeHistory
|
|
||||||
|
|
||||||
err := f.RPCClient.Call(&feeHistory, chainID, "eth_feeHistory", 101, "latest", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fees := []*big.Int{}
|
|
||||||
for _, fee := range feeHistory.BaseFeePerGas {
|
|
||||||
i := new(big.Int)
|
|
||||||
i.SetString(strings.Replace(fee, "0x", "", 1), 16)
|
|
||||||
fees = append(fees, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(fees, func(i, j int) bool { return fees[i].Cmp(fees[j]) < 0 })
|
|
||||||
return fees, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns L1 fee for placing a transaction to L1 chain, appicable only for txs made from L2.
|
|
||||||
func (f *FeeManager) GetL1Fee(ctx context.Context, chainID uint64, input []byte) (uint64, error) {
|
|
||||||
if chainID == common.EthereumMainnet || chainID == common.EthereumSepolia {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ethClient, err := f.RPCClient.EthClient(chainID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
contractAddress, err := gaspriceoracle.ContractAddress(chainID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
contract, err := gaspriceoracle.NewGaspriceoracleCaller(contractAddress, ethClient)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
callOpt := &bind.CallOpts{}
|
|
||||||
|
|
||||||
result, err := contract.GetL1Fee(callOpt, input)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Uint64(), nil
|
|
||||||
}
|
|
||||||
|
71
services/wallet/router/fees/fees_history.go
Normal file
71
services/wallet/router/fees/fees_history.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package fees
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
|
||||||
|
"github.com/status-im/status-go/services/wallet/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FeeHistory struct {
|
||||||
|
BaseFeePerGas []string `json:"baseFeePerGas"`
|
||||||
|
GasUsedRatio []float64 `json:"gasUsedRatio"`
|
||||||
|
OldestBlock string `json:"oldestBlock"`
|
||||||
|
Reward [][]string `json:"reward,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fh *FeeHistory) isEIP1559Compatible() bool {
|
||||||
|
if len(fh.BaseFeePerGas) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fee := range fh.BaseFeePerGas {
|
||||||
|
if fee != "0x0" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FeeManager) getFeeHistory(ctx context.Context, chainID uint64, blockCount uint64, newestBlock string, rewardPercentiles []int) (feeHistory *FeeHistory, err error) {
|
||||||
|
feeHistory = &FeeHistory{}
|
||||||
|
err = f.RPCClient.Call(feeHistory, chainID, "eth_feeHistory", blockCount, newestBlock, rewardPercentiles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return feeHistory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetL1Fee returns L1 fee for placing a transaction to L1 chain, appicable only for txs made from L2.
|
||||||
|
func (f *FeeManager) GetL1Fee(ctx context.Context, chainID uint64, input []byte) (uint64, error) {
|
||||||
|
if chainID == common.EthereumMainnet || chainID == common.EthereumSepolia {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ethClient, err := f.RPCClient.EthClient(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contractAddress, err := gaspriceoracle.ContractAddress(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contract, err := gaspriceoracle.NewGaspriceoracleCaller(contractAddress, ethClient)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpt := &bind.CallOpts{}
|
||||||
|
|
||||||
|
result, err := contract.GetL1Fee(callOpt, input)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Uint64(), nil
|
||||||
|
}
|
169
services/wallet/router/fees/fees_test.go
Normal file
169
services/wallet/router/fees/fees_test.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package fees
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
|
||||||
|
mock_client "github.com/status-im/status-go/rpc/chain/mock/client"
|
||||||
|
mock_rpcclient "github.com/status-im/status-go/rpc/mock/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testState struct {
|
||||||
|
ctx context.Context
|
||||||
|
mockCtrl *gomock.Controller
|
||||||
|
rpcClient *mock_rpcclient.MockClientInterface
|
||||||
|
feeManager *FeeManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTest(t *testing.T) (state testState) {
|
||||||
|
state.ctx = context.Background()
|
||||||
|
state.mockCtrl = gomock.NewController(t)
|
||||||
|
state.rpcClient = mock_rpcclient.NewMockClientInterface(state.mockCtrl)
|
||||||
|
state.feeManager = &FeeManager{
|
||||||
|
RPCClient: state.rpcClient,
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEstimatedTime(t *testing.T) {
|
||||||
|
state := setupTest(t)
|
||||||
|
// no fee history
|
||||||
|
feeHistory := &FeeHistory{}
|
||||||
|
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(100), "latest", nil).Times(1).Return(nil)
|
||||||
|
|
||||||
|
maxFeesPerGas := big.NewInt(2e9)
|
||||||
|
estimation := state.feeManager.TransactionEstimatedTime(context.Background(), uint64(1), maxFeesPerGas)
|
||||||
|
|
||||||
|
assert.Equal(t, Unknown, estimation)
|
||||||
|
|
||||||
|
// there is fee history
|
||||||
|
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(100), "latest", nil).Times(1).Return(nil).
|
||||||
|
Do(func(feeHistory, chainID, method any, args ...any) {
|
||||||
|
feeHistoryResponse := &FeeHistory{
|
||||||
|
BaseFeePerGas: []string{
|
||||||
|
"0x12f0e070b",
|
||||||
|
"0x13f10da8b",
|
||||||
|
"0x126c30d5e",
|
||||||
|
"0x136e4fe51",
|
||||||
|
"0x134180d5a",
|
||||||
|
"0x134e32c33",
|
||||||
|
"0x137da8d22",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*feeHistory.(*FeeHistory) = *feeHistoryResponse
|
||||||
|
})
|
||||||
|
|
||||||
|
maxFeesPerGas = big.NewInt(100e9)
|
||||||
|
estimation = state.feeManager.TransactionEstimatedTime(context.Background(), uint64(1), maxFeesPerGas)
|
||||||
|
|
||||||
|
assert.Equal(t, LessThanOneMinute, estimation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestedFeesForNotEIP1559CompatibleChains(t *testing.T) {
|
||||||
|
state := setupTest(t)
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
gasPrice := big.NewInt(1)
|
||||||
|
feeHistory := &FeeHistory{}
|
||||||
|
|
||||||
|
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
|
||||||
|
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
|
||||||
|
state.rpcClient.EXPECT().EthClient(chainID).Times(1).Return(mockedChainClient, nil)
|
||||||
|
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(gasPrice, nil)
|
||||||
|
|
||||||
|
suggestedFees, err := state.feeManager.SuggestedFees(context.Background(), chainID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, suggestedFees)
|
||||||
|
assert.Equal(t, gasPrice, suggestedFees.GasPrice)
|
||||||
|
assert.False(t, suggestedFees.EIP1559Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
|
||||||
|
state := setupTest(t)
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
feeHistory := &FeeHistory{}
|
||||||
|
|
||||||
|
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil).
|
||||||
|
Do(func(feeHistory, chainID, method any, args ...any) {
|
||||||
|
feeHistoryResponse := &FeeHistory{
|
||||||
|
BaseFeePerGas: []string{
|
||||||
|
"0x12f0e070b",
|
||||||
|
"0x13f10da8b",
|
||||||
|
"0x126c30d5e",
|
||||||
|
"0x136e4fe51",
|
||||||
|
"0x134180d5a",
|
||||||
|
"0x134e32c33",
|
||||||
|
"0x137da8d22",
|
||||||
|
},
|
||||||
|
GasUsedRatio: []float64{
|
||||||
|
0.7113286209349903,
|
||||||
|
0.19531163333333335,
|
||||||
|
0.7189235666666667,
|
||||||
|
0.4639678021079083,
|
||||||
|
0.5103012666666666,
|
||||||
|
0.538413,
|
||||||
|
0.16543626666666666,
|
||||||
|
},
|
||||||
|
OldestBlock: "0x1497d4b",
|
||||||
|
Reward: [][]string{
|
||||||
|
{
|
||||||
|
"0x2faf080",
|
||||||
|
"0x39d10680",
|
||||||
|
"0x722d7ef5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0x5f5e100",
|
||||||
|
"0x3b9aca00",
|
||||||
|
"0x59682f00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0x342e4a2",
|
||||||
|
"0x39d10680",
|
||||||
|
"0x77359400",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0x14a22237",
|
||||||
|
"0x40170350",
|
||||||
|
"0x77359400",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0x9134860",
|
||||||
|
"0x39d10680",
|
||||||
|
"0x618400ad",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0x2faf080",
|
||||||
|
"0x39d10680",
|
||||||
|
"0x77359400",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0x1ed69035",
|
||||||
|
"0x39d10680",
|
||||||
|
"0x41d0a8d6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*feeHistory.(*FeeHistory) = *feeHistoryResponse
|
||||||
|
})
|
||||||
|
|
||||||
|
suggestedFees, err := state.feeManager.SuggestedFees(context.Background(), chainID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, suggestedFees)
|
||||||
|
|
||||||
|
assert.Nil(t, suggestedFees.GasPrice)
|
||||||
|
assert.Equal(t, big.NewInt(6958609414), suggestedFees.BaseFee)
|
||||||
|
assert.Equal(t, big.NewInt(6958609414), suggestedFees.CurrentBaseFee)
|
||||||
|
assert.Equal(t, big.NewInt(7928609414), suggestedFees.MaxFeesLevels.Low.ToInt())
|
||||||
|
assert.Equal(t, big.NewInt(14887218828), suggestedFees.MaxFeesLevels.Medium.ToInt())
|
||||||
|
assert.Equal(t, big.NewInt(21845828242), suggestedFees.MaxFeesLevels.High.ToInt())
|
||||||
|
assert.Equal(t, big.NewInt(970000000), suggestedFees.MaxPriorityFeePerGas)
|
||||||
|
assert.Equal(t, big.NewInt(54715554), suggestedFees.MaxPriorityFeeSuggestedBounds.Lower)
|
||||||
|
assert.Equal(t, big.NewInt(1636040877), suggestedFees.MaxPriorityFeeSuggestedBounds.Upper)
|
||||||
|
assert.True(t, suggestedFees.EIP1559Enabled)
|
||||||
|
}
|
138
services/wallet/router/fees/suggested_priority.go
Normal file
138
services/wallet/router/fees/suggested_priority.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package fees
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const baseFeeIncreaseFactor = 1.33
|
||||||
|
|
||||||
|
func hexStringToBigInt(value string) (*big.Int, error) {
|
||||||
|
valueWitoutPrefix := strings.TrimPrefix(value, "0x")
|
||||||
|
val, success := new(big.Int).SetString(valueWitoutPrefix, 16)
|
||||||
|
if !success {
|
||||||
|
return nil, errors.New("failed to convert hex string to big.Int")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scaleBaseFeePerGas(value string) (*big.Int, error) {
|
||||||
|
val, err := hexStringToBigInt(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
valueDouble := new(big.Float).SetInt(val)
|
||||||
|
valueDouble.Mul(valueDouble, big.NewFloat(baseFeeIncreaseFactor))
|
||||||
|
scaledValue := new(big.Int)
|
||||||
|
valueDouble.Int(scaledValue)
|
||||||
|
return scaledValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FeeManager) getNonEIP1559SuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {
|
||||||
|
backend, err := f.RPCClient.EthClient(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gasPrice, err := backend.SuggestGasPrice(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &SuggestedFees{
|
||||||
|
GasPrice: gasPrice,
|
||||||
|
BaseFee: big.NewInt(0),
|
||||||
|
MaxPriorityFeePerGas: big.NewInt(0),
|
||||||
|
MaxPriorityFeeSuggestedBounds: &MaxPriorityFeesSuggestedBounds{
|
||||||
|
Lower: big.NewInt(0),
|
||||||
|
Upper: big.NewInt(0),
|
||||||
|
},
|
||||||
|
MaxFeesLevels: &MaxFeesLevels{
|
||||||
|
Low: (*hexutil.Big)(gasPrice),
|
||||||
|
Medium: (*hexutil.Big)(gasPrice),
|
||||||
|
High: (*hexutil.Big)(gasPrice),
|
||||||
|
},
|
||||||
|
EIP1559Enabled: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEIP1559SuggestedFees returns suggested fees for EIP-1559 compatible chains
|
||||||
|
// source https://github.com/brave/brave-core/blob/master/components/brave_wallet/browser/eth_gas_utils.cc
|
||||||
|
func getEIP1559SuggestedFees(feeHistory *FeeHistory) (lowPriorityFee, avgPriorityFee, highPriorityFee, suggestedBaseFee *big.Int, err error) {
|
||||||
|
if feeHistory == nil || !feeHistory.isEIP1559Compatible() {
|
||||||
|
return nil, nil, nil, nil, ErrEIP1559IncompaibleChain
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingBaseFee := feeHistory.BaseFeePerGas[len(feeHistory.BaseFeePerGas)-1]
|
||||||
|
suggestedBaseFee, err = scaleBaseFeePerGas(pendingBaseFee)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fallbackPriorityFee := big.NewInt(2e9) // 2 Gwei
|
||||||
|
lowPriorityFee = new(big.Int).Set(fallbackPriorityFee)
|
||||||
|
avgPriorityFee = new(big.Int).Set(fallbackPriorityFee)
|
||||||
|
highPriorityFee = new(big.Int).Set(fallbackPriorityFee)
|
||||||
|
|
||||||
|
if len(feeHistory.Reward) == 0 {
|
||||||
|
return lowPriorityFee, avgPriorityFee, highPriorityFee, suggestedBaseFee, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
priorityFees := make([][]*big.Int, 3)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
currentPriorityFees := []*big.Int{}
|
||||||
|
invalidData := false
|
||||||
|
|
||||||
|
for _, r := range feeHistory.Reward {
|
||||||
|
if len(r) != 3 {
|
||||||
|
invalidData = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fee, err := hexStringToBigInt(r[i])
|
||||||
|
if err != nil {
|
||||||
|
invalidData = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentPriorityFees = append(currentPriorityFees, fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
if invalidData {
|
||||||
|
return nil, nil, nil, nil, ErrInvalidRewardData
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(currentPriorityFees, func(a, b int) bool {
|
||||||
|
return currentPriorityFees[a].Cmp(currentPriorityFees[b]) < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
percentileIndex := int(float64(len(currentPriorityFees)) * 0.4)
|
||||||
|
if i == 0 {
|
||||||
|
lowPriorityFee = currentPriorityFees[percentileIndex]
|
||||||
|
} else if i == 1 {
|
||||||
|
avgPriorityFee = currentPriorityFees[percentileIndex]
|
||||||
|
} else {
|
||||||
|
highPriorityFee = currentPriorityFees[percentileIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
priorityFees[i] = currentPriorityFees
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust low priority fee if it's equal to avg
|
||||||
|
lowIndex := int(float64(len(priorityFees[0])) * 0.4)
|
||||||
|
for lowIndex > 0 && lowPriorityFee == avgPriorityFee {
|
||||||
|
lowIndex--
|
||||||
|
lowPriorityFee = priorityFees[0][lowIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust high priority fee if it's equal to avg
|
||||||
|
highIndex := int(float64(len(priorityFees[2])) * 0.4)
|
||||||
|
for highIndex < len(priorityFees[2])-1 && highPriorityFee == avgPriorityFee {
|
||||||
|
highIndex++
|
||||||
|
highPriorityFee = priorityFees[2][highIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowPriorityFee, avgPriorityFee, highPriorityFee, suggestedBaseFee, nil
|
||||||
|
}
|
@ -186,6 +186,19 @@ func (r *Router) applyCustomFields(ctx context.Context, path *routes.Path, fetch
|
|||||||
r.lastInputParamsMutex.Lock()
|
r.lastInputParamsMutex.Lock()
|
||||||
defer r.lastInputParamsMutex.Unlock()
|
defer r.lastInputParamsMutex.Unlock()
|
||||||
|
|
||||||
|
// set network fields
|
||||||
|
if fetchedFees.CurrentBaseFee != nil {
|
||||||
|
path.CurrentBaseFee = (*hexutil.Big)(fetchedFees.CurrentBaseFee)
|
||||||
|
}
|
||||||
|
if fetchedFees.MaxPriorityFeeSuggestedBounds != nil {
|
||||||
|
if fetchedFees.MaxPriorityFeeSuggestedBounds.Lower != nil {
|
||||||
|
path.SuggestedMinPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeeSuggestedBounds.Lower)
|
||||||
|
}
|
||||||
|
if fetchedFees.MaxPriorityFeeSuggestedBounds.Upper != nil {
|
||||||
|
path.SuggestedMaxPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeeSuggestedBounds.Upper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// set appropriate nonce/s, and update later in this function if custom nonce/s are provided
|
// set appropriate nonce/s, and update later in this function if custom nonce/s are provided
|
||||||
err := r.resolveNonceForPath(ctx, path, r.lastInputParams.AddrFrom, usedNonces)
|
err := r.resolveNonceForPath(ctx, path, r.lastInputParams.AddrFrom, usedNonces)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -341,7 +354,11 @@ func (r *Router) evaluateAndUpdatePathDetails(ctx context.Context, path *routes.
|
|||||||
|
|
||||||
path.TxEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.TxMaxFeesPerGas.ToInt())
|
path.TxEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.TxMaxFeesPerGas.ToInt())
|
||||||
if path.ApprovalRequired {
|
if path.ApprovalRequired {
|
||||||
path.ApprovalEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.ApprovalMaxFeesPerGas.ToInt())
|
if path.TxMaxFeesPerGas.ToInt().Cmp(path.ApprovalMaxFeesPerGas.ToInt()) == 0 {
|
||||||
|
path.ApprovalEstimatedTime = path.TxEstimatedTime
|
||||||
|
} else {
|
||||||
|
path.ApprovalEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.ApprovalMaxFeesPerGas.ToInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -22,7 +22,10 @@ type Path struct {
|
|||||||
AmountInLocked bool // Is the amount locked
|
AmountInLocked bool // Is the amount locked
|
||||||
AmountOut *hexutil.Big // Amount that will be received on the destination chain
|
AmountOut *hexutil.Big // Amount that will be received on the destination chain
|
||||||
|
|
||||||
SuggestedLevelsForMaxFeesPerGas *fees.MaxFeesLevels // Suggested max fees for the transaction (in ETH WEI)
|
SuggestedLevelsForMaxFeesPerGas *fees.MaxFeesLevels // Suggested max fees by the network (in ETH WEI)
|
||||||
|
SuggestedMinPriorityFee *hexutil.Big // Suggested min priority fee by the network (in ETH WEI)
|
||||||
|
SuggestedMaxPriorityFee *hexutil.Big // Suggested max priority fee by the network (in ETH WEI)
|
||||||
|
CurrentBaseFee *hexutil.Big // Current network base fee (in ETH WEI)
|
||||||
|
|
||||||
TxNonce *hexutil.Uint64 // Nonce for the transaction
|
TxNonce *hexutil.Uint64 // Nonce for the transaction
|
||||||
TxMaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI)
|
TxMaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI)
|
||||||
@ -117,6 +120,18 @@ func (p *Path) Copy() *Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.SuggestedMinPriorityFee != nil {
|
||||||
|
newPath.SuggestedMinPriorityFee = (*hexutil.Big)(big.NewInt(0).Set(p.SuggestedMinPriorityFee.ToInt()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.SuggestedMaxPriorityFee != nil {
|
||||||
|
newPath.SuggestedMaxPriorityFee = (*hexutil.Big)(big.NewInt(0).Set(p.SuggestedMaxPriorityFee.ToInt()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.CurrentBaseFee != nil {
|
||||||
|
newPath.CurrentBaseFee = (*hexutil.Big)(big.NewInt(0).Set(p.CurrentBaseFee.ToInt()))
|
||||||
|
}
|
||||||
|
|
||||||
if p.TxNonce != nil {
|
if p.TxNonce != nil {
|
||||||
txNonce := *p.TxNonce
|
txNonce := *p.TxNonce
|
||||||
newPath.TxNonce = &txNonce
|
newPath.TxNonce = &txNonce
|
||||||
|
Loading…
x
Reference in New Issue
Block a user