feat(wallet)!: allowing client to set custom values for fees, nonce, gas

Removed properties from `Path` type:
- `MaxFeesPerGas`, based on the sending flow progress appropriate new properties (`TxMaxFeesPerGas`, `ApprovalMaxFeesPerGas`) should be used

Added new properties to `Path` type:
- `RouterInputParamsUuid`, used to identify from which router input params this path was created
- `TxNonce`, used to set nonce for the tx
- `TxMaxFeesPerGas`, used to set max fees per gas for the tx
- `TxEstimatedTime`, used to estimate time for executing the tx
- `ApprovalTxNonce`, used to set nonce for the approval tx
- `ApprovalTxMaxFeesPerGas`, used to set max fees per gas for the approval tx
- `ApprovalTxEstimatedTime`, used to estimate time for executing the approval tx

New request types added:
- `PathTxCustomParams`, used to pass tx custom params from the client
- `PathTxIdentity`, used to uniquely identify path (tx) to which the custom params need to be applied

New endpoints added:
- `SetFeeMode` used to set fee mode (`GasFeeLow`, `GasFeeMedium` or `GasFeeHigh`)
- `SetCustomTxDetails` used to set custom fee mode (`SetCustomTxDetails`), if this mode is set, client needs to provide:
  - Max fees per gas
  - Max priority fee
  - Nonce
  - Gas amount
This commit is contained in:
Sale Djenic 2024-11-26 12:29:00 +01:00 committed by saledjenic
parent 4106224acb
commit b91c5fd29c
16 changed files with 409 additions and 122 deletions

View File

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

View File

@ -486,6 +486,21 @@ func (api *API) StopSuggestedRoutesCalculation(ctx context.Context) {
api.s.router.StopSuggestedRoutesCalculation()
}
// SetFeeMode sets the fee mode for the provided path it should be used for setting predefined fee modes `GasFeeLow`, `GasFeeMedium` and `GasFeeHigh`
// in case of setting custom fee use `SetCustomTxDetails` function
func (api *API) SetFeeMode(ctx context.Context, pathTxIdentity *requests.PathTxIdentity, feeMode fees.GasFeeMode) error {
logutils.ZapLogger().Debug("call to SetFeeMode")
return api.s.router.SetFeeMode(ctx, pathTxIdentity, feeMode)
}
// SetCustomTxDetails sets custom tx details for the provided path, in case of setting predefined fee modes use `SetFeeMode` function
func (api *API) SetCustomTxDetails(ctx context.Context, pathTxIdentity *requests.PathTxIdentity, pathTxCustomParams *requests.PathTxCustomParams) error {
logutils.ZapLogger().Debug("call to SetCustomTxDetails")
return api.s.router.SetCustomTxDetails(ctx, pathTxIdentity, pathTxCustomParams)
}
// Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function)
func (api *API) GetDerivedAddresses(ctx context.Context, password string, derivedFrom string, paths []string) ([]*DerivedAddress, error) {
info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password)

View File

@ -55,6 +55,9 @@ type RouteInputParams struct {
PublicKey string `json:"publicKey"`
PackID *hexutil.Big `json:"packID"`
// Used internally
PathTxCustomParams map[string]*PathTxCustomParams `json:"-"`
// TODO: Remove two fields below once we implement a better solution for tests
// Currently used for tests only
TestsMode bool

View File

@ -0,0 +1,50 @@
package requests
import (
"fmt"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/errors"
"github.com/status-im/status-go/services/wallet/router/fees"
)
var (
ErrMaxFeesPerGasRequired = &errors.ErrorResponse{Code: errors.ErrorCode("WRC-001"), Details: "maxFeesPerGas is required"}
ErrPriorityFeeRequired = &errors.ErrorResponse{Code: errors.ErrorCode("WRC-002"), Details: "priorityFee is required"}
)
type PathTxCustomParams struct {
GasFeeMode fees.GasFeeMode `json:"gasFeeMode" validate:"required"`
Nonce uint64 `json:"nonce"`
GasAmount uint64 `json:"gasAmount"`
MaxFeesPerGas *hexutil.Big `json:"maxFeesPerGas"`
PriorityFee *hexutil.Big `json:"priorityFee"`
}
type PathTxIdentity struct {
RouterInputParamsUuid string `json:"routerInputParamsUuid" validate:"required"`
PathName string `json:"pathName" validate:"required"`
ChainID uint64 `json:"chainID" validate:"required"`
IsApprovalTx bool `json:"isApprovalTx"`
}
func (p *PathTxIdentity) PathIdentity() string {
return fmt.Sprintf("%s-%s-%d", p.RouterInputParamsUuid, p.PathName, p.ChainID)
}
func (p *PathTxIdentity) TxIdentityKey() string {
return fmt.Sprintf("%s-%v", p.PathIdentity(), p.IsApprovalTx)
}
func (p *PathTxCustomParams) Validate() error {
if p.GasFeeMode != fees.GasFeeCustom {
return nil
}
if p.MaxFeesPerGas == nil {
return ErrMaxFeesPerGasRequired
}
if p.PriorityFee == nil {
return ErrPriorityFeeRequired
}
return nil
}

View File

@ -14,4 +14,10 @@ var (
ErrCannotCheckBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-006"), Details: "cannot check balance"}
ErrLowAmountInForHopBridge = &errors.ErrorResponse{Code: errors.ErrorCode("WR-007"), Details: "bonder fee greater than estimated received, a higher amount is needed to cover fees"}
ErrNoPositiveBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-008"), Details: "no positive balance"}
ErrCustomFeeModeCannotBeSetThisWay = &errors.ErrorResponse{Code: errors.ErrorCode("WR-009"), Details: "custom fee mode cannot be set this way"}
ErrOnlyCustomFeeModeCanBeSetThisWay = &errors.ErrorResponse{Code: errors.ErrorCode("WR-010"), Details: "only custom fee mode can be set this way"}
ErrTxIdentityNotProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WR-011"), Details: "transaction identity not provided"}
ErrTxCustomParamsNotProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WR-012"), Details: "transaction custom params not provided"}
ErrCannotCustomizeIfNoRoute = &errors.ErrorResponse{Code: errors.ErrorCode("WR-013"), Details: "cannot customize params if no route"}
ErrCannotFindPathForProvidedIdentity = &errors.ErrorResponse{Code: errors.ErrorCode("WR-014"), Details: "cannot find path for provided identity"}
)

View File

@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc"
"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/rpc"
"github.com/status-im/status-go/rpc/chain"
"github.com/status-im/status-go/services/wallet/common"
@ -23,6 +24,11 @@ const (
GasFeeLow GasFeeMode = iota
GasFeeMedium
GasFeeHigh
GasFeeCustom
)
var (
ErrCustomFeeModeNotAvailableInSuggestedFees = &errors.ErrorResponse{Code: errors.ErrorCode("WRF-001"), Details: "custom fee mode is not available in suggested fees"}
)
type MaxFeesLevels struct {
@ -50,23 +56,28 @@ type SuggestedFeesGwei struct {
MaxFeePerGasLow *big.Float `json:"maxFeePerGasLow"`
MaxFeePerGasMedium *big.Float `json:"maxFeePerGasMedium"`
MaxFeePerGasHigh *big.Float `json:"maxFeePerGasHigh"`
MaxFeePerGasCustom *big.Float `json:"maxFeePerGasCustom"`
L1GasFee *big.Float `json:"l1GasFee,omitempty"`
EIP1559Enabled bool `json:"eip1559Enabled"`
}
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) *big.Int {
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) (*big.Int, error) {
if mode == GasFeeCustom {
return nil, ErrCustomFeeModeNotAvailableInSuggestedFees
}
if mode == GasFeeLow {
return m.Low.ToInt()
return m.Low.ToInt(), nil
}
if mode == GasFeeHigh {
return m.High.ToInt()
return m.High.ToInt(), nil
}
return m.Medium.ToInt()
return m.Medium.ToInt(), nil
}
func (s *SuggestedFees) FeeFor(mode GasFeeMode) *big.Int {
func (s *SuggestedFees) FeeFor(mode GasFeeMode) (*big.Int, error) {
return s.MaxFeesLevels.FeeFor(mode)
}

View File

@ -296,7 +296,7 @@ func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, sig
var nonce uint64
if lastUsedNonce < 0 {
nonce, err = h.transactor.NextNonce(h.contractMaker.RPCClient, fromChain.ChainID, sendArgs.HopTx.From)
nonce, err = h.transactor.NextNonce(context.Background(), h.contractMaker.RPCClient, fromChain.ChainID, sendArgs.HopTx.From)
if err != nil {
return tx, createBridgeHopErrorResponse(err)
}
@ -363,7 +363,7 @@ func (h *HopBridgeProcessor) sendOrBuildV2(sendArgs *wallettypes.SendTxArgs, sig
var nonce uint64
if lastUsedNonce < 0 {
nonce, err = h.transactor.NextNonce(h.contractMaker.RPCClient, fromChain.ChainID, sendArgs.From)
nonce, err = h.transactor.NextNonce(context.Background(), h.contractMaker.RPCClient, fromChain.ChainID, sendArgs.From)
if err != nil {
return tx, createBridgeHopErrorResponse(err)
}

View File

@ -125,7 +125,7 @@ func (s *ERC1155Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signe
var nonce uint64
if lastUsedNonce < 0 {
nonce, err = s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC1155TransferTx.From)
nonce, err = s.transactor.NextNonce(context.Background(), s.rpcClient, sendArgs.ChainID, sendArgs.ERC1155TransferTx.From)
if err != nil {
return tx, createERC1155ErrorResponse(err)
}

View File

@ -182,7 +182,7 @@ func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signer
var nonce uint64
if lastUsedNonce < 0 {
nonce, err = s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC721TransferTx.From)
nonce, err = s.transactor.NextNonce(context.Background(), s.rpcClient, sendArgs.ChainID, sendArgs.ERC721TransferTx.From)
if err != nil {
return tx, createERC721ErrorResponse(err)
}

View File

@ -64,6 +64,7 @@ type SuggestedRoutes struct {
type Router struct {
rpcClient *rpc.Client
transactor *transactions.Transactor
tokenManager *token.Manager
marketManager *market.Manager
collectiblesService *collectibles.Service
@ -92,6 +93,7 @@ func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, token
return &Router{
rpcClient: rpcClient,
transactor: transactor,
tokenManager: tokenManager,
marketManager: marketManager,
collectiblesService: collectibles,
@ -140,6 +142,57 @@ func (r *Router) SetTestBalanceMap(balanceMap map[string]*big.Int) {
}
}
func (r *Router) setCustomTxDetails(pathTxIdentity *requests.PathTxIdentity, pathTxCustomParams *requests.PathTxCustomParams) error {
if pathTxIdentity == nil {
return ErrTxIdentityNotProvided
}
if pathTxCustomParams == nil {
return ErrTxCustomParamsNotProvided
}
err := pathTxCustomParams.Validate()
if err != nil {
return err
}
r.activeRoutesMutex.Lock()
defer r.activeRoutesMutex.Unlock()
if r.activeRoutes == nil || len(r.activeRoutes.Best) == 0 {
return ErrCannotCustomizeIfNoRoute
}
for _, path := range r.activeRoutes.Best {
if path.PathIdentity() != pathTxIdentity.PathIdentity() {
continue
}
r.lastInputParamsMutex.Lock()
if r.lastInputParams.PathTxCustomParams == nil {
r.lastInputParams.PathTxCustomParams = make(map[string]*requests.PathTxCustomParams)
}
r.lastInputParams.PathTxCustomParams[pathTxIdentity.TxIdentityKey()] = pathTxCustomParams
r.lastInputParamsMutex.Unlock()
return nil
}
return ErrCannotFindPathForProvidedIdentity
}
func (r *Router) SetFeeMode(ctx context.Context, pathTxIdentity *requests.PathTxIdentity, feeMode fees.GasFeeMode) error {
if feeMode == fees.GasFeeCustom {
return ErrCustomFeeModeCannotBeSetThisWay
}
return r.setCustomTxDetails(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)
}
func newSuggestedRoutes(
input *requests.RouteInputParams,
candidates routes.Route,
@ -585,7 +638,11 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
var (
testsMode = input.TestsMode && input.TestParams != nil
group = async.NewAtomicGroup(ctx)
mu sync.Mutex
candidatesMu sync.Mutex
usedNonces = make(map[uint64]uint64)
usedNoncesMu sync.Mutex
)
crossChainAmountOptions, err := r.findOptionsForSendingAmount(input, selectedFromChains)
@ -601,8 +658,8 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
zap.Uint64("toChainId", toChainID),
zap.Stringer("amount", amount),
zap.Error(err))
mu.Lock()
defer mu.Unlock()
candidatesMu.Lock()
defer candidatesMu.Unlock()
processorErrors = append(processorErrors, &ProcessorError{
ProcessorName: processorName,
Error: err,
@ -610,8 +667,8 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
}
appendPathFn := func(path *routes.Path) {
mu.Lock()
defer mu.Unlock()
candidatesMu.Lock()
defer candidatesMu.Unlock()
candidates = append(candidates, path)
}
@ -765,14 +822,8 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
continue
}
maxFeesPerGas := fetchedFees.FeeFor(input.GasFeeMode)
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas)
if approvalRequired && estimatedTime < fees.MoreThanFiveMinutes {
estimatedTime += 1
}
path := &routes.Path{
RouterInputParamsUuid: input.Uuid,
ProcessorName: pProcessor.Name(),
FromChain: network,
ToChain: dest,
@ -792,12 +843,12 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
ApprovalContractAddress: &approvalContractAddress,
ApprovalGasAmount: approvalGasLimit,
EstimatedTime: estimatedTime,
SubtractFees: amountOption.subtractFees,
}
err = r.cacluateFees(ctx, path, fetchedFees, processorInputParams.TestsMode, processorInputParams.TestApprovalL1Fee)
usedNoncesMu.Lock()
err = r.evaluateAndUpdatePathDetails(ctx, path, fetchedFees, usedNonces, processorInputParams.TestsMode, processorInputParams.TestApprovalL1Fee)
usedNoncesMu.Unlock()
if err != nil {
appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err)
continue

View File

@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/contracts"
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc/chain"
"github.com/status-im/status-go/services/wallet/bigint"
@ -21,7 +22,7 @@ import (
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/router/fees"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
routs "github.com/status-im/status-go/services/wallet/router/routes"
"github.com/status-im/status-go/services/wallet/router/routes"
"github.com/status-im/status-go/services/wallet/router/sendtype"
"github.com/status-im/status-go/services/wallet/token"
)
@ -156,11 +157,110 @@ func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.To
return r.tokenManager.GetBalance(ctx, client, account, token.Address)
}
func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees *fees.SuggestedFees, testsMode bool, testApprovalL1Fee uint64) (err error) {
func (r *Router) resolveNonceForPath(ctx context.Context, path *routes.Path, address common.Address, usedNonces map[uint64]uint64) error {
var nextNonce uint64
if nonce, ok := usedNonces[path.FromChain.ChainID]; ok {
nextNonce = nonce + 1
} else {
nonce, err := r.transactor.NextNonce(ctx, r.rpcClient, path.FromChain.ChainID, types.Address(address))
if err != nil {
return err
}
nextNonce = nonce
}
usedNonces[path.FromChain.ChainID] = nextNonce
if !path.ApprovalRequired {
path.TxNonce = (*hexutil.Uint64)(&nextNonce)
} else {
path.ApprovalTxNonce = (*hexutil.Uint64)(&nextNonce)
txNonce := nextNonce + 1
path.TxNonce = (*hexutil.Uint64)(&txNonce)
usedNonces[path.FromChain.ChainID] = txNonce
}
return nil
}
func (r *Router) applyCustomFields(ctx context.Context, path *routes.Path, fetchedFees *fees.SuggestedFees, usedNonces map[uint64]uint64) error {
r.lastInputParamsMutex.Lock()
defer r.lastInputParamsMutex.Unlock()
// 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)
if err != nil {
return err
}
if r.lastInputParams.PathTxCustomParams == nil || len(r.lastInputParams.PathTxCustomParams) == 0 {
// if no custom params are provided, use the initial fee mode
maxFeesPerGas, err := fetchedFees.FeeFor(r.lastInputParams.GasFeeMode)
if err != nil {
return err
}
if path.ApprovalRequired {
path.ApprovalMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.ApprovalPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
}
path.TxMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.TxPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
} else {
if path.ApprovalRequired {
approvalTxIdentityKey := path.TxIdentityKey(true)
if approvalTxCustomParams, ok := r.lastInputParams.PathTxCustomParams[approvalTxIdentityKey]; ok {
if approvalTxCustomParams.GasFeeMode != fees.GasFeeCustom {
maxFeesPerGas, 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)(fetchedFees.MaxPriorityFeePerGas)
} 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
}
}
}
txIdentityKey := path.TxIdentityKey(false)
if txCustomParams, ok := r.lastInputParams.PathTxCustomParams[txIdentityKey]; ok {
if txCustomParams.GasFeeMode != fees.GasFeeCustom {
maxFeesPerGas, 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)(fetchedFees.MaxPriorityFeePerGas)
} 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
}
}
}
return nil
}
func (r *Router) evaluateAndUpdatePathDetails(ctx context.Context, path *routes.Path, fetchedFees *fees.SuggestedFees,
usedNonces map[uint64]uint64, testsMode bool, testApprovalL1Fee uint64) (err error) {
var (
l1ApprovalFee uint64
)
if testsMode {
usedNonces[path.FromChain.ChainID] = usedNonces[path.FromChain.ChainID] + 1
}
if path.ApprovalRequired {
if testsMode {
l1ApprovalFee = testApprovalL1Fee
@ -172,6 +272,11 @@ func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees
}
}
err = r.applyCustomFields(ctx, path, fetchedFees, usedNonces)
if err != nil {
return
}
// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees
var l1FeeWei uint64 = 0
// if input.SendType.needL1Fee() {
@ -183,14 +288,9 @@ func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
// }
r.lastInputParamsMutex.Lock()
gasFeeMode := r.lastInputParams.GasFeeMode
r.lastInputParamsMutex.Unlock()
maxFeesPerGas := fetchedFees.FeeFor(gasFeeMode)
// calculate ETH fees
ethTotalFees := big.NewInt(0)
txFeeInWei := new(big.Int).Mul(maxFeesPerGas, big.NewInt(int64(path.TxGasAmount)))
txFeeInWei := new(big.Int).Mul(path.TxMaxFeesPerGas.ToInt(), big.NewInt(int64(path.TxGasAmount)))
ethTotalFees.Add(ethTotalFees, txFeeInWei)
txL1FeeInWei := big.NewInt(0)
@ -202,7 +302,7 @@ func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees
approvalFeeInWei := big.NewInt(0)
approvalL1FeeInWei := big.NewInt(0)
if path.ApprovalRequired {
approvalFeeInWei.Mul(maxFeesPerGas, big.NewInt(int64(path.ApprovalGasAmount)))
approvalFeeInWei.Mul(path.ApprovalMaxFeesPerGas.ToInt(), big.NewInt(int64(path.ApprovalGasAmount)))
ethTotalFees.Add(ethTotalFees, approvalFeeInWei)
if l1ApprovalFee > 0 {
@ -227,17 +327,10 @@ func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees
// set the values
path.SuggestedLevelsForMaxFeesPerGas = fetchedFees.MaxFeesLevels
path.MaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.TxPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
path.TxFee = (*hexutil.Big)(txFeeInWei)
path.TxL1Fee = (*hexutil.Big)(txL1FeeInWei)
path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.ApprovalPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
path.ApprovalFee = (*hexutil.Big)(approvalFeeInWei)
path.ApprovalL1Fee = (*hexutil.Big)(approvalL1FeeInWei)
@ -246,7 +339,12 @@ func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees
path.RequiredTokenBalance = requiredTokenBalance
path.RequiredNativeBalance = requiredNativeBalance
return nil
path.TxEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.TxMaxFeesPerGas.ToInt())
if path.ApprovalRequired {
path.ApprovalEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.ApprovalMaxFeesPerGas.ToInt())
}
return
}
func findToken(sendType sendtype.SendType, tokenManager *token.Manager, collectibles *collectibles.Service, account common.Address, network *params.Network, tokenID string) *token.Token {

View File

@ -78,18 +78,21 @@ func (r *Router) subscribeForUdates(chainID uint64) error {
blockNumber, err := ethClient.BlockNumber(ctx)
if err != nil {
logutils.ZapLogger().Error("Failed to get block number", zap.Error(err))
r.sendUpdatesError(err)
continue
}
val, ok := r.clientsForUpdatesPerChains.Load(chainID)
if !ok {
logutils.ZapLogger().Error("Failed to get fetchingLastBlock", zap.Uint64("chain", chainID))
logutils.ZapLogger().Error("Failed to get last block details", zap.Uint64("chain", chainID))
r.sendUpdatesError(err)
continue
}
flbLoaded, ok := val.(fetchingLastBlock)
if !ok {
logutils.ZapLogger().Error("Failed to get fetchingLastBlock", zap.Uint64("chain", chainID))
logutils.ZapLogger().Error("Failed to cast last block details", zap.Uint64("chain", chainID))
r.sendUpdatesError(err)
continue
}
@ -100,28 +103,37 @@ func (r *Router) subscribeForUdates(chainID uint64) error {
fees, err := r.feesManager.SuggestedFees(ctx, chainID)
if err != nil {
logutils.ZapLogger().Error("Failed to get suggested fees", zap.Error(err))
r.sendUpdatesError(err)
continue
}
r.lastInputParamsMutex.Lock()
uuid := r.lastInputParams.Uuid
r.lastInputParamsMutex.Unlock()
r.activeRoutesMutex.Lock()
if r.activeRoutes != nil && r.activeRoutes.Best != nil && len(r.activeRoutes.Best) > 0 {
usedNonces := make(map[uint64]uint64)
for _, path := range r.activeRoutes.Best {
err = r.cacluateFees(ctx, path, fees, false, 0)
err = r.evaluateAndUpdatePathDetails(ctx, path, fees, usedNonces, false, 0)
if err != nil {
break
}
}
if err != nil {
logutils.ZapLogger().Error("Failed to calculate fees", zap.Error(err))
r.activeRoutesMutex.Unlock()
r.sendUpdatesError(err)
continue
}
}
_, err = r.checkBalancesForTheBestRoute(ctx, r.activeRoutes.Best)
sendRouterResult(uuid, r.activeRoutes, err)
if err != nil {
logutils.ZapLogger().Error("Failed to check balances for the best route", zap.Error(err))
r.activeRoutesMutex.Unlock()
r.sendUpdatesError(err)
continue
}
}
r.activeRoutesMutex.Unlock()
r.sendUpdatesError(err)
}
case <-flb.closeCh:
ticker.Stop()
@ -133,6 +145,17 @@ func (r *Router) subscribeForUdates(chainID uint64) error {
return nil
}
func (r *Router) sendUpdatesError(err error) {
r.lastInputParamsMutex.Lock()
uuid := r.lastInputParams.Uuid
r.lastInputParamsMutex.Unlock()
r.activeRoutesMutex.Lock()
defer r.activeRoutesMutex.Unlock()
sendRouterResult(uuid, r.activeRoutes, err)
}
func (r *Router) startTimeoutForUpdates(closeCh chan struct{}, timeout time.Duration) {
dedlineTicker := time.NewTicker(timeout)
go func() {

View File

@ -1,8 +1,8 @@
package routes
import (
"fmt"
"math/big"
"strconv"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@ -12,6 +12,7 @@ import (
)
type Path struct {
RouterInputParamsUuid string
ProcessorName string
FromChain *params.Network // Source chain
ToChain *params.Network // Destination chain
@ -22,13 +23,15 @@ type Path struct {
AmountOut *hexutil.Big // Amount that will be received on the destination chain
SuggestedLevelsForMaxFeesPerGas *fees.MaxFeesLevels // Suggested max fees for the transaction (in ETH WEI)
MaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI)
TxNonce *hexutil.Uint64 // Nonce for the transaction
TxMaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI)
TxBaseFee *hexutil.Big // Base fee for the transaction (in ETH WEI)
TxPriorityFee *hexutil.Big // Priority fee for the transaction (in ETH WEI)
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
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)
@ -36,34 +39,45 @@ type Path struct {
ApprovalRequired bool // Is approval required for the transaction
ApprovalAmountRequired *hexutil.Big // Amount required for the approval transaction
ApprovalContractAddress *common.Address // Address of the contract that needs to be approved
ApprovalTxNonce *hexutil.Uint64 // Nonce for the transaction
ApprovalMaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI)
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
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)
TxTotalFee *hexutil.Big // Total fee for the transaction (includes tx fees, approval fees, l1 fees, l1 approval fees, in ETH WEI)
EstimatedTime fees.TransactionEstimation
RequiredTokenBalance *big.Int // (in selected token)
RequiredNativeBalance *big.Int // (in ETH WEI)
SubtractFees bool
}
func (p *Path) PathIdentity() string {
return fmt.Sprintf("%s-%s-%d", p.RouterInputParamsUuid, p.ProcessorName, p.FromChain.ChainID)
}
func (p *Path) TxIdentityKey(approval bool) string {
return fmt.Sprintf("%s-%v", p.PathIdentity(), approval)
}
func (p *Path) Equal(o *Path) bool {
return p.FromChain.ChainID == o.FromChain.ChainID && p.ToChain.ChainID == o.ToChain.ChainID
}
func (p *Path) Copy() *Path {
newPath := &Path{
RouterInputParamsUuid: p.RouterInputParamsUuid,
ProcessorName: p.ProcessorName,
AmountInLocked: p.AmountInLocked,
TxGasAmount: p.TxGasAmount,
TxEstimatedTime: p.TxEstimatedTime,
ApprovalRequired: p.ApprovalRequired,
ApprovalGasAmount: p.ApprovalGasAmount,
EstimatedTime: p.EstimatedTime,
ApprovalEstimatedTime: p.ApprovalEstimatedTime,
SubtractFees: p.SubtractFees,
}
@ -103,8 +117,13 @@ func (p *Path) Copy() *Path {
}
}
if p.MaxFeesPerGas != nil {
newPath.MaxFeesPerGas = (*hexutil.Big)(big.NewInt(0).Set(p.MaxFeesPerGas.ToInt()))
if p.TxNonce != nil {
txNonce := *p.TxNonce
newPath.TxNonce = &txNonce
}
if p.TxMaxFeesPerGas != nil {
newPath.TxMaxFeesPerGas = (*hexutil.Big)(big.NewInt(0).Set(p.TxMaxFeesPerGas.ToInt()))
}
if p.TxBaseFee != nil {
@ -140,6 +159,15 @@ func (p *Path) Copy() *Path {
newPath.ApprovalContractAddress = &addr
}
if p.ApprovalTxNonce != nil {
approvalTxNonce := *p.ApprovalTxNonce
newPath.ApprovalTxNonce = &approvalTxNonce
}
if p.ApprovalMaxFeesPerGas != nil {
newPath.ApprovalMaxFeesPerGas = (*hexutil.Big)(big.NewInt(0).Set(p.ApprovalMaxFeesPerGas.ToInt()))
}
if p.ApprovalBaseFee != nil {
newPath.ApprovalBaseFee = (*hexutil.Big)(big.NewInt(0).Set(p.ApprovalBaseFee.ToInt()))
}
@ -170,9 +198,3 @@ func (p *Path) Copy() *Path {
return newPath
}
// ID that uniquely identifies a path in a given route
func (p *Path) ID() string {
// A route will contain at most a single path from a given processor for a given origin chain
return p.ProcessorName + "-" + strconv.Itoa(int(p.FromChain.ChainID))
}

View File

@ -30,24 +30,26 @@ func TestCopyPath(t *testing.T) {
Medium: (*hexutil.Big)(big.NewInt(200)),
High: (*hexutil.Big)(big.NewInt(300)),
},
MaxFeesPerGas: (*hexutil.Big)(big.NewInt(100)),
TxMaxFeesPerGas: (*hexutil.Big)(big.NewInt(100)),
TxBaseFee: (*hexutil.Big)(big.NewInt(100)),
TxPriorityFee: (*hexutil.Big)(big.NewInt(100)),
TxGasAmount: 100,
TxBonderFees: (*hexutil.Big)(big.NewInt(100)),
TxTokenFees: (*hexutil.Big)(big.NewInt(100)),
TxEstimatedTime: fees.TransactionEstimation(100),
TxFee: (*hexutil.Big)(big.NewInt(100)),
TxL1Fee: (*hexutil.Big)(big.NewInt(100)),
ApprovalRequired: true,
ApprovalAmountRequired: (*hexutil.Big)(big.NewInt(100)),
ApprovalContractAddress: &addr,
ApprovalMaxFeesPerGas: (*hexutil.Big)(big.NewInt(100)),
ApprovalBaseFee: (*hexutil.Big)(big.NewInt(100)),
ApprovalPriorityFee: (*hexutil.Big)(big.NewInt(100)),
ApprovalGasAmount: 100,
ApprovalEstimatedTime: fees.TransactionEstimation(100),
ApprovalFee: (*hexutil.Big)(big.NewInt(100)),
ApprovalL1Fee: (*hexutil.Big)(big.NewInt(100)),
TxTotalFee: (*hexutil.Big)(big.NewInt(100)),
EstimatedTime: fees.TransactionEstimation(100),
RequiredTokenBalance: big.NewInt(100),
RequiredNativeBalance: big.NewInt(100),
SubtractFees: true,

View File

@ -65,7 +65,7 @@ func (tm *TransactionManager) TxPlacedForPath(pathProcessorName string) bool {
func (tm *TransactionManager) getOrInitDetailsForPath(path *routes.Path) *wallettypes.RouterTransactionDetails {
for _, desc := range tm.routerTransactions {
if desc.RouterPath.ID() == path.ID() {
if desc.RouterPath.PathIdentity() == path.PathIdentity() {
return desc
}
}
@ -99,8 +99,9 @@ func buildApprovalTxForPath(transactor transactions.TransactorIface, path *route
To: &addrTo,
Value: (*hexutil.Big)(big.NewInt(0)),
Data: data,
Nonce: path.ApprovalTxNonce,
Gas: (*hexutil.Uint64)(&path.ApprovalGasAmount),
MaxFeePerGas: path.MaxFeesPerGas,
MaxFeePerGas: path.ApprovalMaxFeesPerGas,
MaxPriorityFeePerGas: path.ApprovalPriorityFee,
ValueOut: (*hexutil.Big)(big.NewInt(0)),
@ -125,7 +126,7 @@ func buildApprovalTxForPath(transactor transactions.TransactorIface, path *route
}, nil
}
func buildTxForPath(transactor transactions.TransactorIface, path *routes.Path, pathProcessors map[string]pathprocessor.PathProcessor,
func buildTxForPath(path *routes.Path, pathProcessors map[string]pathprocessor.PathProcessor,
usedNonces map[uint64]int64, signer ethTypes.Signer, params BuildRouteExtraParams) (*wallettypes.TransactionData, error) {
lastUsedNonce := int64(-1)
if nonce, ok := usedNonces[path.FromChain.ChainID]; ok {
@ -161,8 +162,9 @@ func buildTxForPath(transactor transactions.TransactorIface, path *routes.Path,
To: &addrTo,
Value: path.AmountIn,
Data: data,
Nonce: path.TxNonce,
Gas: (*hexutil.Uint64)(&path.TxGasAmount),
MaxFeePerGas: path.MaxFeesPerGas,
MaxFeePerGas: path.TxMaxFeesPerGas,
MaxPriorityFeePerGas: path.TxPriorityFee,
// additional fields version 1
@ -257,7 +259,7 @@ func (tm *TransactionManager) BuildTransactionsFromRoute(route routes.Route, pat
}
// build tx for the path
txDetails.TxData, err = buildTxForPath(tm.transactor, path, pathProcessors, usedNonces, signer, params)
txDetails.TxData, err = buildTxForPath(path, pathProcessors, usedNonces, signer, params)
if err != nil {
return nil, path.FromChain.ChainID, path.ToChain.ChainID, err
}

View File

@ -51,7 +51,7 @@ func (e *ErrBadNonce) Error() string {
// Transactor is an interface that defines the methods for validating and sending transactions.
type TransactorIface interface {
NextNonce(rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error)
NextNonce(ctx context.Context, rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error)
EstimateGas(network *params.Network, from common.Address, to common.Address, value *big.Int, input []byte) (uint64, error)
SendTransaction(sendArgs wallettypes.SendTxArgs, verifiedAccount *account.SelectedExtKey, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error)
SendTransactionWithChainID(chainID uint64, sendArgs wallettypes.SendTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error)
@ -102,9 +102,8 @@ func (t *Transactor) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
t.rpcCallTimeout = timeout
}
func (t *Transactor) NextNonce(rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error) {
func (t *Transactor) NextNonce(ctx context.Context, rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error) {
wrapper := newRPCWrapper(rpcClient, chainID)
ctx := context.Background()
nonce, err := wrapper.PendingNonceAt(ctx, common.Address(from))
if err != nil {
return 0, err
@ -256,8 +255,11 @@ func (t *Transactor) BuildTransactionWithSignature(chainID uint64, args walletty
return nil, ErrInvalidSignatureSize
}
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel()
tx := t.buildTransaction(args)
expectedNonce, err := t.NextNonce(t.rpcWrapper.RPCClient, chainID, args.From)
expectedNonce, err := t.NextNonce(ctx, t.rpcWrapper.RPCClient, chainID, args.From)
if err != nil {
return nil, err
}
@ -281,7 +283,10 @@ func (t *Transactor) HashTransaction(args wallettypes.SendTxArgs) (validatedArgs
validatedArgs = args
nonce, err := t.NextNonce(t.rpcWrapper.RPCClient, t.rpcWrapper.chainID, args.From)
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel()
nonce, err := t.NextNonce(ctx, t.rpcWrapper.RPCClient, t.rpcWrapper.chainID, args.From)
if err != nil {
return validatedArgs, hash, err
}
@ -290,8 +295,6 @@ func (t *Transactor) HashTransaction(args wallettypes.SendTxArgs) (validatedArgs
gasFeeCap := (*big.Int)(args.MaxFeePerGas)
gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas)
if args.GasPrice == nil && !args.IsDynamicFeeTx() {
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel()
gasPrice, err = t.rpcWrapper.SuggestGasPrice(ctx)
if err != nil {
return validatedArgs, hash, err
@ -303,9 +306,6 @@ func (t *Transactor) HashTransaction(args wallettypes.SendTxArgs) (validatedArgs
var gas uint64
if args.Gas == nil {
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel()
var (
gethTo common.Address
gethToPtr *common.Address
@ -374,13 +374,16 @@ func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args wa
return tx, wallettypes.ErrInvalidSendTxArgs
}
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel()
var nonce uint64
if args.Nonce != nil {
nonce = uint64(*args.Nonce)
} else {
// some chains, like arbitrum doesn't count pending txs in the nonce, so we need to calculate it manually
if lastUsedNonce < 0 {
nonce, err = t.NextNonce(rpcWrapper.RPCClient, rpcWrapper.chainID, args.From)
nonce, err = t.NextNonce(ctx, rpcWrapper.RPCClient, rpcWrapper.chainID, args.From)
if err != nil {
return tx, err
}
@ -389,9 +392,6 @@ func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args wa
}
}
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel()
gasPrice := (*big.Int)(args.GasPrice)
// GasPrice should be estimated only for LegacyTx
if !args.IsDynamicFeeTx() && args.GasPrice == nil {