feat(wallet)_: calculating tx estimated time

This commit is contained in:
Sale Djenic 2025-02-17 13:25:08 +01:00 committed by saledjenic
parent 1bfb0cef02
commit 0bf6048801
10 changed files with 337 additions and 95 deletions

View File

@ -97,7 +97,7 @@ func (c *SendTransactionCommand) Execute(ctx context.Context, request RPCRequest
if !fetchedFees.EIP1559Enabled {
params.GasPrice = (*hexutil.Big)(fetchedFees.GasPrice)
} else {
maxFees, priorityFee, err := fetchedFees.FeeFor(fees.GasFeeMedium)
maxFees, priorityFee, _, err := fetchedFees.FeeFor(fees.GasFeeMedium)
if err != nil {
return "", err
}

View File

@ -481,6 +481,11 @@ func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64,
return api.s.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil
}
func (api *API) GetTransactionEstimatedTimeV2(ctx context.Context, chainID uint64, maxFeePerGas *hexutil.Big, maxPriorityFeePerGas *hexutil.Big) (uint, error) {
logutils.ZapLogger().Debug("call to getTransactionEstimatedTimeV2")
return api.s.router.GetFeesManager().TransactionEstimatedTimeV2(ctx, chainID, maxFeePerGas.ToInt(), maxPriorityFeePerGas.ToInt()), nil
}
func gweiToWei(val *big.Float) *big.Int {
res, _ := new(big.Float).Mul(val, big.NewFloat(1000000000)).Int(nil)
return res

View File

@ -4,6 +4,7 @@ import (
"context"
"math/big"
"reflect"
"time"
gethParams "github.com/ethereum/go-ethereum/params"
"github.com/status-im/status-go/params"
@ -75,3 +76,11 @@ func WeiToGwei(val *big.Int) *big.Float {
return result.Quo(result, new(big.Float).SetInt(unit))
}
func GetBlockCreationTimeForChain(chainID uint64) time.Duration {
blockDuration, found := AverageBlockDurationForChain[ChainID(chainID)]
if !found {
blockDuration = AverageBlockDurationForChain[ChainID(UnknownChainID)]
}
return blockDuration
}

View File

@ -5,10 +5,24 @@ import (
"math"
"math/big"
"sort"
"strings"
"github.com/status-im/status-go/services/wallet/common"
)
const inclusionThreshold = 0.95
const (
inclusionThreshold = 0.95
priorityFeePercentileHigh = 0.6
priorityFeePercentileMedium = 0.5
priorityFeePercentileLow = 0.4
baseFeePercentileFirstBlock = 0.8
baseFeePercentileSecondBlock = 0.7
baseFeePercentileThirdBlock = 0.6
baseFeePercentileFourthBlock = 0.5
baseFeePercentileFifthBlock = 0.4
baseFeePercentileSixthBlock = 0.3
)
type TransactionEstimation int
@ -30,8 +44,8 @@ func (f *FeeManager) TransactionEstimatedTime(ctx context.Context, chainID uint6
}
func (f *FeeManager) estimatedTime(feeHistory *FeeHistory, maxFeePerGas *big.Int) TransactionEstimation {
fees, err := f.getFeeHistorySorted(feeHistory)
if err != nil || len(fees) == 0 {
fees := f.convertToBigIntAndSort(feeHistory.BaseFeePerGas)
if len(fees) == 0 {
return Unknown
}
@ -102,14 +116,110 @@ func (f *FeeManager) estimatedTime(feeHistory *FeeHistory, maxFeePerGas *big.Int
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)
func (f *FeeManager) convertToBigIntAndSort(hexArray []string) []*big.Int {
values := []*big.Int{}
for _, sValue := range hexArray {
iValue := new(big.Int)
_, ok := iValue.SetString(sValue, 0)
if !ok {
continue
}
values = append(values, iValue)
}
sort.Slice(fees, func(i, j int) bool { return fees[i].Cmp(fees[j]) < 0 })
return fees, nil
sort.Slice(values, func(i, j int) bool { return values[i].Cmp(values[j]) < 0 })
return values
}
func (f *FeeManager) getFeeHistoryForTimeEstimation(ctx context.Context, chainID uint64) (*FeeHistory, error) {
blockCount := uint64(10) // use the last 10 blocks for L1 chains
if chainID != common.EthereumMainnet &&
chainID != common.EthereumSepolia &&
chainID != common.AnvilMainnet {
blockCount = 50 // use the last 50 blocks for L2 chains
}
return f.getFeeHistory(ctx, chainID, blockCount, "latest", []int{RewardPercentiles2})
}
func calculateTimeForInclusion(chainID uint64, expectedInclusionInBlock int) uint {
blockCreationTime := common.GetBlockCreationTimeForChain(chainID)
blockCreationTimeInSeconds := uint(blockCreationTime.Seconds())
// the client will decide how to display estimated times, status-go sends it in the steps of 5 (for example the client
// should display "more than 1 minute" if the expected inclusion time is 60 seconds or more.
expectedInclusionTime := uint(expectedInclusionInBlock) * blockCreationTimeInSeconds
return (expectedInclusionTime/5 + 1) * 5
}
func getBaseFeePercentileIndex(sortedBaseFees []*big.Int, percentile float64, networkCongestion float64) int {
// calculate the index of the base fee for the given percentile corrected by the network congestion
index := int(float64(len(sortedBaseFees)) * percentile * networkCongestion)
if index >= len(sortedBaseFees) {
return len(sortedBaseFees) - 1
}
return index
}
// TransactionEstimatedTimeV2 returns the estimated time in seconds for a transaction to be included in a block
func (f *FeeManager) TransactionEstimatedTimeV2(ctx context.Context, chainID uint64, maxFeePerGas *big.Int, priorityFee *big.Int) uint {
feeHistory, err := f.getFeeHistoryForTimeEstimation(ctx, chainID)
if err != nil {
return 0
}
return f.estimatedTimeV2(feeHistory, maxFeePerGas, priorityFee, chainID)
}
func (f *FeeManager) estimatedTimeV2(feeHistory *FeeHistory, txMaxFeePerGas *big.Int, txPriorityFee *big.Int, chainID uint64) uint {
sortedBaseFees := f.convertToBigIntAndSort(feeHistory.BaseFeePerGas)
if len(sortedBaseFees) == 0 {
return 0
}
var mediumPriorityFees []string // based on 50th percentile in the last 100 blocks
for _, fee := range feeHistory.Reward {
mediumPriorityFees = append(mediumPriorityFees, fee[0])
}
mediumPriorityFeesSorted := f.convertToBigIntAndSort(mediumPriorityFees)
if len(mediumPriorityFeesSorted) == 0 {
return 0
}
txBaseFee := new(big.Int).Sub(txMaxFeePerGas, txPriorityFee)
networkCongestion := calculateNetworkCongestion(feeHistory)
// Priority fee for the first two blocks has to be higher than 60th percentile of the mediumPriorityFeesSorted
priorityFeePercentileIndex := int(float64(len(mediumPriorityFeesSorted)) * priorityFeePercentileHigh)
priorityFeeForFirstTwoBlock := mediumPriorityFeesSorted[priorityFeePercentileIndex]
// Priority fee for the second two blocks has to be higher than 50th percentile of the mediumPriorityFeesSorted
priorityFeePercentileIndex = int(float64(len(mediumPriorityFeesSorted)) * priorityFeePercentileMedium)
priorityFeeForSecondTwoBlocks := mediumPriorityFeesSorted[priorityFeePercentileIndex]
// Priority fee for the third two blocks has to be higher than 40th percentile of the mediumPriorityFeesSorted
priorityFeePercentileIndex = int(float64(len(mediumPriorityFeesSorted)) * priorityFeePercentileLow)
priorityFeeForThirdTwoBlocks := mediumPriorityFeesSorted[priorityFeePercentileIndex]
// To include the transaction in the block `inclusionInBlock` its base fee has to be in a higher than `baseFeePercentile`
// and its priority fee has to be higher than the `priorityFee`
inclusions := []struct {
inclusionInBlock int
baseFeePercentile float64
priorityFee *big.Int
}{
{1, baseFeePercentileFirstBlock, priorityFeeForFirstTwoBlock},
{2, baseFeePercentileSecondBlock, priorityFeeForFirstTwoBlock},
{3, baseFeePercentileThirdBlock, priorityFeeForSecondTwoBlocks},
{4, baseFeePercentileFourthBlock, priorityFeeForSecondTwoBlocks},
{5, baseFeePercentileFifthBlock, priorityFeeForThirdTwoBlocks},
{6, baseFeePercentileSixthBlock, priorityFeeForThirdTwoBlocks},
}
for _, p := range inclusions {
baseFeePercentileIndex := getBaseFeePercentileIndex(sortedBaseFees, p.baseFeePercentile, networkCongestion)
if txBaseFee.Cmp(sortedBaseFees[baseFeePercentileIndex]) >= 0 && txPriorityFee.Cmp(p.priorityFee) >= 0 {
return calculateTimeForInclusion(chainID, p.inclusionInBlock)
}
}
return 0
}

View File

@ -32,28 +32,31 @@ var (
)
type MaxFeesLevels struct {
Low *hexutil.Big `json:"low"`
LowPriority *hexutil.Big `json:"lowPriority"`
Medium *hexutil.Big `json:"medium"`
MediumPriority *hexutil.Big `json:"mediumPriority"`
High *hexutil.Big `json:"high"`
HighPriority *hexutil.Big `json:"highPriority"`
Low *hexutil.Big `json:"low"` // Low max fee per gas in WEI
LowPriority *hexutil.Big `json:"lowPriority"` // Low priority fee in WEI
LowEstimatedTime uint `json:"lowEstimatedTime"` // Estimated time for low fees in seconds
Medium *hexutil.Big `json:"medium"` // Medium max fee per gas in WEI
MediumPriority *hexutil.Big `json:"mediumPriority"` // Medium priority fee in WEI
MediumEstimatedTime uint `json:"mediumEstimatedTime"` // Estimated time for medium fees in seconds
High *hexutil.Big `json:"high"` // High max fee per gas in WEI
HighPriority *hexutil.Big `json:"highPriority"` // High priority fee in WEI
HighEstimatedTime uint `json:"highEstimatedTime"` // Estimated time for high fees in seconds
}
type MaxPriorityFeesSuggestedBounds struct {
Lower *big.Int
Upper *big.Int
Lower *big.Int // Lower bound for priority fee per gas in WEI
Upper *big.Int // Upper bound for priority fee per gas in WEI
}
type SuggestedFees struct {
GasPrice *big.Int
BaseFee *big.Int
CurrentBaseFee *big.Int // Current network base fee (in ETH WEI)
MaxFeesLevels *MaxFeesLevels
MaxPriorityFeePerGas *big.Int // TODO: remove once clients stop using this field
MaxPriorityFeeSuggestedBounds *MaxPriorityFeesSuggestedBounds
L1GasFee *big.Float
EIP1559Enabled bool
GasPrice *big.Int // TODO: remove once clients stop using this field, used for EIP-1559 incompatible chains, not in use anymore
BaseFee *big.Int // TODO: remove once clients stop using this field, current network base fee (in ETH WEI), kept for backward compatibility
CurrentBaseFee *big.Int // Current network base fee (in ETH WEI)
MaxFeesLevels *MaxFeesLevels // Max fees levels for low, medium and high fee modes
MaxPriorityFeePerGas *big.Int // TODO: remove once clients stop using this field, kept for backward compatibility
MaxPriorityFeeSuggestedBounds *MaxPriorityFeesSuggestedBounds // Lower and upper bounds for priority fee per gas in WEI
L1GasFee *big.Float // TODO: remove once clients stop using this field, not in use anymore
EIP1559Enabled bool // TODO: remove it since all chains we have support EIP-1559
}
// //////////////////////////////////////////////////////////////////////////////
@ -71,23 +74,23 @@ type SuggestedFeesGwei struct {
EIP1559Enabled bool `json:"eip1559Enabled"`
}
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, error) {
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, uint, error) {
if mode == GasFeeCustom {
return nil, nil, ErrCustomFeeModeNotAvailableInSuggestedFees
return nil, nil, 0, ErrCustomFeeModeNotAvailableInSuggestedFees
}
if mode == GasFeeLow {
return m.Low.ToInt(), m.LowPriority.ToInt(), nil
return m.Low.ToInt(), m.LowPriority.ToInt(), m.LowEstimatedTime, nil
}
if mode == GasFeeHigh {
return m.High.ToInt(), m.HighPriority.ToInt(), nil
return m.High.ToInt(), m.HighPriority.ToInt(), m.MediumEstimatedTime, nil
}
return m.Medium.ToInt(), m.MediumPriority.ToInt(), nil
return m.Medium.ToInt(), m.MediumPriority.ToInt(), m.HighEstimatedTime, nil
}
func (s *SuggestedFees) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, error) {
func (s *SuggestedFees) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, uint, error) {
return s.MaxFeesLevels.FeeFor(mode)
}
@ -150,6 +153,14 @@ func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*Sugges
}
}
feeHistory, err = f.getFeeHistoryForTimeEstimation(ctx, chainID)
if err != nil {
return nil, err
}
suggestedFees.MaxFeesLevels.LowEstimatedTime = f.estimatedTimeV2(feeHistory, suggestedFees.MaxFeesLevels.Low.ToInt(), suggestedFees.MaxFeesLevels.LowPriority.ToInt(), chainID)
suggestedFees.MaxFeesLevels.MediumEstimatedTime = f.estimatedTimeV2(feeHistory, suggestedFees.MaxFeesLevels.Medium.ToInt(), suggestedFees.MaxFeesLevels.MediumPriority.ToInt(), chainID)
suggestedFees.MaxFeesLevels.HighEstimatedTime = f.estimatedTimeV2(feeHistory, suggestedFees.MaxFeesLevels.High.ToInt(), suggestedFees.MaxFeesLevels.HighPriority.ToInt(), chainID)
return suggestedFees, nil
}

View File

@ -64,32 +64,43 @@ func TestEstimatedTime(t *testing.T) {
assert.Equal(t, LessThanOneMinute, estimation)
}
func TestSuggestedFeesForNotEIP1559CompatibleChains(t *testing.T) {
func TestEstimatedTimeV2(t *testing.T) {
state := setupTest(t)
chainID := uint64(1)
gasPrice := big.NewInt(1)
// no fee history
feeHistory := &FeeHistory{}
percentiles := []int{RewardPercentiles1, RewardPercentiles2, RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", percentiles).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)
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(10), "latest", []int{RewardPercentiles2}).Times(1).Return(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)
}
maxFeesPerGas := big.NewInt(2e9)
priorityFeesPerGas := big.NewInt(2e8)
estimation := state.feeManager.TransactionEstimatedTimeV2(context.Background(), uint64(1), maxFeesPerGas, priorityFeesPerGas)
func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
state := setupTest(t)
assert.Equal(t, uint(0), estimation)
chainID := uint64(1)
feeHistory := &FeeHistory{}
percentiles := []int{RewardPercentiles1, RewardPercentiles2, RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil).
// there is fee history
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(10), "latest", []int{RewardPercentiles2}).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)
priorityFeesPerGas = big.NewInt(10e9)
estimation = state.feeManager.TransactionEstimatedTimeV2(context.Background(), uint64(1), maxFeesPerGas, priorityFeesPerGas)
assert.Equal(t, uint(0), estimation)
// there is fee history and rewards
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(10), "latest", []int{RewardPercentiles2}).Times(1).Return(nil).
Do(func(feeHistory, chainID, method any, args ...any) {
feeHistoryResponse := &FeeHistory{
BaseFeePerGas: []string{
@ -152,6 +163,107 @@ func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
*feeHistory.(*FeeHistory) = *feeHistoryResponse
})
estimation = state.feeManager.TransactionEstimatedTimeV2(context.Background(), uint64(1), maxFeesPerGas, priorityFeesPerGas)
assert.Equal(t, uint(15), estimation)
}
func TestSuggestedFeesForNotEIP1559CompatibleChains(t *testing.T) {
state := setupTest(t)
chainID := uint64(1)
gasPrice := big.NewInt(1)
feeHistory := &FeeHistory{}
percentiles := []int{RewardPercentiles1, RewardPercentiles2, RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", percentiles).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)
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",
},
},
}
chainID := uint64(1)
feeHistory := &FeeHistory{}
percentiles := []int{RewardPercentiles1, RewardPercentiles2, RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil).
Do(func(feeHistory, chainID, method any, args ...any) {
*feeHistory.(*FeeHistory) = *feeHistoryResponse
})
feeHistory = &FeeHistory{}
percentiles = []int{RewardPercentiles2}
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(10), "latest", percentiles).Times(1).Return(nil).
Do(func(feeHistory, chainID, method any, args ...any) {
*feeHistory.(*FeeHistory) = *feeHistoryResponse
})
suggestedFees, err := state.feeManager.SuggestedFees(context.Background(), chainID)
assert.NoError(t, err)
assert.NotNil(t, suggestedFees)
@ -177,4 +289,7 @@ func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
assert.Equal(t, big.NewInt(100000000), suggestedFees.MaxPriorityFeeSuggestedBounds.Lower)
assert.Equal(t, big.NewInt(1915584245), suggestedFees.MaxPriorityFeeSuggestedBounds.Upper)
assert.True(t, suggestedFees.EIP1559Enabled)
assert.Equal(t, uint(40), suggestedFees.MaxFeesLevels.LowEstimatedTime)
assert.Equal(t, uint(15), suggestedFees.MaxFeesLevels.MediumEstimatedTime)
assert.Equal(t, uint(15), suggestedFees.MaxFeesLevels.HighEstimatedTime)
}

View File

@ -142,7 +142,7 @@ func (r *Router) SetTestBalanceMap(balanceMap map[string]*big.Int) {
}
}
func (r *Router) setCustomTxDetails(pathTxIdentity *requests.PathTxIdentity, pathTxCustomParams *requests.PathTxCustomParams) error {
func (r *Router) setCustomTxDetails(ctx context.Context, pathTxIdentity *requests.PathTxIdentity, pathTxCustomParams *requests.PathTxCustomParams) error {
if pathTxIdentity == nil {
return ErrTxIdentityNotProvided
}
@ -164,31 +164,17 @@ func (r *Router) setCustomTxDetails(pathTxIdentity *requests.PathTxIdentity, pat
return ErrCannotCustomizeIfNoRoute
}
fetchedFees, err := r.feesManager.SuggestedFees(ctx, pathTxIdentity.ChainID)
if err != nil {
return err
}
for _, path := range r.activeRoutes.Best {
if path.PathIdentity() != pathTxIdentity.PathIdentity() {
continue
}
if pathTxIdentity.IsApprovalTx {
path.ApprovalGasFeeMode = pathTxCustomParams.GasFeeMode
if pathTxCustomParams.GasFeeMode == fees.GasFeeCustom {
path.ApprovalTxNonce = (*hexutil.Uint64)(&pathTxCustomParams.Nonce)
path.ApprovalGasAmount = pathTxCustomParams.GasAmount
path.ApprovalMaxFeesPerGas = pathTxCustomParams.MaxFeesPerGas
path.ApprovalBaseFee = (*hexutil.Big)(new(big.Int).Sub(pathTxCustomParams.MaxFeesPerGas.ToInt(), pathTxCustomParams.PriorityFee.ToInt()))
path.ApprovalPriorityFee = pathTxCustomParams.PriorityFee
}
} else {
path.TxGasFeeMode = pathTxCustomParams.GasFeeMode
if pathTxCustomParams.GasFeeMode == fees.GasFeeCustom {
path.TxNonce = (*hexutil.Uint64)(&pathTxCustomParams.Nonce)
path.TxGasAmount = pathTxCustomParams.GasAmount
path.TxMaxFeesPerGas = pathTxCustomParams.MaxFeesPerGas
path.TxBaseFee = (*hexutil.Big)(new(big.Int).Sub(pathTxCustomParams.MaxFeesPerGas.ToInt(), pathTxCustomParams.PriorityFee.ToInt()))
path.TxPriorityFee = pathTxCustomParams.PriorityFee
}
}
// update the custom params
r.lastInputParamsMutex.Lock()
if r.lastInputParams.PathTxCustomParams == nil {
r.lastInputParams.PathTxCustomParams = make(map[string]*requests.PathTxCustomParams)
@ -196,6 +182,15 @@ func (r *Router) setCustomTxDetails(pathTxIdentity *requests.PathTxIdentity, pat
r.lastInputParams.PathTxCustomParams[pathTxIdentity.TxIdentityKey()] = pathTxCustomParams
r.lastInputParamsMutex.Unlock()
// update the path details
usedNonces := make(map[uint64]uint64)
err = r.evaluateAndUpdatePathDetails(ctx, path, fetchedFees, usedNonces, false, 0)
if err != nil {
return err
}
// inform the client about the changes
sendRouterResult(pathTxIdentity.RouterInputParamsUuid, r.activeRoutes, nil)
return nil
}
@ -207,14 +202,14 @@ func (r *Router) SetFeeMode(ctx context.Context, pathTxIdentity *requests.PathTx
return ErrCustomFeeModeCannotBeSetThisWay
}
return r.setCustomTxDetails(pathTxIdentity, &requests.PathTxCustomParams{GasFeeMode: feeMode})
return r.setCustomTxDetails(ctx, pathTxIdentity, &requests.PathTxCustomParams{GasFeeMode: feeMode})
}
func (r *Router) SetCustomTxDetails(ctx context.Context, pathTxIdentity *requests.PathTxIdentity, pathTxCustomParams *requests.PathTxCustomParams) error {
if pathTxCustomParams != nil && pathTxCustomParams.GasFeeMode != fees.GasFeeCustom {
return ErrOnlyCustomFeeModeCanBeSetThisWay
}
return r.setCustomTxDetails(pathTxIdentity, pathTxCustomParams)
return r.setCustomTxDetails(ctx, pathTxIdentity, pathTxCustomParams)
}
func newSuggestedRoutes(

View File

@ -195,7 +195,7 @@ func (r *Router) applyCustomFields(ctx context.Context, path *routes.Path, fetch
if r.lastInputParams.PathTxCustomParams == nil || len(r.lastInputParams.PathTxCustomParams) == 0 {
// if no custom params are provided, use the initial fee mode
maxFeesPerGas, priorityFee, err := fetchedFees.FeeFor(r.lastInputParams.GasFeeMode)
maxFeesPerGas, priorityFee, estimatedTime, err := fetchedFees.FeeFor(r.lastInputParams.GasFeeMode)
if err != nil {
return err
}
@ -204,31 +204,35 @@ func (r *Router) applyCustomFields(ctx context.Context, path *routes.Path, fetch
path.ApprovalMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.ApprovalPriorityFee = (*hexutil.Big)(priorityFee)
path.ApprovalEstimatedTime = estimatedTime
}
path.TxGasFeeMode = r.lastInputParams.GasFeeMode
path.TxMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.TxPriorityFee = (*hexutil.Big)(priorityFee)
path.TxEstimatedTime = estimatedTime
} else {
if path.ApprovalRequired {
approvalTxIdentityKey := path.TxIdentityKey(true)
if approvalTxCustomParams, ok := r.lastInputParams.PathTxCustomParams[approvalTxIdentityKey]; ok {
path.ApprovalGasFeeMode = approvalTxCustomParams.GasFeeMode
if approvalTxCustomParams.GasFeeMode != fees.GasFeeCustom {
maxFeesPerGas, priorityFee, err := fetchedFees.FeeFor(approvalTxCustomParams.GasFeeMode)
maxFeesPerGas, priorityFee, estimatedTime, err := fetchedFees.FeeFor(approvalTxCustomParams.GasFeeMode)
if err != nil {
return err
}
path.ApprovalMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.ApprovalPriorityFee = (*hexutil.Big)(priorityFee)
path.ApprovalEstimatedTime = estimatedTime
} else {
path.ApprovalTxNonce = (*hexutil.Uint64)(&approvalTxCustomParams.Nonce)
path.ApprovalGasAmount = approvalTxCustomParams.GasAmount
path.ApprovalMaxFeesPerGas = approvalTxCustomParams.MaxFeesPerGas
path.ApprovalBaseFee = (*hexutil.Big)(new(big.Int).Sub(approvalTxCustomParams.MaxFeesPerGas.ToInt(), approvalTxCustomParams.PriorityFee.ToInt()))
path.ApprovalPriorityFee = approvalTxCustomParams.PriorityFee
path.ApprovalEstimatedTime = r.feesManager.TransactionEstimatedTimeV2(ctx, path.FromChain.ChainID, path.ApprovalMaxFeesPerGas.ToInt(), path.ApprovalPriorityFee.ToInt())
}
}
}
@ -237,19 +241,21 @@ func (r *Router) applyCustomFields(ctx context.Context, path *routes.Path, fetch
if txCustomParams, ok := r.lastInputParams.PathTxCustomParams[txIdentityKey]; ok {
path.TxGasFeeMode = txCustomParams.GasFeeMode
if txCustomParams.GasFeeMode != fees.GasFeeCustom {
maxFeesPerGas, priorityFee, err := fetchedFees.FeeFor(txCustomParams.GasFeeMode)
maxFeesPerGas, priorityFee, estimatedTime, err := fetchedFees.FeeFor(txCustomParams.GasFeeMode)
if err != nil {
return err
}
path.TxMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.TxPriorityFee = (*hexutil.Big)(priorityFee)
path.TxEstimatedTime = estimatedTime
} else {
path.TxNonce = (*hexutil.Uint64)(&txCustomParams.Nonce)
path.TxGasAmount = txCustomParams.GasAmount
path.TxMaxFeesPerGas = txCustomParams.MaxFeesPerGas
path.TxBaseFee = (*hexutil.Big)(new(big.Int).Sub(txCustomParams.MaxFeesPerGas.ToInt(), txCustomParams.PriorityFee.ToInt()))
path.TxPriorityFee = txCustomParams.PriorityFee
path.TxEstimatedTime = r.feesManager.TransactionEstimatedTimeV2(ctx, path.FromChain.ChainID, path.TxMaxFeesPerGas.ToInt(), path.TxPriorityFee.ToInt())
}
}
}
@ -336,15 +342,6 @@ func (r *Router) evaluateAndUpdatePathDetails(ctx context.Context, path *routes.
path.RequiredTokenBalance = requiredTokenBalance
path.RequiredNativeBalance = requiredNativeBalance
path.TxEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.TxMaxFeesPerGas.ToInt())
if path.ApprovalRequired {
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
}

View File

@ -42,7 +42,7 @@ type Path struct {
TxGasAmount uint64 // Gas used for the transaction
TxBonderFees *hexutil.Big // Bonder fees for the transaction - used for Hop bridge (in selected token)
TxTokenFees *hexutil.Big // Token fees for the transaction - used for bridges (represent the difference between the amount in and the amount out, in selected token)
TxEstimatedTime fees.TransactionEstimation
TxEstimatedTime uint // Estimated time for the transaction in seconds
TxFee *hexutil.Big // fee for the transaction (includes tx fee only, doesn't include approval fees, l1 fees, l1 approval fees, token fees or bonders fees, in ETH WEI)
TxL1Fee *hexutil.Big // L1 fee for the transaction - used for for transactions placed on L2 chains (in ETH WEI)
@ -57,7 +57,7 @@ type Path struct {
ApprovalBaseFee *hexutil.Big // Base fee for the approval transaction (in ETH WEI)
ApprovalPriorityFee *hexutil.Big // Priority fee for the approval transaction (in ETH WEI)
ApprovalGasAmount uint64 // Gas used for the approval transaction
ApprovalEstimatedTime fees.TransactionEstimation
ApprovalEstimatedTime uint // Estimated time for the approval transaction in seconds
ApprovalFee *hexutil.Big // Total fee for the approval transaction (includes approval tx fees only, doesn't include approval l1 fees, in ETH WEI)
ApprovalL1Fee *hexutil.Big // L1 fee for the approval transaction - used for for transactions placed on L2 chains (in ETH WEI)

View File

@ -36,7 +36,7 @@ func TestCopyPath(t *testing.T) {
TxGasAmount: 100,
TxBonderFees: (*hexutil.Big)(big.NewInt(100)),
TxTokenFees: (*hexutil.Big)(big.NewInt(100)),
TxEstimatedTime: fees.TransactionEstimation(100),
TxEstimatedTime: 100,
TxFee: (*hexutil.Big)(big.NewInt(100)),
TxL1Fee: (*hexutil.Big)(big.NewInt(100)),
ApprovalRequired: true,
@ -46,7 +46,7 @@ func TestCopyPath(t *testing.T) {
ApprovalBaseFee: (*hexutil.Big)(big.NewInt(100)),
ApprovalPriorityFee: (*hexutil.Big)(big.NewInt(100)),
ApprovalGasAmount: 100,
ApprovalEstimatedTime: fees.TransactionEstimation(100),
ApprovalEstimatedTime: 100,
ApprovalFee: (*hexutil.Big)(big.NewInt(100)),
ApprovalL1Fee: (*hexutil.Big)(big.NewInt(100)),
TxTotalFee: (*hexutil.Big)(big.NewInt(100)),