From ad56b696edeae006ca0a70f50793e356b118c539 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Mon, 28 Oct 2024 18:30:17 +0100 Subject: [PATCH] feat(wallet)_: refresh paraswap proposal on intervals defined per chains --- services/wallet/router/router.go | 220 ++++++++++++----------- services/wallet/router/router_updates.go | 70 ++++++-- 2 files changed, 170 insertions(+), 120 deletions(-) diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index 2d462a27e..6853a3a8a 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -582,6 +582,33 @@ func (r *Router) getSelectedChains(input *requests.RouteInputParams) (selectedFr return selectedFromChains, selectedToChains, nil } +func mapRouteInputParamsToProcessorInputParams(input *requests.RouteInputParams, amountOption amountOption, fromChain *params.Network, + toChain *params.Network, token *walletToken.Token, toToken *walletToken.Token) pathprocessor.ProcessorInputParams { + processorInputParams := pathprocessor.ProcessorInputParams{ + FromChain: fromChain, + ToChain: toChain, + FromToken: token, + ToToken: toToken, + ToAddr: input.AddrTo, + FromAddr: input.AddrFrom, + AmountIn: amountOption.amount, + AmountOut: input.AmountOut.ToInt(), + + Username: input.Username, + PublicKey: input.PublicKey, + PackID: input.PackID.ToInt(), + } + if input.TestsMode { + processorInputParams.TestsMode = input.TestsMode + processorInputParams.TestEstimationMap = input.TestParams.EstimationMap + processorInputParams.TestBonderFeeMap = input.TestParams.BonderFeeMap + processorInputParams.TestApprovalGasEstimation = input.TestParams.ApprovalGasEstimation + processorInputParams.TestApprovalL1Fee = input.TestParams.ApprovalL1Fee + } + + return processorInputParams +} + func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInputParams, selectedFromChains []*params.Network, selectedToChains []*params.Network) (candidates routes.Route, processorErrors []*ProcessorError, err error) { var ( @@ -694,114 +721,13 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp continue } - processorInputParams := pathprocessor.ProcessorInputParams{ - FromChain: network, - ToChain: dest, - FromToken: token, - ToToken: toToken, - ToAddr: input.AddrTo, - FromAddr: input.AddrFrom, - AmountIn: amountOption.amount, - AmountOut: input.AmountOut.ToInt(), - - Username: input.Username, - PublicKey: input.PublicKey, - PackID: input.PackID.ToInt(), - } - if input.TestsMode { - processorInputParams.TestsMode = input.TestsMode - processorInputParams.TestEstimationMap = input.TestParams.EstimationMap - processorInputParams.TestBonderFeeMap = input.TestParams.BonderFeeMap - processorInputParams.TestApprovalGasEstimation = input.TestParams.ApprovalGasEstimation - processorInputParams.TestApprovalL1Fee = input.TestParams.ApprovalL1Fee - } - - can, err := pProcessor.AvailableFor(processorInputParams) + processorInputParams := mapRouteInputParamsToProcessorInputParams(input, amountOption, network, dest, token, toToken) + path, err := r.resolvePath(ctx, input.SendType, input.GasFeeMode, amountOption.locked, amountOption.subtractFees, processorInputParams, pProcessor, fetchedFees) if err != nil { appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) continue } - if !can { - continue - } - - bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - - gasLimit, err := pProcessor.EstimateGas(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - - approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - 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 - } - - 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 { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - - maxFeesPerGas := fetchedFees.FeeFor(input.GasFeeMode) - - estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas) - if approvalRequired && estimatedTime < fees.MoreThanFiveMinutes { - estimatedTime += 1 - } - - path := &routes.Path{ - ProcessorName: pProcessor.Name(), - FromChain: network, - ToChain: dest, - FromToken: token, - ToToken: toToken, - AmountIn: (*hexutil.Big)(amountOption.amount), - AmountInLocked: amountOption.locked, - AmountOut: (*hexutil.Big)(amountOut), - - // 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, - ApprovalGasAmount: approvalGasLimit, - - EstimatedTime: estimatedTime, - - 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) + if path == nil { continue } @@ -823,6 +749,92 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp return candidates, processorErrors, nil } +func (r *Router) resolvePath(ctx context.Context, sendType sendtype.SendType, gasFeeMode fees.GasFeeMode, amountInLocked bool, subtractFees bool, + processorInputParams pathprocessor.ProcessorInputParams, pProcessor pathprocessor.PathProcessor, fetchedFees *fees.SuggestedFees) (*routes.Path, error) { + can, err := pProcessor.AvailableFor(processorInputParams) + if err != nil { + return nil, err + } + if !can { + return nil, nil + } + + bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams) + if err != nil { + return nil, err + } + + gasLimit, err := pProcessor.EstimateGas(processorInputParams) + if err != nil { + return nil, err + } + + approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams) + if err != nil { + return nil, err + } + approvalRequired, approvalAmountRequired, err := r.requireApproval(ctx, sendType, &approvalContractAddress, processorInputParams) + if err != nil { + return nil, err + } + + var approvalGasLimit uint64 + if approvalRequired { + if processorInputParams.TestsMode { + approvalGasLimit = processorInputParams.TestApprovalGasEstimation + } else { + approvalGasLimit, err = r.estimateGasForApproval(processorInputParams, &approvalContractAddress) + if err != nil { + return nil, err + } + } + } + + amountOut, err := pProcessor.CalculateAmountOut(processorInputParams) + if err != nil { + return nil, err + } + + maxFeesPerGas := fetchedFees.FeeFor(gasFeeMode) + + estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, processorInputParams.FromChain.ChainID, maxFeesPerGas) + if approvalRequired && estimatedTime < fees.MoreThanFiveMinutes { + estimatedTime += 1 + } + + path := &routes.Path{ + ProcessorName: pProcessor.Name(), + FromChain: processorInputParams.FromChain, + ToChain: processorInputParams.ToChain, + FromToken: processorInputParams.FromToken, + ToToken: processorInputParams.ToToken, + AmountIn: (*hexutil.Big)(processorInputParams.AmountIn), + AmountInLocked: amountInLocked, + AmountOut: (*hexutil.Big)(amountOut), + + // 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, + ApprovalGasAmount: approvalGasLimit, + + EstimatedTime: estimatedTime, + + SubtractFees: subtractFees, + } + + err = r.cacluateFees(ctx, path, fetchedFees, processorInputParams.TestsMode, processorInputParams.TestApprovalL1Fee) + if err != nil { + return nil, err + } + + return path, nil +} + func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute routes.Route) (hasPositiveBalance bool, err error) { // make a copy of the active balance map balanceMapCopy := make(map[string]*big.Int) diff --git a/services/wallet/router/router_updates.go b/services/wallet/router/router_updates.go index 91cb1a687..b7d017b2b 100644 --- a/services/wallet/router/router_updates.go +++ b/services/wallet/router/router_updates.go @@ -10,14 +10,21 @@ import ( "github.com/status-im/status-go/logutils" "github.com/status-im/status-go/rpc/chain" walletCommon "github.com/status-im/status-go/services/wallet/common" + "github.com/status-im/status-go/services/wallet/router/pathprocessor" + "github.com/status-im/status-go/services/wallet/router/sendtype" ) var ( newBlockCheckIntervalMainnet = 3 * time.Second - newBlockCheckIntervalOptimism = 1 * time.Second - newBlockCheckIntervalArbitrum = 200 * time.Millisecond + newBlockCheckIntervalOptimism = 2 * time.Second + newBlockCheckIntervalArbitrum = 1 * time.Second newBlockCheckIntervalAnvilMainnet = 2 * time.Second + paraswapProposalCheckIntervalMainnet = 12 * time.Second + paraswapProposalCheckIntervalOptimism = 3 * time.Second + paraswapProposalCheckIntervalArbitrum = 3 * time.Second + paraswapProposalCheckIntervalAnvilMainnet = 2 * time.Second + feeRecalculationTimeout = 5 * time.Minute feeRecalculationAnvilTimeout = 5 * time.Second ) @@ -52,21 +59,31 @@ func (r *Router) subscribeForUdates(chainID uint64) error { } r.startTimeoutForUpdates(flb.closeCh, timeout) - var ticker *time.Ticker + var ( + duration time.Duration + step time.Duration + limit time.Duration + ) switch chainID { case walletCommon.EthereumMainnet, walletCommon.EthereumSepolia: - ticker = time.NewTicker(newBlockCheckIntervalMainnet) + step = newBlockCheckIntervalMainnet + limit = paraswapProposalCheckIntervalMainnet case walletCommon.OptimismMainnet, walletCommon.OptimismSepolia: - ticker = time.NewTicker(newBlockCheckIntervalOptimism) + step = newBlockCheckIntervalOptimism + limit = paraswapProposalCheckIntervalOptimism case walletCommon.ArbitrumMainnet, walletCommon.ArbitrumSepolia: - ticker = time.NewTicker(newBlockCheckIntervalArbitrum) + step = newBlockCheckIntervalArbitrum + limit = paraswapProposalCheckIntervalArbitrum case walletCommon.AnvilMainnet: - ticker = time.NewTicker(newBlockCheckIntervalAnvilMainnet) + step = newBlockCheckIntervalAnvilMainnet + limit = paraswapProposalCheckIntervalAnvilMainnet } + ticker := time.NewTicker(step) + ctx, cancelCtx := context.WithCancel(context.Background()) go func() { @@ -74,6 +91,13 @@ func (r *Router) subscribeForUdates(chainID uint64) error { for { select { case <-ticker.C: + refreshParaswapProposal := false + duration += step + if duration >= limit { + refreshParaswapProposal = true + duration = 0 + } + var blockNumber uint64 blockNumber, err := ethClient.BlockNumber(ctx) if err != nil { @@ -103,23 +127,37 @@ func (r *Router) subscribeForUdates(chainID uint64) error { continue } - r.lastInputParamsMutex.Lock() - uuid := r.lastInputParams.Uuid - r.lastInputParamsMutex.Unlock() + _, inputParams := r.GetBestRouteAndAssociatedInputParams() 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 { - logutils.ZapLogger().Error("Failed to calculate fees", zap.Error(err)) - continue + for i, path := range r.activeRoutes.Best { + if path.ProcessorName == pathprocessor.ProcessorSwapParaswapName && refreshParaswapProposal { + amountOption := amountOption{ + amount: path.AmountIn.ToInt(), + locked: path.AmountInLocked, + subtractFees: path.SubtractFees, + } + processorInputParams := mapRouteInputParamsToProcessorInputParams(&inputParams, amountOption, path.FromChain, path.ToChain, path.FromToken, path.ToToken) + swapProcessor := r.pathProcessors[path.ProcessorName] + newPath, err := r.resolvePath(ctx, sendtype.Swap, inputParams.GasFeeMode, amountOption.locked, amountOption.subtractFees, processorInputParams, swapProcessor, fees) + if err != nil { + logutils.ZapLogger().Error("Failed to calculate fees", zap.Error(err)) + continue + } + r.activeRoutes.Best[i] = newPath + } else { + err = r.cacluateFees(ctx, path, fees, false, 0) + if err != nil { + logutils.ZapLogger().Error("Failed to calculate fees", zap.Error(err)) + continue + } } } _, err = r.checkBalancesForTheBestRoute(ctx, r.activeRoutes.Best) - sendRouterResult(uuid, r.activeRoutes, err) + sendRouterResult(inputParams.Uuid, r.activeRoutes, err) } r.activeRoutesMutex.Unlock() }