2024-05-16 08:55:46 +00:00
package router
2024-05-14 19:11:16 +00:00
import (
"context"
2024-06-30 20:44:21 +00:00
"fmt"
2024-05-14 19:11:16 +00:00
"math/big"
"sort"
2024-08-23 14:01:49 +00:00
"strings"
2024-05-14 19:11:16 +00:00
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
2024-07-18 12:20:54 +00:00
"github.com/ethereum/go-ethereum/log"
2024-07-03 09:30:17 +00:00
"github.com/status-im/status-go/errors"
2024-05-14 19:11:16 +00:00
"github.com/status-im/status-go/params"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/rpc"
2024-06-06 20:08:25 +00:00
"github.com/status-im/status-go/services/ens"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/stickers"
2024-05-14 19:11:16 +00:00
"github.com/status-im/status-go/services/wallet/async"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/collectibles"
2024-06-05 07:56:02 +00:00
walletCommon "github.com/status-im/status-go/services/wallet/common"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/market"
2024-08-28 11:17:59 +00:00
"github.com/status-im/status-go/services/wallet/requests"
2024-08-28 12:06:50 +00:00
"github.com/status-im/status-go/services/wallet/responses"
2024-08-28 11:17:59 +00:00
"github.com/status-im/status-go/services/wallet/router/fees"
2024-06-06 20:08:25 +00:00
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
2024-08-28 12:06:50 +00:00
"github.com/status-im/status-go/services/wallet/router/routes"
2024-08-28 11:17:59 +00:00
"github.com/status-im/status-go/services/wallet/router/sendtype"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/token"
2024-05-14 19:11:16 +00:00
walletToken "github.com/status-im/status-go/services/wallet/token"
2024-06-20 09:03:42 +00:00
"github.com/status-im/status-go/signal"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/transactions"
2024-06-20 09:03:42 +00:00
)
var (
routerTask = async . TaskType {
ID : 1 ,
Policy : async . ReplacementPolicyCancelOld ,
}
2024-05-14 19:11:16 +00:00
)
2024-07-04 10:48:14 +00:00
type amountOption struct {
2024-07-25 12:15:30 +00:00
amount * big . Int
locked bool
subtractFees bool
2024-06-30 20:44:21 +00:00
}
2024-07-04 10:48:14 +00:00
func makeBalanceKey ( chainID uint64 , symbol string ) string {
return fmt . Sprintf ( "%d-%s" , chainID , symbol )
2024-06-30 20:44:21 +00:00
}
2024-07-18 12:20:54 +00:00
type ProcessorError struct {
ProcessorName string
Error error
}
2024-08-28 09:23:32 +00:00
type SuggestedRoutes struct {
2024-09-06 11:00:22 +00:00
Uuid string
Best routes . Route
Candidates routes . Route
UpdatedPrices map [ string ] float64
2024-05-14 19:11:16 +00:00
}
2024-08-28 08:59:19 +00:00
type Router struct {
rpcClient * rpc . Client
tokenManager * token . Manager
marketManager * market . Manager
collectiblesService * collectibles . Service
collectiblesManager * collectibles . Manager
ensService * ens . Service
stickersService * stickers . Service
2024-08-28 11:17:59 +00:00
feesManager * fees . FeeManager
2024-08-28 08:59:19 +00:00
pathProcessors map [ string ] pathprocessor . PathProcessor
scheduler * async . Scheduler
2024-08-29 13:40:30 +00:00
2024-08-30 09:03:52 +00:00
activeBalanceMap sync . Map // map[string]*big.Int
2024-08-29 13:40:30 +00:00
activeRoutesMutex sync . Mutex
activeRoutes * SuggestedRoutes
2024-09-24 10:39:28 +00:00
routeCanceledMutex sync . Mutex
routeCanceled bool
2024-08-29 13:40:30 +00:00
lastInputParamsMutex sync . Mutex
lastInputParams * requests . RouteInputParams
clientsForUpdatesPerChains sync . Map
2024-08-28 08:59:19 +00:00
}
func NewRouter ( rpcClient * rpc . Client , transactor * transactions . Transactor , tokenManager * token . Manager , marketManager * market . Manager ,
collectibles * collectibles . Service , collectiblesManager * collectibles . Manager , ensService * ens . Service , stickersService * stickers . Service ) * Router {
processors := make ( map [ string ] pathprocessor . PathProcessor )
return & Router {
rpcClient : rpcClient ,
tokenManager : tokenManager ,
marketManager : marketManager ,
collectiblesService : collectibles ,
collectiblesManager : collectiblesManager ,
ensService : ensService ,
stickersService : stickersService ,
2024-08-28 11:17:59 +00:00
feesManager : & fees . FeeManager {
RPCClient : rpcClient ,
} ,
pathProcessors : processors ,
scheduler : async . NewScheduler ( ) ,
2024-08-28 08:59:19 +00:00
}
}
func ( r * Router ) AddPathProcessor ( processor pathprocessor . PathProcessor ) {
r . pathProcessors [ processor . Name ( ) ] = processor
}
func ( r * Router ) Stop ( ) {
r . scheduler . Stop ( )
}
2024-08-28 11:17:59 +00:00
func ( r * Router ) GetFeesManager ( ) * fees . FeeManager {
2024-08-28 08:59:19 +00:00
return r . feesManager
}
func ( r * Router ) GetPathProcessors ( ) map [ string ] pathprocessor . PathProcessor {
return r . pathProcessors
}
2024-09-23 07:19:00 +00:00
func ( r * Router ) GetBestRouteAndAssociatedInputParams ( ) ( routes . Route , requests . RouteInputParams ) {
r . activeRoutesMutex . Lock ( )
defer r . activeRoutesMutex . Unlock ( )
if r . activeRoutes == nil {
return nil , requests . RouteInputParams { }
}
r . lastInputParamsMutex . Lock ( )
defer r . lastInputParamsMutex . Unlock ( )
ip := * r . lastInputParams
return r . activeRoutes . Best . Copy ( ) , ip
}
2024-08-30 09:03:52 +00:00
func ( r * Router ) SetTestBalanceMap ( balanceMap map [ string ] * big . Int ) {
for k , v := range balanceMap {
r . activeBalanceMap . Store ( k , v )
}
}
2024-08-28 09:23:32 +00:00
func newSuggestedRoutes (
2024-09-06 11:00:22 +00:00
input * requests . RouteInputParams ,
2024-08-28 12:06:50 +00:00
candidates routes . Route ,
2024-09-06 11:00:22 +00:00
updatedPrices map [ string ] float64 ,
2024-08-28 12:06:50 +00:00
) ( * SuggestedRoutes , [ ] routes . Route ) {
2024-08-28 09:23:32 +00:00
suggestedRoutes := & SuggestedRoutes {
2024-09-06 11:00:22 +00:00
Uuid : input . Uuid ,
Candidates : candidates ,
UpdatedPrices : updatedPrices ,
2024-05-14 19:11:16 +00:00
}
if len ( candidates ) == 0 {
2024-06-30 20:44:21 +00:00
return suggestedRoutes , nil
2024-05-14 19:11:16 +00:00
}
2024-08-28 12:06:50 +00:00
node := & routes . Node {
2024-05-14 19:11:16 +00:00
Path : nil ,
2024-09-06 11:00:22 +00:00
Children : routes . BuildGraph ( input . AmountIn . ToInt ( ) , candidates , 0 , [ ] uint64 { } ) ,
2024-05-14 19:11:16 +00:00
}
2024-08-28 12:06:50 +00:00
allRoutes := node . BuildAllRoutes ( )
2024-09-06 11:00:22 +00:00
allRoutes = filterRoutes ( allRoutes , input . AmountIn . ToInt ( ) , input . FromLockedAmount )
2024-05-14 19:11:16 +00:00
2024-07-29 10:54:59 +00:00
if len ( allRoutes ) > 0 {
sort . Slice ( allRoutes , func ( i , j int ) bool {
iRoute := getRoutePriority ( allRoutes [ i ] )
jRoute := getRoutePriority ( allRoutes [ j ] )
return iRoute <= jRoute
} )
}
2024-06-30 20:44:21 +00:00
return suggestedRoutes , allRoutes
2024-05-14 19:11:16 +00:00
}
2024-08-29 13:40:30 +00:00
func sendRouterResult ( uuid string , result interface { } , err error ) {
routesResponse := responses . RouterSuggestedRoutes {
Uuid : uuid ,
}
if err != nil {
errorResponse := errors . CreateErrorResponseFromError ( err )
routesResponse . ErrorResponse = errorResponse . ( * errors . ErrorResponse )
}
if suggestedRoutes , ok := result . ( * SuggestedRoutes ) ; ok && suggestedRoutes != nil {
routesResponse . Best = suggestedRoutes . Best
routesResponse . Candidates = suggestedRoutes . Candidates
2024-09-06 11:00:22 +00:00
routesResponse . UpdatedPrices = suggestedRoutes . UpdatedPrices
2024-08-29 13:40:30 +00:00
}
signal . SendWalletEvent ( signal . SuggestedRoutes , routesResponse )
}
2024-08-28 11:17:59 +00:00
func ( r * Router ) SuggestedRoutesAsync ( input * requests . RouteInputParams ) {
2024-06-20 09:03:42 +00:00
r . scheduler . Enqueue ( routerTask , func ( ctx context . Context ) ( interface { } , error ) {
2024-08-28 09:23:32 +00:00
return r . SuggestedRoutes ( ctx , input )
2024-06-20 09:03:42 +00:00
} , func ( result interface { } , taskType async . TaskType , err error ) {
2024-08-29 13:40:30 +00:00
sendRouterResult ( input . Uuid , result , err )
2024-06-20 09:03:42 +00:00
} )
}
2024-10-09 19:52:35 +00:00
func ( r * Router ) clearActiveRoute ( ) {
r . activeRoutesMutex . Lock ( )
r . activeRoutes = nil
r . activeRoutesMutex . Unlock ( )
}
2024-09-24 10:39:28 +00:00
func ( r * Router ) markRouteCanceled ( value bool ) {
r . routeCanceledMutex . Lock ( )
r . routeCanceled = value
r . routeCanceledMutex . Unlock ( )
}
2024-10-09 19:52:35 +00:00
func ( r * Router ) abortUpdates ( ) {
2024-09-24 10:39:28 +00:00
r . markRouteCanceled ( true )
2024-08-29 13:40:30 +00:00
r . unsubscribeFeesUpdateAccrossAllChains ( )
2024-10-09 19:52:35 +00:00
}
func ( r * Router ) StopSuggestedRoutesAsyncCalculation ( ) {
r . abortUpdates ( )
2024-06-20 09:03:42 +00:00
r . scheduler . Stop ( )
}
2024-08-29 13:40:30 +00:00
func ( r * Router ) StopSuggestedRoutesCalculation ( ) {
2024-10-09 19:52:35 +00:00
r . abortUpdates ( )
2024-08-29 13:40:30 +00:00
}
2024-06-20 09:03:42 +00:00
2024-08-29 13:40:30 +00:00
func ( r * Router ) SuggestedRoutes ( ctx context . Context , input * requests . RouteInputParams ) ( suggestedRoutes * SuggestedRoutes , err error ) {
2024-10-09 19:52:35 +00:00
r . clearActiveRoute ( )
r . abortUpdates ( )
2024-09-24 10:39:28 +00:00
r . markRouteCanceled ( false )
2024-06-20 09:03:42 +00:00
2024-06-14 11:27:53 +00:00
// clear all processors
for _ , processor := range r . pathProcessors {
if clearable , ok := processor . ( pathprocessor . PathProcessorClearable ) ; ok {
clearable . Clear ( )
}
}
2024-08-29 13:40:30 +00:00
r . lastInputParamsMutex . Lock ( )
r . lastInputParams = input
r . lastInputParamsMutex . Unlock ( )
defer func ( ) {
r . activeRoutesMutex . Lock ( )
r . activeRoutes = suggestedRoutes
r . activeRoutesMutex . Unlock ( )
2024-09-24 10:39:28 +00:00
r . routeCanceledMutex . Lock ( )
if suggestedRoutes != nil && err == nil && ! r . routeCanceled {
2024-08-29 13:40:30 +00:00
// subscribe for updates
for _ , path := range suggestedRoutes . Best {
err = r . subscribeForUdates ( path . FromChain . ChainID )
}
}
2024-09-24 10:39:28 +00:00
r . routeCanceledMutex . Unlock ( )
2024-08-29 13:40:30 +00:00
} ( )
testnetMode , err := r . rpcClient . NetworkManager . GetTestNetworksEnabled ( )
if err != nil {
return nil , errors . CreateErrorResponseFromError ( err )
}
input . TestnetMode = testnetMode
2024-08-28 11:17:59 +00:00
err = input . Validate ( )
2024-06-18 10:31:23 +00:00
if err != nil {
2024-07-03 09:30:17 +00:00
return nil , errors . CreateErrorResponseFromError ( err )
2024-06-18 10:31:23 +00:00
}
2024-08-19 12:44:46 +00:00
selectedFromChains , selectedToChains , err := r . getSelectedChains ( input )
2024-06-14 11:27:53 +00:00
if err != nil {
2024-07-03 09:30:17 +00:00
return nil , errors . CreateErrorResponseFromError ( err )
2024-06-14 11:27:53 +00:00
}
2024-08-30 09:03:52 +00:00
err = r . prepareBalanceMapForTokenOnChains ( ctx , input , selectedFromChains )
2024-08-12 10:35:43 +00:00
// return only if there are no balances, otherwise try to resolve the candidates for chains we know the balances for
2024-08-30 09:03:52 +00:00
noBalanceOnAnyChain := true
r . activeBalanceMap . Range ( func ( key , value interface { } ) bool {
2024-09-23 06:35:34 +00:00
if value . ( * big . Int ) . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-08-30 09:03:52 +00:00
noBalanceOnAnyChain = false
return false
}
return true
} )
if noBalanceOnAnyChain {
2024-08-23 14:01:49 +00:00
if err != nil {
return nil , errors . CreateErrorResponseFromError ( err )
}
2024-08-30 09:03:52 +00:00
return nil , ErrNoPositiveBalance
2024-07-04 10:48:14 +00:00
}
2024-06-18 10:31:23 +00:00
2024-08-30 09:03:52 +00:00
candidates , processorErrors , err := r . resolveCandidates ( ctx , input , selectedFromChains , selectedToChains )
2024-05-14 19:11:16 +00:00
if err != nil {
2024-07-03 09:30:17 +00:00
return nil , errors . CreateErrorResponseFromError ( err )
2024-05-14 19:11:16 +00:00
}
2024-08-30 09:03:52 +00:00
suggestedRoutes , err = r . resolveRoutes ( ctx , input , candidates )
2024-07-18 12:20:54 +00:00
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.
2024-07-29 06:04:35 +00:00
// 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 )
}
2024-07-18 12:20:54 +00:00
} else {
err = ErrNoBestRouteFound
}
}
2024-08-23 14:01:49 +00:00
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 {
2024-08-28 11:17:59 +00:00
addressIndex += len ( pattern ) + walletCommon . HexAddressLength
2024-08-23 14:01:49 +00:00
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 )
2024-07-04 10:48:14 +00:00
}
2024-05-14 19:11:16 +00:00
2024-08-30 09:03:52 +00:00
// prepareBalanceMapForTokenOnChains prepares the balance map for passed address, where the key is in format "chainID-tokenSymbol" and
2024-07-04 10:48:14 +00:00
// value is the balance of the token. Native token (EHT) is always added to the balance map.
2024-08-30 09:03:52 +00:00
func ( r * Router ) prepareBalanceMapForTokenOnChains ( ctx context . Context , input * requests . RouteInputParams , selectedFromChains [ ] * params . Network ) ( err error ) {
// clear the active balance map
r . activeBalanceMap = sync . Map { }
2024-08-28 11:17:59 +00:00
if input . TestsMode {
2024-08-30 09:03:52 +00:00
for k , v := range input . TestParams . BalanceMap {
r . activeBalanceMap . Store ( k , v )
}
return nil
2024-07-04 10:48:14 +00:00
}
2024-08-12 10:35:43 +00:00
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 )
}
}
2024-07-04 10:48:14 +00:00
for _ , chain := range selectedFromChains {
2024-08-12 10:35:43 +00:00
// check token existence
2024-07-04 10:48:14 +00:00
token := input . SendType . FindToken ( r . tokenManager , r . collectiblesService , input . AddrFrom , chain , input . TokenID )
if token == nil {
2024-08-19 06:13:40 +00:00
chainError ( chain . ChainID , input . TokenID , ErrTokenNotFound )
2024-08-12 10:35:43 +00:00
continue
}
// check native token existence
nativeToken := r . tokenManager . FindToken ( chain , chain . NativeCurrencySymbol )
if nativeToken == nil {
chainError ( chain . ChainID , chain . NativeCurrencySymbol , ErrNativeTokenNotFound )
2024-05-14 19:11:16 +00:00
continue
}
2024-07-04 10:48:14 +00:00
// add token balance for the chain
2024-08-12 10:35:43 +00:00
var tokenBalance * big . Int
2024-08-28 11:17:59 +00:00
if input . SendType == sendtype . ERC721Transfer {
2024-08-12 10:35:43 +00:00
tokenBalance = big . NewInt ( 1 )
2024-08-28 11:17:59 +00:00
} else if input . SendType == sendtype . ERC1155Transfer {
2024-07-04 10:48:14 +00:00
tokenBalance , err = r . getERC1155Balance ( ctx , chain , token , input . AddrFrom )
if err != nil {
2024-08-12 10:35:43 +00:00
chainError ( chain . ChainID , token . Symbol , errors . CreateErrorResponseFromError ( err ) )
2024-07-04 10:48:14 +00:00
}
2024-08-12 10:35:43 +00:00
} else {
2024-07-04 10:48:14 +00:00
tokenBalance , err = r . getBalance ( ctx , chain . ChainID , token , input . AddrFrom )
if err != nil {
2024-08-12 10:35:43 +00:00
chainError ( chain . ChainID , token . Symbol , errors . CreateErrorResponseFromError ( err ) )
2024-07-04 10:48:14 +00:00
}
2024-05-14 19:11:16 +00:00
}
2024-08-12 10:35:43 +00:00
// add only if balance is not nil
if tokenBalance != nil {
2024-08-30 09:03:52 +00:00
r . activeBalanceMap . Store ( makeBalanceKey ( chain . ChainID , token . Symbol ) , tokenBalance )
2024-07-04 10:48:14 +00:00
}
2024-08-23 14:01:49 +00:00
if token . IsNative ( ) {
continue
}
2024-08-12 10:35:43 +00:00
// add native token balance for the chain
2024-07-04 10:48:14 +00:00
nativeBalance , err := r . getBalance ( ctx , chain . ChainID , nativeToken , input . AddrFrom )
if err != nil {
2024-08-12 10:35:43 +00:00
chainError ( chain . ChainID , token . Symbol , errors . CreateErrorResponseFromError ( err ) )
}
// add only if balance is not nil
if nativeBalance != nil {
2024-08-30 09:03:52 +00:00
r . activeBalanceMap . Store ( makeBalanceKey ( chain . ChainID , nativeToken . Symbol ) , nativeBalance )
2024-07-04 10:48:14 +00:00
}
}
return
}
2024-08-28 11:17:59 +00:00
func ( r * Router ) getSelectedUnlockedChains ( input * requests . RouteInputParams , processingChain * params . Network , selectedFromChains [ ] * params . Network ) [ ] * params . Network {
2024-07-04 10:48:14 +00:00
selectedButNotLockedChains := [ ] * params . Network { processingChain } // always add the processing chain at the beginning
for _ , net := range selectedFromChains {
if net . ChainID == processingChain . ChainID {
2024-05-14 19:11:16 +00:00
continue
}
2024-07-04 10:48:14 +00:00
if _ , ok := input . FromLockedAmount [ net . ChainID ] ; ! ok {
selectedButNotLockedChains = append ( selectedButNotLockedChains , net )
}
}
return selectedButNotLockedChains
}
2024-08-28 11:17:59 +00:00
func ( r * Router ) getOptionsForAmoutToSplitAccrossChainsForProcessingChain ( input * requests . RouteInputParams , amountToSplit * big . Int , processingChain * params . Network ,
2024-08-30 09:03:52 +00:00
selectedFromChains [ ] * params . Network ) map [ uint64 ] [ ] amountOption {
2024-07-04 10:48:14 +00:00
selectedButNotLockedChains := r . getSelectedUnlockedChains ( input , processingChain , selectedFromChains )
2024-05-14 19:11:16 +00:00
2024-07-04 10:48:14 +00:00
crossChainAmountOptions := make ( map [ uint64 ] [ ] amountOption )
for _ , chain := range selectedButNotLockedChains {
2024-06-05 07:56:02 +00:00
var (
2024-07-04 10:48:14 +00:00
ok bool
tokenBalance * big . Int
2024-06-05 07:56:02 +00:00
)
2024-08-30 09:03:52 +00:00
value , ok := r . activeBalanceMap . Load ( makeBalanceKey ( chain . ChainID , input . TokenID ) )
if ! ok {
continue
}
tokenBalance , ok = value . ( * big . Int )
if ! ok {
2024-05-14 19:11:16 +00:00
continue
}
2024-09-23 06:35:34 +00:00
if tokenBalance . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-07-04 10:48:14 +00:00
if tokenBalance . Cmp ( amountToSplit ) <= 0 {
crossChainAmountOptions [ chain . ChainID ] = append ( crossChainAmountOptions [ chain . ChainID ] , amountOption {
2024-07-25 12:15:30 +00:00
amount : tokenBalance ,
locked : false ,
subtractFees : true , // for chains where we're taking the full balance, we want to subtract the fees
2024-07-04 10:48:14 +00:00
} )
amountToSplit = new ( big . Int ) . Sub ( amountToSplit , tokenBalance )
2024-09-23 06:35:34 +00:00
} else if amountToSplit . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-07-04 10:48:14 +00:00
crossChainAmountOptions [ chain . ChainID ] = append ( crossChainAmountOptions [ chain . ChainID ] , amountOption {
amount : amountToSplit ,
locked : false ,
} )
// break since amountToSplit is fully addressed and the rest is 0
break
}
2024-05-14 19:11:16 +00:00
}
2024-07-04 10:48:14 +00:00
}
return crossChainAmountOptions
}
2024-08-30 09:03:52 +00:00
func ( r * Router ) getCrossChainsOptionsForSendingAmount ( input * requests . RouteInputParams , selectedFromChains [ ] * params . Network ) map [ uint64 ] [ ] amountOption {
2024-07-04 10:48:14 +00:00
// 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 {
2024-05-14 19:11:16 +00:00
amountLocked := false
2024-05-16 07:37:36 +00:00
amountToSend := input . AmountIn . ToInt ( )
2024-07-23 10:15:29 +00:00
2024-09-23 06:35:34 +00:00
if amountToSend . Cmp ( walletCommon . ZeroBigIntValue ( ) ) == 0 {
2024-07-23 10:15:29 +00:00
finalCrossChainAmountOptions [ selectedFromChain . ChainID ] = append ( finalCrossChainAmountOptions [ selectedFromChain . ChainID ] , amountOption {
amount : amountToSend ,
locked : false ,
} )
continue
}
2024-07-04 10:48:14 +00:00
lockedAmount , fromChainLocked := input . FromLockedAmount [ selectedFromChain . ChainID ]
if fromChainLocked {
2024-05-14 19:11:16 +00:00
amountToSend = lockedAmount . ToInt ( )
amountLocked = true
2024-06-14 11:27:53 +00:00
} else if len ( input . FromLockedAmount ) > 0 {
2024-05-16 07:37:36 +00:00
for chainID , lockedAmount := range input . FromLockedAmount {
2024-07-04 10:48:14 +00:00
if chainID == selectedFromChain . ChainID {
2024-05-14 19:11:16 +00:00
continue
}
amountToSend = new ( big . Int ) . Sub ( amountToSend , lockedAmount . ToInt ( ) )
}
}
2024-09-23 06:35:34 +00:00
if amountToSend . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-07-04 10:48:14 +00:00
// 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 {
2024-06-20 09:03:42 +00:00
continue
2024-05-14 19:11:16 +00:00
}
2024-07-04 10:48:14 +00:00
// 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.
2024-08-28 11:17:59 +00:00
if input . SendType == sendtype . Transfer && len ( selectedFromChains ) > 1 {
2024-07-04 10:48:14 +00:00
// 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 )
2024-05-14 19:11:16 +00:00
2024-08-30 09:03:52 +00:00
crossChainAmountOptions := r . getOptionsForAmoutToSplitAccrossChainsForProcessingChain ( input , amountToSend , selectedFromChain , selectedFromChains )
2024-05-14 19:11:16 +00:00
2024-07-04 10:48:14 +00:00
// 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 )
2024-05-16 10:22:32 +00:00
}
2024-07-04 10:48:14 +00:00
}
2024-05-16 10:22:32 +00:00
2024-07-04 10:48:14 +00:00
// 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 ... )
2024-05-16 10:22:32 +00:00
}
2024-07-04 10:48:14 +00:00
}
}
}
}
2024-05-16 10:22:32 +00:00
2024-07-04 10:48:14 +00:00
return finalCrossChainAmountOptions
}
2024-05-14 19:11:16 +00:00
2024-08-30 09:03:52 +00:00
func ( r * Router ) findOptionsForSendingAmount ( input * requests . RouteInputParams , selectedFromChains [ ] * params . Network ) ( map [ uint64 ] [ ] amountOption , error ) {
2024-06-05 07:56:02 +00:00
2024-08-30 09:03:52 +00:00
crossChainAmountOptions := r . getCrossChainsOptionsForSendingAmount ( input , selectedFromChains )
2024-05-14 19:11:16 +00:00
2024-07-04 10:48:14 +00:00
// 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
}
2024-05-14 19:11:16 +00:00
2024-07-04 10:48:14 +00:00
crossChainAmountOptions [ chainID ] = make ( [ ] amountOption , 0 )
for _ , amountOption := range uniqueAmountOptions {
crossChainAmountOptions [ chainID ] = append ( crossChainAmountOptions [ chainID ] , amountOption )
}
}
2024-05-14 19:11:16 +00:00
2024-07-04 10:48:14 +00:00
return crossChainAmountOptions , nil
}
2024-05-14 19:11:16 +00:00
2024-08-28 11:17:59 +00:00
func ( r * Router ) getSelectedChains ( input * requests . RouteInputParams ) ( selectedFromChains [ ] * params . Network , selectedToChains [ ] * params . Network , err error ) {
2024-07-04 10:48:14 +00:00
var networks [ ] * params . Network
networks , err = r . rpcClient . NetworkManager . Get ( false )
if err != nil {
return nil , nil , errors . CreateErrorResponseFromError ( err )
}
2024-05-14 19:11:16 +00:00
2024-07-04 10:48:14 +00:00
for _ , network := range networks {
2024-08-28 11:17:59 +00:00
if network . IsTest != input . TestnetMode {
2024-07-04 10:48:14 +00:00
continue
}
2024-05-14 19:11:16 +00:00
2024-08-28 11:17:59 +00:00
if ! walletCommon . ArrayContainsElement ( network . ChainID , input . DisabledFromChainIDs ) {
2024-07-04 10:48:14 +00:00
selectedFromChains = append ( selectedFromChains , network )
}
2024-08-28 11:17:59 +00:00
if ! walletCommon . ArrayContainsElement ( network . ChainID , input . DisabledToChainIDs ) {
2024-08-19 12:44:46 +00:00
selectedToChains = append ( selectedToChains , network )
2024-07-04 10:48:14 +00:00
}
}
2024-05-14 19:11:16 +00:00
2024-08-19 12:44:46 +00:00
return selectedFromChains , selectedToChains , nil
2024-07-04 10:48:14 +00:00
}
2024-06-19 09:20:41 +00:00
2024-08-28 11:17:59 +00:00
func ( r * Router ) resolveCandidates ( ctx context . Context , input * requests . RouteInputParams , selectedFromChains [ ] * params . Network ,
2024-08-30 09:03:52 +00:00
selectedToChains [ ] * params . Network ) ( candidates routes . Route , processorErrors [ ] * ProcessorError , err error ) {
2024-07-04 10:48:14 +00:00
var (
2024-08-28 11:17:59 +00:00
testsMode = input . TestsMode && input . TestParams != nil
2024-07-04 10:48:14 +00:00
group = async . NewAtomicGroup ( ctx )
mu sync . Mutex
)
2024-08-30 09:03:52 +00:00
crossChainAmountOptions , err := r . findOptionsForSendingAmount ( input , selectedFromChains )
2024-07-04 10:48:14 +00:00
if err != nil {
2024-07-18 12:20:54 +00:00
return nil , nil , errors . CreateErrorResponseFromError ( err )
}
2024-08-28 11:17:59 +00:00
appendProcessorErrorFn := func ( processorName string , sendType sendtype . SendType , fromChainID uint64 , toChainID uint64 , amount * big . Int , err error ) {
2024-08-28 09:23:32 +00:00
log . Error ( "router.resolveCandidates error" , "processor" , processorName , "sendType" , sendType , "fromChainId: " , fromChainID , "toChainId" , toChainID , "amount" , amount , "err" , err )
2024-07-18 12:20:54 +00:00
mu . Lock ( )
defer mu . Unlock ( )
processorErrors = append ( processorErrors , & ProcessorError {
ProcessorName : processorName ,
Error : err ,
} )
}
2024-08-28 12:06:50 +00:00
appendPathFn := func ( path * routes . Path ) {
2024-07-18 12:20:54 +00:00
mu . Lock ( )
defer mu . Unlock ( )
candidates = append ( candidates , path )
2024-07-04 10:48:14 +00:00
}
for networkIdx := range selectedFromChains {
network := selectedFromChains [ networkIdx ]
2024-08-28 11:17:59 +00:00
if ! input . SendType . IsAvailableFor ( network ) {
2024-07-04 10:48:14 +00:00
continue
}
var (
token * walletToken . Token
toToken * walletToken . Token
)
if testsMode {
2024-08-28 11:17:59 +00:00
token = input . TestParams . TokenFrom
2024-07-04 10:48:14 +00:00
} else {
token = input . SendType . FindToken ( r . tokenManager , r . collectiblesService , input . AddrFrom , network , input . TokenID )
}
if token == nil {
continue
}
2024-08-28 11:17:59 +00:00
if input . SendType == sendtype . Swap {
2024-07-04 10:48:14 +00:00
toToken = input . SendType . FindToken ( r . tokenManager , r . collectiblesService , common . Address { } , network , input . ToTokenID )
}
2024-08-28 11:17:59 +00:00
var fetchedFees * fees . SuggestedFees
2024-07-04 10:48:14 +00:00
if testsMode {
2024-08-28 11:17:59 +00:00
fetchedFees = input . TestParams . SuggestedFees
2024-07-04 10:48:14 +00:00
} else {
2024-08-28 11:17:59 +00:00
fetchedFees , err = r . feesManager . SuggestedFees ( ctx , network . ChainID )
2024-07-04 10:48:14 +00:00
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.
2024-08-28 11:17:59 +00:00
if ! input . SendType . CanUseProcessor ( pProcessor ) {
2024-07-04 10:48:14 +00:00
continue
2024-05-14 19:11:16 +00:00
}
2024-08-29 15:34:53 +00:00
// if we're doing a single chain operation, we can skip bridge processors
2024-08-28 11:17:59 +00:00
if walletCommon . IsSingleChainOperation ( selectedFromChains , selectedToChains ) && pathprocessor . IsProcessorBridge ( pProcessor . Name ( ) ) {
2024-08-23 14:01:49 +00:00
continue
}
2024-08-28 11:17:59 +00:00
if ! input . SendType . ProcessZeroAmountInProcessor ( amountOption . amount , input . AmountOut . ToInt ( ) , pProcessor . Name ( ) ) {
2024-07-23 10:15:29 +00:00
continue
}
2024-08-19 12:44:46 +00:00
for _ , dest := range selectedToChains {
2024-07-04 10:48:14 +00:00
2024-08-28 11:17:59 +00:00
if ! input . SendType . IsAvailableFor ( network ) {
2024-07-04 10:48:14 +00:00
continue
}
2024-08-28 11:17:59 +00:00
if ! input . SendType . IsAvailableBetween ( network , dest ) {
2024-07-04 10:48:14 +00:00
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 ( ) ,
}
2024-08-28 11:17:59 +00:00
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
2024-07-04 10:48:14 +00:00
}
can , err := pProcessor . AvailableFor ( processorInputParams )
2024-07-18 12:20:54 +00:00
if err != nil {
2024-08-23 14:01:49 +00:00
appendProcessorErrorFn ( pProcessor . Name ( ) , input . SendType , processorInputParams . FromChain . ChainID , processorInputParams . ToChain . ChainID , processorInputParams . AmountIn , err )
2024-07-18 12:20:54 +00:00
continue
}
if ! can {
2024-07-04 10:48:14 +00:00
continue
}
bonderFees , tokenFees , err := pProcessor . CalculateFees ( processorInputParams )
if err != nil {
2024-08-23 14:01:49 +00:00
appendProcessorErrorFn ( pProcessor . Name ( ) , input . SendType , processorInputParams . FromChain . ChainID , processorInputParams . ToChain . ChainID , processorInputParams . AmountIn , err )
2024-07-04 10:48:14 +00:00
continue
}
gasLimit , err := pProcessor . EstimateGas ( processorInputParams )
if err != nil {
2024-08-23 14:01:49 +00:00
appendProcessorErrorFn ( pProcessor . Name ( ) , input . SendType , processorInputParams . FromChain . ChainID , processorInputParams . ToChain . ChainID , processorInputParams . AmountIn , err )
2024-07-04 10:48:14 +00:00
continue
}
approvalContractAddress , err := pProcessor . GetContractAddress ( processorInputParams )
if err != nil {
2024-08-23 14:01:49 +00:00
appendProcessorErrorFn ( pProcessor . Name ( ) , input . SendType , processorInputParams . FromChain . ChainID , processorInputParams . ToChain . ChainID , processorInputParams . AmountIn , err )
2024-07-04 10:48:14 +00:00
continue
}
2024-08-29 13:40:30 +00:00
approvalRequired , approvalAmountRequired , err := r . requireApproval ( ctx , input . SendType , & approvalContractAddress , processorInputParams )
2024-07-04 10:48:14 +00:00
if err != nil {
2024-08-23 14:01:49 +00:00
appendProcessorErrorFn ( pProcessor . Name ( ) , input . SendType , processorInputParams . FromChain . ChainID , processorInputParams . ToChain . ChainID , processorInputParams . AmountIn , err )
2024-07-04 10:48:14 +00:00
continue
}
2024-08-29 13:40:30 +00:00
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
}
}
}
2024-07-04 10:48:14 +00:00
amountOut , err := pProcessor . CalculateAmountOut ( processorInputParams )
if err != nil {
2024-08-23 14:01:49 +00:00
appendProcessorErrorFn ( pProcessor . Name ( ) , input . SendType , processorInputParams . FromChain . ChainID , processorInputParams . ToChain . ChainID , processorInputParams . AmountIn , err )
2024-07-04 10:48:14 +00:00
continue
}
2024-08-28 11:17:59 +00:00
maxFeesPerGas := fetchedFees . FeeFor ( input . GasFeeMode )
2024-07-04 10:48:14 +00:00
estimatedTime := r . feesManager . TransactionEstimatedTime ( ctx , network . ChainID , maxFeesPerGas )
2024-08-28 11:17:59 +00:00
if approvalRequired && estimatedTime < fees . MoreThanFiveMinutes {
2024-07-04 10:48:14 +00:00
estimatedTime += 1
}
2024-08-29 13:40:30 +00:00
path := & routes . Path {
2024-07-04 10:48:14 +00:00
ProcessorName : pProcessor . Name ( ) ,
FromChain : network ,
ToChain : dest ,
FromToken : token ,
ToToken : toToken ,
AmountIn : ( * hexutil . Big ) ( amountOption . amount ) ,
AmountInLocked : amountOption . locked ,
AmountOut : ( * hexutil . Big ) ( amountOut ) ,
2024-08-29 13:40:30 +00:00
// 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 ) ,
2024-07-04 10:48:14 +00:00
ApprovalRequired : approvalRequired ,
ApprovalAmountRequired : ( * hexutil . Big ) ( approvalAmountRequired ) ,
ApprovalContractAddress : & approvalContractAddress ,
ApprovalGasAmount : approvalGasLimit ,
2024-07-25 12:15:30 +00:00
2024-08-29 13:40:30 +00:00
EstimatedTime : estimatedTime ,
2024-07-25 12:15:30 +00:00
2024-08-29 13:40:30 +00:00
SubtractFees : amountOption . subtractFees ,
}
2024-07-04 10:48:14 +00:00
2024-08-29 13:40:30 +00:00
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
}
2024-07-25 12:15:30 +00:00
2024-08-29 13:40:30 +00:00
appendPathFn ( path )
2024-07-04 10:48:14 +00:00
}
2024-05-14 19:11:16 +00:00
}
}
return nil
} )
}
2024-07-29 10:54:59 +00:00
sort . Slice ( candidates , func ( i , j int ) bool {
iChain := getChainPriority ( candidates [ i ] . FromChain . ChainID )
jChain := getChainPriority ( candidates [ j ] . FromChain . ChainID )
return iChain <= jChain
} )
2024-05-14 19:11:16 +00:00
group . Wait ( )
2024-07-18 12:20:54 +00:00
return candidates , processorErrors , nil
2024-06-14 11:27:53 +00:00
}
2024-05-14 19:11:16 +00:00
2024-08-30 09:03:52 +00:00
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
} )
2024-07-25 12:15:30 +00:00
if balanceMapCopy == nil {
2024-07-31 20:40:15 +00:00
return false , ErrCannotCheckBalance
2024-07-25 12:15:30 +00:00
}
2024-05-14 19:11:16 +00:00
// check the best route for the required balances
2024-06-30 20:44:21 +00:00
for _ , path := range bestRoute {
2024-07-31 20:40:15 +00:00
tokenKey := makeBalanceKey ( path . FromChain . ChainID , path . FromToken . Symbol )
if tokenBalance , ok := balanceMapCopy [ tokenKey ] ; ok {
2024-09-23 06:35:34 +00:00
if tokenBalance . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-07-31 20:40:15 +00:00
hasPositiveBalance = true
}
}
2024-07-31 12:12:57 +00:00
if path . ProcessorName == pathprocessor . ProcessorBridgeHopName {
if path . TxBonderFees . ToInt ( ) . Cmp ( path . AmountOut . ToInt ( ) ) > 0 {
2024-07-31 20:40:15 +00:00
return hasPositiveBalance , ErrLowAmountInForHopBridge
2024-07-31 12:12:57 +00:00
}
}
2024-09-23 06:35:34 +00:00
if path . RequiredTokenBalance != nil && path . RequiredTokenBalance . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-07-31 20:40:15 +00:00
if tokenBalance , ok := balanceMapCopy [ tokenKey ] ; ok {
2024-08-28 12:06:50 +00:00
if tokenBalance . Cmp ( path . RequiredTokenBalance ) == - 1 {
2024-07-25 12:15:30 +00:00
err := & errors . ErrorResponse {
Code : ErrNotEnoughTokenBalance . Code ,
Details : fmt . Sprintf ( ErrNotEnoughTokenBalance . Details , path . FromToken . Symbol , path . FromChain . ChainID ) ,
}
2024-07-31 20:40:15 +00:00
return hasPositiveBalance , err
2024-05-14 19:11:16 +00:00
}
2024-08-28 12:06:50 +00:00
balanceMapCopy [ tokenKey ] . Sub ( tokenBalance , path . RequiredTokenBalance )
2024-07-04 10:48:14 +00:00
} else {
2024-07-31 20:40:15 +00:00
return hasPositiveBalance , ErrTokenNotFound
2024-05-14 19:11:16 +00:00
}
}
2024-07-31 20:40:15 +00:00
ethKey := makeBalanceKey ( path . FromChain . ChainID , pathprocessor . EthSymbol )
if nativeBalance , ok := balanceMapCopy [ ethKey ] ; ok {
2024-08-28 12:06:50 +00:00
if nativeBalance . Cmp ( path . RequiredNativeBalance ) == - 1 {
2024-07-25 12:15:30 +00:00
err := & errors . ErrorResponse {
Code : ErrNotEnoughNativeBalance . Code ,
Details : fmt . Sprintf ( ErrNotEnoughNativeBalance . Details , pathprocessor . EthSymbol , path . FromChain . ChainID ) ,
}
2024-07-31 20:40:15 +00:00
return hasPositiveBalance , err
2024-06-18 10:31:23 +00:00
}
2024-08-28 12:06:50 +00:00
balanceMapCopy [ ethKey ] . Sub ( nativeBalance , path . RequiredNativeBalance )
2024-07-04 10:48:14 +00:00
} else {
2024-07-31 20:40:15 +00:00
return hasPositiveBalance , ErrNativeTokenNotFound
2024-06-30 20:44:21 +00:00
}
}
2024-07-31 20:40:15 +00:00
return hasPositiveBalance , nil
2024-06-30 20:44:21 +00:00
}
2024-08-30 09:03:52 +00:00
func ( r * Router ) resolveRoutes ( ctx context . Context , input * requests . RouteInputParams , candidates routes . Route ) ( suggestedRoutes * SuggestedRoutes , err error ) {
2024-06-30 20:44:21 +00:00
var prices map [ string ] float64
2024-08-28 11:17:59 +00:00
if input . TestsMode {
prices = input . TestParams . TokenPrices
2024-06-30 20:44:21 +00:00
} else {
2024-09-06 11:00:22 +00:00
prices , err = input . SendType . FetchPrices ( r . marketManager , [ ] string { input . TokenID , input . ToTokenID } )
2024-06-30 20:44:21 +00:00
if err != nil {
2024-07-03 09:30:17 +00:00
return nil , errors . CreateErrorResponseFromError ( err )
2024-06-30 20:44:21 +00:00
}
}
tokenPrice := prices [ input . TokenID ]
2024-07-25 12:15:30 +00:00
nativeTokenPrice := prices [ pathprocessor . EthSymbol ]
2024-06-30 20:44:21 +00:00
2024-08-28 12:06:50 +00:00
var allRoutes [ ] routes . Route
2024-09-06 11:00:22 +00:00
suggestedRoutes , allRoutes = newSuggestedRoutes ( input , candidates , prices )
2024-06-30 20:44:21 +00:00
2024-07-29 10:54:59 +00:00
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
} )
}
} ( )
2024-07-31 20:40:15 +00:00
var (
2024-08-28 12:06:50 +00:00
bestRoute routes . Route
lastBestRouteWithPositiveBalance routes . Route
2024-07-31 20:40:15 +00:00
lastBestRouteErr error
)
2024-06-30 20:44:21 +00:00
for len ( allRoutes ) > 0 {
2024-08-28 12:06:50 +00:00
bestRoute = routes . FindBestRoute ( allRoutes , tokenPrice , nativeTokenPrice )
2024-07-31 20:40:15 +00:00
var hasPositiveBalance bool
2024-08-30 09:03:52 +00:00
hasPositiveBalance , err = r . checkBalancesForTheBestRoute ( ctx , bestRoute )
2024-06-30 20:44:21 +00:00
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
2024-08-28 11:17:59 +00:00
if input . SendType == sendtype . Transfer ||
input . SendType == sendtype . Bridge {
2024-07-31 20:40:15 +00:00
if hasPositiveBalance {
lastBestRouteWithPositiveBalance = bestRoute
lastBestRouteErr = err
}
2024-07-29 10:54:59 +00:00
2024-07-31 20:40:15 +00:00
if len ( allRoutes ) > 1 {
allRoutes = removeBestRouteFromAllRouters ( allRoutes , bestRoute )
continue
} else {
break
}
2024-06-30 20:44:21 +00:00
}
}
2024-07-31 20:40:15 +00:00
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 {
2024-08-28 12:06:50 +00:00
if path . SubtractFees && path . FromToken . IsNative ( ) {
2024-07-31 20:40:15 +00:00
path . AmountIn . ToInt ( ) . Sub ( path . AmountIn . ToInt ( ) , path . TxFee . ToInt ( ) )
2024-09-23 06:35:34 +00:00
if path . TxL1Fee . ToInt ( ) . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-07-31 20:40:15 +00:00
path . AmountIn . ToInt ( ) . Sub ( path . AmountIn . ToInt ( ) , path . TxL1Fee . ToInt ( ) )
}
if path . ApprovalRequired {
path . AmountIn . ToInt ( ) . Sub ( path . AmountIn . ToInt ( ) , path . ApprovalFee . ToInt ( ) )
2024-09-23 06:35:34 +00:00
if path . ApprovalL1Fee . ToInt ( ) . Cmp ( walletCommon . ZeroBigIntValue ( ) ) > 0 {
2024-07-31 20:40:15 +00:00
path . AmountIn . ToInt ( ) . Sub ( path . AmountIn . ToInt ( ) , path . ApprovalL1Fee . ToInt ( ) )
2024-07-25 12:15:30 +00:00
}
}
}
2024-05-14 19:11:16 +00:00
}
}
2024-07-31 20:40:15 +00:00
suggestedRoutes . Best = bestRoute
2024-05-14 19:11:16 +00:00
2024-07-31 20:40:15 +00:00
return suggestedRoutes , err
2024-05-14 19:11:16 +00:00
}