From 506a76bf9fd31ae7c12172f331b675f4002b8f38 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Thu, 29 Aug 2024 15:40:30 +0200 Subject: [PATCH] feat_: recalculate route fees with every new block --- services/wallet/api.go | 6 + services/wallet/router/router.go | 183 +++++++++++------------ services/wallet/router/router_helper.go | 158 ++++++++++++++++--- services/wallet/router/router_updates.go | 150 +++++++++++++++++++ 4 files changed, 381 insertions(+), 116 deletions(-) create mode 100644 services/wallet/router/router_updates.go diff --git a/services/wallet/api.go b/services/wallet/api.go index 3058aabe7..eae8c7abe 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -507,6 +507,12 @@ func (api *API) StopSuggestedRoutesAsyncCalculation(ctx context.Context) { api.router.StopSuggestedRoutesAsyncCalculation() } +func (api *API) StopSuggestedRoutesCalculation(ctx context.Context) { + log.Debug("call to StopSuggestedRoutesCalculation") + + api.router.StopSuggestedRoutesCalculation() +} + // 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) diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index 396cb42f9..a5d19d45b 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -73,6 +73,14 @@ type Router struct { feesManager *fees.FeeManager pathProcessors map[string]pathprocessor.PathProcessor scheduler *async.Scheduler + + activeRoutesMutex sync.Mutex + activeRoutes *SuggestedRoutes + + lastInputParamsMutex sync.Mutex + lastInputParams *requests.RouteInputParams + + clientsForUpdatesPerChains sync.Map } func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *token.Manager, marketManager *market.Manager, @@ -147,41 +155,46 @@ func newSuggestedRoutes( return suggestedRoutes, allRoutes } +func sendRouterResult(uuid string, result interface{}, err error) { + routesResponse := responses.RouterSuggestedRoutes{ + Uuid: uuid, + } + + if err != nil { + errorResponse := errors.CreateErrorResponseFromError(err) + routesResponse.ErrorResponse = errorResponse.(*errors.ErrorResponse) + } + + if suggestedRoutes, ok := result.(*SuggestedRoutes); ok && suggestedRoutes != nil { + routesResponse.Best = suggestedRoutes.Best + routesResponse.Candidates = suggestedRoutes.Candidates + routesResponse.TokenPrice = &suggestedRoutes.TokenPrice + routesResponse.NativeChainTokenPrice = &suggestedRoutes.NativeChainTokenPrice + } + + signal.SendWalletEvent(signal.SuggestedRoutes, routesResponse) +} + func (r *Router) SuggestedRoutesAsync(input *requests.RouteInputParams) { r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) { return r.SuggestedRoutes(ctx, input) }, func(result interface{}, taskType async.TaskType, err error) { - routesResponse := responses.RouterSuggestedRoutes{ - Uuid: input.Uuid, - } - - if err != nil { - errorResponse := errors.CreateErrorResponseFromError(err) - routesResponse.ErrorResponse = errorResponse.(*errors.ErrorResponse) - } - - if suggestedRoutes, ok := result.(*SuggestedRoutes); ok && suggestedRoutes != nil { - routesResponse.Best = suggestedRoutes.Best - routesResponse.Candidates = suggestedRoutes.Candidates - routesResponse.TokenPrice = &suggestedRoutes.TokenPrice - routesResponse.NativeChainTokenPrice = &suggestedRoutes.NativeChainTokenPrice - } - - signal.SendWalletEvent(signal.SuggestedRoutes, routesResponse) + sendRouterResult(input.Uuid, result, err) }) } func (r *Router) StopSuggestedRoutesAsyncCalculation() { + r.unsubscribeFeesUpdateAccrossAllChains() r.scheduler.Stop() } -func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInputParams) (*SuggestedRoutes, error) { - testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled() - if err != nil { - return nil, errors.CreateErrorResponseFromError(err) - } +func (r *Router) StopSuggestedRoutesCalculation() { + r.unsubscribeFeesUpdateAccrossAllChains() +} - input.TestnetMode = testnetMode +func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInputParams) (suggestedRoutes *SuggestedRoutes, err error) { + // unsubscribe from updates + r.unsubscribeFeesUpdateAccrossAllChains() // clear all processors for _, processor := range r.pathProcessors { @@ -190,6 +203,29 @@ func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInput } } + r.lastInputParamsMutex.Lock() + r.lastInputParams = input + r.lastInputParamsMutex.Unlock() + + defer func() { + r.activeRoutesMutex.Lock() + r.activeRoutes = suggestedRoutes + r.activeRoutesMutex.Unlock() + if suggestedRoutes != nil && err == nil { + // subscribe for updates + for _, path := range suggestedRoutes.Best { + err = r.subscribeForUdates(path.FromChain.ChainID) + } + } + }() + + testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled() + if err != nil { + return nil, errors.CreateErrorResponseFromError(err) + } + + input.TestnetMode = testnetMode + err = input.Validate() if err != nil { return nil, errors.CreateErrorResponseFromError(err) @@ -224,7 +260,7 @@ func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInput return nil, errors.CreateErrorResponseFromError(err) } - suggestedRoutes, err := r.resolveRoutes(ctx, input, candidates, balanceMap) + suggestedRoutes, err = r.resolveRoutes(ctx, input, candidates, balanceMap) if err == nil && (suggestedRoutes == nil || len(suggestedRoutes.Best) == 0) { // No best route found, but no error given. @@ -657,22 +693,24 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) continue } - approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams) + approvalRequired, approvalAmountRequired, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams) if err != nil { appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) continue } - // 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() { - // txInputData, err := pProcessor.PackTxInputData(processorInputParams) - // if err != nil { - // continue - // } - - // l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData) - // } + var approvalGasLimit uint64 + if approvalRequired { + if processorInputParams.TestsMode { + approvalGasLimit = processorInputParams.TestApprovalGasEstimation + } else { + approvalGasLimit, err = r.estimateGasForApproval(processorInputParams, &approvalContractAddress) + if err != nil { + appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) + continue + } + } + } amountOut, err := pProcessor.CalculateAmountOut(processorInputParams) if err != nil { @@ -687,44 +725,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp estimatedTime += 1 } - // calculate ETH fees - ethTotalFees := big.NewInt(0) - txFeeInWei := new(big.Int).Mul(maxFeesPerGas, big.NewInt(int64(gasLimit))) - ethTotalFees.Add(ethTotalFees, txFeeInWei) - - txL1FeeInWei := big.NewInt(0) - if l1FeeWei > 0 { - txL1FeeInWei = big.NewInt(int64(l1FeeWei)) - ethTotalFees.Add(ethTotalFees, txL1FeeInWei) - } - - approvalFeeInWei := big.NewInt(0) - approvalL1FeeInWei := big.NewInt(0) - if approvalRequired { - approvalFeeInWei.Mul(maxFeesPerGas, big.NewInt(int64(approvalGasLimit))) - ethTotalFees.Add(ethTotalFees, approvalFeeInWei) - - if l1ApprovalFee > 0 { - approvalL1FeeInWei = big.NewInt(int64(l1ApprovalFee)) - ethTotalFees.Add(ethTotalFees, approvalL1FeeInWei) - } - } - - // calculate required balances (bonder and token fees are already included in the amountIn by Hop bridge (once we include Celar we need to check how they handle the fees)) - requiredNativeBalance := big.NewInt(0) - requiredTokenBalance := big.NewInt(0) - - if token.IsNative() { - requiredNativeBalance.Add(requiredNativeBalance, amountOption.amount) - if !amountOption.subtractFees { - requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees) - } - } else { - requiredTokenBalance.Add(requiredTokenBalance, amountOption.amount) - requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees) - } - - appendPathFn(&routes.Path{ + path := &routes.Path{ ProcessorName: pProcessor.Name(), FromChain: network, ToChain: dest, @@ -734,36 +735,28 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp AmountInLocked: amountOption.locked, AmountOut: (*hexutil.Big)(amountOut), - SuggestedLevelsForMaxFeesPerGas: fetchedFees.MaxFeesLevels, - MaxFeesPerGas: (*hexutil.Big)(maxFeesPerGas), - - TxBaseFee: (*hexutil.Big)(fetchedFees.BaseFee), - TxPriorityFee: (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas), - TxGasAmount: gasLimit, - TxBonderFees: (*hexutil.Big)(bonderFees), - TxTokenFees: (*hexutil.Big)(tokenFees), - - TxFee: (*hexutil.Big)(txFeeInWei), - TxL1Fee: (*hexutil.Big)(txL1FeeInWei), + // set params that we don't want to be recalculated with every new block creation + TxGasAmount: gasLimit, + TxBonderFees: (*hexutil.Big)(bonderFees), + TxTokenFees: (*hexutil.Big)(tokenFees), ApprovalRequired: approvalRequired, ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired), ApprovalContractAddress: &approvalContractAddress, - ApprovalBaseFee: (*hexutil.Big)(fetchedFees.BaseFee), - ApprovalPriorityFee: (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas), ApprovalGasAmount: approvalGasLimit, - ApprovalFee: (*hexutil.Big)(approvalFeeInWei), - ApprovalL1Fee: (*hexutil.Big)(approvalL1FeeInWei), - - TxTotalFee: (*hexutil.Big)(ethTotalFees), - EstimatedTime: estimatedTime, - SubtractFees: amountOption.subtractFees, - RequiredTokenBalance: requiredTokenBalance, - RequiredNativeBalance: requiredNativeBalance, - }) + SubtractFees: amountOption.subtractFees, + } + + err = r.cacluateFees(ctx, path, fetchedFees, processorInputParams.TestsMode, processorInputParams.TestApprovalL1Fee) + if err != nil { + appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) + continue + } + + appendPathFn(path) } } } diff --git a/services/wallet/router/router_helper.go b/services/wallet/router/router_helper.go index 0e07851b0..b373ccff1 100644 --- a/services/wallet/router/router_helper.go +++ b/services/wallet/router/router_helper.go @@ -10,43 +10,46 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "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/contracts/ierc20" "github.com/status-im/status-go/params" "github.com/status-im/status-go/services/wallet/bigint" 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/services/wallet/router/pathprocessor" + routs "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" ) func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) ( - bool, *big.Int, uint64, uint64, error) { + bool, *big.Int, error) { if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() { - return false, nil, 0, 0, nil + return false, nil, nil } if params.FromToken.IsNative() { - return false, nil, 0, 0, nil + return false, nil, nil } contractMaker, err := contracts.NewContractMaker(r.rpcClient) if err != nil { - return false, nil, 0, 0, err + return false, nil, err } contract, err := contractMaker.NewERC20(params.FromChain.ChainID, params.FromToken.Address) if err != nil { - return false, nil, 0, 0, err + return false, nil, err } if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress { - return false, nil, 0, 0, nil + return false, nil, nil } if params.TestsMode { - return true, params.AmountIn, params.TestApprovalGasEstimation, params.TestApprovalL1Fee, nil + return true, params.AmountIn, nil } allowance, err := contract.Allowance(&bind.CallOpts{ @@ -54,45 +57,65 @@ func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType }, params.FromAddr, *approvalContractAddress) if err != nil { - return false, nil, 0, 0, err + return false, nil, err } if allowance.Cmp(params.AmountIn) >= 0 { - return false, nil, 0, 0, nil + return false, nil, nil } - ethClient, err := r.rpcClient.EthClient(params.FromChain.ChainID) - if err != nil { - return false, nil, 0, 0, err + return true, params.AmountIn, nil +} + +func (r *Router) packApprovalInputData(amountIn *big.Int, approvalContractAddress *common.Address) ([]byte, error) { + if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress { + return []byte{}, nil } erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) if err != nil { - return false, nil, 0, 0, err + return []byte{}, err } - data, err := erc20ABI.Pack("approve", approvalContractAddress, params.AmountIn) + return erc20ABI.Pack("approve", approvalContractAddress, amountIn) +} + +func (r *Router) estimateGasForApproval(params pathprocessor.ProcessorInputParams, approvalContractAddress *common.Address) (uint64, error) { + data, err := r.packApprovalInputData(params.AmountIn, approvalContractAddress) if err != nil { - return false, nil, 0, 0, err + return 0, err } - estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{ + ethClient, err := r.rpcClient.EthClient(params.FromChain.ChainID) + if err != nil { + return 0, err + } + + return ethClient.EstimateGas(context.Background(), ethereum.CallMsg{ From: params.FromAddr, To: ¶ms.FromToken.Address, Value: pathprocessor.ZeroBigIntValue, Data: data, }) +} + +func (r *Router) calculateApprovalL1Fee(amountIn *big.Int, chainID uint64, approvalContractAddress *common.Address) (uint64, error) { + data, err := r.packApprovalInputData(amountIn, approvalContractAddress) if err != nil { - return false, nil, 0, 0, err + return 0, err + } + + ethClient, err := r.rpcClient.EthClient(chainID) + if err != nil { + return 0, err } - // fetching l1 fee var l1Fee uint64 - oracleContractAddress, err := gaspriceoracle.ContractAddress(params.FromChain.ChainID) + oracleContractAddress, err := gaspriceoracle.ContractAddress(chainID) if err == nil { oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient) if err != nil { - return false, nil, 0, 0, err + return 0, err } callOpt := &bind.CallOpts{} @@ -101,7 +124,7 @@ func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType l1Fee = l1FeeResult.Uint64() } - return true, params.AmountIn, estimate, l1Fee, nil + return l1Fee, nil } func (r *Router) getERC1155Balance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) { @@ -136,3 +159,96 @@ 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) { + + var ( + l1ApprovalFee uint64 + ) + if path.ApprovalRequired { + if testsMode { + l1ApprovalFee = testApprovalL1Fee + } else { + l1ApprovalFee, err = r.calculateApprovalL1Fee(path.AmountIn.ToInt(), path.FromChain.ChainID, path.ApprovalContractAddress) + if err != nil { + return err + } + } + } + + // 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() { + // txInputData, err := pProcessor.PackTxInputData(processorInputParams) + // if err != nil { + // continue + // } + + // 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))) + ethTotalFees.Add(ethTotalFees, txFeeInWei) + + txL1FeeInWei := big.NewInt(0) + if l1FeeWei > 0 { + txL1FeeInWei = big.NewInt(int64(l1FeeWei)) + ethTotalFees.Add(ethTotalFees, txL1FeeInWei) + } + + approvalFeeInWei := big.NewInt(0) + approvalL1FeeInWei := big.NewInt(0) + if path.ApprovalRequired { + approvalFeeInWei.Mul(maxFeesPerGas, big.NewInt(int64(path.ApprovalGasAmount))) + ethTotalFees.Add(ethTotalFees, approvalFeeInWei) + + if l1ApprovalFee > 0 { + approvalL1FeeInWei = big.NewInt(int64(l1ApprovalFee)) + ethTotalFees.Add(ethTotalFees, approvalL1FeeInWei) + } + } + + // calculate required balances (bonder and token fees are already included in the amountIn by Hop bridge (once we include Celar we need to check how they handle the fees)) + requiredNativeBalance := big.NewInt(0) + requiredTokenBalance := big.NewInt(0) + + if path.FromToken.IsNative() { + requiredNativeBalance.Add(requiredNativeBalance, path.AmountIn.ToInt()) + if !path.SubtractFees { + requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees) + } + } else { + requiredTokenBalance.Add(requiredTokenBalance, path.AmountIn.ToInt()) + requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees) + } + + // 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) + + path.TxTotalFee = (*hexutil.Big)(ethTotalFees) + + path.RequiredTokenBalance = requiredTokenBalance + path.RequiredNativeBalance = requiredNativeBalance + + return nil +} diff --git a/services/wallet/router/router_updates.go b/services/wallet/router/router_updates.go new file mode 100644 index 000000000..e3b26c531 --- /dev/null +++ b/services/wallet/router/router_updates.go @@ -0,0 +1,150 @@ +package router + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/status-im/status-go/rpc/chain" + walletCommon "github.com/status-im/status-go/services/wallet/common" +) + +var ( + newBlockCheckIntervalMainnet = 3 * time.Second + newBlockCheckIntervalOptimism = 1 * time.Second + newBlockCheckIntervalArbitrum = 200 * time.Millisecond + + feeRecalculationTimeout = 5 * time.Minute +) + +type fetchingLastBlock struct { + client chain.ClientInterface + lastBlock uint64 + closeCh chan struct{} +} + +func (r *Router) subscribeForUdates(chainID uint64) error { + if _, ok := r.clientsForUpdatesPerChains.Load(chainID); ok { + return nil + } + + ethClient, err := r.rpcClient.EthClient(chainID) + if err != nil { + log.Error("Failed to get eth client", "error", err) + return err + } + + flb := fetchingLastBlock{ + client: ethClient, + lastBlock: 0, + closeCh: make(chan struct{}), + } + r.clientsForUpdatesPerChains.Store(chainID, flb) + + r.startTimeoutForUpdates(flb.closeCh) + + var ticker *time.Ticker + switch chainID { + case walletCommon.EthereumMainnet, + walletCommon.EthereumSepolia: + ticker = time.NewTicker(newBlockCheckIntervalMainnet) + case walletCommon.OptimismMainnet, + walletCommon.OptimismSepolia: + ticker = time.NewTicker(newBlockCheckIntervalOptimism) + case walletCommon.ArbitrumMainnet, + walletCommon.ArbitrumSepolia: + ticker = time.NewTicker(newBlockCheckIntervalArbitrum) + } + + ctx, cancelCtx := context.WithCancel(context.Background()) + + go func() { + for { + select { + case <-ticker.C: + var blockNumber uint64 + blockNumber, err := ethClient.BlockNumber(ctx) + if err != nil { + log.Error("Failed to get block number", "error", err) + continue + } + + val, ok := r.clientsForUpdatesPerChains.Load(chainID) + if !ok { + log.Error("Failed to get fetchingLastBlock", "chain", chainID) + continue + } + + flbLoaded, ok := val.(fetchingLastBlock) + if !ok { + log.Error("Failed to get fetchingLastBlock", "chain", chainID) + continue + } + + if blockNumber > flbLoaded.lastBlock { + flbLoaded.lastBlock = blockNumber + r.clientsForUpdatesPerChains.Store(chainID, flbLoaded) + + fees, err := r.feesManager.SuggestedFees(ctx, chainID) + if err != nil { + log.Error("Failed to get suggested fees", "error", 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 { + for _, path := range r.activeRoutes.Best { + err = r.cacluateFees(ctx, path, fees, false, 0) + if err != nil { + log.Error("Failed to calculate fees", "error", err) + continue + } + } + + sendRouterResult(uuid, r.activeRoutes, nil) + } + r.activeRoutesMutex.Unlock() + } + case <-flb.closeCh: + ticker.Stop() + cancelCtx() + return + } + } + }() + return nil +} + +func (r *Router) startTimeoutForUpdates(closeCh chan struct{}) { + dedlineTicker := time.NewTicker(feeRecalculationTimeout) + go func() { + for { + select { + case <-dedlineTicker.C: + r.unsubscribeFeesUpdateAccrossAllChains() + return + case <-closeCh: + dedlineTicker.Stop() + return + } + } + }() +} + +func (r *Router) unsubscribeFeesUpdateAccrossAllChains() { + r.clientsForUpdatesPerChains.Range(func(key, value interface{}) bool { + flb, ok := value.(fetchingLastBlock) + if !ok { + log.Error("Failed to get fetchingLastBlock", "chain", key) + return false + } + + close(flb.closeCh) + r.clientsForUpdatesPerChains.Delete(key) + return true + }) +}