feat: estimate time for a transaction (#2744)
This commit is contained in:
parent
f6c9ec7838
commit
35c4001e57
|
@ -318,6 +318,11 @@ func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*Suggeste
|
||||||
return api.s.feesManager.suggestedFees(ctx, chainID)
|
return api.s.feesManager.suggestedFees(ctx, chainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas float64) (TransactionEstimation, error) {
|
||||||
|
log.Debug("call to getTransactionEstimatedTime")
|
||||||
|
return api.s.feesManager.transactionEstimatedTime(ctx, chainID, maxFeePerGas), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (api *API) GetSuggestedRoutes(ctx context.Context, account common.Address, amount float64, tokenSymbol string) (*SuggestedRoutes, error) {
|
func (api *API) GetSuggestedRoutes(ctx context.Context, account common.Address, amount float64, tokenSymbol string) (*SuggestedRoutes, error) {
|
||||||
log.Debug("call to GetSuggestedRoutes")
|
log.Debug("call to GetSuggestedRoutes")
|
||||||
return api.router.suggestedRoutes(ctx, account, amount, tokenSymbol)
|
return api.router.suggestedRoutes(ctx, account, amount, tokenSymbol)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package wallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -21,6 +22,18 @@ type SuggestedFees struct {
|
||||||
EIP1559Enabled bool `json:"eip1559Enabled"`
|
EIP1559Enabled bool `json:"eip1559Enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inclusionThreshold = 0.95
|
||||||
|
|
||||||
|
type TransactionEstimation int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unknown TransactionEstimation = iota
|
||||||
|
LessThanOneMinute
|
||||||
|
LessThanThreeMinutes
|
||||||
|
LessThanFiveMinutes
|
||||||
|
MoreThanFiveMinutes
|
||||||
|
)
|
||||||
|
|
||||||
type FeeHistory struct {
|
type FeeHistory struct {
|
||||||
BaseFeePerGas []string `json:"baseFeePerGas"`
|
BaseFeePerGas []string `json:"baseFeePerGas"`
|
||||||
}
|
}
|
||||||
|
@ -39,6 +52,12 @@ func weiToGwei(val *big.Int) *big.Float {
|
||||||
return result.Quo(result, new(big.Float).SetInt(unit))
|
return result.Quo(result, new(big.Float).SetInt(unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gweiToWei(val float64) *big.Int {
|
||||||
|
res := new(big.Int)
|
||||||
|
res.SetUint64(uint64(val * 1000000000))
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
backend, err := f.RPCClient.EthClient(chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -70,21 +89,11 @@ func (f *FeeManager) suggestedFees(ctx context.Context, chainID uint64) (*Sugges
|
||||||
config := params.MainnetChainConfig
|
config := params.MainnetChainConfig
|
||||||
baseFee := misc.CalcBaseFee(config, block.Header())
|
baseFee := misc.CalcBaseFee(config, block.Header())
|
||||||
|
|
||||||
var feeHistory FeeHistory
|
fees, err := f.getFeeHistorySorted(chainID)
|
||||||
|
|
||||||
err = f.RPCClient.Call(&feeHistory, chainID, "eth_feeHistory", 101, "latest", nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 })
|
|
||||||
|
|
||||||
perc10 := fees[int64(0.1*float64(len(fees)))-1]
|
perc10 := fees[int64(0.1*float64(len(fees)))-1]
|
||||||
perc20 := fees[int64(0.2*float64(len(fees)))-1]
|
perc20 := fees[int64(0.2*float64(len(fees)))-1]
|
||||||
|
|
||||||
|
@ -115,3 +124,96 @@ func (f *FeeManager) suggestedFees(ctx context.Context, chainID uint64) (*Sugges
|
||||||
EIP1559Enabled: true,
|
EIP1559Enabled: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FeeManager) transactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas float64) TransactionEstimation {
|
||||||
|
fees, err := f.getFeeHistorySorted(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
maxFeePerGasWei := gweiToWei(maxFeePerGas)
|
||||||
|
// 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(maxFeePerGasWei) == 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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue