Skip to content

Commit

Permalink
feat(wallet)_: added suggested min and max priority fee and current b…
Browse files Browse the repository at this point in the history
…ase 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`
  • Loading branch information
saledjenic committed Jan 17, 2025
1 parent 67134d9 commit 90f4740
Show file tree
Hide file tree
Showing 10 changed files with 567 additions and 181 deletions.
11 changes: 7 additions & 4 deletions services/connector/commands/send_transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package commands

import (
"encoding/json"
"errors"
"math/big"
"testing"
"time"
Expand All @@ -13,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/eth-node/types"
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/signal"
)
Expand Down Expand Up @@ -82,9 +82,10 @@ func TestSendTransactionWithSignalTimout(t *testing.T) {
WalletResponseMaxInterval = 1 * time.Millisecond

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)
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)
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)

Expand Down Expand Up @@ -127,9 +128,10 @@ func TestSendTransactionWithSignalAccepted(t *testing.T) {
t.Cleanup(signal.ResetMobileSignalHandler)

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)
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)
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)

Expand Down Expand Up @@ -169,9 +171,10 @@ func TestSendTransactionWithSignalRejected(t *testing.T) {
t.Cleanup(signal.ResetMobileSignalHandler)

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)
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)
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)

Expand Down
5 changes: 3 additions & 2 deletions services/connector/connector_flows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package connector

import (
"encoding/json"
"errors"
"fmt"
"math/big"
"testing"
Expand All @@ -15,6 +14,7 @@ import (
"github.com/status-im/status-go/services/connector/chainutils"
"github.com/status-im/status-go/services/connector/commands"
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"
)

Expand Down Expand Up @@ -104,9 +104,10 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {

// Send transaction
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)
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)
mockedChainClient.EXPECT().PendingNonceAt(state.ctx, common.Address(accountAddress)).Times(1).Return(uint64(10), nil)

Expand Down
2 changes: 2 additions & 0 deletions services/wallet/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ func (api *API) FetchTokenDetails(ctx context.Context, symbols []string) (map[st
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) {
logutils.ZapLogger().Debug("call to GetSuggestedFees")
return api.s.router.GetFeesManager().SuggestedFeesGwei(ctx, chainID)
Expand All @@ -448,6 +449,7 @@ func (api *API) GetEstimatedLatestBlockNumber(ctx context.Context, chainID uint6
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) {
logutils.ZapLogger().Debug("call to getTransactionEstimatedTime")
return api.s.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil
Expand Down
115 changes: 115 additions & 0 deletions services/wallet/router/fees/estimated_time.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 90f4740

Please sign in to comment.