diff --git a/services/communitytokens/estimations.go b/services/communitytokens/estimations.go index c9b85bb5d78..91d133421a6 100644 --- a/services/communitytokens/estimations.go +++ b/services/communitytokens/estimations.go @@ -18,13 +18,13 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/services/wallet/bigint" - "github.com/status-im/status-go/services/wallet/router" + "github.com/status-im/status-go/services/wallet/router/fees" "github.com/status-im/status-go/transactions" ) type CommunityTokenFees struct { - GasUnits uint64 `json:"gasUnits"` - SuggestedFees *router.SuggestedFeesGwei `json:"suggestedFees"` + GasUnits uint64 `json:"gasUnits"` + SuggestedFees *fees.SuggestedFeesGwei `json:"suggestedFees"` } func weiToGwei(val *big.Int) *big.Float { @@ -373,7 +373,7 @@ func (s *Service) prepareCommunityTokenFees(ctx context.Context, from common.Add }, nil } -func (s *Service) suggestedFeesToSendTxArgs(from common.Address, to *common.Address, gas uint64, suggestedFees *router.SuggestedFeesGwei) transactions.SendTxArgs { +func (s *Service) suggestedFeesToSendTxArgs(from common.Address, to *common.Address, gas uint64, suggestedFees *fees.SuggestedFeesGwei) transactions.SendTxArgs { sendArgs := transactions.SendTxArgs{} sendArgs.From = types.Address(from) sendArgs.To = (*types.Address)(to) diff --git a/services/communitytokens/service.go b/services/communitytokens/service.go index 598eaa1fbf9..bea446d0d4c 100644 --- a/services/communitytokens/service.go +++ b/services/communitytokens/service.go @@ -32,7 +32,7 @@ import ( "github.com/status-im/status-go/services/utils" "github.com/status-im/status-go/services/wallet/bigint" wcommon "github.com/status-im/status-go/services/wallet/common" - "github.com/status-im/status-go/services/wallet/router" + "github.com/status-im/status-go/services/wallet/router/fees" "github.com/status-im/status-go/services/wallet/walletevent" "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" @@ -49,7 +49,7 @@ type Service struct { walletFeed *event.Feed walletWatcher *walletevent.Watcher transactor *transactions.Transactor - feeManager *router.FeeManager + feeManager *fees.FeeManager } // Returns a new Collectibles Service. @@ -63,7 +63,7 @@ func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pen db: communitytokensdatabase.NewCommunityTokensDatabase(appDb), walletFeed: walletFeed, transactor: transactor, - feeManager: &router.FeeManager{RPCClient: rpcClient}, + feeManager: &fees.FeeManager{RPCClient: rpcClient}, } } diff --git a/services/wallet/api.go b/services/wallet/api.go index ddaf4239e4d..eae8c7abe2b 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -31,6 +31,7 @@ import ( "github.com/status-im/status-go/services/wallet/onramp" "github.com/status-im/status-go/services/wallet/requests" "github.com/status-im/status-go/services/wallet/router" + "github.com/status-im/status-go/services/wallet/router/fees" "github.com/status-im/status-go/services/wallet/router/pathprocessor" "github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/token" @@ -468,7 +469,7 @@ func (api *API) FetchTokenDetails(ctx context.Context, symbols []string) (map[st return api.s.marketManager.FetchTokenDetails(symbols) } -func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*router.SuggestedFeesGwei, error) { +func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*fees.SuggestedFeesGwei, error) { log.Debug("call to GetSuggestedFees") return api.router.GetFeesManager().SuggestedFeesGwei(ctx, chainID) } @@ -478,7 +479,7 @@ func (api *API) GetEstimatedLatestBlockNumber(ctx context.Context, chainID uint6 return api.s.blockChainState.GetEstimatedLatestBlockNumber(ctx, chainID) } -func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Float) (router.TransactionEstimation, error) { +func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Float) (fees.TransactionEstimation, error) { log.Debug("call to getTransactionEstimatedTime") return api.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil } @@ -488,47 +489,28 @@ func gweiToWei(val *big.Float) *big.Int { return res } -func (api *API) GetSuggestedRoutes( - ctx context.Context, - sendType router.SendType, - addrFrom common.Address, - addrTo common.Address, - amountIn *hexutil.Big, - tokenID string, - toTokenID string, - disabledFromChainIDs, - disabledToChainIDs, - preferedChainIDs []uint64, - gasFeeMode router.GasFeeMode, - fromLockedAmount map[uint64]*hexutil.Big, -) (*router.SuggestedRoutes, error) { +func (api *API) GetSuggestedRoutes(ctx context.Context, input *requests.RouteInputParams) (*router.SuggestedRoutes, error) { log.Debug("call to GetSuggestedRoutes") - testnetMode, err := api.s.rpcClient.NetworkManager.GetTestNetworksEnabled() - if err != nil { - return nil, err - } - - return api.router.SuggestedRoutes(ctx, sendType, addrFrom, addrTo, amountIn.ToInt(), tokenID, toTokenID, disabledFromChainIDs, - disabledToChainIDs, preferedChainIDs, gasFeeMode, fromLockedAmount, testnetMode) + return api.router.SuggestedRoutes(ctx, input) } -func (api *API) GetSuggestedRoutesV2(ctx context.Context, input *router.RouteInputParams) (*router.SuggestedRoutesV2, error) { - log.Debug("call to GetSuggestedRoutesV2") +func (api *API) GetSuggestedRoutesAsync(ctx context.Context, input *requests.RouteInputParams) { + log.Debug("call to GetSuggestedRoutesAsync") - return api.router.SuggestedRoutesV2(ctx, input) + api.router.SuggestedRoutesAsync(input) } -func (api *API) GetSuggestedRoutesV2Async(ctx context.Context, input *router.RouteInputParams) { - log.Debug("call to GetSuggestedRoutesV2Async") +func (api *API) StopSuggestedRoutesAsyncCalculation(ctx context.Context) { + log.Debug("call to StopSuggestedRoutesAsyncCalculation") - api.router.SuggestedRoutesV2Async(input) + api.router.StopSuggestedRoutesAsyncCalculation() } -func (api *API) StopSuggestedRoutesV2AsyncCalcualtion(ctx context.Context) { - log.Debug("call to StopSuggestedRoutesV2AsyncCalcualtion") +func (api *API) StopSuggestedRoutesCalculation(ctx context.Context) { + log.Debug("call to StopSuggestedRoutesCalculation") - api.router.StopSuggestedRoutesV2AsyncCalcualtion() + api.router.StopSuggestedRoutesCalculation() } // Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function) diff --git a/services/wallet/common/const.go b/services/wallet/common/const.go index 4f1a9e05bb4..a3021dc83e8 100644 --- a/services/wallet/common/const.go +++ b/services/wallet/common/const.go @@ -11,6 +11,7 @@ type MultiTransactionIDType int64 const ( NoMultiTransactionID = MultiTransactionIDType(0) + HexAddressLength = 42 ) type ChainID uint64 @@ -32,6 +33,18 @@ const ( var ( ZeroAddress = ethCommon.HexToAddress("0x0000000000000000000000000000000000000000") + + SupportedNetworks = map[uint64]bool{ + EthereumMainnet: true, + OptimismMainnet: true, + ArbitrumMainnet: true, + } + + SupportedTestNetworks = map[uint64]bool{ + EthereumSepolia: true, + OptimismSepolia: true, + ArbitrumSepolia: true, + } ) type ContractType byte diff --git a/services/wallet/common/utils.go b/services/wallet/common/utils.go index 41efca4623d..793fb0a8661 100644 --- a/services/wallet/common/utils.go +++ b/services/wallet/common/utils.go @@ -2,7 +2,10 @@ package common import ( "context" + "math/big" + "reflect" + gethParams "github.com/ethereum/go-ethereum/params" "github.com/status-im/status-go/params" ) @@ -24,3 +27,51 @@ func NetworksToChainIDs(networks []*params.Network) []uint64 { return chainIDs } + +func ArrayContainsElement[T comparable](el T, arr []T) bool { + for _, e := range arr { + if e == el { + return true + } + } + return false +} + +func IsSingleChainOperation(fromChains []*params.Network, toChains []*params.Network) bool { + return len(fromChains) == 1 && + len(toChains) == 1 && + fromChains[0].ChainID == toChains[0].ChainID +} + +// CopyMapGeneric creates a copy of any map, if the deepCopyValue function is provided, it will be used to copy values. +func CopyMapGeneric(original interface{}, deepCopyValueFn func(interface{}) interface{}) interface{} { + originalVal := reflect.ValueOf(original) + if originalVal.Kind() != reflect.Map { + return nil + } + + newMap := reflect.MakeMap(originalVal.Type()) + for iter := originalVal.MapRange(); iter.Next(); { + if deepCopyValueFn != nil { + newMap.SetMapIndex(iter.Key(), reflect.ValueOf(deepCopyValueFn(iter.Value().Interface()))) + } else { + newMap.SetMapIndex(iter.Key(), iter.Value()) + } + } + + return newMap.Interface() +} + +func GweiToEth(val *big.Float) *big.Float { + return new(big.Float).Quo(val, big.NewFloat(1000000000)) +} + +func WeiToGwei(val *big.Int) *big.Float { + result := new(big.Float) + result.SetInt(val) + + unit := new(big.Int) + unit.SetInt64(gethParams.GWei) + + return result.Quo(result, new(big.Float).SetInt(unit)) +} diff --git a/services/wallet/requests/router_input_params.go b/services/wallet/requests/router_input_params.go new file mode 100644 index 00000000000..b701f3ec873 --- /dev/null +++ b/services/wallet/requests/router_input_params.go @@ -0,0 +1,200 @@ +package requests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/status-im/status-go/errors" + "github.com/status-im/status-go/services/ens" + 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" + "github.com/status-im/status-go/services/wallet/router/sendtype" + "github.com/status-im/status-go/services/wallet/token" +) + +var ( + ErrENSRegisterRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-001"), Details: "username and public key are required for ENSRegister"} + ErrENSRegisterTestnetSTTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-002"), Details: "only STT is supported for ENSRegister on testnet"} + ErrENSRegisterMainnetSNTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-003"), Details: "only SNT is supported for ENSRegister on mainnet"} + ErrENSReleaseRequiresUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-004"), Details: "username is required for ENSRelease"} + ErrENSSetPubKeyRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-005"), Details: "username and public key are required for ENSSetPubKey"} + ErrStickersBuyRequiresPackID = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-006"), Details: "packID is required for StickersBuy"} + ErrSwapRequiresToTokenID = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-007"), Details: "toTokenID is required for Swap"} + ErrSwapTokenIDMustBeDifferent = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-008"), Details: "tokenID and toTokenID must be different"} + ErrSwapAmountInAmountOutMustBeExclusive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-009"), Details: "only one of amountIn or amountOut can be set"} + ErrSwapAmountInMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-010"), Details: "amountIn must be positive"} + ErrSwapAmountOutMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-011"), Details: "amountOut must be positive"} + ErrLockedAmountNotSupportedForNetwork = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-012"), Details: "locked amount is not supported for the selected network"} + ErrLockedAmountNotNegative = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-013"), Details: "locked amount must not be negative"} + ErrLockedAmountExceedsTotalSendAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-014"), Details: "locked amount exceeds the total amount to send"} + ErrLockedAmountLessThanSendAmountAllNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-015"), Details: "locked amount is less than the total amount to send, but all networks are locked"} + ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-016"), Details: "disabled chain found among locked networks"} + ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-017"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"} + ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-018"), Details: "all supported chains are excluded, routing impossible"} + ErrCannotCheckLockedAmounts = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-019"), Details: "cannot check locked amounts"} +) + +type RouteInputParams struct { + Uuid string `json:"uuid"` + SendType sendtype.SendType `json:"sendType" validate:"required"` + AddrFrom common.Address `json:"addrFrom" validate:"required"` + AddrTo common.Address `json:"addrTo" validate:"required"` + AmountIn *hexutil.Big `json:"amountIn" validate:"required"` + AmountOut *hexutil.Big `json:"amountOut"` + TokenID string `json:"tokenID" validate:"required"` + ToTokenID string `json:"toTokenID"` + DisabledFromChainIDs []uint64 `json:"disabledFromChainIDs"` + DisabledToChainIDs []uint64 `json:"disabledToChainIDs"` + GasFeeMode fees.GasFeeMode `json:"gasFeeMode" validate:"required"` + FromLockedAmount map[uint64]*hexutil.Big `json:"fromLockedAmount"` + TestnetMode bool + + // For send types like EnsRegister, EnsRelease, EnsSetPubKey, StickersBuy + Username string `json:"username"` + PublicKey string `json:"publicKey"` + PackID *hexutil.Big `json:"packID"` + + // TODO: Remove two fields below once we implement a better solution for tests + // Currently used for tests only + TestsMode bool + TestParams *RouterTestParams +} + +type RouterTestParams struct { + TokenFrom *token.Token + TokenPrices map[string]float64 + EstimationMap map[string]pathprocessor.Estimation // [processor-name, estimation] + BonderFeeMap map[string]*big.Int // [token-symbol, bonder-fee] + SuggestedFees *fees.SuggestedFees + BaseFee *big.Int + BalanceMap map[string]*big.Int // [token-symbol, balance] + ApprovalGasEstimation uint64 + ApprovalL1Fee uint64 +} + +func (i *RouteInputParams) Validate() error { + if i.SendType == sendtype.ENSRegister { + if i.Username == "" || i.PublicKey == "" { + return ErrENSRegisterRequiresUsernameAndPubKey + } + if i.TestnetMode { + if i.TokenID != pathprocessor.SttSymbol { + return ErrENSRegisterTestnetSTTOnly + } + } else { + if i.TokenID != pathprocessor.SntSymbol { + return ErrENSRegisterMainnetSNTOnly + } + } + return nil + } + + if i.SendType == sendtype.ENSRelease { + if i.Username == "" { + return ErrENSReleaseRequiresUsername + } + } + + if i.SendType == sendtype.ENSSetPubKey { + if i.Username == "" || i.PublicKey == "" { + return ErrENSSetPubKeyRequiresUsernameAndPubKey + } + + if ens.ValidateENSUsername(i.Username) != nil { + return ErrENSSetPubKeyInvalidUsername + } + } + + if i.SendType == sendtype.StickersBuy { + if i.PackID == nil { + return ErrStickersBuyRequiresPackID + } + } + + if i.SendType == sendtype.Swap { + if i.ToTokenID == "" { + return ErrSwapRequiresToTokenID + } + if i.TokenID == i.ToTokenID { + return ErrSwapTokenIDMustBeDifferent + } + + if i.AmountIn != nil && + i.AmountOut != nil && + i.AmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && + i.AmountOut.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + return ErrSwapAmountInAmountOutMustBeExclusive + } + + if i.AmountIn != nil && i.AmountIn.ToInt().Sign() < 0 { + return ErrSwapAmountInMustBePositive + } + + if i.AmountOut != nil && i.AmountOut.ToInt().Sign() < 0 { + return ErrSwapAmountOutMustBePositive + } + } + + return i.validateFromLockedAmount() +} + +func (i *RouteInputParams) validateFromLockedAmount() error { + if i.FromLockedAmount == nil || len(i.FromLockedAmount) == 0 { + return nil + } + + var suppNetworks map[uint64]bool + if i.TestnetMode { + suppNetworks = walletCommon.CopyMapGeneric(walletCommon.SupportedTestNetworks, nil).(map[uint64]bool) + } else { + suppNetworks = walletCommon.CopyMapGeneric(walletCommon.SupportedNetworks, nil).(map[uint64]bool) + } + + if suppNetworks == nil { + return ErrCannotCheckLockedAmounts + } + + totalLockedAmount := big.NewInt(0) + excludedChainCount := 0 + + for chainID, amount := range i.FromLockedAmount { + if walletCommon.ArrayContainsElement(chainID, i.DisabledFromChainIDs) { + return ErrDisabledChainFoundAmongLockedNetworks + } + + if i.TestnetMode { + if !walletCommon.SupportedTestNetworks[chainID] { + return ErrLockedAmountNotSupportedForNetwork + } + } else { + if !walletCommon.SupportedNetworks[chainID] { + return ErrLockedAmountNotSupportedForNetwork + } + } + + if amount == nil || amount.ToInt().Sign() < 0 { + return ErrLockedAmountNotNegative + } + + if !(amount.ToInt().Sign() > 0) { + excludedChainCount++ + } + delete(suppNetworks, chainID) + totalLockedAmount = new(big.Int).Add(totalLockedAmount, amount.ToInt()) + } + + if (!i.TestnetMode && excludedChainCount == len(walletCommon.SupportedNetworks)) || + (i.TestnetMode && excludedChainCount == len(walletCommon.SupportedTestNetworks)) { + return ErrLockedAmountExcludesAllSupported + } + + if totalLockedAmount.Cmp(i.AmountIn.ToInt()) > 0 { + return ErrLockedAmountExceedsTotalSendAmount + } + if totalLockedAmount.Cmp(i.AmountIn.ToInt()) < 0 && len(suppNetworks) == 0 { + return ErrLockedAmountLessThanSendAmountAllNetworks + } + return nil +} diff --git a/services/wallet/responses/router_suggested_routes.go b/services/wallet/responses/router_suggested_routes.go new file mode 100644 index 00000000000..2ff0dcd2ad5 --- /dev/null +++ b/services/wallet/responses/router_suggested_routes.go @@ -0,0 +1,15 @@ +package responses + +import ( + "github.com/status-im/status-go/errors" + "github.com/status-im/status-go/services/wallet/router/routes" +) + +type RouterSuggestedRoutes struct { + Uuid string `json:"Uuid"` + Best routes.Route `json:"Best,omitempty"` + Candidates routes.Route `json:"Candidates,omitempty"` + TokenPrice *float64 `json:"TokenPrice,omitempty"` + NativeChainTokenPrice *float64 `json:"NativeChainTokenPrice,omitempty"` + ErrorResponse *errors.ErrorResponse `json:"ErrorResponse,omitempty"` +} diff --git a/services/wallet/router/common.go b/services/wallet/router/common.go new file mode 100644 index 00000000000..ffbca801f63 --- /dev/null +++ b/services/wallet/router/common.go @@ -0,0 +1,55 @@ +package router + +import ( + "github.com/status-im/status-go/services/wallet/common" + "github.com/status-im/status-go/services/wallet/router/routes" +) + +func removeBestRouteFromAllRouters(allRoutes []routes.Route, best routes.Route) []routes.Route { + for i := len(allRoutes) - 1; i >= 0; i-- { + route := allRoutes[i] + routeFound := true + for _, p := range route { + found := false + for _, b := range best { + if p.ProcessorName == b.ProcessorName && + (p.FromChain == nil && b.FromChain == nil || p.FromChain.ChainID == b.FromChain.ChainID) && + (p.ToChain == nil && b.ToChain == nil || p.ToChain.ChainID == b.ToChain.ChainID) && + (p.FromToken == nil && b.FromToken == nil || p.FromToken.Symbol == b.FromToken.Symbol) { + found = true + break + } + } + if !found { + routeFound = false + break + } + } + if routeFound { + return append(allRoutes[:i], allRoutes[i+1:]...) + } + } + + return nil +} + +func getChainPriority(chainID uint64) int { + switch chainID { + case common.EthereumMainnet, common.EthereumSepolia: + return 1 + case common.OptimismMainnet, common.OptimismSepolia: + return 2 + case common.ArbitrumMainnet, common.ArbitrumSepolia: + return 3 + default: + return 0 + } +} + +func getRoutePriority(route routes.Route) int { + priority := 0 + for _, path := range route { + priority += getChainPriority(path.FromChain.ChainID) + } + return priority +} diff --git a/services/wallet/router/errors.go b/services/wallet/router/errors.go index a2886b44d46..3d7d1ac8c7a 100644 --- a/services/wallet/router/errors.go +++ b/services/wallet/router/errors.go @@ -6,31 +6,12 @@ import ( // Abbreviation `WR` for the error code stands for Wallet Router var ( - ErrENSRegisterRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WR-001"), Details: "username and public key are required for ENSRegister"} - ErrENSRegisterTestnetSTTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WR-002"), Details: "only STT is supported for ENSRegister on testnet"} - ErrENSRegisterMainnetSNTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WR-003"), Details: "only SNT is supported for ENSRegister on mainnet"} - ErrENSReleaseRequiresUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WR-004"), Details: "username is required for ENSRelease"} - ErrENSSetPubKeyRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WR-005"), Details: "username and public key are required for ENSSetPubKey"} - ErrStickersBuyRequiresPackID = &errors.ErrorResponse{Code: errors.ErrorCode("WR-006"), Details: "packID is required for StickersBuy"} - ErrSwapRequiresToTokenID = &errors.ErrorResponse{Code: errors.ErrorCode("WR-007"), Details: "toTokenID is required for Swap"} - ErrSwapTokenIDMustBeDifferent = &errors.ErrorResponse{Code: errors.ErrorCode("WR-008"), Details: "tokenID and toTokenID must be different"} - ErrSwapAmountInAmountOutMustBeExclusive = &errors.ErrorResponse{Code: errors.ErrorCode("WR-009"), Details: "only one of amountIn or amountOut can be set"} - ErrSwapAmountInMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WR-010"), Details: "amountIn must be positive"} - ErrSwapAmountOutMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WR-011"), Details: "amountOut must be positive"} - ErrLockedAmountNotSupportedForNetwork = &errors.ErrorResponse{Code: errors.ErrorCode("WR-012"), Details: "locked amount is not supported for the selected network"} - ErrLockedAmountNotNegative = &errors.ErrorResponse{Code: errors.ErrorCode("WR-013"), Details: "locked amount must not be negative"} - ErrLockedAmountExceedsTotalSendAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WR-014"), Details: "locked amount exceeds the total amount to send"} - ErrLockedAmountLessThanSendAmountAllNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-015"), Details: "locked amount is less than the total amount to send, but all networks are locked"} - ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-016"), Details: "not enough token balance, token: %s, chainId: %d"} - ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-017"), Details: "not enough native balance, token: %s, chainId: %d"} - ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-018"), Details: "native token not found"} - ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-019"), Details: "disabled chain found among locked networks"} - ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WR-020"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"} - ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WR-021"), Details: "all supported chains are excluded, routing impossible"} - ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-022"), Details: "token not found"} - ErrNoBestRouteFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-023"), Details: "no best route found"} - ErrCannotCheckBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-024"), Details: "cannot check balance"} - ErrCannotCheckLockedAmounts = &errors.ErrorResponse{Code: errors.ErrorCode("WR-025"), Details: "cannot check locked amounts"} - ErrLowAmountInForHopBridge = &errors.ErrorResponse{Code: errors.ErrorCode("WR-026"), Details: "bonder fee greater than estimated received, a higher amount is needed to cover fees"} - ErrNoPositiveBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-027"), Details: "no positive balance"} + ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-001"), Details: "not enough token balance, token: %s, chainId: %d"} + ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-002"), Details: "not enough native balance, token: %s, chainId: %d"} + ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-003"), Details: "native token not found"} + ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-004"), Details: "token not found"} + ErrNoBestRouteFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-005"), Details: "no best route found"} + 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"} ) diff --git a/services/wallet/router/fees.go b/services/wallet/router/fees/fees.go similarity index 87% rename from services/wallet/router/fees.go rename to services/wallet/router/fees/fees.go index 4dd45f45795..07387ca2599 100644 --- a/services/wallet/router/fees.go +++ b/services/wallet/router/fees/fees.go @@ -1,4 +1,4 @@ -package router +package fees import ( "context" @@ -54,7 +54,7 @@ type SuggestedFeesGwei struct { EIP1559Enabled bool `json:"eip1559Enabled"` } -func (m *MaxFeesLevels) feeFor(mode GasFeeMode) *big.Int { +func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) *big.Int { if mode == GasFeeLow { return m.Low.ToInt() } @@ -66,20 +66,8 @@ func (m *MaxFeesLevels) feeFor(mode GasFeeMode) *big.Int { return m.Medium.ToInt() } -func (s *SuggestedFees) feeFor(mode GasFeeMode) *big.Int { - return s.MaxFeesLevels.feeFor(mode) -} - -func (s *SuggestedFeesGwei) feeFor(mode GasFeeMode) *big.Float { - if mode == GasFeeLow { - return s.MaxFeePerGasLow - } - - if mode == GasFeeHigh { - return s.MaxFeePerGasHigh - } - - return s.MaxFeePerGasMedium +func (s *SuggestedFees) FeeFor(mode GasFeeMode) *big.Int { + return s.MaxFeesLevels.FeeFor(mode) } const inclusionThreshold = 0.95 @@ -102,25 +90,6 @@ type FeeManager struct { RPCClient *rpc.Client } -func weiToGwei(val *big.Int) *big.Float { - result := new(big.Float) - result.SetInt(val) - - unit := new(big.Int) - unit.SetInt64(params.GWei) - - return result.Quo(result, new(big.Float).SetInt(unit)) -} - -func gweiToEth(val *big.Float) *big.Float { - return new(big.Float).Quo(val, big.NewFloat(1000000000)) -} - -func gweiToWei(val *big.Float) *big.Int { - res, _ := new(big.Float).Mul(val, big.NewFloat(1000000000)).Int(nil) - return res -} - func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) { backend, err := f.RPCClient.EthClient(chainID) if err != nil { @@ -168,12 +137,12 @@ func (f *FeeManager) SuggestedFeesGwei(ctx context.Context, chainID uint64) (*Su return nil, err } return &SuggestedFeesGwei{ - GasPrice: weiToGwei(fees.GasPrice), - BaseFee: weiToGwei(fees.BaseFee), - MaxPriorityFeePerGas: weiToGwei(fees.MaxPriorityFeePerGas), - MaxFeePerGasLow: weiToGwei(fees.MaxFeesLevels.Low.ToInt()), - MaxFeePerGasMedium: weiToGwei(fees.MaxFeesLevels.Medium.ToInt()), - MaxFeePerGasHigh: weiToGwei(fees.MaxFeesLevels.High.ToInt()), + GasPrice: common.WeiToGwei(fees.GasPrice), + BaseFee: common.WeiToGwei(fees.BaseFee), + MaxPriorityFeePerGas: common.WeiToGwei(fees.MaxPriorityFeePerGas), + MaxFeePerGasLow: common.WeiToGwei(fees.MaxFeesLevels.Low.ToInt()), + MaxFeePerGasMedium: common.WeiToGwei(fees.MaxFeesLevels.Medium.ToInt()), + MaxFeePerGasHigh: common.WeiToGwei(fees.MaxFeesLevels.High.ToInt()), EIP1559Enabled: fees.EIP1559Enabled, }, nil } diff --git a/services/wallet/router/filter.go b/services/wallet/router/filter.go index be696725fbf..21bc79fa6cd 100644 --- a/services/wallet/router/filter.go +++ b/services/wallet/router/filter.go @@ -2,10 +2,11 @@ package router import ( "math/big" - "reflect" "github.com/ethereum/go-ethereum/common/hexutil" + "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/routes" "go.uber.org/zap" ) @@ -20,7 +21,7 @@ func init() { } } -func filterRoutesV2(routes [][]*PathV2, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) [][]*PathV2 { +func filterRoutes(routes []routes.Route, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route { for i := len(routes) - 1; i >= 0; i-- { routeAmount := big.NewInt(0) for _, p := range routes[i] { @@ -38,34 +39,34 @@ func filterRoutesV2(routes [][]*PathV2, amountIn *big.Int, fromLockedAmount map[ return routes } - routesAfterNetworkCompliance := filterNetworkComplianceV2(routes, fromLockedAmount) - return filterCapacityValidationV2(routesAfterNetworkCompliance, amountIn, fromLockedAmount) + routesAfterNetworkCompliance := filterNetworkCompliance(routes, fromLockedAmount) + return filterCapacityValidation(routesAfterNetworkCompliance, amountIn, fromLockedAmount) } -// filterNetworkComplianceV2 performs the first level of filtering based on network inclusion/exclusion criteria. -func filterNetworkComplianceV2(routes [][]*PathV2, fromLockedAmount map[uint64]*hexutil.Big) [][]*PathV2 { - filteredRoutes := make([][]*PathV2, 0) - if routes == nil || fromLockedAmount == nil { +// filterNetworkCompliance performs the first level of filtering based on network inclusion/exclusion criteria. +func filterNetworkCompliance(allRoutes []routes.Route, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route { + filteredRoutes := make([]routes.Route, 0) + if allRoutes == nil || fromLockedAmount == nil { return filteredRoutes } - fromIncluded, fromExcluded := setupRouteValidationMapsV2(fromLockedAmount) + fromIncluded, fromExcluded := setupRouteValidationMaps(fromLockedAmount) - for _, route := range routes { + for _, route := range allRoutes { if route == nil { continue } // Create fresh copies of the maps for each route check, because they are manipulated - if isValidForNetworkComplianceV2(route, copyMapGeneric(fromIncluded, nil).(map[uint64]bool), copyMapGeneric(fromExcluded, nil).(map[uint64]bool)) { + if isValidForNetworkCompliance(route, common.CopyMapGeneric(fromIncluded, nil).(map[uint64]bool), common.CopyMapGeneric(fromExcluded, nil).(map[uint64]bool)) { filteredRoutes = append(filteredRoutes, route) } } return filteredRoutes } -// isValidForNetworkComplianceV2 checks if a route complies with network inclusion/exclusion criteria. -func isValidForNetworkComplianceV2(route []*PathV2, fromIncluded, fromExcluded map[uint64]bool) bool { +// isValidForNetworkCompliance checks if a route complies with network inclusion/exclusion criteria. +func isValidForNetworkCompliance(route routes.Route, fromIncluded, fromExcluded map[uint64]bool) bool { logger.Debug("Initial inclusion/exclusion maps", zap.Any("fromIncluded", fromIncluded), zap.Any("fromExcluded", fromExcluded), @@ -101,8 +102,8 @@ func isValidForNetworkComplianceV2(route []*PathV2, fromIncluded, fromExcluded m return true } -// setupRouteValidationMapsV2 initializes maps for network inclusion and exclusion based on locked amounts. -func setupRouteValidationMapsV2(fromLockedAmount map[uint64]*hexutil.Big) (map[uint64]bool, map[uint64]bool) { +// setupRouteValidationMaps initializes maps for network inclusion and exclusion based on locked amounts. +func setupRouteValidationMaps(fromLockedAmount map[uint64]*hexutil.Big) (map[uint64]bool, map[uint64]bool) { fromIncluded := make(map[uint64]bool) fromExcluded := make(map[uint64]bool) @@ -116,20 +117,20 @@ func setupRouteValidationMapsV2(fromLockedAmount map[uint64]*hexutil.Big) (map[u return fromIncluded, fromExcluded } -// filterCapacityValidationV2 performs the second level of filtering based on amount and capacity validation. -func filterCapacityValidationV2(routes [][]*PathV2, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) [][]*PathV2 { - filteredRoutes := make([][]*PathV2, 0) +// filterCapacityValidation performs the second level of filtering based on amount and capacity validation. +func filterCapacityValidation(allRoutes []routes.Route, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route { + filteredRoutes := make([]routes.Route, 0) - for _, route := range routes { - if hasSufficientCapacityV2(route, amountIn, fromLockedAmount) { + for _, route := range allRoutes { + if hasSufficientCapacity(route, amountIn, fromLockedAmount) { filteredRoutes = append(filteredRoutes, route) } } return filteredRoutes } -// hasSufficientCapacityV2 checks if a route has sufficient capacity to handle the required amount. -func hasSufficientCapacityV2(route []*PathV2, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) bool { +// hasSufficientCapacity checks if a route has sufficient capacity to handle the required amount. +func hasSufficientCapacity(route routes.Route, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) bool { for _, path := range route { if amount, ok := fromLockedAmount[path.FromChain.ChainID]; ok { if path.AmountIn.ToInt().Cmp(amount.ToInt()) != 0 { @@ -137,7 +138,7 @@ func hasSufficientCapacityV2(route []*PathV2, amountIn *big.Int, fromLockedAmoun return false } requiredAmountIn := new(big.Int).Sub(amountIn, amount.ToInt()) - restAmountIn := calculateRestAmountInV2(route, path) + restAmountIn := calculateRestAmountIn(route, path) logger.Debug("Checking path", zap.Any("path", path)) logger.Debug("Required amount in", zap.String("requiredAmountIn", requiredAmountIn.String())) @@ -153,7 +154,7 @@ func hasSufficientCapacityV2(route []*PathV2, amountIn *big.Int, fromLockedAmoun } // calculateRestAmountIn calculates the remaining amount in for the route excluding the specified path -func calculateRestAmountInV2(route []*PathV2, excludePath *PathV2) *big.Int { +func calculateRestAmountIn(route routes.Route, excludePath *routes.Path) *big.Int { restAmountIn := big.NewInt(0) for _, path := range route { if path != excludePath { @@ -162,22 +163,3 @@ func calculateRestAmountInV2(route []*PathV2, excludePath *PathV2) *big.Int { } return restAmountIn } - -// copyMapGeneric creates a copy of any map, if the deepCopyValue function is provided, it will be used to copy values. -func copyMapGeneric(original interface{}, deepCopyValueFn func(interface{}) interface{}) interface{} { - originalVal := reflect.ValueOf(original) - if originalVal.Kind() != reflect.Map { - return nil - } - - newMap := reflect.MakeMap(originalVal.Type()) - for iter := originalVal.MapRange(); iter.Next(); { - if deepCopyValueFn != nil { - newMap.SetMapIndex(iter.Key(), reflect.ValueOf(deepCopyValueFn(iter.Value().Interface()))) - } else { - newMap.SetMapIndex(iter.Key(), iter.Value()) - } - } - - return newMap.Interface() -} diff --git a/services/wallet/router/filter_test.go b/services/wallet/router/filter_test.go index b513fad561c..ae0a974e729 100644 --- a/services/wallet/router/filter_test.go +++ b/services/wallet/router/filter_test.go @@ -8,6 +8,7 @@ import ( "github.com/status-im/status-go/params" "github.com/status-im/status-go/services/wallet/router/pathprocessor" + "github.com/status-im/status-go/services/wallet/router/routes" "github.com/stretchr/testify/assert" ) @@ -26,24 +27,24 @@ var ( amount4 = hexutil.Big(*big.NewInt(400)) amount5 = hexutil.Big(*big.NewInt(500)) - path0 = &PathV2{FromChain: network4, AmountIn: &amount0} + path0 = &routes.Path{FromChain: network4, AmountIn: &amount0} - pathC1A1 = &PathV2{FromChain: network1, AmountIn: &amount1} + pathC1A1 = &routes.Path{FromChain: network1, AmountIn: &amount1} - pathC2A1 = &PathV2{FromChain: network2, AmountIn: &amount1} - pathC2A2 = &PathV2{FromChain: network2, AmountIn: &amount2} + pathC2A1 = &routes.Path{FromChain: network2, AmountIn: &amount1} + pathC2A2 = &routes.Path{FromChain: network2, AmountIn: &amount2} - pathC3A1 = &PathV2{FromChain: network3, AmountIn: &amount1} - pathC3A2 = &PathV2{FromChain: network3, AmountIn: &amount2} - pathC3A3 = &PathV2{FromChain: network3, AmountIn: &amount3} + pathC3A1 = &routes.Path{FromChain: network3, AmountIn: &amount1} + pathC3A2 = &routes.Path{FromChain: network3, AmountIn: &amount2} + pathC3A3 = &routes.Path{FromChain: network3, AmountIn: &amount3} - pathC4A1 = &PathV2{FromChain: network4, AmountIn: &amount1} - pathC4A4 = &PathV2{FromChain: network4, AmountIn: &amount4} + pathC4A1 = &routes.Path{FromChain: network4, AmountIn: &amount1} + pathC4A4 = &routes.Path{FromChain: network4, AmountIn: &amount4} - pathC5A5 = &PathV2{FromChain: network5, AmountIn: &amount5} + pathC5A5 = &routes.Path{FromChain: network5, AmountIn: &amount5} ) -func routesEqual(t *testing.T, expected, actual [][]*PathV2) bool { +func routesEqual(t *testing.T, expected, actual []routes.Route) bool { if len(expected) != len(actual) { return false } @@ -55,7 +56,7 @@ func routesEqual(t *testing.T, expected, actual [][]*PathV2) bool { return true } -func pathsEqual(t *testing.T, expected, actual []*PathV2) bool { +func pathsEqual(t *testing.T, expected, actual routes.Route) bool { if len(expected) != len(actual) { return false } @@ -67,7 +68,7 @@ func pathsEqual(t *testing.T, expected, actual []*PathV2) bool { return true } -func pathEqual(t *testing.T, expected, actual *PathV2) bool { +func pathEqual(t *testing.T, expected, actual *routes.Path) bool { if expected.FromChain.ChainID != actual.FromChain.ChainID { t.Logf("expected chain ID '%d' , actual chain ID '%d'", expected.FromChain.ChainID, actual.FromChain.ChainID) return false @@ -83,7 +84,7 @@ func pathEqual(t *testing.T, expected, actual *PathV2) bool { return true } -func TestSetupRouteValidationMapsV2(t *testing.T) { +func TestSetupRouteValidationMaps(t *testing.T) { tests := []struct { name string fromLockedAmount map[uint64]*hexutil.Big @@ -161,53 +162,53 @@ func TestSetupRouteValidationMapsV2(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - included, excluded := setupRouteValidationMapsV2(tt.fromLockedAmount) + included, excluded := setupRouteValidationMaps(tt.fromLockedAmount) assert.Equal(t, tt.expectedIncluded, included) assert.Equal(t, tt.expectedExcluded, excluded) }) } } -func TestCalculateRestAmountInV2(t *testing.T) { +func TestCalculateRestAmountIn(t *testing.T) { tests := []struct { name string - route []*PathV2 - excludePath *PathV2 + route routes.Route + excludePath *routes.Path expected *big.Int }{ { name: "Exclude pathC1A1", - route: []*PathV2{pathC1A1, pathC2A2, pathC3A3}, + route: routes.Route{pathC1A1, pathC2A2, pathC3A3}, excludePath: pathC1A1, expected: big.NewInt(500), // 200 + 300 }, { name: "Exclude pathC2A2", - route: []*PathV2{pathC1A1, pathC2A2, pathC3A3}, + route: routes.Route{pathC1A1, pathC2A2, pathC3A3}, excludePath: pathC2A2, expected: big.NewInt(400), // 100 + 300 }, { name: "Exclude pathC3A3", - route: []*PathV2{pathC1A1, pathC2A2, pathC3A3}, + route: routes.Route{pathC1A1, pathC2A2, pathC3A3}, excludePath: pathC3A3, expected: big.NewInt(300), // 100 + 200 }, { name: "Single path, exclude that path", - route: []*PathV2{pathC1A1}, + route: routes.Route{pathC1A1}, excludePath: pathC1A1, expected: big.NewInt(0), // No other paths }, { name: "Empty route", - route: []*PathV2{}, + route: routes.Route{}, excludePath: pathC1A1, expected: big.NewInt(0), // No paths }, { name: "Empty route, with nil exclude", - route: []*PathV2{}, + route: routes.Route{}, excludePath: nil, expected: big.NewInt(0), // No paths }, @@ -215,65 +216,65 @@ func TestCalculateRestAmountInV2(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := calculateRestAmountInV2(tt.route, tt.excludePath) + result := calculateRestAmountIn(tt.route, tt.excludePath) assert.Equal(t, tt.expected, result) }) } } -func TestIsValidForNetworkComplianceV2(t *testing.T) { +func TestIsValidForNetworkCompliance(t *testing.T) { tests := []struct { name string - route []*PathV2 + route routes.Route fromIncluded map[uint64]bool fromExcluded map[uint64]bool expectedResult bool }{ { name: "Route with all included chain IDs", - route: []*PathV2{pathC1A1, pathC2A2}, + route: routes.Route{pathC1A1, pathC2A2}, fromIncluded: map[uint64]bool{1: true, 2: true}, fromExcluded: map[uint64]bool{}, expectedResult: true, }, { name: "Route with fromExcluded only", - route: []*PathV2{pathC1A1, pathC2A2}, + route: routes.Route{pathC1A1, pathC2A2}, fromIncluded: map[uint64]bool{}, fromExcluded: map[uint64]bool{3: false, 4: false}, expectedResult: true, }, { name: "Route without excluded chain IDs", - route: []*PathV2{pathC1A1, pathC2A2}, + route: routes.Route{pathC1A1, pathC2A2}, fromIncluded: map[uint64]bool{1: false, 2: false}, fromExcluded: map[uint64]bool{3: false, 4: false}, expectedResult: true, }, { name: "Route with an excluded chain ID", - route: []*PathV2{pathC1A1, pathC3A3}, + route: routes.Route{pathC1A1, pathC3A3}, fromIncluded: map[uint64]bool{1: false, 2: false}, fromExcluded: map[uint64]bool{3: false, 4: false}, expectedResult: false, }, { name: "Route missing one included chain ID", - route: []*PathV2{pathC1A1}, + route: routes.Route{pathC1A1}, fromIncluded: map[uint64]bool{1: false, 2: false}, fromExcluded: map[uint64]bool{}, expectedResult: false, }, { name: "Route with no fromIncluded or fromExcluded", - route: []*PathV2{pathC1A1, pathC2A2}, + route: routes.Route{pathC1A1, pathC2A2}, fromIncluded: map[uint64]bool{}, fromExcluded: map[uint64]bool{}, expectedResult: true, }, { name: "Empty route", - route: []*PathV2{}, + route: routes.Route{}, fromIncluded: map[uint64]bool{1: false, 2: false}, fromExcluded: map[uint64]bool{3: false, 4: false}, expectedResult: false, @@ -282,23 +283,23 @@ func TestIsValidForNetworkComplianceV2(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := isValidForNetworkComplianceV2(tt.route, tt.fromIncluded, tt.fromExcluded) + result := isValidForNetworkCompliance(tt.route, tt.fromIncluded, tt.fromExcluded) assert.Equal(t, tt.expectedResult, result) }) } } -func TestHasSufficientCapacityV2(t *testing.T) { +func TestHasSufficientCapacity(t *testing.T) { tests := []struct { name string - route []*PathV2 + route routes.Route amountIn *big.Int fromLockedAmount map[uint64]*hexutil.Big expected bool }{ { name: "All paths meet required amount", - route: []*PathV2{pathC1A1, pathC2A2, pathC3A3}, + route: routes.Route{pathC1A1, pathC2A2, pathC3A3}, amountIn: big.NewInt(600), fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3}, expected: true, @@ -308,7 +309,7 @@ func TestHasSufficientCapacityV2(t *testing.T) { /* { name: "A path does not meet required amount", - route: []*PathV2{pathC1A1, pathC2A2, pathC3A3}, + route: routes.Route{pathC1A1, pathC2A2, pathC3A3}, amountIn: big.NewInt(600), fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 4: &amount4}, expected: false, @@ -316,42 +317,42 @@ func TestHasSufficientCapacityV2(t *testing.T) { */ { name: "No fromLockedAmount", - route: []*PathV2{pathC1A1, pathC2A2, pathC3A3}, + route: routes.Route{pathC1A1, pathC2A2, pathC3A3}, amountIn: big.NewInt(600), fromLockedAmount: map[uint64]*hexutil.Big{}, expected: true, }, { name: "Single path meets required amount", - route: []*PathV2{pathC1A1}, + route: routes.Route{pathC1A1}, amountIn: big.NewInt(100), fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1}, expected: true, }, { name: "Single path does not meet required amount", - route: []*PathV2{pathC1A1}, + route: routes.Route{pathC1A1}, amountIn: big.NewInt(200), fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1}, expected: false, }, { name: "Path meets required amount with excess", - route: []*PathV2{pathC1A1, pathC2A2}, + route: routes.Route{pathC1A1, pathC2A2}, amountIn: big.NewInt(250), fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2}, expected: true, }, { name: "Path does not meet required amount due to insufficient rest", - route: []*PathV2{pathC1A1, pathC2A2, pathC4A4}, + route: routes.Route{pathC1A1, pathC2A2, pathC4A4}, amountIn: big.NewInt(800), fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 4: &amount4}, expected: false, }, { name: "Empty route", - route: []*PathV2{}, + route: routes.Route{}, amountIn: big.NewInt(500), fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2}, expected: true, @@ -360,22 +361,22 @@ func TestHasSufficientCapacityV2(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := hasSufficientCapacityV2(tt.route, tt.amountIn, tt.fromLockedAmount) + result := hasSufficientCapacity(tt.route, tt.amountIn, tt.fromLockedAmount) assert.Equal(t, tt.expected, result) }) } } -func TestFilterNetworkComplianceV2(t *testing.T) { +func TestFilterNetworkCompliance(t *testing.T) { tests := []struct { name string - routes [][]*PathV2 + routes []routes.Route fromLockedAmount map[uint64]*hexutil.Big - expected [][]*PathV2 + expected []routes.Route }{ { name: "Mixed routes with valid and invalid paths", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1}, {FromChain: network3}, @@ -394,7 +395,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { 1: (*hexutil.Big)(big.NewInt(100)), 2: (*hexutil.Big)(big.NewInt(0)), }, - expected: [][]*PathV2{ + expected: []routes.Route{ { {FromChain: network1}, {FromChain: network3}, @@ -403,7 +404,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { }, { name: "All valid routes", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1}, {FromChain: network3}, @@ -416,7 +417,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(100)), }, - expected: [][]*PathV2{ + expected: []routes.Route{ { {FromChain: network1}, {FromChain: network3}, @@ -429,7 +430,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { }, { name: "All invalid routes", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network2}, {FromChain: network3}, @@ -443,19 +444,19 @@ func TestFilterNetworkComplianceV2(t *testing.T) { 1: (*hexutil.Big)(big.NewInt(100)), 2: (*hexutil.Big)(big.NewInt(0)), }, - expected: [][]*PathV2{}, + expected: []routes.Route{}, }, { name: "Empty routes", - routes: [][]*PathV2{}, + routes: []routes.Route{}, fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(100)), }, - expected: [][]*PathV2{}, + expected: []routes.Route{}, }, { name: "No locked amounts", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1}, {FromChain: network2}, @@ -466,7 +467,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { }, }, fromLockedAmount: map[uint64]*hexutil.Big{}, - expected: [][]*PathV2{ + expected: []routes.Route{ { {FromChain: network1}, {FromChain: network2}, @@ -479,7 +480,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { }, { name: "Single route with mixed valid and invalid paths", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1}, {FromChain: network2}, @@ -490,11 +491,11 @@ func TestFilterNetworkComplianceV2(t *testing.T) { 1: (*hexutil.Big)(big.NewInt(100)), 2: (*hexutil.Big)(big.NewInt(0)), }, - expected: [][]*PathV2{}, + expected: []routes.Route{}, }, { name: "Routes with duplicate chain IDs", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1}, {FromChain: network1}, @@ -504,7 +505,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(100)), }, - expected: [][]*PathV2{ + expected: []routes.Route{ { {FromChain: network1}, {FromChain: network1}, @@ -514,7 +515,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { }, { name: "Minimum and maximum chain IDs", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: ¶ms.Network{ChainID: 0}}, {FromChain: ¶ms.Network{ChainID: ^uint64(0)}}, @@ -524,7 +525,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { 0: (*hexutil.Big)(big.NewInt(100)), ^uint64(0): (*hexutil.Big)(big.NewInt(100)), }, - expected: [][]*PathV2{ + expected: []routes.Route{ { {FromChain: ¶ms.Network{ChainID: 0}}, {FromChain: ¶ms.Network{ChainID: ^uint64(0)}}, @@ -533,34 +534,34 @@ func TestFilterNetworkComplianceV2(t *testing.T) { }, { name: "Large number of routes", - routes: func() [][]*PathV2 { - var routes [][]*PathV2 + routes: func() []routes.Route { + var routes1 []routes.Route for i := 0; i < 1000; i++ { - routes = append(routes, []*PathV2{ + routes1 = append(routes1, routes.Route{ {FromChain: ¶ms.Network{ChainID: uint64(i + 1)}}, {FromChain: ¶ms.Network{ChainID: uint64(i + 1001)}}, }) } - return routes + return routes1 }(), fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(100)), 1001: (*hexutil.Big)(big.NewInt(100)), }, - expected: func() [][]*PathV2 { - var routes [][]*PathV2 + expected: func() []routes.Route { + var routes1 []routes.Route for i := 0; i < 1; i++ { - routes = append(routes, []*PathV2{ + routes1 = append(routes1, routes.Route{ {FromChain: ¶ms.Network{ChainID: uint64(i + 1)}}, {FromChain: ¶ms.Network{ChainID: uint64(i + 1001)}}, }) } - return routes + return routes1 }(), }, { name: "Routes with missing data", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: nil}, {FromChain: network2}, @@ -574,11 +575,11 @@ func TestFilterNetworkComplianceV2(t *testing.T) { 1: (*hexutil.Big)(big.NewInt(100)), 2: (*hexutil.Big)(big.NewInt(0)), }, - expected: [][]*PathV2{}, + expected: []routes.Route{}, }, { name: "Consistency check", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1}, {FromChain: network2}, @@ -591,7 +592,7 @@ func TestFilterNetworkComplianceV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(100)), }, - expected: [][]*PathV2{ + expected: []routes.Route{ { {FromChain: network1}, {FromChain: network2}, @@ -604,101 +605,101 @@ func TestFilterNetworkComplianceV2(t *testing.T) { }, { name: "Routes without excluded chain IDs, missing included path", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC2A2, pathC3A3}, }, fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2}, - expected: [][]*PathV2{ + expected: []routes.Route{ {pathC1A1, pathC2A2}, }, }, { name: "Routes with an excluded chain ID", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC2A2, pathC3A3, path0}, }, fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 4: &amount0}, - expected: [][]*PathV2{ + expected: []routes.Route{ {pathC1A1, pathC2A2}, }, }, { name: "Routes with all included chain IDs", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2, pathC3A3}, }, fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3}, - expected: [][]*PathV2{ + expected: []routes.Route{ {pathC1A1, pathC2A2, pathC3A3}, }, }, { name: "Routes missing one included chain ID", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC1A1}, }, fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3}, - expected: [][]*PathV2{}, + expected: []routes.Route{}, }, { name: "Routes with no fromLockedAmount", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC2A2, pathC3A3}, }, fromLockedAmount: map[uint64]*hexutil.Big{}, - expected: [][]*PathV2{ + expected: []routes.Route{ {pathC1A1, pathC2A2}, {pathC2A2, pathC3A3}, }, }, { name: "Routes with fromExcluded only", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC2A2, pathC3A3}, }, fromLockedAmount: map[uint64]*hexutil.Big{4: &amount0}, - expected: [][]*PathV2{ + expected: []routes.Route{ {pathC1A1, pathC2A2}, {pathC2A2, pathC3A3}, }, }, { name: "Routes with all excluded chain IDs", - routes: [][]*PathV2{ + routes: []routes.Route{ {path0, pathC1A1}, {path0, pathC2A2}, }, fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3, 4: &amount0}, - expected: [][]*PathV2{}, + expected: []routes.Route{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Logf("Original Routes: %+v\n", tt.routes) - filteredRoutes := filterNetworkComplianceV2(tt.routes, tt.fromLockedAmount) + filteredRoutes := filterNetworkCompliance(tt.routes, tt.fromLockedAmount) t.Logf("Filtered Routes: %+v\n", filteredRoutes) assert.Equal(t, tt.expected, filteredRoutes) }) } } -func TestFilterCapacityValidationV2(t *testing.T) { +func TestFilterCapacityValidation(t *testing.T) { tests := []struct { name string - routes [][]*PathV2 + routes []routes.Route amountIn *big.Int fromLockedAmount map[uint64]*hexutil.Big - expectedRoutes [][]*PathV2 + expectedRoutes []routes.Route }{ { name: "Sufficient capacity with multiple paths", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))}, @@ -717,7 +718,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(50)), }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))}, @@ -731,7 +732,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { }, { name: "Insufficient capacity", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))}, @@ -742,11 +743,11 @@ func TestFilterCapacityValidationV2(t *testing.T) { 1: (*hexutil.Big)(big.NewInt(50)), 2: (*hexutil.Big)(big.NewInt(50)), }, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, { name: "Exact capacity match", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))}, @@ -757,7 +758,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { 1: (*hexutil.Big)(big.NewInt(100)), 2: (*hexutil.Big)(big.NewInt(50)), }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))}, @@ -766,7 +767,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { }, { name: "No locked amounts", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))}, @@ -774,7 +775,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { }, amountIn: big.NewInt(150), fromLockedAmount: map[uint64]*hexutil.Big{}, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))}, @@ -783,7 +784,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { }, { name: "Single route with sufficient capacity", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))}, @@ -793,7 +794,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(50)), }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))}, {FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))}, @@ -802,7 +803,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { }, { name: "Single route with inappropriately locked amount", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, }, @@ -811,11 +812,11 @@ func TestFilterCapacityValidationV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(50)), }, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, { name: "Single route with insufficient capacity", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))}, }, @@ -824,20 +825,20 @@ func TestFilterCapacityValidationV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(50)), }, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, { name: "Empty routes", - routes: [][]*PathV2{}, + routes: []routes.Route{}, amountIn: big.NewInt(150), fromLockedAmount: map[uint64]*hexutil.Big{ 1: (*hexutil.Big)(big.NewInt(50)), }, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, { name: "Partial locked amounts", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))}, {FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))}, @@ -850,7 +851,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { 2: (*hexutil.Big)(big.NewInt(0)), // Excluded path 3: (*hexutil.Big)(big.NewInt(100)), }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))}, {FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))}, @@ -860,7 +861,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { }, { name: "Mixed networks with sufficient capacity", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, {FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(200))}, @@ -871,7 +872,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { 1: (*hexutil.Big)(big.NewInt(100)), 3: (*hexutil.Big)(big.NewInt(200)), }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, {FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(200))}, @@ -880,7 +881,7 @@ func TestFilterCapacityValidationV2(t *testing.T) { }, { name: "Mixed networks with insufficient capacity", - routes: [][]*PathV2{ + routes: []routes.Route{ { {FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))}, {FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))}, @@ -891,13 +892,13 @@ func TestFilterCapacityValidationV2(t *testing.T) { 1: (*hexutil.Big)(big.NewInt(50)), 3: (*hexutil.Big)(big.NewInt(100)), }, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - filteredRoutes := filterCapacityValidationV2(tt.routes, tt.amountIn, tt.fromLockedAmount) + filteredRoutes := filterCapacityValidation(tt.routes, tt.amountIn, tt.fromLockedAmount) if !routesEqual(t, tt.expectedRoutes, filteredRoutes) { t.Errorf("Expected: %+v, Actual: %+v", tt.expectedRoutes, filteredRoutes) } @@ -905,53 +906,53 @@ func TestFilterCapacityValidationV2(t *testing.T) { } } -func TestFilterRoutesV2(t *testing.T) { +func TestFilterRoutes(t *testing.T) { tests := []struct { name string - routes [][]*PathV2 + routes []routes.Route amountIn *big.Int fromLockedAmount map[uint64]*hexutil.Big - expectedRoutes [][]*PathV2 + expectedRoutes []routes.Route }{ { name: "Empty fromLockedAmount and routes don't match amountIn", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC3A3, pathC4A4}, }, amountIn: big.NewInt(150), fromLockedAmount: map[uint64]*hexutil.Big{}, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, { name: "Empty fromLockedAmount and sigle route match amountIn", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC3A3, pathC4A4}, }, amountIn: big.NewInt(300), fromLockedAmount: map[uint64]*hexutil.Big{}, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ {pathC1A1, pathC2A2}, }, }, { name: "Empty fromLockedAmount and more routes match amountIn", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC3A3, pathC4A4}, {pathC1A1, pathC2A1, pathC3A1}, }, amountIn: big.NewInt(300), fromLockedAmount: map[uint64]*hexutil.Big{}, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC1A1, pathC2A1, pathC3A1}, }, }, { name: "All paths appear in fromLockedAmount but not within a single route", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC3A3}, {pathC2A2, pathC4A4}, }, @@ -962,11 +963,11 @@ func TestFilterRoutesV2(t *testing.T) { 3: &amount3, 4: &amount4, }, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, { name: "Mixed valid and invalid routes I", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC2A2, pathC3A3}, {pathC1A1, pathC4A4}, @@ -977,13 +978,13 @@ func TestFilterRoutesV2(t *testing.T) { 1: &amount1, 2: &amount2, }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ {pathC1A1, pathC2A2}, }, }, { name: "Mixed valid and invalid routes II", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC2A2, pathC3A3}, {pathC1A1, pathC4A4}, @@ -993,14 +994,14 @@ func TestFilterRoutesV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: &amount1, }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ {pathC1A1, pathC2A2}, {pathC1A1, pathC2A1, pathC3A1}, }, }, { name: "All invalid routes", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC2A2, pathC3A3}, {pathC4A4, pathC5A5}, }, @@ -1008,11 +1009,11 @@ func TestFilterRoutesV2(t *testing.T) { fromLockedAmount: map[uint64]*hexutil.Big{ 1: &amount1, }, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, { name: "Single valid route", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC3A3}, {pathC2A2, pathC3A3}, }, @@ -1021,13 +1022,13 @@ func TestFilterRoutesV2(t *testing.T) { 1: &amount1, 3: &amount3, }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ {pathC1A1, pathC3A3}, }, }, { name: "Route with mixed valid and invalid paths I", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC2A2, pathC3A3}, }, amountIn: big.NewInt(300), @@ -1035,11 +1036,11 @@ func TestFilterRoutesV2(t *testing.T) { 1: &amount1, 2: &amount0, // This path should be filtered out due to being excluded via a zero amount }, - expectedRoutes: [][]*PathV2{}, + expectedRoutes: []routes.Route{}, }, { name: "Route with mixed valid and invalid paths II", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC3A3}, }, amountIn: big.NewInt(400), @@ -1047,13 +1048,13 @@ func TestFilterRoutesV2(t *testing.T) { 1: &amount1, 2: &amount0, // This path should be filtered out due to being excluded via a zero amount, 0 value locked means this chain is disabled }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ {pathC1A1, pathC3A3}, }, }, { name: "Route with mixed valid and invalid paths III", - routes: [][]*PathV2{ + routes: []routes.Route{ {pathC1A1, pathC3A3}, {pathC1A1, pathC3A2, pathC4A1}, }, @@ -1062,7 +1063,7 @@ func TestFilterRoutesV2(t *testing.T) { 1: &amount1, 2: &amount0, // This path should be filtered out due to being excluded via a zero amount, 0 value locked means this chain is disabled }, - expectedRoutes: [][]*PathV2{ + expectedRoutes: []routes.Route{ {pathC1A1, pathC3A3}, {pathC1A1, pathC3A2, pathC4A1}, }, @@ -1072,7 +1073,7 @@ func TestFilterRoutesV2(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Logf("Original Routes: %+v\n", tt.routes) - filteredRoutes := filterRoutesV2(tt.routes, tt.amountIn, tt.fromLockedAmount) + filteredRoutes := filterRoutes(tt.routes, tt.amountIn, tt.fromLockedAmount) t.Logf("Filtered Routes: %+v\n", filteredRoutes) assert.Equal(t, tt.expectedRoutes, filteredRoutes) }) diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index 28ecd7cf1b4..278812187d5 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -2,276 +2,87 @@ package router import ( "context" - "errors" - "math" + "fmt" "math/big" "sort" "strings" "sync" - "github.com/ethereum/go-ethereum" - "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/ethereum/go-ethereum/log" + "github.com/status-im/status-go/errors" "github.com/status-im/status-go/params" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/ens" "github.com/status-im/status-go/services/stickers" "github.com/status-im/status-go/services/wallet/async" - "github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/collectibles" walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/market" + "github.com/status-im/status-go/services/wallet/requests" + "github.com/status-im/status-go/services/wallet/responses" + "github.com/status-im/status-go/services/wallet/router/fees" "github.com/status-im/status-go/services/wallet/router/pathprocessor" + "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" walletToken "github.com/status-im/status-go/services/wallet/token" + "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" ) -// ////////////////////////////////////////////////////////////////////////////// -// TODO: once new router is in place, remove this `router.go` file, -// rename and make `router_v2.go` file the main and only file -// ////////////////////////////////////////////////////////////////////////////// - -// TODO: remove the following two consts once we fully move to routerV2 -const EstimateUsername = "RandomUsername" -const EstimatePubKey = "0x04bb2024ce5d72e45d4a4f8589ae657ef9745855006996115a23a1af88d536cf02c0524a585fce7bfa79d6a9669af735eda6205d6c7e5b3cdc2b8ff7b2fa1f0b56" - -type Path struct { - BridgeName string - From *params.Network - To *params.Network - MaxAmountIn *hexutil.Big - AmountIn *hexutil.Big - AmountInLocked bool - AmountOut *hexutil.Big - GasAmount uint64 - GasFees *SuggestedFeesGwei - BonderFees *hexutil.Big - TokenFees *big.Float - Cost *big.Float - EstimatedTime TransactionEstimation - ApprovalRequired bool - ApprovalGasFees *big.Float - ApprovalAmountRequired *hexutil.Big - ApprovalContractAddress *common.Address -} - -func (p *Path) Equal(o *Path) bool { - return p.From.ChainID == o.From.ChainID && p.To.ChainID == o.To.ChainID -} - -type Graph []*Node - -type Node struct { - Path *Path - Children Graph -} - -func newNode(path *Path) *Node { - return &Node{Path: path, Children: make(Graph, 0)} -} - -func buildGraph(AmountIn *big.Int, routes []*Path, level int, sourceChainIDs []uint64) Graph { - graph := make(Graph, 0) - for _, route := range routes { - found := false - for _, chainID := range sourceChainIDs { - if chainID == route.From.ChainID { - found = true - break - } - } - if found { - continue - } - node := newNode(route) - - newRoutes := make([]*Path, 0) - for _, r := range routes { - if route.Equal(r) { - continue - } - newRoutes = append(newRoutes, r) - } - - newAmountIn := new(big.Int).Sub(AmountIn, route.MaxAmountIn.ToInt()) - if newAmountIn.Sign() > 0 { - newSourceChainIDs := make([]uint64, len(sourceChainIDs)) - copy(newSourceChainIDs, sourceChainIDs) - newSourceChainIDs = append(newSourceChainIDs, route.From.ChainID) - node.Children = buildGraph(newAmountIn, newRoutes, level+1, newSourceChainIDs) - - if len(node.Children) == 0 { - continue - } - } - - graph = append(graph, node) +var ( + routerTask = async.TaskType{ + ID: 1, + Policy: async.ReplacementPolicyCancelOld, } +) - return graph +type amountOption struct { + amount *big.Int + locked bool + subtractFees bool } -func (n Node) buildAllRoutes() [][]*Path { - res := make([][]*Path, 0) - - if len(n.Children) == 0 && n.Path != nil { - res = append(res, []*Path{n.Path}) - } - - for _, node := range n.Children { - for _, route := range node.buildAllRoutes() { - extendedRoute := route - if n.Path != nil { - extendedRoute = append([]*Path{n.Path}, route...) - } - res = append(res, extendedRoute) - } - } - - return res +func makeBalanceKey(chainID uint64, symbol string) string { + return fmt.Sprintf("%d-%s", chainID, symbol) } -func filterRoutes(routes [][]*Path, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) [][]*Path { - if len(fromLockedAmount) == 0 { - return routes - } - - filteredRoutesLevel1 := make([][]*Path, 0) - for _, route := range routes { - routeOk := true - fromIncluded := make(map[uint64]bool) - fromExcluded := make(map[uint64]bool) - for chainID, amount := range fromLockedAmount { - if amount.ToInt().Cmp(pathprocessor.ZeroBigIntValue) == 0 { - fromExcluded[chainID] = false - } else { - fromIncluded[chainID] = false - } - - } - for _, path := range route { - if _, ok := fromExcluded[path.From.ChainID]; ok { - routeOk = false - break - } - if _, ok := fromIncluded[path.From.ChainID]; ok { - fromIncluded[path.From.ChainID] = true - } - } - for _, value := range fromIncluded { - if !value { - routeOk = false - break - } - } - - if routeOk { - filteredRoutesLevel1 = append(filteredRoutesLevel1, route) - } - } - - filteredRoutesLevel2 := make([][]*Path, 0) - for _, route := range filteredRoutesLevel1 { - routeOk := true - for _, path := range route { - if amount, ok := fromLockedAmount[path.From.ChainID]; ok { - requiredAmountIn := new(big.Int).Sub(amountIn, amount.ToInt()) - restAmountIn := big.NewInt(0) - - for _, otherPath := range route { - if path.Equal(otherPath) { - continue - } - restAmountIn = new(big.Int).Add(otherPath.MaxAmountIn.ToInt(), restAmountIn) - } - if restAmountIn.Cmp(requiredAmountIn) >= 0 { - path.AmountIn = amount - path.AmountInLocked = true - } else { - routeOk = false - break - } - } - } - if routeOk { - filteredRoutesLevel2 = append(filteredRoutesLevel2, route) - } - } - - return filteredRoutesLevel2 -} - -func findBest(routes [][]*Path) []*Path { - var best []*Path - bestCost := big.NewFloat(math.Inf(1)) - for _, route := range routes { - currentCost := big.NewFloat(0) - for _, path := range route { - currentCost = new(big.Float).Add(currentCost, path.Cost) - } - - if currentCost.Cmp(bestCost) == -1 { - best = route - bestCost = currentCost - } - } - - return best +type ProcessorError struct { + ProcessorName string + Error error } type SuggestedRoutes struct { - Best []*Path - Candidates []*Path + Uuid string + Best routes.Route + Candidates routes.Route TokenPrice float64 NativeChainTokenPrice float64 } -func newSuggestedRoutes( - amountIn *big.Int, - candidates []*Path, - fromLockedAmount map[uint64]*hexutil.Big, -) *SuggestedRoutes { - if len(candidates) == 0 { - return &SuggestedRoutes{ - Candidates: candidates, - Best: candidates, - } - } +type Router struct { + rpcClient *rpc.Client + tokenManager *token.Manager + marketManager *market.Manager + collectiblesService *collectibles.Service + collectiblesManager *collectibles.Manager + ensService *ens.Service + stickersService *stickers.Service + feesManager *fees.FeeManager + pathProcessors map[string]pathprocessor.PathProcessor + scheduler *async.Scheduler - node := &Node{ - Path: nil, - Children: buildGraph(amountIn, candidates, 0, []uint64{}), - } - routes := node.buildAllRoutes() - routes = filterRoutes(routes, amountIn, fromLockedAmount) - best := findBest(routes) + activeBalanceMap sync.Map // map[string]*big.Int - if len(best) > 0 { - sort.Slice(best, func(i, j int) bool { - return best[i].AmountInLocked - }) - rest := new(big.Int).Set(amountIn) - for _, path := range best { - diff := new(big.Int).Sub(rest, path.MaxAmountIn.ToInt()) - if diff.Cmp(pathprocessor.ZeroBigIntValue) >= 0 { - path.AmountIn = (*hexutil.Big)(path.MaxAmountIn.ToInt()) - } else { - path.AmountIn = (*hexutil.Big)(new(big.Int).Set(rest)) - } - rest.Sub(rest, path.AmountIn.ToInt()) - } - } + activeRoutesMutex sync.Mutex + activeRoutes *SuggestedRoutes - return &SuggestedRoutes{ - Candidates: candidates, - Best: best, - } + lastInputParamsMutex sync.Mutex + lastInputParams *requests.RouteInputParams + + clientsForUpdatesPerChains sync.Map } func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *token.Manager, marketManager *market.Manager, @@ -286,9 +97,11 @@ func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, token collectiblesManager: collectiblesManager, ensService: ensService, stickersService: stickersService, - feesManager: &FeeManager{rpcClient}, - pathProcessors: processors, - scheduler: async.NewScheduler(), + feesManager: &fees.FeeManager{ + RPCClient: rpcClient, + }, + pathProcessors: processors, + scheduler: async.NewScheduler(), } } @@ -300,7 +113,7 @@ func (r *Router) Stop() { r.scheduler.Stop() } -func (r *Router) GetFeesManager() *FeeManager { +func (r *Router) GetFeesManager() *fees.FeeManager { return r.feesManager } @@ -308,430 +121,815 @@ func (r *Router) GetPathProcessors() map[string]pathprocessor.PathProcessor { return r.pathProcessors } -func arrayContainsElement[T comparable](el T, arr []T) bool { - for _, e := range arr { - if e == el { - return true - } +func (r *Router) SetTestBalanceMap(balanceMap map[string]*big.Int) { + for k, v := range balanceMap { + r.activeBalanceMap.Store(k, v) } - return false -} - -func isSingleChainOperation(fromChains []*params.Network, toChains []*params.Network) bool { - return len(fromChains) == 1 && - len(toChains) == 1 && - fromChains[0].ChainID == toChains[0].ChainID } -type Router struct { - rpcClient *rpc.Client - tokenManager *token.Manager - marketManager *market.Manager - collectiblesService *collectibles.Service - collectiblesManager *collectibles.Manager - ensService *ens.Service - stickersService *stickers.Service - feesManager *FeeManager - pathProcessors map[string]pathprocessor.PathProcessor - scheduler *async.Scheduler -} +func newSuggestedRoutes( + uuid string, + amountIn *big.Int, + candidates routes.Route, + fromLockedAmount map[uint64]*hexutil.Big, + tokenPrice float64, + nativeChainTokenPrice float64, +) (*SuggestedRoutes, []routes.Route) { + suggestedRoutes := &SuggestedRoutes{ + Uuid: uuid, + Candidates: candidates, + TokenPrice: tokenPrice, + NativeChainTokenPrice: nativeChainTokenPrice, + } + if len(candidates) == 0 { + return suggestedRoutes, nil + } -func (r *Router) requireApproval(ctx context.Context, sendType SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) ( - bool, *big.Int, uint64, uint64, error) { - if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() { - return false, nil, 0, 0, nil + node := &routes.Node{ + Path: nil, + Children: routes.BuildGraph(amountIn, candidates, 0, []uint64{}), } + allRoutes := node.BuildAllRoutes() + allRoutes = filterRoutes(allRoutes, amountIn, fromLockedAmount) - if params.FromToken.IsNative() { - return false, nil, 0, 0, nil + if len(allRoutes) > 0 { + sort.Slice(allRoutes, func(i, j int) bool { + iRoute := getRoutePriority(allRoutes[i]) + jRoute := getRoutePriority(allRoutes[j]) + return iRoute <= jRoute + }) } - contractMaker, err := contracts.NewContractMaker(r.rpcClient) - if err != nil { - return false, nil, 0, 0, err + return suggestedRoutes, allRoutes +} + +func sendRouterResult(uuid string, result interface{}, err error) { + routesResponse := responses.RouterSuggestedRoutes{ + Uuid: uuid, } - contract, err := contractMaker.NewERC20(params.FromChain.ChainID, params.FromToken.Address) if err != nil { - return false, nil, 0, 0, err + errorResponse := errors.CreateErrorResponseFromError(err) + routesResponse.ErrorResponse = errorResponse.(*errors.ErrorResponse) } - if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress { - return false, nil, 0, 0, nil + if suggestedRoutes, ok := result.(*SuggestedRoutes); ok && suggestedRoutes != nil { + routesResponse.Best = suggestedRoutes.Best + routesResponse.Candidates = suggestedRoutes.Candidates + routesResponse.TokenPrice = &suggestedRoutes.TokenPrice + routesResponse.NativeChainTokenPrice = &suggestedRoutes.NativeChainTokenPrice } - if params.TestsMode { - return true, params.AmountIn, params.TestApprovalGasEstimation, params.TestApprovalL1Fee, nil - } + signal.SendWalletEvent(signal.SuggestedRoutes, routesResponse) +} - allowance, err := contract.Allowance(&bind.CallOpts{ - Context: ctx, - }, params.FromAddr, *approvalContractAddress) +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) { + sendRouterResult(input.Uuid, result, err) + }) +} - if err != nil { - return false, nil, 0, 0, err - } +func (r *Router) StopSuggestedRoutesAsyncCalculation() { + r.unsubscribeFeesUpdateAccrossAllChains() + r.scheduler.Stop() +} - if allowance.Cmp(params.AmountIn) >= 0 { - return false, nil, 0, 0, nil - } +func (r *Router) StopSuggestedRoutesCalculation() { + r.unsubscribeFeesUpdateAccrossAllChains() +} - ethClient, err := r.rpcClient.EthClient(params.FromChain.ChainID) - if err != nil { - return false, nil, 0, 0, err +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 { + if clearable, ok := processor.(pathprocessor.PathProcessorClearable); ok { + clearable.Clear() + } } - erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) + 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 false, nil, 0, 0, err + return nil, errors.CreateErrorResponseFromError(err) } - data, err := erc20ABI.Pack("approve", approvalContractAddress, params.AmountIn) + input.TestnetMode = testnetMode + + err = input.Validate() if err != nil { - return false, nil, 0, 0, err + return nil, errors.CreateErrorResponseFromError(err) } - estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{ - From: params.FromAddr, - To: ¶ms.FromToken.Address, - Value: pathprocessor.ZeroBigIntValue, - Data: data, - }) + selectedFromChains, selectedToChains, err := r.getSelectedChains(input) if err != nil { - return false, nil, 0, 0, err + return nil, errors.CreateErrorResponseFromError(err) } - // fetching l1 fee - var l1Fee uint64 - oracleContractAddress, err := gaspriceoracle.ContractAddress(params.FromChain.ChainID) - if err == nil { - oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient) + err = r.prepareBalanceMapForTokenOnChains(ctx, input, selectedFromChains) + // return only if there are no balances, otherwise try to resolve the candidates for chains we know the balances for + noBalanceOnAnyChain := true + r.activeBalanceMap.Range(func(key, value interface{}) bool { + if value.(*big.Int).Cmp(pathprocessor.ZeroBigIntValue) > 0 { + noBalanceOnAnyChain = false + return false + } + return true + }) + if noBalanceOnAnyChain { if err != nil { - return false, nil, 0, 0, err + return nil, errors.CreateErrorResponseFromError(err) } - - callOpt := &bind.CallOpts{} - - l1FeeResult, _ := oracleContract.GetL1Fee(callOpt, data) - l1Fee = l1FeeResult.Uint64() + return nil, ErrNoPositiveBalance } - return true, params.AmountIn, estimate, l1Fee, nil -} - -func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.Token, account common.Address) (*big.Int, error) { - client, err := r.rpcClient.EthClient(chainID) + candidates, processorErrors, err := r.resolveCandidates(ctx, input, selectedFromChains, selectedToChains) if err != nil { - return nil, err + return nil, errors.CreateErrorResponseFromError(err) } - return r.tokenManager.GetBalance(ctx, client, account, token.Address) -} - -func (r *Router) getERC1155Balance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) { - tokenID, success := new(big.Int).SetString(token.Symbol, 10) - if !success { - return nil, errors.New("failed to convert token symbol to big.Int") - } + suggestedRoutes, err = r.resolveRoutes(ctx, input, candidates) - balances, err := r.collectiblesManager.FetchERC1155Balances( - ctx, - account, - walletCommon.ChainID(network.ChainID), - token.Address, - []*bigint.BigInt{&bigint.BigInt{Int: tokenID}}, - ) - if err != nil { - return nil, err + if err == nil && (suggestedRoutes == nil || len(suggestedRoutes.Best) == 0) { + // No best route found, but no error given. + if len(processorErrors) > 0 { + // Return one of the path processor errors if present. + // Give precedence to the custom error message. + for _, processorError := range processorErrors { + if processorError.Error != nil && pathprocessor.IsCustomError(processorError.Error) { + err = processorError.Error + break + } + } + if err == nil { + err = errors.CreateErrorResponseFromError(processorErrors[0].Error) + } + } else { + err = ErrNoBestRouteFound + } } - if len(balances) != 1 || balances[0] == nil { - return nil, errors.New("invalid ERC1155 balance fetch response") + mapError := func(err error) error { + if err == nil { + return nil + } + pattern := "insufficient funds for gas * price + value: address " + addressIndex := strings.Index(errors.DetailsFromError(err), pattern) + if addressIndex != -1 { + addressIndex += len(pattern) + walletCommon.HexAddressLength + return errors.CreateErrorResponseFromError(&errors.ErrorResponse{ + Code: errors.ErrorCodeFromError(err), + Details: errors.DetailsFromError(err)[:addressIndex], + }) + } + return err } - - return balances[0].Int, nil + // map some errors to more user-friendly messages + return suggestedRoutes, mapError(err) } -func (r *Router) SuggestedRoutes( - ctx context.Context, - sendType SendType, - addrFrom common.Address, - addrTo common.Address, - amountIn *big.Int, - tokenID string, - toTokenID string, - disabledFromChainIDs, - disabledToChainIDs, - preferedChainIDs []uint64, - gasFeeMode GasFeeMode, - fromLockedAmount map[uint64]*hexutil.Big, - testnetMode bool, -) (*SuggestedRoutes, error) { +// prepareBalanceMapForTokenOnChains prepares the balance map for passed address, where the key is in format "chainID-tokenSymbol" and +// value is the balance of the token. Native token (EHT) is always added to the balance map. +func (r *Router) prepareBalanceMapForTokenOnChains(ctx context.Context, input *requests.RouteInputParams, selectedFromChains []*params.Network) (err error) { + // clear the active balance map + r.activeBalanceMap = sync.Map{} - networks, err := r.rpcClient.NetworkManager.Get(false) - if err != nil { - return nil, err + if input.TestsMode { + for k, v := range input.TestParams.BalanceMap { + r.activeBalanceMap.Store(k, v) + } + return nil } - prices, err := sendType.FetchPrices(r.marketManager, tokenID) - if err != nil { - return nil, err + chainError := func(chainId uint64, token string, intErr error) { + if err == nil { + err = fmt.Errorf("chain %d, token %s: %w", chainId, token, intErr) + } else { + err = fmt.Errorf("%s; chain %d, token %s: %w", err.Error(), chainId, token, intErr) + } } - var ( - group = async.NewAtomicGroup(ctx) - mu sync.Mutex - candidates = make([]*Path, 0) - ) - for networkIdx := range networks { - network := networks[networkIdx] - if network.IsTest != testnetMode { + for _, chain := range selectedFromChains { + // check token existence + token := input.SendType.FindToken(r.tokenManager, r.collectiblesService, input.AddrFrom, chain, input.TokenID) + if token == nil { + chainError(chain.ChainID, input.TokenID, ErrTokenNotFound) continue } + // check native token existence + nativeToken := r.tokenManager.FindToken(chain, chain.NativeCurrencySymbol) + if nativeToken == nil { + chainError(chain.ChainID, chain.NativeCurrencySymbol, ErrNativeTokenNotFound) + continue + } + + // add token balance for the chain + var tokenBalance *big.Int + if input.SendType == sendtype.ERC721Transfer { + tokenBalance = big.NewInt(1) + } else if input.SendType == sendtype.ERC1155Transfer { + tokenBalance, err = r.getERC1155Balance(ctx, chain, token, input.AddrFrom) + if err != nil { + chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err)) + } + } else { + tokenBalance, err = r.getBalance(ctx, chain.ChainID, token, input.AddrFrom) + if err != nil { + chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err)) + } + } + // add only if balance is not nil + if tokenBalance != nil { + r.activeBalanceMap.Store(makeBalanceKey(chain.ChainID, token.Symbol), tokenBalance) + } - if arrayContainsElement(network.ChainID, disabledFromChainIDs) { + if token.IsNative() { continue } - if !sendType.isAvailableFor(network) { + // add native token balance for the chain + nativeBalance, err := r.getBalance(ctx, chain.ChainID, nativeToken, input.AddrFrom) + if err != nil { + chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err)) + } + // add only if balance is not nil + if nativeBalance != nil { + r.activeBalanceMap.Store(makeBalanceKey(chain.ChainID, nativeToken.Symbol), nativeBalance) + } + } + + return +} + +func (r *Router) getSelectedUnlockedChains(input *requests.RouteInputParams, processingChain *params.Network, selectedFromChains []*params.Network) []*params.Network { + selectedButNotLockedChains := []*params.Network{processingChain} // always add the processing chain at the beginning + for _, net := range selectedFromChains { + if net.ChainID == processingChain.ChainID { continue } + if _, ok := input.FromLockedAmount[net.ChainID]; !ok { + selectedButNotLockedChains = append(selectedButNotLockedChains, net) + } + } + return selectedButNotLockedChains +} - token := sendType.FindToken(r.tokenManager, r.collectiblesService, addrFrom, network, tokenID) - if token == nil { +func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input *requests.RouteInputParams, amountToSplit *big.Int, processingChain *params.Network, + selectedFromChains []*params.Network) map[uint64][]amountOption { + selectedButNotLockedChains := r.getSelectedUnlockedChains(input, processingChain, selectedFromChains) + + crossChainAmountOptions := make(map[uint64][]amountOption) + for _, chain := range selectedButNotLockedChains { + var ( + ok bool + tokenBalance *big.Int + ) + + value, ok := r.activeBalanceMap.Load(makeBalanceKey(chain.ChainID, input.TokenID)) + if !ok { + continue + } + tokenBalance, ok = value.(*big.Int) + if !ok { continue } - var toToken *walletToken.Token - if sendType == Swap { - toToken = sendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, toTokenID) + if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if tokenBalance.Cmp(amountToSplit) <= 0 { + crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{ + amount: tokenBalance, + locked: false, + subtractFees: true, // for chains where we're taking the full balance, we want to subtract the fees + }) + amountToSplit = new(big.Int).Sub(amountToSplit, tokenBalance) + } else if amountToSplit.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{ + amount: amountToSplit, + locked: false, + }) + // break since amountToSplit is fully addressed and the rest is 0 + break + } } + } - nativeToken := r.tokenManager.FindToken(network, network.NativeCurrencySymbol) - if nativeToken == nil { + return crossChainAmountOptions +} + +func (r *Router) getCrossChainsOptionsForSendingAmount(input *requests.RouteInputParams, selectedFromChains []*params.Network) map[uint64][]amountOption { + // All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount + // was properly set and if there is something unexpected it will return an error and we will not reach this point + finalCrossChainAmountOptions := make(map[uint64][]amountOption) // represents all possible amounts that can be sent from the "from" chain + + for _, selectedFromChain := range selectedFromChains { + + amountLocked := false + amountToSend := input.AmountIn.ToInt() + + if amountToSend.Cmp(pathprocessor.ZeroBigIntValue) == 0 { + finalCrossChainAmountOptions[selectedFromChain.ChainID] = append(finalCrossChainAmountOptions[selectedFromChain.ChainID], amountOption{ + amount: amountToSend, + locked: false, + }) continue } - group.Add(func(c context.Context) error { - gasFees, err := r.feesManager.SuggestedFeesGwei(ctx, network.ChainID) - if err != nil { - return err + lockedAmount, fromChainLocked := input.FromLockedAmount[selectedFromChain.ChainID] + if fromChainLocked { + amountToSend = lockedAmount.ToInt() + amountLocked = true + } else if len(input.FromLockedAmount) > 0 { + for chainID, lockedAmount := range input.FromLockedAmount { + if chainID == selectedFromChain.ChainID { + continue + } + amountToSend = new(big.Int).Sub(amountToSend, lockedAmount.ToInt()) } + } - // Default value is 1 as in case of erc721 as we built the token we are sure the account owns it - balance := big.NewInt(1) - if sendType == ERC1155Transfer { - balance, err = r.getERC1155Balance(ctx, network, token, addrFrom) - if err != nil { - return err - } - } else if sendType != ERC721Transfer { - balance, err = r.getBalance(ctx, network.ChainID, token, addrFrom) - if err != nil { - return err - } + if amountToSend.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + // add full amount always, cause we want to check for balance errors at the end of the routing algorithm + // TODO: once we introduce bettwer error handling and start checking for the balance at the beginning of the routing algorithm + // we can remove this line and optimize the routing algorithm more + finalCrossChainAmountOptions[selectedFromChain.ChainID] = append(finalCrossChainAmountOptions[selectedFromChain.ChainID], amountOption{ + amount: amountToSend, + locked: amountLocked, + }) + + if amountLocked { + continue } - maxAmountIn := (*hexutil.Big)(balance) - if amount, ok := fromLockedAmount[network.ChainID]; ok { - if amount.ToInt().Cmp(balance) == 1 { - return errors.New("locked amount cannot be bigger than balance") + // If the amount that need to be send is bigger than the balance on the chain, then we want to check options if that + // amount can be splitted and sent across multiple chains. + if input.SendType == sendtype.Transfer && len(selectedFromChains) > 1 { + // All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount + // was properly set and if there is something unexpected it will return an error and we will not reach this point + amountToSplitAccrossChains := new(big.Int).Set(amountToSend) + + crossChainAmountOptions := r.getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input, amountToSend, selectedFromChain, selectedFromChains) + + // sum up all the allocated amounts accorss all chains + allocatedAmount := big.NewInt(0) + for _, amountOptions := range crossChainAmountOptions { + for _, amountOption := range amountOptions { + allocatedAmount = new(big.Int).Add(allocatedAmount, amountOption.amount) + } + } + + // if the allocated amount is the same as the amount that need to be sent, then we can add the options to the finalCrossChainAmountOptions + if allocatedAmount.Cmp(amountToSplitAccrossChains) == 0 { + for cID, amountOptions := range crossChainAmountOptions { + finalCrossChainAmountOptions[cID] = append(finalCrossChainAmountOptions[cID], amountOptions...) + } } - maxAmountIn = amount } + } + } + + return finalCrossChainAmountOptions +} + +func (r *Router) findOptionsForSendingAmount(input *requests.RouteInputParams, selectedFromChains []*params.Network) (map[uint64][]amountOption, error) { + + crossChainAmountOptions := r.getCrossChainsOptionsForSendingAmount(input, selectedFromChains) + + // filter out duplicates values for the same chain + for chainID, amountOptions := range crossChainAmountOptions { + uniqueAmountOptions := make(map[string]amountOption) + for _, amountOption := range amountOptions { + uniqueAmountOptions[amountOption.amount.String()] = amountOption + } + + crossChainAmountOptions[chainID] = make([]amountOption, 0) + for _, amountOption := range uniqueAmountOptions { + crossChainAmountOptions[chainID] = append(crossChainAmountOptions[chainID], amountOption) + } + } - nativeBalance, err := r.getBalance(ctx, network.ChainID, nativeToken, addrFrom) + return crossChainAmountOptions, nil +} + +func (r *Router) getSelectedChains(input *requests.RouteInputParams) (selectedFromChains []*params.Network, selectedToChains []*params.Network, err error) { + var networks []*params.Network + networks, err = r.rpcClient.NetworkManager.Get(false) + if err != nil { + return nil, nil, errors.CreateErrorResponseFromError(err) + } + + for _, network := range networks { + if network.IsTest != input.TestnetMode { + continue + } + + if !walletCommon.ArrayContainsElement(network.ChainID, input.DisabledFromChainIDs) { + selectedFromChains = append(selectedFromChains, network) + } + + if !walletCommon.ArrayContainsElement(network.ChainID, input.DisabledToChainIDs) { + selectedToChains = append(selectedToChains, network) + } + } + + return selectedFromChains, selectedToChains, nil +} + +func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInputParams, selectedFromChains []*params.Network, + selectedToChains []*params.Network) (candidates routes.Route, processorErrors []*ProcessorError, err error) { + var ( + testsMode = input.TestsMode && input.TestParams != nil + group = async.NewAtomicGroup(ctx) + mu sync.Mutex + ) + + crossChainAmountOptions, err := r.findOptionsForSendingAmount(input, selectedFromChains) + if err != nil { + return nil, nil, errors.CreateErrorResponseFromError(err) + } + + appendProcessorErrorFn := func(processorName string, sendType sendtype.SendType, fromChainID uint64, toChainID uint64, amount *big.Int, err error) { + log.Error("router.resolveCandidates error", "processor", processorName, "sendType", sendType, "fromChainId: ", fromChainID, "toChainId", toChainID, "amount", amount, "err", err) + mu.Lock() + defer mu.Unlock() + processorErrors = append(processorErrors, &ProcessorError{ + ProcessorName: processorName, + Error: err, + }) + } + + appendPathFn := func(path *routes.Path) { + mu.Lock() + defer mu.Unlock() + candidates = append(candidates, path) + } + + for networkIdx := range selectedFromChains { + network := selectedFromChains[networkIdx] + + if !input.SendType.IsAvailableFor(network) { + continue + } + + var ( + token *walletToken.Token + toToken *walletToken.Token + ) + + if testsMode { + token = input.TestParams.TokenFrom + } else { + token = input.SendType.FindToken(r.tokenManager, r.collectiblesService, input.AddrFrom, network, input.TokenID) + } + if token == nil { + continue + } + + if input.SendType == sendtype.Swap { + toToken = input.SendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID) + } + + var fetchedFees *fees.SuggestedFees + if testsMode { + fetchedFees = input.TestParams.SuggestedFees + } else { + fetchedFees, err = r.feesManager.SuggestedFees(ctx, network.ChainID) if err != nil { - return err + continue } - maxFees := gasFees.feeFor(gasFeeMode) - - estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, gweiToWei(maxFees)) - for _, pProcessor := range r.pathProcessors { - // Skip processors that are added because of the Router V2, to not break the current functionality - if pProcessor.Name() == pathprocessor.ProcessorENSRegisterName || - pProcessor.Name() == pathprocessor.ProcessorENSReleaseName || - pProcessor.Name() == pathprocessor.ProcessorENSPublicKeyName || - pProcessor.Name() == pathprocessor.ProcessorStickersBuyName { - continue - } - - if !sendType.canUseProcessor(pProcessor) { - continue - } + } - for _, dest := range networks { - if dest.IsTest != testnetMode { + group.Add(func(c context.Context) error { + for _, amountOption := range crossChainAmountOptions[network.ChainID] { + for _, pProcessor := range r.pathProcessors { + // With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route + // once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function. + // This also applies to including another (Celer) bridge in the calculation. + // TODO: + // this algorithm, includeing finding the best route, has to be updated to include more bridges and one (for now) or more swap options + // it means that candidates should not be treated linearly, but improve the logic to have multiple routes with different processors of the same type. + // Example: + // Routes for sending SNT from Ethereum to Optimism can be: + // 1. Swap SNT(mainnet) to ETH(mainnet); then bridge via Hop ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination + // 2. Swap SNT(mainnet) to ETH(mainnet); then bridge via Celer ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination + // 3. Swap SNT(mainnet) to USDC(mainnet); then bridge via Hop USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination + // 4. Swap SNT(mainnet) to USDC(mainnet); then bridge via Celer USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination + // 5. ... + // 6. ... + // + // With the current routing algorithm atm we're not able to generate all possible routes. + if !input.SendType.CanUseProcessor(pProcessor) { continue } - if !sendType.isAvailableFor(network) { + // if we're doing a single chain operation, we can skip bridge processors + if walletCommon.IsSingleChainOperation(selectedFromChains, selectedToChains) && pathprocessor.IsProcessorBridge(pProcessor.Name()) { continue } - if !sendType.isAvailableBetween(network, dest) { + if !input.SendType.ProcessZeroAmountInProcessor(amountOption.amount, input.AmountOut.ToInt(), pProcessor.Name()) { continue } - if len(preferedChainIDs) > 0 && !arrayContainsElement(dest.ChainID, preferedChainIDs) { - continue - } - if arrayContainsElement(dest.ChainID, disabledToChainIDs) { - continue - } + for _, dest := range selectedToChains { - processorInputParams := pathprocessor.ProcessorInputParams{ - FromChain: network, - ToChain: dest, - FromToken: token, - ToToken: toToken, - ToAddr: addrTo, - FromAddr: addrFrom, - AmountIn: amountIn, - } + if !input.SendType.IsAvailableFor(network) { + continue + } - can, err := pProcessor.AvailableFor(processorInputParams) - if err != nil || !can { - continue - } - if maxAmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) == 0 { - continue - } + if !input.SendType.IsAvailableBetween(network, dest) { + continue + } - bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams) - if err != nil { - continue - } - if bonderFees.Cmp(pathprocessor.ZeroBigIntValue) != 0 { - if maxAmountIn.ToInt().Cmp(amountIn) >= 0 { - if bonderFees.Cmp(amountIn) >= 0 { - continue - } - } else { - if bonderFees.Cmp(maxAmountIn.ToInt()) >= 0 { - 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(), } - } - gasLimit := uint64(0) - if sendType.isTransfer(false) { - gasLimit, err = pProcessor.EstimateGas(processorInputParams) + 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) if err != nil { + appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) + continue + } + if !can { continue } - } else { - gasLimit = sendType.EstimateGas(r.ensService, r.stickersService, network, addrFrom, tokenID) - } - approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams) - if err != nil { - continue - } - approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, sendType, &approvalContractAddress, processorInputParams) - if err != nil { - 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 + } - var l1GasFeeWei uint64 - if sendType.needL1Fee() { - txInputData, err := pProcessor.PackTxInputData(processorInputParams) + gasLimit, err := pProcessor.EstimateGas(processorInputParams) if err != nil { + appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) continue } - l1GasFeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData) - l1GasFeeWei += l1ApprovalFee - } + 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 + } - gasFees.L1GasFee = weiToGwei(big.NewInt(int64(l1GasFeeWei))) + 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 + } + } + } - requiredNativeBalance := new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(gasLimit))) - requiredNativeBalance.Add(requiredNativeBalance, new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(approvalGasLimit)))) - requiredNativeBalance.Add(requiredNativeBalance, big.NewInt(int64(l1GasFeeWei))) // add l1Fee to requiredNativeBalance, in case of L1 chain l1Fee is 0 + amountOut, err := pProcessor.CalculateAmountOut(processorInputParams) + if err != nil { + appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) + continue + } - if nativeBalance.Cmp(requiredNativeBalance) <= 0 { - continue - } + maxFeesPerGas := fetchedFees.FeeFor(input.GasFeeMode) - // Removed the required fees from maxAMount in case of native token tx - if token.IsNative() { - maxAmountIn = (*hexutil.Big)(new(big.Int).Sub(maxAmountIn.ToInt(), requiredNativeBalance)) - } + 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) + continue + } - ethPrice := big.NewFloat(prices["ETH"]) - - approvalGasFees := new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat((float64(approvalGasLimit)))) - - approvalGasCost := new(big.Float) - approvalGasCost.Mul(approvalGasFees, ethPrice) - - l1GasCost := new(big.Float) - l1GasCost.Mul(gasFees.L1GasFee, ethPrice) - - gasCost := new(big.Float) - gasCost.Mul(new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat(float64(gasLimit))), ethPrice) - - tokenFeesAsFloat := new(big.Float).Quo( - new(big.Float).SetInt(tokenFees), - big.NewFloat(math.Pow(10, float64(token.Decimals))), - ) - tokenCost := new(big.Float) - tokenCost.Mul(tokenFeesAsFloat, big.NewFloat(prices[tokenID])) - - cost := new(big.Float) - cost.Add(tokenCost, gasCost) - cost.Add(cost, approvalGasCost) - cost.Add(cost, l1GasCost) - mu.Lock() - candidates = append(candidates, &Path{ - BridgeName: pProcessor.Name(), - From: network, - To: dest, - MaxAmountIn: maxAmountIn, - AmountIn: (*hexutil.Big)(pathprocessor.ZeroBigIntValue), - AmountOut: (*hexutil.Big)(pathprocessor.ZeroBigIntValue), - GasAmount: gasLimit, - GasFees: gasFees, - BonderFees: (*hexutil.Big)(bonderFees), - TokenFees: tokenFeesAsFloat, - Cost: cost, - EstimatedTime: estimatedTime, - ApprovalRequired: approvalRequired, - ApprovalGasFees: approvalGasFees, - ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired), - ApprovalContractAddress: &approvalContractAddress, - }) - mu.Unlock() + appendPathFn(path) + } } } return nil }) } + sort.Slice(candidates, func(i, j int) bool { + iChain := getChainPriority(candidates[i].FromChain.ChainID) + jChain := getChainPriority(candidates[j].FromChain.ChainID) + return iChain <= jChain + }) + group.Wait() + return candidates, processorErrors, 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) + r.activeBalanceMap.Range(func(k, v interface{}) bool { + balanceMapCopy[k.(string)] = new(big.Int).Set(v.(*big.Int)) + return true + }) + if balanceMapCopy == nil { + return false, ErrCannotCheckBalance + } - suggestedRoutes := newSuggestedRoutes(amountIn, candidates, fromLockedAmount) - suggestedRoutes.TokenPrice = prices[tokenID] - suggestedRoutes.NativeChainTokenPrice = prices["ETH"] - for _, path := range suggestedRoutes.Best { - processorInputParams := pathprocessor.ProcessorInputParams{ - FromChain: path.From, - ToChain: path.To, - AmountIn: path.AmountIn.ToInt(), - FromToken: &token.Token{ - Symbol: tokenID, - }, - ToToken: &token.Token{ - Symbol: toTokenID, - }, - } - - amountOut, err := r.pathProcessors[path.BridgeName].CalculateAmountOut(processorInputParams) + // check the best route for the required balances + for _, path := range bestRoute { + tokenKey := makeBalanceKey(path.FromChain.ChainID, path.FromToken.Symbol) + if tokenBalance, ok := balanceMapCopy[tokenKey]; ok { + if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + hasPositiveBalance = true + } + } + + if path.ProcessorName == pathprocessor.ProcessorBridgeHopName { + if path.TxBonderFees.ToInt().Cmp(path.AmountOut.ToInt()) > 0 { + return hasPositiveBalance, ErrLowAmountInForHopBridge + } + } + + if path.RequiredTokenBalance != nil && path.RequiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { + if tokenBalance, ok := balanceMapCopy[tokenKey]; ok { + if tokenBalance.Cmp(path.RequiredTokenBalance) == -1 { + err := &errors.ErrorResponse{ + Code: ErrNotEnoughTokenBalance.Code, + Details: fmt.Sprintf(ErrNotEnoughTokenBalance.Details, path.FromToken.Symbol, path.FromChain.ChainID), + } + return hasPositiveBalance, err + } + balanceMapCopy[tokenKey].Sub(tokenBalance, path.RequiredTokenBalance) + } else { + return hasPositiveBalance, ErrTokenNotFound + } + } + + ethKey := makeBalanceKey(path.FromChain.ChainID, pathprocessor.EthSymbol) + if nativeBalance, ok := balanceMapCopy[ethKey]; ok { + if nativeBalance.Cmp(path.RequiredNativeBalance) == -1 { + err := &errors.ErrorResponse{ + Code: ErrNotEnoughNativeBalance.Code, + Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, path.FromChain.ChainID), + } + return hasPositiveBalance, err + } + balanceMapCopy[ethKey].Sub(nativeBalance, path.RequiredNativeBalance) + } else { + return hasPositiveBalance, ErrNativeTokenNotFound + } + } + + return hasPositiveBalance, nil +} + +func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputParams, candidates routes.Route) (suggestedRoutes *SuggestedRoutes, err error) { + var prices map[string]float64 + if input.TestsMode { + prices = input.TestParams.TokenPrices + } else { + prices, err = input.SendType.FetchPrices(r.marketManager, input.TokenID) if err != nil { - continue + return nil, errors.CreateErrorResponseFromError(err) + } + } + + tokenPrice := prices[input.TokenID] + nativeTokenPrice := prices[pathprocessor.EthSymbol] + + var allRoutes []routes.Route + suggestedRoutes, allRoutes = newSuggestedRoutes(input.Uuid, input.AmountIn.ToInt(), candidates, input.FromLockedAmount, tokenPrice, nativeTokenPrice) + + defer func() { + if suggestedRoutes.Best != nil && len(suggestedRoutes.Best) > 0 { + sort.Slice(suggestedRoutes.Best, func(i, j int) bool { + iChain := getChainPriority(suggestedRoutes.Best[i].FromChain.ChainID) + jChain := getChainPriority(suggestedRoutes.Best[j].FromChain.ChainID) + return iChain <= jChain + }) + } + }() + + var ( + bestRoute routes.Route + lastBestRouteWithPositiveBalance routes.Route + lastBestRouteErr error + ) + + for len(allRoutes) > 0 { + bestRoute = routes.FindBestRoute(allRoutes, tokenPrice, nativeTokenPrice) + var hasPositiveBalance bool + hasPositiveBalance, err = r.checkBalancesForTheBestRoute(ctx, bestRoute) + + if err != nil { + // If it's about transfer or bridge and there is more routes, but on the best (cheapest) one there is not enugh balance + // we shold check other routes even though there are not the cheapest ones + if input.SendType == sendtype.Transfer || + input.SendType == sendtype.Bridge { + if hasPositiveBalance { + lastBestRouteWithPositiveBalance = bestRoute + lastBestRouteErr = err + } + + if len(allRoutes) > 1 { + allRoutes = removeBestRouteFromAllRouters(allRoutes, bestRoute) + continue + } else { + break + } + } + } + + break + } + + // if none of the routes have positive balance, we should return the last best route with positive balance + if err != nil && lastBestRouteWithPositiveBalance != nil { + bestRoute = lastBestRouteWithPositiveBalance + err = lastBestRouteErr + } + + if len(bestRoute) > 0 { + // At this point we have to do the final check and update the amountIn (subtracting fees) if complete balance is going to be sent for native token (ETH) + for _, path := range bestRoute { + if path.SubtractFees && path.FromToken.IsNative() { + path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.TxFee.ToInt()) + if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.TxL1Fee.ToInt()) + } + if path.ApprovalRequired { + path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.ApprovalFee.ToInt()) + if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.ApprovalL1Fee.ToInt()) + } + } + } } - path.AmountOut = (*hexutil.Big)(amountOut) } + suggestedRoutes.Best = bestRoute - return suggestedRoutes, nil + return suggestedRoutes, err } diff --git a/services/wallet/router/router_helper.go b/services/wallet/router/router_helper.go new file mode 100644 index 00000000000..b373ccff116 --- /dev/null +++ b/services/wallet/router/router_helper.go @@ -0,0 +1,254 @@ +package router + +import ( + "context" + "errors" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum" + "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, error) { + if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() { + return false, nil, nil + } + + if params.FromToken.IsNative() { + return false, nil, nil + } + + contractMaker, err := contracts.NewContractMaker(r.rpcClient) + if err != nil { + return false, nil, err + } + + contract, err := contractMaker.NewERC20(params.FromChain.ChainID, params.FromToken.Address) + if err != nil { + return false, nil, err + } + + if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress { + return false, nil, nil + } + + if params.TestsMode { + return true, params.AmountIn, nil + } + + allowance, err := contract.Allowance(&bind.CallOpts{ + Context: ctx, + }, params.FromAddr, *approvalContractAddress) + + if err != nil { + return false, nil, err + } + + if allowance.Cmp(params.AmountIn) >= 0 { + return false, nil, nil + } + + 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 []byte{}, err + } + + 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 0, err + } + + 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 0, err + } + + ethClient, err := r.rpcClient.EthClient(chainID) + if err != nil { + return 0, err + } + + var l1Fee uint64 + oracleContractAddress, err := gaspriceoracle.ContractAddress(chainID) + if err == nil { + oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient) + if err != nil { + return 0, err + } + + callOpt := &bind.CallOpts{} + + l1FeeResult, _ := oracleContract.GetL1Fee(callOpt, data) + l1Fee = l1FeeResult.Uint64() + } + + return l1Fee, nil +} + +func (r *Router) getERC1155Balance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) { + tokenID, success := new(big.Int).SetString(token.Symbol, 10) + if !success { + return nil, errors.New("failed to convert token symbol to big.Int") + } + + balances, err := r.collectiblesManager.FetchERC1155Balances( + ctx, + account, + walletCommon.ChainID(network.ChainID), + token.Address, + []*bigint.BigInt{&bigint.BigInt{Int: tokenID}}, + ) + if err != nil { + return nil, err + } + + if len(balances) != 1 || balances[0] == nil { + return nil, errors.New("invalid ERC1155 balance fetch response") + } + + return balances[0].Int, nil +} + +func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.Token, account common.Address) (*big.Int, error) { + client, err := r.rpcClient.EthClient(chainID) + if err != nil { + return nil, err + } + + 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_v2_test.go b/services/wallet/router/router_test.go similarity index 87% rename from services/wallet/router/router_v2_test.go rename to services/wallet/router/router_test.go index ae1e76a5d6b..8990539a65e 100644 --- a/services/wallet/router/router_v2_test.go +++ b/services/wallet/router/router_test.go @@ -10,7 +10,9 @@ import ( "github.com/status-im/status-go/appdatabase" "github.com/status-im/status-go/params" "github.com/status-im/status-go/rpc" + "github.com/status-im/status-go/services/wallet/responses" "github.com/status-im/status-go/services/wallet/router/pathprocessor" + "github.com/status-im/status-go/services/wallet/router/routes" "github.com/status-im/status-go/signal" "github.com/status-im/status-go/t/helpers" @@ -58,7 +60,7 @@ func amountOptionsMapsEqual(map1, map2 map[uint64][]amountOption) bool { return true } -func assertPathsEqual(t *testing.T, expected, actual []*PathV2) { +func assertPathsEqual(t *testing.T, expected, actual routes.Route) { assert.Equal(t, len(expected), len(actual)) if len(expected) == 0 { return @@ -124,19 +126,19 @@ func setupRouter(t *testing.T) (*Router, func()) { return router, cleanTmpDb } -type suggestedRoutesV2ResponseEnvelope struct { - Type string `json:"type"` - Routes SuggestedRoutesV2Response `json:"event"` +type routerSuggestedRoutesEnvelope struct { + Type string `json:"type"` + Routes responses.RouterSuggestedRoutes `json:"event"` } -func setupSignalHandler(t *testing.T) (chan SuggestedRoutesV2Response, func()) { - suggestedRoutesCh := make(chan SuggestedRoutesV2Response) +func setupSignalHandler(t *testing.T) (chan responses.RouterSuggestedRoutes, func()) { + suggestedRoutesCh := make(chan responses.RouterSuggestedRoutes) signalHandler := signal.MobileSignalHandler(func(data []byte) { var envelope signal.Envelope err := json.Unmarshal(data, &envelope) assert.NoError(t, err) if envelope.Type == string(signal.SuggestedRoutes) { - var response suggestedRoutesV2ResponseEnvelope + var response routerSuggestedRoutesEnvelope err := json.Unmarshal(data, &response) assert.NoError(t, err) @@ -153,7 +155,7 @@ func setupSignalHandler(t *testing.T) (chan SuggestedRoutesV2Response, func()) { return suggestedRoutesCh, closeFn } -func TestRouterV2(t *testing.T) { +func TestRouter(t *testing.T) { router, cleanTmpDb := setupRouter(t) defer cleanTmpDb() @@ -165,7 +167,7 @@ func TestRouterV2(t *testing.T) { // Test blocking endpoints for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - routes, err := router.SuggestedRoutesV2(context.Background(), tt.input) + routes, err := router.SuggestedRoutes(context.Background(), tt.input) if tt.expectedError != nil { assert.Error(t, err) @@ -184,7 +186,7 @@ func TestRouterV2(t *testing.T) { // Test async endpoints for _, tt := range tests { - router.SuggestedRoutesV2Async(tt.input) + router.SuggestedRoutesAsync(tt.input) select { case asyncRoutes := <-suggestedRoutesCh: @@ -198,7 +200,7 @@ func TestRouterV2(t *testing.T) { } } -func TestNoBalanceForTheBestRouteRouterV2(t *testing.T) { +func TestNoBalanceForTheBestRouteRouter(t *testing.T) { router, cleanTmpDb := setupRouter(t) defer cleanTmpDb() @@ -211,7 +213,7 @@ func TestNoBalanceForTheBestRouteRouterV2(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - routes, err := router.SuggestedRoutesV2(context.Background(), tt.input) + routes, err := router.SuggestedRoutes(context.Background(), tt.input) if tt.expectedError != nil { assert.Error(t, err) @@ -236,7 +238,7 @@ func TestNoBalanceForTheBestRouteRouterV2(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - router.SuggestedRoutesV2Async(tt.input) + router.SuggestedRoutesAsync(tt.input) select { case asyncRoutes := <-suggestedRoutesCh: @@ -266,7 +268,8 @@ func TestAmountOptions(t *testing.T) { selectedFromChains, _, err := router.getSelectedChains(tt.input) assert.NoError(t, err) - amountOptions, err := router.findOptionsForSendingAmount(tt.input, selectedFromChains, tt.input.testParams.balanceMap) + router.SetTestBalanceMap(tt.input.TestParams.BalanceMap) + amountOptions, err := router.findOptionsForSendingAmount(tt.input, selectedFromChains) assert.NoError(t, err) assert.Equal(t, len(tt.expectedAmountOptions), len(amountOptions)) diff --git a/services/wallet/router/router_v2_test_data.go b/services/wallet/router/router_test_data.go similarity index 71% rename from services/wallet/router/router_v2_test_data.go rename to services/wallet/router/router_test_data.go index b177c6d4836..4e63ab4cba4 100644 --- a/services/wallet/router/router_v2_test_data.go +++ b/services/wallet/router/router_test_data.go @@ -12,7 +12,11 @@ import ( "github.com/status-im/status-go/errors" "github.com/status-im/status-go/params" walletCommon "github.com/status-im/status-go/services/wallet/common" + "github.com/status-im/status-go/services/wallet/requests" + "github.com/status-im/status-go/services/wallet/router/fees" "github.com/status-im/status-go/services/wallet/router/pathprocessor" + "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" ) @@ -50,7 +54,7 @@ var ( pathprocessor.ProcessorBridgeHopName: {Value: uint64(5000), Err: nil}, } - testBbonderFeeMap = map[string]*big.Int{ + testBBonderFeeMap = map[string]*big.Int{ pathprocessor.EthSymbol: big.NewInt(testBonderFeeETH), pathprocessor.UsdcSymbol: big.NewInt(testBonderFeeUSDC), } @@ -60,11 +64,11 @@ var ( pathprocessor.UsdcSymbol: 1, } - testSuggestedFees = &SuggestedFees{ + testSuggestedFees = &fees.SuggestedFees{ GasPrice: big.NewInt(testGasPrice), BaseFee: big.NewInt(testBaseFee), MaxPriorityFeePerGas: big.NewInt(testPriorityFeeLow), - MaxFeesLevels: &MaxFeesLevels{ + MaxFeesLevels: &fees.MaxFeesLevels{ Low: (*hexutil.Big)(big.NewInt(testPriorityFeeLow)), Medium: (*hexutil.Big)(big.NewInt(testPriorityFeeMedium)), High: (*hexutil.Big)(big.NewInt(testPriorityFeeHigh)), @@ -201,8 +205,8 @@ var defaultNetworks = []params.Network{ type normalTestParams struct { name string - input *RouteInputParams - expectedCandidates []*PathV2 + input *requests.RouteInputParams + expectedCandidates routes.Route expectedError *errors.ErrorResponse } @@ -210,10 +214,10 @@ func getNormalTestParamsList() []normalTestParams { return []normalTestParams{ { name: "ETH transfer - Insufficient Funds", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -221,61 +225,61 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: map[string]pathprocessor.Estimation{ + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: map[string]pathprocessor.Estimation{ pathprocessor.ProcessorTransferName: { Value: uint64(0), Err: fmt.Errorf("failed with 50000000 gas: insufficient funds for gas * price + value: address %s have 68251537427723 want 100000000000000", common.HexToAddress("0x1")), }, }, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: &errors.ErrorResponse{ Code: errors.GenericErrorCode, Details: fmt.Sprintf("failed with 50000000 gas: insufficient funds for gas * price + value: address %s", common.HexToAddress("0x1")), }, - expectedCandidates: []*PathV2{}, + expectedCandidates: routes.Route{}, }, { name: "ETH transfer - No Specific FromChain - No Specific ToChain - 0 AmountIn", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(0)), TokenID: pathprocessor.EthSymbol, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -298,32 +302,32 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - No Specific FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), TokenID: pathprocessor.EthSymbol, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -382,34 +386,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - No Specific FromChain - Specific Single ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), TokenID: pathprocessor.EthSymbol, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -432,34 +436,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - No Specific FromChain - Specific Multiple ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), TokenID: pathprocessor.EthSymbol, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &optimism, @@ -500,35 +504,35 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - Specific Single FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), TokenID: pathprocessor.EthSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &arbitrum, @@ -551,34 +555,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - Specific Multiple FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), TokenID: pathprocessor.EthSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &optimism, @@ -619,10 +623,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - Specific Single FromChain - Specific Single ToChain - Same Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -630,24 +634,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &arbitrum, @@ -658,10 +662,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - Specific Single FromChain - Specific Single ToChain - Different Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -669,24 +673,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -697,10 +701,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - Specific Multiple FromChain - Specific Multiple ToChain - Single Common Chain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -708,24 +712,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &arbitrum, @@ -736,10 +740,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - Specific Multiple FromChain - Specific Multiple ToChain - Multiple Common Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -747,24 +751,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -793,10 +797,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - Specific Multiple FromChain - Specific Multiple ToChain - No Common Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -804,24 +808,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -832,10 +836,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - All FromChains Disabled - All ToChains Disabled", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -843,33 +847,33 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: ErrNoBestRouteFound, - expectedCandidates: []*PathV2{}, + expectedCandidates: routes.Route{}, }, { name: "ETH transfer - No Specific FromChain - No Specific ToChain - Single Chain LockedAmount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -878,24 +882,24 @@ func getNormalTestParamsList() []normalTestParams { walletCommon.EthereumMainnet: (*hexutil.Big)(big.NewInt(testAmount0Point2ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -963,10 +967,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - No Specific FromChain - Specific ToChain - Single Chain LockedAmount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -977,24 +981,24 @@ func getNormalTestParamsList() []normalTestParams { walletCommon.ArbitrumMainnet: (*hexutil.Big)(big.NewInt(testAmount0Point3ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &optimism, @@ -1020,10 +1024,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - No Specific FromChain - No Specific ToChain - Multiple Chains LockedAmount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -1033,24 +1037,24 @@ func getNormalTestParamsList() []normalTestParams { walletCommon.OptimismMainnet: (*hexutil.Big)(big.NewInt(testAmount0Point3ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -1118,10 +1122,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - No Specific FromChain - No Specific ToChain - All Chains LockedAmount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -1132,24 +1136,24 @@ func getNormalTestParamsList() []normalTestParams { walletCommon.ArbitrumMainnet: (*hexutil.Big)(big.NewInt(testAmount0Point5ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -1217,10 +1221,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - No Specific FromChain - No Specific ToChain - All Chains LockedAmount with insufficient amount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -1231,32 +1235,32 @@ func getNormalTestParamsList() []normalTestParams { walletCommon.ArbitrumMainnet: (*hexutil.Big)(big.NewInt(testAmount0Point4ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedError: ErrLockedAmountLessThanSendAmountAllNetworks, - expectedCandidates: []*PathV2{}, + expectedError: requests.ErrLockedAmountLessThanSendAmountAllNetworks, + expectedCandidates: routes.Route{}, }, { name: "ETH transfer - No Specific FromChain - No Specific ToChain - LockedAmount exceeds sending amount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), @@ -1266,55 +1270,55 @@ func getNormalTestParamsList() []normalTestParams { walletCommon.ArbitrumMainnet: (*hexutil.Big)(big.NewInt(testAmount0Point8ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedError: ErrLockedAmountExceedsTotalSendAmount, - expectedCandidates: []*PathV2{}, + expectedError: requests.ErrLockedAmountExceedsTotalSendAmount, + expectedCandidates: routes.Route{}, }, { name: "ERC20 transfer - No Specific FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -1373,34 +1377,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - No Specific FromChain - Specific Single ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -1423,34 +1427,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - No Specific FromChain - Specific Multiple ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &optimism, @@ -1491,34 +1495,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - Specific Single FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &arbitrum, @@ -1541,34 +1545,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - Specific Multiple FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &optimism, @@ -1609,10 +1613,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - Specific Single FromChain - Specific Single ToChain - Same Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -1620,24 +1624,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &arbitrum, @@ -1648,10 +1652,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - Specific Single FromChain - Specific Single ToChain - Different Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -1659,24 +1663,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -1687,10 +1691,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - Specific Multiple FromChain - Specific Multiple ToChain - Single Common Chain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -1698,24 +1702,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &arbitrum, @@ -1726,10 +1730,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - Specific Multiple FromChain - Specific Multiple ToChain - Multiple Common Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -1737,24 +1741,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -1783,10 +1787,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - Specific Multiple FromChain - Specific Multiple ToChain - No Common Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -1794,24 +1798,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -1822,10 +1826,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ERC20 transfer - All FromChains Disabled - All ToChains Disabled", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -1833,55 +1837,55 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: ErrNoBestRouteFound, - expectedCandidates: []*PathV2{}, + expectedCandidates: routes.Route{}, }, { name: "ERC20 transfer - All FromChains - No Locked Amount - Enough Token Balance Across All Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(2.5 * testAmount100USDC)), TokenID: pathprocessor.UsdcSymbol, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &mainnet, @@ -2054,33 +2058,33 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - No Specific FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &mainnet, @@ -2121,34 +2125,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - No Specific FromChain - Specific Single ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &optimism, @@ -2165,34 +2169,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - No Specific FromChain - Specific Multiple ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &mainnet, @@ -2221,34 +2225,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - Specific Single FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -2265,34 +2269,34 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - Specific Multiple FromChain - No Specific ToChain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &optimism, @@ -2321,10 +2325,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - Specific Single FromChain - Specific Single ToChain - Same Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -2332,32 +2336,32 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: ErrNoBestRouteFound, - expectedCandidates: []*PathV2{}, + expectedCandidates: routes.Route{}, }, { name: "Bridge - Specific Single FromChain - Specific Single ToChain - Different Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -2365,24 +2369,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -2393,10 +2397,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - Specific Multiple FromChain - Specific Multiple ToChain - Single Common Chain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -2404,32 +2408,32 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: ErrNoBestRouteFound, - expectedCandidates: []*PathV2{}, + expectedCandidates: routes.Route{}, }, { name: "Bridge - Specific Multiple FromChain - Specific Multiple ToChain - Multiple Common Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -2437,24 +2441,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &mainnet, @@ -2471,10 +2475,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - Specific Multiple FromChain - Specific Multiple ToChain - No Common Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -2482,24 +2486,24 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -2510,10 +2514,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - All FromChains Disabled - All ToChains Disabled", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount1USDC)), @@ -2521,32 +2525,32 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: ErrNoBestRouteFound, - expectedCandidates: []*PathV2{}, + expectedCandidates: routes.Route{}, }, { name: "ETH transfer - Not Enough Native Balance", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount3ETHInWei)), @@ -2554,27 +2558,27 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: &errors.ErrorResponse{ Code: ErrNotEnoughNativeBalance.Code, Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, walletCommon.EthereumMainnet), }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &mainnet, @@ -2585,10 +2589,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "ETH transfer - Not Enough Native Balance", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(5 * testAmount100USDC)), @@ -2596,27 +2600,27 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: &errors.ErrorResponse{ Code: ErrNotEnoughTokenBalance.Code, Details: fmt.Sprintf(ErrNotEnoughTokenBalance.Details, pathprocessor.UsdcSymbol, walletCommon.EthereumMainnet), }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &mainnet, @@ -2627,10 +2631,10 @@ func getNormalTestParamsList() []normalTestParams { }, { name: "Bridge - Specific Single FromChain - Specific Single ToChain - Sending Small Amount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Bridge, + SendType: sendtype.Bridge, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(0.01 * testAmount1USDC)), @@ -2638,25 +2642,25 @@ func getNormalTestParamsList() []normalTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.OptimismMainnet}, DisabledToChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - baseFee: big.NewInt(testBaseFee), - suggestedFees: testSuggestedFees, - balanceMap: testBalanceMapPerChain, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + TokenPrices: testTokenPrices, + BaseFee: big.NewInt(testBaseFee), + SuggestedFees: testSuggestedFees, + BalanceMap: testBalanceMapPerChain, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: ErrLowAmountInForHopBridge, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -2670,9 +2674,9 @@ func getNormalTestParamsList() []normalTestParams { type noBalanceTestParams struct { name string - input *RouteInputParams - expectedCandidates []*PathV2 - expectedBest []*PathV2 + input *requests.RouteInputParams + expectedCandidates routes.Route + expectedBest routes.Route expectedError *errors.ErrorResponse } @@ -2680,10 +2684,10 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { return []noBalanceTestParams{ { name: "ERC20 transfer - Specific FromChain - Specific ToChain - Not Enough Token Balance", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)), @@ -2691,32 +2695,32 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: map[string]*big.Int{ + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.UsdcSymbol): big.NewInt(0), }, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: ErrNoPositiveBalance, }, { name: "ERC20 transfer - Specific FromChain - Specific ToChain - Not Enough Native Balance", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)), @@ -2724,62 +2728,62 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: map[string]*big.Int{ + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC), makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.EthSymbol): big.NewInt(0), }, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: &errors.ErrorResponse{ Code: ErrNotEnoughNativeBalance.Code, Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, walletCommon.OptimismMainnet), }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorTransferName, FromChain: &optimism, ToChain: &optimism, ApprovalRequired: false, - requiredTokenBalance: big.NewInt(testAmount100USDC), - requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation), + RequiredTokenBalance: big.NewInt(testAmount100USDC), + RequiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation), }, }, }, { name: "ERC20 transfer - No Specific FromChain - Specific ToChain - Not Enough Token Balance Across All Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: map[string]*big.Int{ + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.EthereumMainnet, pathprocessor.UsdcSymbol): big.NewInt(0), makeBalanceKey(walletCommon.EthereumMainnet, pathprocessor.EthSymbol): big.NewInt(0), makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.UsdcSymbol): big.NewInt(0), @@ -2787,36 +2791,36 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.UsdcSymbol): big.NewInt(0), makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.EthSymbol): big.NewInt(0), }, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: ErrNoPositiveBalance, }, { name: "ERC20 transfer - No Specific FromChain - Specific ToChain - Enough Token Balance On Arbitrum Chain But Not Enough Native Balance", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: map[string]*big.Int{ + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC + testAmount100USDC), makeBalanceKey(walletCommon.EthereumMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC + testAmount100USDC), makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC + testAmount100USDC), @@ -2824,17 +2828,17 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { makeBalanceKey(walletCommon.EthereumMainnet, pathprocessor.EthSymbol): big.NewInt(0), makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.EthSymbol): big.NewInt(0), }, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, expectedError: &errors.ErrorResponse{ Code: ErrNotEnoughNativeBalance.Code, Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, walletCommon.ArbitrumMainnet), }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &mainnet, @@ -2846,8 +2850,8 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { FromChain: &optimism, ToChain: &optimism, ApprovalRequired: false, - requiredTokenBalance: big.NewInt(testAmount100USDC), - requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation), + RequiredTokenBalance: big.NewInt(testAmount100USDC), + RequiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation), }, { ProcessorName: pathprocessor.ProcessorBridgeHopName, @@ -2859,36 +2863,36 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { }, { name: "ERC20 transfer - No Specific FromChain - Specific ToChain - Enough Token Balance On Arbitrum Chain And Enough Native Balance On Arbitrum Chain", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AddrFrom: common.HexToAddress("0x1"), AddrTo: common.HexToAddress("0x2"), AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)), TokenID: pathprocessor.UsdcSymbol, DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.UsdcSymbol, Decimals: 6, }, - tokenPrices: testTokenPrices, - suggestedFees: testSuggestedFees, - balanceMap: map[string]*big.Int{ + TokenPrices: testTokenPrices, + SuggestedFees: testSuggestedFees, + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC + testAmount100USDC), makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), }, - estimationMap: testEstimationMap, - bonderFeeMap: testBbonderFeeMap, - approvalGasEstimation: testApprovalGasEstimation, - approvalL1Fee: testApprovalL1Fee, + EstimationMap: testEstimationMap, + BonderFeeMap: testBBonderFeeMap, + ApprovalGasEstimation: testApprovalGasEstimation, + ApprovalL1Fee: testApprovalL1Fee, }, }, - expectedCandidates: []*PathV2{ + expectedCandidates: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &mainnet, @@ -2900,8 +2904,8 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { FromChain: &optimism, ToChain: &optimism, ApprovalRequired: false, - requiredTokenBalance: big.NewInt(testAmount100USDC), - requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation), + RequiredTokenBalance: big.NewInt(testAmount100USDC), + RequiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation), }, { ProcessorName: pathprocessor.ProcessorBridgeHopName, @@ -2910,7 +2914,7 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { ApprovalRequired: true, }, }, - expectedBest: []*PathV2{ + expectedBest: routes.Route{ { ProcessorName: pathprocessor.ProcessorBridgeHopName, FromChain: &arbitrum, @@ -2924,7 +2928,7 @@ func getNoBalanceTestParamsList() []noBalanceTestParams { type amountOptionsTestParams struct { name string - input *RouteInputParams + input *requests.RouteInputParams expectedAmountOptions map[uint64][]amountOption } @@ -2932,22 +2936,22 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { return []amountOptionsTestParams{ { name: "Transfer - Single From Chain - No Locked Amount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), TokenID: pathprocessor.EthSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{}, + BalanceMap: map[string]*big.Int{}, }, }, expectedAmountOptions: map[uint64][]amountOption{ @@ -2961,10 +2965,10 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - Single From Chain - Locked Amount To Single Chain Equal Total Amount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), TokenID: pathprocessor.EthSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet}, @@ -2972,14 +2976,14 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { walletCommon.OptimismMainnet: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{}, + BalanceMap: map[string]*big.Int{}, }, }, expectedAmountOptions: map[uint64][]amountOption{ @@ -2993,10 +2997,10 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - Multiple From Chains - Locked Amount To Single Chain Is Less Than Total Amount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount2ETHInWei)), TokenID: pathprocessor.EthSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet}, @@ -3004,14 +3008,14 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { walletCommon.OptimismMainnet: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{}, + BalanceMap: map[string]*big.Int{}, }, }, expectedAmountOptions: map[uint64][]amountOption{ @@ -3031,10 +3035,10 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - Multiple From Chains - Locked Amount To Multiple Chains", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount2ETHInWei)), TokenID: pathprocessor.EthSymbol, DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet}, @@ -3043,14 +3047,14 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { walletCommon.ArbitrumMainnet: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{}, + BalanceMap: map[string]*big.Int{}, }, }, expectedAmountOptions: map[uint64][]amountOption{ @@ -3070,10 +3074,10 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - All From Chains - Locked Amount To Multiple Chains Equal Total Amount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount2ETHInWei)), TokenID: pathprocessor.EthSymbol, FromLockedAmount: map[uint64]*hexutil.Big{ @@ -3081,14 +3085,14 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { walletCommon.ArbitrumMainnet: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{}, + BalanceMap: map[string]*big.Int{}, }, }, expectedAmountOptions: map[uint64][]amountOption{ @@ -3108,10 +3112,10 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - All From Chains - Locked Amount To Multiple Chains Is Less Than Total Amount", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount5ETHInWei)), TokenID: pathprocessor.EthSymbol, FromLockedAmount: map[uint64]*hexutil.Big{ @@ -3119,14 +3123,14 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { walletCommon.ArbitrumMainnet: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{}, + BalanceMap: map[string]*big.Int{}, }, }, expectedAmountOptions: map[uint64][]amountOption{ @@ -3152,21 +3156,21 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - All From Chain - No Locked Amount - Enough Token Balance If All Chains Are Used", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount3ETHInWei)), TokenID: pathprocessor.EthSymbol, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{ + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.EthereumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), @@ -3208,24 +3212,24 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - All From Chain - Locked Amount To Single Chain - Enough Token Balance If All Chains Are Used", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount3ETHInWei)), TokenID: pathprocessor.EthSymbol, FromLockedAmount: map[uint64]*hexutil.Big{ walletCommon.OptimismMainnet: (*hexutil.Big)(big.NewInt(testAmount0Point5ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{ + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.EthereumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount2ETHInWei), makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount3ETHInWei), @@ -3263,10 +3267,10 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - All From Chain - Locked Amount To Multiple Chains - Enough Token Balance If All Chains Are Used", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount3ETHInWei)), TokenID: pathprocessor.EthSymbol, FromLockedAmount: map[uint64]*hexutil.Big{ @@ -3274,14 +3278,14 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { walletCommon.EthereumMainnet: (*hexutil.Big)(big.NewInt(testAmount1ETHInWei)), }, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{ + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.EthereumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount2ETHInWei), makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount3ETHInWei), @@ -3311,21 +3315,21 @@ func getAmountOptionsTestParamsList() []amountOptionsTestParams { }, { name: "Transfer - All From Chain - No Locked Amount - Not Enough Token Balance", - input: &RouteInputParams{ - testnetMode: false, + input: &requests.RouteInputParams{ + TestnetMode: false, Uuid: uuid.NewString(), - SendType: Transfer, + SendType: sendtype.Transfer, AmountIn: (*hexutil.Big)(big.NewInt(testAmount5ETHInWei)), TokenID: pathprocessor.EthSymbol, - testsMode: true, - testParams: &routerTestParams{ - tokenFrom: &token.Token{ + TestsMode: true, + TestParams: &requests.RouterTestParams{ + TokenFrom: &token.Token{ ChainID: 1, Symbol: pathprocessor.EthSymbol, Decimals: 18, }, - balanceMap: map[string]*big.Int{ + BalanceMap: map[string]*big.Int{ makeBalanceKey(walletCommon.EthereumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), makeBalanceKey(walletCommon.OptimismMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), makeBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei), diff --git a/services/wallet/router/router_updates.go b/services/wallet/router/router_updates.go new file mode 100644 index 00000000000..ca24073c979 --- /dev/null +++ b/services/wallet/router/router_updates.go @@ -0,0 +1,152 @@ +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 + } + } + + _, err = r.checkBalancesForTheBestRoute(ctx, r.activeRoutes.Best) + + sendRouterResult(uuid, r.activeRoutes, err) + } + 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 + }) +} diff --git a/services/wallet/router/router_v2.go b/services/wallet/router/router_v2.go deleted file mode 100644 index 38cee49cb70..00000000000 --- a/services/wallet/router/router_v2.go +++ /dev/null @@ -1,1278 +0,0 @@ -package router - -import ( - "context" - "fmt" - "math" - "math/big" - "sort" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" - "github.com/status-im/status-go/errors" - "github.com/status-im/status-go/params" - "github.com/status-im/status-go/services/ens" - "github.com/status-im/status-go/services/wallet/async" - walletCommon "github.com/status-im/status-go/services/wallet/common" - "github.com/status-im/status-go/services/wallet/router/pathprocessor" - walletToken "github.com/status-im/status-go/services/wallet/token" - "github.com/status-im/status-go/signal" -) - -const ( - hexAddressLength = 42 -) - -var ( - routerTask = async.TaskType{ - ID: 1, - Policy: async.ReplacementPolicyCancelOld, - } -) - -var ( - supportedNetworks = map[uint64]bool{ - walletCommon.EthereumMainnet: true, - walletCommon.OptimismMainnet: true, - walletCommon.ArbitrumMainnet: true, - } - - supportedTestNetworks = map[uint64]bool{ - walletCommon.EthereumSepolia: true, - walletCommon.OptimismSepolia: true, - walletCommon.ArbitrumSepolia: true, - } -) - -type RouteInputParams struct { - Uuid string `json:"uuid"` - SendType SendType `json:"sendType" validate:"required"` - AddrFrom common.Address `json:"addrFrom" validate:"required"` - AddrTo common.Address `json:"addrTo" validate:"required"` - AmountIn *hexutil.Big `json:"amountIn" validate:"required"` - AmountOut *hexutil.Big `json:"amountOut"` - TokenID string `json:"tokenID" validate:"required"` - ToTokenID string `json:"toTokenID"` - DisabledFromChainIDs []uint64 `json:"disabledFromChainIDs"` - DisabledToChainIDs []uint64 `json:"disabledToChainIDs"` - GasFeeMode GasFeeMode `json:"gasFeeMode" validate:"required"` - FromLockedAmount map[uint64]*hexutil.Big `json:"fromLockedAmount"` - testnetMode bool - - // For send types like EnsRegister, EnsRelease, EnsSetPubKey, StickersBuy - Username string `json:"username"` - PublicKey string `json:"publicKey"` - PackID *hexutil.Big `json:"packID"` - - // TODO: Remove two fields below once we implement a better solution for tests - // Currently used for tests only - testsMode bool - testParams *routerTestParams -} - -type routerTestParams struct { - tokenFrom *walletToken.Token - tokenPrices map[string]float64 - estimationMap map[string]pathprocessor.Estimation // [processor-name, estimation] - bonderFeeMap map[string]*big.Int // [token-symbol, bonder-fee] - suggestedFees *SuggestedFees - baseFee *big.Int - balanceMap map[string]*big.Int // [token-symbol, balance] - approvalGasEstimation uint64 - approvalL1Fee uint64 -} - -type amountOption struct { - amount *big.Int - locked bool - subtractFees bool -} - -func makeBalanceKey(chainID uint64, symbol string) string { - return fmt.Sprintf("%d-%s", chainID, symbol) -} - -type PathV2 struct { - ProcessorName string - FromChain *params.Network // Source chain - ToChain *params.Network // Destination chain - FromToken *walletToken.Token // Source token - ToToken *walletToken.Token // Destination token, set if applicable - AmountIn *hexutil.Big // Amount that will be sent from the source chain - AmountInLocked bool // Is the amount locked - AmountOut *hexutil.Big // Amount that will be received on the destination chain - - SuggestedLevelsForMaxFeesPerGas *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) - - 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) - - 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) - - 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 - 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 - - 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 TransactionEstimation - - requiredTokenBalance *big.Int // (in selected token) - requiredNativeBalance *big.Int // (in ETH WEI) - subtractFees bool -} - -type ProcessorError struct { - ProcessorName string - Error error -} - -func (p *PathV2) Equal(o *PathV2) bool { - return p.FromChain.ChainID == o.FromChain.ChainID && p.ToChain.ChainID == o.ToChain.ChainID -} - -type SuggestedRoutesV2 struct { - Uuid string - Best []*PathV2 - Candidates []*PathV2 - TokenPrice float64 - NativeChainTokenPrice float64 -} - -type SuggestedRoutesV2Response struct { - Uuid string `json:"Uuid"` - Best []*PathV2 `json:"Best,omitempty"` - Candidates []*PathV2 `json:"Candidates,omitempty"` - TokenPrice *float64 `json:"TokenPrice,omitempty"` - NativeChainTokenPrice *float64 `json:"NativeChainTokenPrice,omitempty"` - ErrorResponse *errors.ErrorResponse `json:"ErrorResponse,omitempty"` -} - -type GraphV2 []*NodeV2 - -type NodeV2 struct { - Path *PathV2 - Children GraphV2 -} - -func newSuggestedRoutesV2( - uuid string, - amountIn *big.Int, - candidates []*PathV2, - fromLockedAmount map[uint64]*hexutil.Big, - tokenPrice float64, - nativeChainTokenPrice float64, -) (*SuggestedRoutesV2, [][]*PathV2) { - suggestedRoutes := &SuggestedRoutesV2{ - Uuid: uuid, - Candidates: candidates, - TokenPrice: tokenPrice, - NativeChainTokenPrice: nativeChainTokenPrice, - } - if len(candidates) == 0 { - return suggestedRoutes, nil - } - - node := &NodeV2{ - Path: nil, - Children: buildGraphV2(amountIn, candidates, 0, []uint64{}), - } - allRoutes := node.buildAllRoutesV2() - allRoutes = filterRoutesV2(allRoutes, amountIn, fromLockedAmount) - - if len(allRoutes) > 0 { - sort.Slice(allRoutes, func(i, j int) bool { - iRoute := getRoutePriority(allRoutes[i]) - jRoute := getRoutePriority(allRoutes[j]) - return iRoute <= jRoute - }) - } - - return suggestedRoutes, allRoutes -} - -func newNodeV2(path *PathV2) *NodeV2 { - return &NodeV2{Path: path, Children: make(GraphV2, 0)} -} - -func buildGraphV2(AmountIn *big.Int, routes []*PathV2, level int, sourceChainIDs []uint64) GraphV2 { - graph := make(GraphV2, 0) - for _, route := range routes { - found := false - for _, chainID := range sourceChainIDs { - if chainID == route.FromChain.ChainID { - found = true - break - } - } - if found { - continue - } - node := newNodeV2(route) - - newRoutes := make([]*PathV2, 0) - for _, r := range routes { - if route.Equal(r) { - continue - } - newRoutes = append(newRoutes, r) - } - - newAmountIn := new(big.Int).Sub(AmountIn, route.AmountIn.ToInt()) - if newAmountIn.Sign() > 0 { - newSourceChainIDs := make([]uint64, len(sourceChainIDs)) - copy(newSourceChainIDs, sourceChainIDs) - newSourceChainIDs = append(newSourceChainIDs, route.FromChain.ChainID) - node.Children = buildGraphV2(newAmountIn, newRoutes, level+1, newSourceChainIDs) - - if len(node.Children) == 0 { - continue - } - } - - graph = append(graph, node) - } - - return graph -} - -func (n NodeV2) buildAllRoutesV2() [][]*PathV2 { - res := make([][]*PathV2, 0) - - if len(n.Children) == 0 && n.Path != nil { - res = append(res, []*PathV2{n.Path}) - } - - for _, node := range n.Children { - for _, route := range node.buildAllRoutesV2() { - extendedRoute := route - if n.Path != nil { - extendedRoute = append([]*PathV2{n.Path}, route...) - } - res = append(res, extendedRoute) - } - } - - return res -} - -func findBestV2(routes [][]*PathV2, tokenPrice float64, nativeTokenPrice float64) []*PathV2 { - var best []*PathV2 - bestCost := big.NewFloat(math.Inf(1)) - for _, route := range routes { - currentCost := big.NewFloat(0) - for _, path := range route { - tokenDenominator := big.NewFloat(math.Pow(10, float64(path.FromToken.Decimals))) - - // calculate the cost of the path - nativeTokenPrice := new(big.Float).SetFloat64(nativeTokenPrice) - - // tx fee - txFeeInEth := gweiToEth(weiToGwei(path.TxFee.ToInt())) - pathCost := new(big.Float).Mul(txFeeInEth, nativeTokenPrice) - - if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { - txL1FeeInEth := gweiToEth(weiToGwei(path.TxL1Fee.ToInt())) - pathCost.Add(pathCost, new(big.Float).Mul(txL1FeeInEth, nativeTokenPrice)) - } - - if path.TxBonderFees != nil && path.TxBonderFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { - pathCost.Add(pathCost, new(big.Float).Mul( - new(big.Float).Quo(new(big.Float).SetInt(path.TxBonderFees.ToInt()), tokenDenominator), - new(big.Float).SetFloat64(tokenPrice))) - - } - - if path.TxTokenFees != nil && path.TxTokenFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && path.FromToken != nil { - pathCost.Add(pathCost, new(big.Float).Mul( - new(big.Float).Quo(new(big.Float).SetInt(path.TxTokenFees.ToInt()), tokenDenominator), - new(big.Float).SetFloat64(tokenPrice))) - } - - if path.ApprovalRequired { - // tx approval fee - approvalFeeInEth := gweiToEth(weiToGwei(path.ApprovalFee.ToInt())) - pathCost.Add(pathCost, new(big.Float).Mul(approvalFeeInEth, nativeTokenPrice)) - - if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { - approvalL1FeeInEth := gweiToEth(weiToGwei(path.ApprovalL1Fee.ToInt())) - pathCost.Add(pathCost, new(big.Float).Mul(approvalL1FeeInEth, nativeTokenPrice)) - } - } - - currentCost = new(big.Float).Add(currentCost, pathCost) - } - - if currentCost.Cmp(bestCost) == -1 { - best = route - bestCost = currentCost - } - } - - return best -} - -func validateInputData(input *RouteInputParams) error { - if input.SendType == ENSRegister { - if input.Username == "" || input.PublicKey == "" { - return ErrENSRegisterRequiresUsernameAndPubKey - } - if input.testnetMode { - if input.TokenID != pathprocessor.SttSymbol { - return ErrENSRegisterTestnetSTTOnly - } - } else { - if input.TokenID != pathprocessor.SntSymbol { - return ErrENSRegisterMainnetSNTOnly - } - } - return nil - } - - if input.SendType == ENSRelease { - if input.Username == "" { - return ErrENSReleaseRequiresUsername - } - } - - if input.SendType == ENSSetPubKey { - if input.Username == "" || input.PublicKey == "" { - return ErrENSSetPubKeyRequiresUsernameAndPubKey - } - - if ens.ValidateENSUsername(input.Username) != nil { - return ErrENSSetPubKeyInvalidUsername - } - } - - if input.SendType == StickersBuy { - if input.PackID == nil { - return ErrStickersBuyRequiresPackID - } - } - - if input.SendType == Swap { - if input.ToTokenID == "" { - return ErrSwapRequiresToTokenID - } - if input.TokenID == input.ToTokenID { - return ErrSwapTokenIDMustBeDifferent - } - - if input.AmountIn != nil && - input.AmountOut != nil && - input.AmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && - input.AmountOut.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { - return ErrSwapAmountInAmountOutMustBeExclusive - } - - if input.AmountIn != nil && input.AmountIn.ToInt().Sign() < 0 { - return ErrSwapAmountInMustBePositive - } - - if input.AmountOut != nil && input.AmountOut.ToInt().Sign() < 0 { - return ErrSwapAmountOutMustBePositive - } - } - - return validateFromLockedAmount(input) -} - -func validateFromLockedAmount(input *RouteInputParams) error { - if input.FromLockedAmount == nil || len(input.FromLockedAmount) == 0 { - return nil - } - - var suppNetworks map[uint64]bool - if input.testnetMode { - suppNetworks = copyMapGeneric(supportedTestNetworks, nil).(map[uint64]bool) - } else { - suppNetworks = copyMapGeneric(supportedNetworks, nil).(map[uint64]bool) - } - - if suppNetworks == nil { - return ErrCannotCheckLockedAmounts - } - - totalLockedAmount := big.NewInt(0) - excludedChainCount := 0 - - for chainID, amount := range input.FromLockedAmount { - if arrayContainsElement(chainID, input.DisabledFromChainIDs) { - return ErrDisabledChainFoundAmongLockedNetworks - } - - if input.testnetMode { - if !supportedTestNetworks[chainID] { - return ErrLockedAmountNotSupportedForNetwork - } - } else { - if !supportedNetworks[chainID] { - return ErrLockedAmountNotSupportedForNetwork - } - } - - if amount == nil || amount.ToInt().Sign() < 0 { - return ErrLockedAmountNotNegative - } - - if !(amount.ToInt().Sign() > 0) { - excludedChainCount++ - } - delete(suppNetworks, chainID) - totalLockedAmount = new(big.Int).Add(totalLockedAmount, amount.ToInt()) - } - - if (!input.testnetMode && excludedChainCount == len(supportedNetworks)) || - (input.testnetMode && excludedChainCount == len(supportedTestNetworks)) { - return ErrLockedAmountExcludesAllSupported - } - - if totalLockedAmount.Cmp(input.AmountIn.ToInt()) > 0 { - return ErrLockedAmountExceedsTotalSendAmount - } else if totalLockedAmount.Cmp(input.AmountIn.ToInt()) < 0 && len(suppNetworks) == 0 { - return ErrLockedAmountLessThanSendAmountAllNetworks - } - return nil -} - -func (r *Router) SuggestedRoutesV2Async(input *RouteInputParams) { - r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) { - return r.SuggestedRoutesV2(ctx, input) - }, func(result interface{}, taskType async.TaskType, err error) { - routesResponse := SuggestedRoutesV2Response{ - Uuid: input.Uuid, - } - - if err != nil { - errorResponse := errors.CreateErrorResponseFromError(err) - routesResponse.ErrorResponse = errorResponse.(*errors.ErrorResponse) - } - - if suggestedRoutes, ok := result.(*SuggestedRoutesV2); 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) StopSuggestedRoutesV2AsyncCalcualtion() { - r.scheduler.Stop() -} - -func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams) (*SuggestedRoutesV2, error) { - testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled() - if err != nil { - return nil, errors.CreateErrorResponseFromError(err) - } - - input.testnetMode = testnetMode - - // clear all processors - for _, processor := range r.pathProcessors { - if clearable, ok := processor.(pathprocessor.PathProcessorClearable); ok { - clearable.Clear() - } - } - - err = validateInputData(input) - if err != nil { - return nil, errors.CreateErrorResponseFromError(err) - } - - selectedFromChains, selectedToChains, err := r.getSelectedChains(input) - if err != nil { - return nil, errors.CreateErrorResponseFromError(err) - } - - balanceMap, err := r.getBalanceMapForTokenOnChains(ctx, input, selectedFromChains) - // return only if there are no balances, otherwise try to resolve the candidates for chains we know the balances for - if len(balanceMap) == 0 { - if err != nil { - return nil, errors.CreateErrorResponseFromError(err) - } - } else { - noBalanceOnAnyChain := true - for _, value := range balanceMap { - if value.Cmp(pathprocessor.ZeroBigIntValue) > 0 { - noBalanceOnAnyChain = false - break - } - } - if noBalanceOnAnyChain { - return nil, ErrNoPositiveBalance - } - } - - candidates, processorErrors, err := r.resolveCandidates(ctx, input, selectedFromChains, selectedToChains, balanceMap) - if err != nil { - return nil, errors.CreateErrorResponseFromError(err) - } - - 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. - if len(processorErrors) > 0 { - // Return one of the path processor errors if present. - // Give precedence to the custom error message. - for _, processorError := range processorErrors { - if processorError.Error != nil && pathprocessor.IsCustomError(processorError.Error) { - err = processorError.Error - break - } - } - if err == nil { - err = errors.CreateErrorResponseFromError(processorErrors[0].Error) - } - } else { - err = ErrNoBestRouteFound - } - } - - mapError := func(err error) error { - if err == nil { - return nil - } - pattern := "insufficient funds for gas * price + value: address " - addressIndex := strings.Index(errors.DetailsFromError(err), pattern) - if addressIndex != -1 { - addressIndex += len(pattern) + hexAddressLength - return errors.CreateErrorResponseFromError(&errors.ErrorResponse{ - Code: errors.ErrorCodeFromError(err), - Details: errors.DetailsFromError(err)[:addressIndex], - }) - } - return err - } - // map some errors to more user-friendly messages - return suggestedRoutes, mapError(err) -} - -// getBalanceMapForTokenOnChains returns the balance map for passed address, where the key is in format "chainID-tokenSymbol" and -// value is the balance of the token. Native token (EHT) is always added to the balance map. -func (r *Router) getBalanceMapForTokenOnChains(ctx context.Context, input *RouteInputParams, selectedFromChains []*params.Network) (balanceMap map[string]*big.Int, err error) { - if input.testsMode { - return input.testParams.balanceMap, nil - } - - balanceMap = make(map[string]*big.Int) - - chainError := func(chainId uint64, token string, intErr error) { - if err == nil { - err = fmt.Errorf("chain %d, token %s: %w", chainId, token, intErr) - } else { - err = fmt.Errorf("%s; chain %d, token %s: %w", err.Error(), chainId, token, intErr) - } - } - - for _, chain := range selectedFromChains { - // check token existence - token := input.SendType.FindToken(r.tokenManager, r.collectiblesService, input.AddrFrom, chain, input.TokenID) - if token == nil { - chainError(chain.ChainID, input.TokenID, ErrTokenNotFound) - continue - } - // check native token existence - nativeToken := r.tokenManager.FindToken(chain, chain.NativeCurrencySymbol) - if nativeToken == nil { - chainError(chain.ChainID, chain.NativeCurrencySymbol, ErrNativeTokenNotFound) - continue - } - - // add token balance for the chain - var tokenBalance *big.Int - if input.SendType == ERC721Transfer { - tokenBalance = big.NewInt(1) - } else if input.SendType == ERC1155Transfer { - tokenBalance, err = r.getERC1155Balance(ctx, chain, token, input.AddrFrom) - if err != nil { - chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err)) - } - } else { - tokenBalance, err = r.getBalance(ctx, chain.ChainID, token, input.AddrFrom) - if err != nil { - chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err)) - } - } - // add only if balance is not nil - if tokenBalance != nil { - balanceMap[makeBalanceKey(chain.ChainID, token.Symbol)] = tokenBalance - } - - if token.IsNative() { - continue - } - - // add native token balance for the chain - nativeBalance, err := r.getBalance(ctx, chain.ChainID, nativeToken, input.AddrFrom) - if err != nil { - chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err)) - } - // add only if balance is not nil - if nativeBalance != nil { - balanceMap[makeBalanceKey(chain.ChainID, nativeToken.Symbol)] = nativeBalance - } - } - - return -} - -func (r *Router) getSelectedUnlockedChains(input *RouteInputParams, processingChain *params.Network, selectedFromChains []*params.Network) []*params.Network { - selectedButNotLockedChains := []*params.Network{processingChain} // always add the processing chain at the beginning - for _, net := range selectedFromChains { - if net.ChainID == processingChain.ChainID { - continue - } - if _, ok := input.FromLockedAmount[net.ChainID]; !ok { - selectedButNotLockedChains = append(selectedButNotLockedChains, net) - } - } - return selectedButNotLockedChains -} - -func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input *RouteInputParams, amountToSplit *big.Int, processingChain *params.Network, - selectedFromChains []*params.Network, balanceMap map[string]*big.Int) map[uint64][]amountOption { - selectedButNotLockedChains := r.getSelectedUnlockedChains(input, processingChain, selectedFromChains) - - crossChainAmountOptions := make(map[uint64][]amountOption) - for _, chain := range selectedButNotLockedChains { - var ( - ok bool - tokenBalance *big.Int - ) - - if tokenBalance, ok = balanceMap[makeBalanceKey(chain.ChainID, input.TokenID)]; !ok { - continue - } - - if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { - if tokenBalance.Cmp(amountToSplit) <= 0 { - crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{ - amount: tokenBalance, - locked: false, - subtractFees: true, // for chains where we're taking the full balance, we want to subtract the fees - }) - amountToSplit = new(big.Int).Sub(amountToSplit, tokenBalance) - } else if amountToSplit.Cmp(pathprocessor.ZeroBigIntValue) > 0 { - crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{ - amount: amountToSplit, - locked: false, - }) - // break since amountToSplit is fully addressed and the rest is 0 - break - } - } - } - - return crossChainAmountOptions -} - -func (r *Router) getCrossChainsOptionsForSendingAmount(input *RouteInputParams, selectedFromChains []*params.Network, - balanceMap map[string]*big.Int) map[uint64][]amountOption { - // All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount - // was properly set and if there is something unexpected it will return an error and we will not reach this point - finalCrossChainAmountOptions := make(map[uint64][]amountOption) // represents all possible amounts that can be sent from the "from" chain - - for _, selectedFromChain := range selectedFromChains { - - amountLocked := false - amountToSend := input.AmountIn.ToInt() - - if amountToSend.Cmp(pathprocessor.ZeroBigIntValue) == 0 { - finalCrossChainAmountOptions[selectedFromChain.ChainID] = append(finalCrossChainAmountOptions[selectedFromChain.ChainID], amountOption{ - amount: amountToSend, - locked: false, - }) - continue - } - - lockedAmount, fromChainLocked := input.FromLockedAmount[selectedFromChain.ChainID] - if fromChainLocked { - amountToSend = lockedAmount.ToInt() - amountLocked = true - } else if len(input.FromLockedAmount) > 0 { - for chainID, lockedAmount := range input.FromLockedAmount { - if chainID == selectedFromChain.ChainID { - continue - } - amountToSend = new(big.Int).Sub(amountToSend, lockedAmount.ToInt()) - } - } - - if amountToSend.Cmp(pathprocessor.ZeroBigIntValue) > 0 { - // add full amount always, cause we want to check for balance errors at the end of the routing algorithm - // TODO: once we introduce bettwer error handling and start checking for the balance at the beginning of the routing algorithm - // we can remove this line and optimize the routing algorithm more - finalCrossChainAmountOptions[selectedFromChain.ChainID] = append(finalCrossChainAmountOptions[selectedFromChain.ChainID], amountOption{ - amount: amountToSend, - locked: amountLocked, - }) - - if amountLocked { - continue - } - - // If the amount that need to be send is bigger than the balance on the chain, then we want to check options if that - // amount can be splitted and sent across multiple chains. - if input.SendType == Transfer && len(selectedFromChains) > 1 { - // All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount - // was properly set and if there is something unexpected it will return an error and we will not reach this point - amountToSplitAccrossChains := new(big.Int).Set(amountToSend) - - crossChainAmountOptions := r.getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input, amountToSend, selectedFromChain, selectedFromChains, balanceMap) - - // sum up all the allocated amounts accorss all chains - allocatedAmount := big.NewInt(0) - for _, amountOptions := range crossChainAmountOptions { - for _, amountOption := range amountOptions { - allocatedAmount = new(big.Int).Add(allocatedAmount, amountOption.amount) - } - } - - // if the allocated amount is the same as the amount that need to be sent, then we can add the options to the finalCrossChainAmountOptions - if allocatedAmount.Cmp(amountToSplitAccrossChains) == 0 { - for cID, amountOptions := range crossChainAmountOptions { - finalCrossChainAmountOptions[cID] = append(finalCrossChainAmountOptions[cID], amountOptions...) - } - } - } - } - } - - return finalCrossChainAmountOptions -} - -func (r *Router) findOptionsForSendingAmount(input *RouteInputParams, selectedFromChains []*params.Network, - balanceMap map[string]*big.Int) (map[uint64][]amountOption, error) { - - crossChainAmountOptions := r.getCrossChainsOptionsForSendingAmount(input, selectedFromChains, balanceMap) - - // filter out duplicates values for the same chain - for chainID, amountOptions := range crossChainAmountOptions { - uniqueAmountOptions := make(map[string]amountOption) - for _, amountOption := range amountOptions { - uniqueAmountOptions[amountOption.amount.String()] = amountOption - } - - crossChainAmountOptions[chainID] = make([]amountOption, 0) - for _, amountOption := range uniqueAmountOptions { - crossChainAmountOptions[chainID] = append(crossChainAmountOptions[chainID], amountOption) - } - } - - return crossChainAmountOptions, nil -} - -func (r *Router) getSelectedChains(input *RouteInputParams) (selectedFromChains []*params.Network, selectedToChains []*params.Network, err error) { - var networks []*params.Network - networks, err = r.rpcClient.NetworkManager.Get(false) - if err != nil { - return nil, nil, errors.CreateErrorResponseFromError(err) - } - - for _, network := range networks { - if network.IsTest != input.testnetMode { - continue - } - - if !arrayContainsElement(network.ChainID, input.DisabledFromChainIDs) { - selectedFromChains = append(selectedFromChains, network) - } - - if !arrayContainsElement(network.ChainID, input.DisabledToChainIDs) { - selectedToChains = append(selectedToChains, network) - } - } - - return selectedFromChains, selectedToChains, nil -} - -func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams, selectedFromChains []*params.Network, - selectedToChains []*params.Network, balanceMap map[string]*big.Int) (candidates []*PathV2, processorErrors []*ProcessorError, err error) { - var ( - testsMode = input.testsMode && input.testParams != nil - group = async.NewAtomicGroup(ctx) - mu sync.Mutex - ) - - crossChainAmountOptions, err := r.findOptionsForSendingAmount(input, selectedFromChains, balanceMap) - if err != nil { - return nil, nil, errors.CreateErrorResponseFromError(err) - } - - appendProcessorErrorFn := func(processorName string, sendType SendType, fromChainID uint64, toChainID uint64, amount *big.Int, err error) { - log.Error("routerv2.resolveCandidates error", "processor", processorName, "sendType", sendType, "fromChainId: ", fromChainID, "toChainId", toChainID, "amount", amount, "err", err) - mu.Lock() - defer mu.Unlock() - processorErrors = append(processorErrors, &ProcessorError{ - ProcessorName: processorName, - Error: err, - }) - } - - appendPathFn := func(path *PathV2) { - mu.Lock() - defer mu.Unlock() - candidates = append(candidates, path) - } - - for networkIdx := range selectedFromChains { - network := selectedFromChains[networkIdx] - - if !input.SendType.isAvailableFor(network) { - continue - } - - var ( - token *walletToken.Token - toToken *walletToken.Token - ) - - if testsMode { - token = input.testParams.tokenFrom - } else { - token = input.SendType.FindToken(r.tokenManager, r.collectiblesService, input.AddrFrom, network, input.TokenID) - } - if token == nil { - continue - } - - if input.SendType == Swap { - toToken = input.SendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID) - } - - var fees *SuggestedFees - if testsMode { - fees = input.testParams.suggestedFees - } else { - fees, err = r.feesManager.SuggestedFees(ctx, network.ChainID) - if err != nil { - continue - } - } - - group.Add(func(c context.Context) error { - for _, amountOption := range crossChainAmountOptions[network.ChainID] { - for _, pProcessor := range r.pathProcessors { - // With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route - // once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function. - // This also applies to including another (Celer) bridge in the calculation. - // TODO: - // this algorithm, includeing finding the best route, has to be updated to include more bridges and one (for now) or more swap options - // it means that candidates should not be treated linearly, but improve the logic to have multiple routes with different processors of the same type. - // Example: - // Routes for sending SNT from Ethereum to Optimism can be: - // 1. Swap SNT(mainnet) to ETH(mainnet); then bridge via Hop ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination - // 2. Swap SNT(mainnet) to ETH(mainnet); then bridge via Celer ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination - // 3. Swap SNT(mainnet) to USDC(mainnet); then bridge via Hop USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination - // 4. Swap SNT(mainnet) to USDC(mainnet); then bridge via Celer USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination - // 5. ... - // 6. ... - // - // With the current routing algorithm atm we're not able to generate all possible routes. - if !input.SendType.canUseProcessor(pProcessor) { - continue - } - - // if we're doing a single chain operation, we can skip bridge processors - if isSingleChainOperation(selectedFromChains, selectedToChains) && pathprocessor.IsProcessorBridge(pProcessor.Name()) { - continue - } - - if !input.SendType.processZeroAmountInProcessor(amountOption.amount, input.AmountOut.ToInt(), pProcessor.Name()) { - continue - } - - for _, dest := range selectedToChains { - - if !input.SendType.isAvailableFor(network) { - continue - } - - if !input.SendType.isAvailableBetween(network, dest) { - 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) - 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, approvalGasLimit, l1ApprovalFee, 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) - // } - - amountOut, err := pProcessor.CalculateAmountOut(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - - maxFeesPerGas := fees.feeFor(input.GasFeeMode) - - estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas) - if approvalRequired && estimatedTime < MoreThanFiveMinutes { - 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(&PathV2{ - ProcessorName: pProcessor.Name(), - FromChain: network, - ToChain: dest, - FromToken: token, - ToToken: toToken, - AmountIn: (*hexutil.Big)(amountOption.amount), - AmountInLocked: amountOption.locked, - AmountOut: (*hexutil.Big)(amountOut), - - SuggestedLevelsForMaxFeesPerGas: fees.MaxFeesLevels, - MaxFeesPerGas: (*hexutil.Big)(maxFeesPerGas), - - TxBaseFee: (*hexutil.Big)(fees.BaseFee), - TxPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas), - TxGasAmount: gasLimit, - TxBonderFees: (*hexutil.Big)(bonderFees), - TxTokenFees: (*hexutil.Big)(tokenFees), - - TxFee: (*hexutil.Big)(txFeeInWei), - TxL1Fee: (*hexutil.Big)(txL1FeeInWei), - - ApprovalRequired: approvalRequired, - ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired), - ApprovalContractAddress: &approvalContractAddress, - ApprovalBaseFee: (*hexutil.Big)(fees.BaseFee), - ApprovalPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas), - ApprovalGasAmount: approvalGasLimit, - - ApprovalFee: (*hexutil.Big)(approvalFeeInWei), - ApprovalL1Fee: (*hexutil.Big)(approvalL1FeeInWei), - - TxTotalFee: (*hexutil.Big)(ethTotalFees), - - EstimatedTime: estimatedTime, - - subtractFees: amountOption.subtractFees, - requiredTokenBalance: requiredTokenBalance, - requiredNativeBalance: requiredNativeBalance, - }) - } - } - } - return nil - }) - } - - sort.Slice(candidates, func(i, j int) bool { - iChain := getChainPriority(candidates[i].FromChain.ChainID) - jChain := getChainPriority(candidates[j].FromChain.ChainID) - return iChain <= jChain - }) - - group.Wait() - return candidates, processorErrors, nil -} - -func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*PathV2, balanceMap map[string]*big.Int) (hasPositiveBalance bool, err error) { - balanceMapCopy := copyMapGeneric(balanceMap, func(v interface{}) interface{} { - return new(big.Int).Set(v.(*big.Int)) - }).(map[string]*big.Int) - if balanceMapCopy == nil { - return false, ErrCannotCheckBalance - } - - // check the best route for the required balances - for _, path := range bestRoute { - tokenKey := makeBalanceKey(path.FromChain.ChainID, path.FromToken.Symbol) - if tokenBalance, ok := balanceMapCopy[tokenKey]; ok { - if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { - hasPositiveBalance = true - } - } - - if path.ProcessorName == pathprocessor.ProcessorBridgeHopName { - if path.TxBonderFees.ToInt().Cmp(path.AmountOut.ToInt()) > 0 { - return hasPositiveBalance, ErrLowAmountInForHopBridge - } - } - - if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { - if tokenBalance, ok := balanceMapCopy[tokenKey]; ok { - if tokenBalance.Cmp(path.requiredTokenBalance) == -1 { - err := &errors.ErrorResponse{ - Code: ErrNotEnoughTokenBalance.Code, - Details: fmt.Sprintf(ErrNotEnoughTokenBalance.Details, path.FromToken.Symbol, path.FromChain.ChainID), - } - return hasPositiveBalance, err - } - balanceMapCopy[tokenKey].Sub(tokenBalance, path.requiredTokenBalance) - } else { - return hasPositiveBalance, ErrTokenNotFound - } - } - - ethKey := makeBalanceKey(path.FromChain.ChainID, pathprocessor.EthSymbol) - if nativeBalance, ok := balanceMapCopy[ethKey]; ok { - if nativeBalance.Cmp(path.requiredNativeBalance) == -1 { - err := &errors.ErrorResponse{ - Code: ErrNotEnoughNativeBalance.Code, - Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, path.FromChain.ChainID), - } - return hasPositiveBalance, err - } - balanceMapCopy[ethKey].Sub(nativeBalance, path.requiredNativeBalance) - } else { - return hasPositiveBalance, ErrNativeTokenNotFound - } - } - - return hasPositiveBalance, nil -} - -func removeBestRouteFromAllRouters(allRoutes [][]*PathV2, best []*PathV2) [][]*PathV2 { - for i := len(allRoutes) - 1; i >= 0; i-- { - route := allRoutes[i] - routeFound := true - for _, p := range route { - found := false - for _, b := range best { - if p.ProcessorName == b.ProcessorName && - (p.FromChain == nil && b.FromChain == nil || p.FromChain.ChainID == b.FromChain.ChainID) && - (p.ToChain == nil && b.ToChain == nil || p.ToChain.ChainID == b.ToChain.ChainID) && - (p.FromToken == nil && b.FromToken == nil || p.FromToken.Symbol == b.FromToken.Symbol) { - found = true - break - } - } - if !found { - routeFound = false - break - } - } - if routeFound { - return append(allRoutes[:i], allRoutes[i+1:]...) - } - } - - return nil -} - -func getChainPriority(chainID uint64) int { - switch chainID { - case walletCommon.EthereumMainnet, walletCommon.EthereumSepolia: - return 1 - case walletCommon.OptimismMainnet, walletCommon.OptimismSepolia: - return 2 - case walletCommon.ArbitrumMainnet, walletCommon.ArbitrumSepolia: - return 3 - default: - return 0 - } -} - -func getRoutePriority(route []*PathV2) int { - priority := 0 - for _, path := range route { - priority += getChainPriority(path.FromChain.ChainID) - } - return priority -} - -func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, candidates []*PathV2, balanceMap map[string]*big.Int) (suggestedRoutes *SuggestedRoutesV2, err error) { - var prices map[string]float64 - if input.testsMode { - prices = input.testParams.tokenPrices - } else { - prices, err = input.SendType.FetchPrices(r.marketManager, input.TokenID) - if err != nil { - return nil, errors.CreateErrorResponseFromError(err) - } - } - - tokenPrice := prices[input.TokenID] - nativeTokenPrice := prices[pathprocessor.EthSymbol] - - var allRoutes [][]*PathV2 - suggestedRoutes, allRoutes = newSuggestedRoutesV2(input.Uuid, input.AmountIn.ToInt(), candidates, input.FromLockedAmount, tokenPrice, nativeTokenPrice) - - defer func() { - if suggestedRoutes.Best != nil && len(suggestedRoutes.Best) > 0 { - sort.Slice(suggestedRoutes.Best, func(i, j int) bool { - iChain := getChainPriority(suggestedRoutes.Best[i].FromChain.ChainID) - jChain := getChainPriority(suggestedRoutes.Best[j].FromChain.ChainID) - return iChain <= jChain - }) - } - }() - - var ( - bestRoute []*PathV2 - lastBestRouteWithPositiveBalance []*PathV2 - lastBestRouteErr error - ) - - for len(allRoutes) > 0 { - bestRoute = findBestV2(allRoutes, tokenPrice, nativeTokenPrice) - var hasPositiveBalance bool - hasPositiveBalance, err = r.checkBalancesForTheBestRoute(ctx, bestRoute, balanceMap) - - if err != nil { - // If it's about transfer or bridge and there is more routes, but on the best (cheapest) one there is not enugh balance - // we shold check other routes even though there are not the cheapest ones - if input.SendType == Transfer || - input.SendType == Bridge { - if hasPositiveBalance { - lastBestRouteWithPositiveBalance = bestRoute - lastBestRouteErr = err - } - - if len(allRoutes) > 1 { - allRoutes = removeBestRouteFromAllRouters(allRoutes, bestRoute) - continue - } else { - break - } - } - } - - break - } - - // if none of the routes have positive balance, we should return the last best route with positive balance - if err != nil && lastBestRouteWithPositiveBalance != nil { - bestRoute = lastBestRouteWithPositiveBalance - err = lastBestRouteErr - } - - if len(bestRoute) > 0 { - // At this point we have to do the final check and update the amountIn (subtracting fees) if complete balance is going to be sent for native token (ETH) - for _, path := range bestRoute { - if path.subtractFees && path.FromToken.IsNative() { - path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.TxFee.ToInt()) - if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { - path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.TxL1Fee.ToInt()) - } - if path.ApprovalRequired { - path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.ApprovalFee.ToInt()) - if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { - path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.ApprovalL1Fee.ToInt()) - } - } - } - } - } - suggestedRoutes.Best = bestRoute - - return suggestedRoutes, err -} diff --git a/services/wallet/router/routes/route.go b/services/wallet/router/routes/route.go new file mode 100644 index 00000000000..eee24b00711 --- /dev/null +++ b/services/wallet/router/routes/route.go @@ -0,0 +1,67 @@ +package routes + +import ( + "math" + "math/big" + + "github.com/status-im/status-go/services/wallet/common" + "github.com/status-im/status-go/services/wallet/router/pathprocessor" +) + +type Route []*Path + +func FindBestRoute(routes []Route, tokenPrice float64, nativeTokenPrice float64) Route { + var best Route + bestCost := big.NewFloat(math.Inf(1)) + for _, route := range routes { + currentCost := big.NewFloat(0) + for _, path := range route { + tokenDenominator := big.NewFloat(math.Pow(10, float64(path.FromToken.Decimals))) + + // calculate the cost of the path + nativeTokenPrice := new(big.Float).SetFloat64(nativeTokenPrice) + + // tx fee + txFeeInEth := common.GweiToEth(common.WeiToGwei(path.TxFee.ToInt())) + pathCost := new(big.Float).Mul(txFeeInEth, nativeTokenPrice) + + if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + txL1FeeInEth := common.GweiToEth(common.WeiToGwei(path.TxL1Fee.ToInt())) + pathCost.Add(pathCost, new(big.Float).Mul(txL1FeeInEth, nativeTokenPrice)) + } + + if path.TxBonderFees != nil && path.TxBonderFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + pathCost.Add(pathCost, new(big.Float).Mul( + new(big.Float).Quo(new(big.Float).SetInt(path.TxBonderFees.ToInt()), tokenDenominator), + new(big.Float).SetFloat64(tokenPrice))) + + } + + if path.TxTokenFees != nil && path.TxTokenFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && path.FromToken != nil { + pathCost.Add(pathCost, new(big.Float).Mul( + new(big.Float).Quo(new(big.Float).SetInt(path.TxTokenFees.ToInt()), tokenDenominator), + new(big.Float).SetFloat64(tokenPrice))) + } + + if path.ApprovalRequired { + // tx approval fee + approvalFeeInEth := common.GweiToEth(common.WeiToGwei(path.ApprovalFee.ToInt())) + pathCost.Add(pathCost, new(big.Float).Mul(approvalFeeInEth, nativeTokenPrice)) + + if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 { + approvalL1FeeInEth := common.GweiToEth(common.WeiToGwei(path.ApprovalL1Fee.ToInt())) + pathCost.Add(pathCost, new(big.Float).Mul(approvalL1FeeInEth, nativeTokenPrice)) + } + } + + currentCost = new(big.Float).Add(currentCost, pathCost) + } + + if currentCost.Cmp(bestCost) == -1 { + best = route + bestCost = currentCost + } + } + + return best +} diff --git a/services/wallet/router/routes/router_graph.go b/services/wallet/router/routes/router_graph.go new file mode 100644 index 00000000000..0daf3431481 --- /dev/null +++ b/services/wallet/router/routes/router_graph.go @@ -0,0 +1,77 @@ +package routes + +import ( + "math/big" +) + +type Graph []*Node + +type Node struct { + Path *Path + Children Graph +} + +func newNode(path *Path) *Node { + return &Node{Path: path, Children: make(Graph, 0)} +} + +func BuildGraph(AmountIn *big.Int, route Route, level int, sourceChainIDs []uint64) Graph { + graph := make(Graph, 0) + for _, path := range route { + found := false + for _, chainID := range sourceChainIDs { + if chainID == path.FromChain.ChainID { + found = true + break + } + } + if found { + continue + } + node := newNode(path) + + newRoute := make(Route, 0) + for _, p := range route { + if path.Equal(p) { + continue + } + newRoute = append(newRoute, p) + } + + newAmountIn := new(big.Int).Sub(AmountIn, path.AmountIn.ToInt()) + if newAmountIn.Sign() > 0 { + newSourceChainIDs := make([]uint64, len(sourceChainIDs)) + copy(newSourceChainIDs, sourceChainIDs) + newSourceChainIDs = append(newSourceChainIDs, path.FromChain.ChainID) + node.Children = BuildGraph(newAmountIn, newRoute, level+1, newSourceChainIDs) + + if len(node.Children) == 0 { + continue + } + } + + graph = append(graph, node) + } + + return graph +} + +func (n Node) BuildAllRoutes() []Route { + res := make([]Route, 0) + + if len(n.Children) == 0 && n.Path != nil { + res = append(res, Route{n.Path}) + } + + for _, node := range n.Children { + for _, route := range node.BuildAllRoutes() { + extendedRoute := route + if n.Path != nil { + extendedRoute = append(Route{n.Path}, route...) + } + res = append(res, extendedRoute) + } + } + + return res +} diff --git a/services/wallet/router/routes/router_path.go b/services/wallet/router/routes/router_path.go new file mode 100644 index 00000000000..1aa8ee42110 --- /dev/null +++ b/services/wallet/router/routes/router_path.go @@ -0,0 +1,56 @@ +package routes + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/status-im/status-go/params" + "github.com/status-im/status-go/services/wallet/router/fees" + walletToken "github.com/status-im/status-go/services/wallet/token" +) + +type Path struct { + ProcessorName string + FromChain *params.Network // Source chain + ToChain *params.Network // Destination chain + FromToken *walletToken.Token // Source token + ToToken *walletToken.Token // Destination token, set if applicable + AmountIn *hexutil.Big // Amount that will be sent from the source chain + AmountInLocked bool // Is the amount locked + 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) + + 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) + + 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) + + 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 + 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 + + 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) Equal(o *Path) bool { + return p.FromChain.ChainID == o.FromChain.ChainID && p.ToChain.ChainID == o.ToChain.ChainID +} diff --git a/services/wallet/router/router_send_type.go b/services/wallet/router/sendtype/send_type.go similarity index 68% rename from services/wallet/router/router_send_type.go rename to services/wallet/router/sendtype/send_type.go index 50ddbd7aca3..17b1bca5d4a 100644 --- a/services/wallet/router/router_send_type.go +++ b/services/wallet/router/sendtype/send_type.go @@ -1,24 +1,16 @@ -package router +package sendtype import ( - "context" - "fmt" "math/big" "strings" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" - "github.com/status-im/status-go/services/ens" - "github.com/status-im/status-go/services/stickers" - "github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/collectibles" walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/market" "github.com/status-im/status-go/services/wallet/router/pathprocessor" "github.com/status-im/status-go/services/wallet/token" - "github.com/status-im/status-go/transactions" ) type SendType int @@ -91,20 +83,8 @@ func (s SendType) FindToken(tokenManager *token.Manager, collectibles *collectib } } -// TODO: remove this function once we fully move to routerV2 -func (s SendType) isTransfer(routerV2Logic bool) bool { - return s == Transfer || - s == Bridge && routerV2Logic || - s == Swap || - s.IsCollectiblesTransfer() -} - -func (s SendType) needL1Fee() bool { - return !s.IsEnsTransfer() && !s.IsStickersTransfer() -} - // canUseProcessor is used to check if certain SendType can be used with a given path processor -func (s SendType) canUseProcessor(p pathprocessor.PathProcessor) bool { +func (s SendType) CanUseProcessor(p pathprocessor.PathProcessor) bool { pathProcessorName := p.Name() switch s { case Transfer: @@ -131,7 +111,7 @@ func (s SendType) canUseProcessor(p pathprocessor.PathProcessor) bool { } } -func (s SendType) processZeroAmountInProcessor(amountIn *big.Int, amountOut *big.Int, processorName string) bool { +func (s SendType) ProcessZeroAmountInProcessor(amountIn *big.Int, amountOut *big.Int, processorName string) bool { if amountIn.Cmp(pathprocessor.ZeroBigIntValue) == 0 { if s == Transfer { if processorName != pathprocessor.ProcessorTransferName { @@ -149,7 +129,7 @@ func (s SendType) processZeroAmountInProcessor(amountIn *big.Int, amountOut *big return true } -func (s SendType) isAvailableBetween(from, to *params.Network) bool { +func (s SendType) IsAvailableBetween(from, to *params.Network) bool { if s.IsCollectiblesTransfer() || s.IsEnsTransfer() || s.IsStickersTransfer() || @@ -164,7 +144,7 @@ func (s SendType) isAvailableBetween(from, to *params.Network) bool { return true } -func (s SendType) isAvailableFor(network *params.Network) bool { +func (s SendType) IsAvailableFor(network *params.Network) bool { // Set of network ChainIDs allowed for any type of transaction allAllowedNetworks := map[uint64]bool{ walletCommon.EthereumMainnet: true, @@ -195,44 +175,3 @@ func (s SendType) isAvailableFor(network *params.Network) bool { return false } - -// TODO: remove this function once we fully move to routerV2 -func (s SendType) EstimateGas(ensService *ens.Service, stickersService *stickers.Service, network *params.Network, from common.Address, tokenID string) uint64 { - tx := transactions.SendTxArgs{ - From: (types.Address)(from), - Value: (*hexutil.Big)(pathprocessor.ZeroBigIntValue), - } - switch s { - case ENSRegister: - estimate, err := ensService.API().RegisterEstimate(context.Background(), network.ChainID, tx, EstimateUsername, EstimatePubKey) - if err != nil { - return 400000 - } - return estimate - - case ENSRelease: - estimate, err := ensService.API().ReleaseEstimate(context.Background(), network.ChainID, tx, EstimateUsername) - if err != nil { - return 200000 - } - return estimate - - case ENSSetPubKey: - estimate, err := ensService.API().SetPubKeyEstimate(context.Background(), network.ChainID, tx, fmt.Sprint(EstimateUsername, ".stateofus.eth"), EstimatePubKey) - if err != nil { - return 400000 - } - return estimate - - case StickersBuy: - packID := &bigint.BigInt{Int: big.NewInt(2)} - estimate, err := stickersService.API().BuyEstimate(context.Background(), network.ChainID, (types.Address)(from), packID) - if err != nil { - return 400000 - } - return estimate - - default: - return 0 - } -}