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"
"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-06-06 20:08:25 +00:00
"github.com/status-im/status-go/services/ens"
2024-05-14 19:11:16 +00:00
"github.com/status-im/status-go/services/wallet/async"
2024-06-05 07:56:02 +00:00
walletCommon "github.com/status-im/status-go/services/wallet/common"
2024-06-06 20:08:25 +00:00
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
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-23 14:01:49 +00:00
const (
hexAddressLength = 42
)
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-06-05 07:56:02 +00:00
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 ,
}
)
2024-05-16 07:37:36 +00:00
type RouteInputParams struct {
2024-06-20 09:03:42 +00:00
Uuid string ` json:"uuid" `
2024-05-16 08:55:46 +00:00
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" `
2024-06-12 20:14:30 +00:00
AmountOut * hexutil . Big ` json:"amountOut" `
2024-05-16 08:55:46 +00:00
TokenID string ` json:"tokenID" validate:"required" `
2024-05-16 07:37:36 +00:00
ToTokenID string ` json:"toTokenID" `
DisabledFromChainIDs [ ] uint64 ` json:"disabledFromChainIDs" `
2024-06-14 12:14:14 +00:00
DisabledToChainIDs [ ] uint64 ` json:"disabledToChainIDs" `
2024-05-16 08:55:46 +00:00
GasFeeMode GasFeeMode ` json:"gasFeeMode" validate:"required" `
2024-05-16 07:37:36 +00:00
FromLockedAmount map [ uint64 ] * hexutil . Big ` json:"fromLockedAmount" `
2024-06-18 10:31:23 +00:00
testnetMode bool
2024-06-05 07:56:02 +00:00
// For send types like EnsRegister, EnsRelease, EnsSetPubKey, StickersBuy
2024-06-13 13:38:08 +00:00
Username string ` json:"username" `
PublicKey string ` json:"publicKey" `
PackID * hexutil . Big ` json:"packID" `
2024-06-18 10:31:23 +00:00
// 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
2024-08-23 14:01:49 +00:00
estimationMap map [ string ] pathprocessor . Estimation // [processor-name, estimation]
bonderFeeMap map [ string ] * big . Int // [token-symbol, bonder-fee]
2024-06-18 10:31:23 +00:00
suggestedFees * SuggestedFees
baseFee * big . Int
balanceMap map [ string ] * big . Int // [token-symbol, balance]
approvalGasEstimation uint64
approvalL1Fee uint64
2024-05-16 07:37:36 +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-05-14 19:11:16 +00:00
type PathV2 struct {
2024-06-06 20:08:25 +00:00
ProcessorName string
2024-06-05 08:36:27 +00:00
FromChain * params . Network // Source chain
ToChain * params . Network // Destination chain
2024-06-30 21:38:32 +00:00
FromToken * walletToken . Token // Source token
ToToken * walletToken . Token // Destination token, set if applicable
2024-05-14 19:11:16 +00:00
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
2024-07-25 12:15:30 +00:00
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)
2024-05-14 19:11:16 +00:00
2024-07-25 12:15:30 +00:00
TxBaseFee * hexutil . Big // Base fee for the transaction (in ETH WEI)
TxPriorityFee * hexutil . Big // Priority fee for the transaction (in ETH WEI)
2024-05-14 19:11:16 +00:00
TxGasAmount uint64 // Gas used for the transaction
2024-07-25 12:15:30 +00:00
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)
2024-05-14 19:11:16 +00:00
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
2024-07-25 12:15:30 +00:00
ApprovalBaseFee * hexutil . Big // Base fee for the approval transaction (in ETH WEI)
ApprovalPriorityFee * hexutil . Big // Priority fee for the approval transaction (in ETH WEI)
2024-05-14 19:11:16 +00:00
ApprovalGasAmount uint64 // Gas used for the approval transaction
2024-07-25 12:15:30 +00:00
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)
2024-05-14 19:11:16 +00:00
EstimatedTime TransactionEstimation
2024-07-25 12:15:30 +00:00
requiredTokenBalance * big . Int // (in selected token)
requiredNativeBalance * big . Int // (in ETH WEI)
subtractFees bool
2024-05-14 19:11:16 +00:00
}
2024-07-18 12:20:54 +00:00
type ProcessorError struct {
ProcessorName string
Error error
}
2024-05-14 19:11:16 +00:00
func ( p * PathV2 ) Equal ( o * PathV2 ) bool {
2024-06-05 08:36:27 +00:00
return p . FromChain . ChainID == o . FromChain . ChainID && p . ToChain . ChainID == o . ToChain . ChainID
2024-05-14 19:11:16 +00:00
}
type SuggestedRoutesV2 struct {
2024-06-20 09:03:42 +00:00
Uuid string
2024-05-14 19:11:16 +00:00
Best [ ] * PathV2
Candidates [ ] * PathV2
TokenPrice float64
NativeChainTokenPrice float64
}
2024-07-10 21:06:56 +00:00
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" `
2024-06-20 09:03:42 +00:00
}
2024-07-02 14:59:11 +00:00
type GraphV2 [ ] * NodeV2
2024-05-14 19:11:16 +00:00
type NodeV2 struct {
Path * PathV2
Children GraphV2
}
func newSuggestedRoutesV2 (
2024-06-20 09:03:42 +00:00
uuid string ,
2024-05-14 19:11:16 +00:00
amountIn * big . Int ,
candidates [ ] * PathV2 ,
fromLockedAmount map [ uint64 ] * hexutil . Big ,
tokenPrice float64 ,
nativeChainTokenPrice float64 ,
2024-06-30 20:44:21 +00:00
) ( * SuggestedRoutesV2 , [ ] [ ] * PathV2 ) {
2024-05-14 19:11:16 +00:00
suggestedRoutes := & SuggestedRoutesV2 {
2024-06-20 09:03:42 +00:00
Uuid : uuid ,
2024-05-14 19:11:16 +00:00
Candidates : candidates ,
TokenPrice : tokenPrice ,
NativeChainTokenPrice : nativeChainTokenPrice ,
}
if len ( candidates ) == 0 {
2024-06-30 20:44:21 +00:00
return suggestedRoutes , nil
2024-05-14 19:11:16 +00:00
}
node := & NodeV2 {
Path : nil ,
Children : buildGraphV2 ( amountIn , candidates , 0 , [ ] uint64 { } ) ,
}
2024-06-30 20:44:21 +00:00
allRoutes := node . buildAllRoutesV2 ( )
allRoutes = filterRoutesV2 ( allRoutes , amountIn , 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
}
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 {
2024-06-05 08:36:27 +00:00
if chainID == route . FromChain . ChainID {
2024-05-14 19:11:16 +00:00
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 )
2024-06-05 08:36:27 +00:00
newSourceChainIDs = append ( newSourceChainIDs , route . FromChain . ChainID )
2024-05-14 19:11:16 +00:00
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
}
2024-07-25 12:15:30 +00:00
func findBestV2 ( routes [ ] [ ] * PathV2 , tokenPrice float64 , nativeTokenPrice float64 ) [ ] * PathV2 {
2024-05-14 19:11:16 +00:00
var best [ ] * PathV2
bestCost := big . NewFloat ( math . Inf ( 1 ) )
for _ , route := range routes {
currentCost := big . NewFloat ( 0 )
for _ , path := range route {
2024-05-23 12:38:39 +00:00
tokenDenominator := big . NewFloat ( math . Pow ( 10 , float64 ( path . FromToken . Decimals ) ) )
2024-07-25 12:15:30 +00:00
// calculate the cost of the path
nativeTokenPrice := new ( big . Float ) . SetFloat64 ( nativeTokenPrice )
2024-05-14 19:11:16 +00:00
2024-07-25 12:15:30 +00:00
// tx fee
txFeeInEth := gweiToEth ( weiToGwei ( path . TxFee . ToInt ( ) ) )
pathCost := new ( big . Float ) . Mul ( txFeeInEth , nativeTokenPrice )
2024-05-14 19:11:16 +00:00
2024-07-25 12:15:30 +00:00
if path . TxL1Fee . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
txL1FeeInEth := gweiToEth ( weiToGwei ( path . TxL1Fee . ToInt ( ) ) )
pathCost . Add ( pathCost , new ( big . Float ) . Mul ( txL1FeeInEth , nativeTokenPrice ) )
2024-05-14 19:11:16 +00:00
}
2024-07-25 12:15:30 +00:00
2024-06-06 20:08:25 +00:00
if path . TxBonderFees != nil && path . TxBonderFees . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
2024-05-23 12:38:39 +00:00
pathCost . Add ( pathCost , new ( big . Float ) . Mul (
new ( big . Float ) . Quo ( new ( big . Float ) . SetInt ( path . TxBonderFees . ToInt ( ) ) , tokenDenominator ) ,
new ( big . Float ) . SetFloat64 ( tokenPrice ) ) )
2024-05-14 19:11:16 +00:00
}
2024-06-06 20:08:25 +00:00
if path . TxTokenFees != nil && path . TxTokenFees . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 && path . FromToken != nil {
2024-05-14 19:11:16 +00:00
pathCost . Add ( pathCost , new ( big . Float ) . Mul (
2024-05-23 12:38:39 +00:00
new ( big . Float ) . Quo ( new ( big . Float ) . SetInt ( path . TxTokenFees . ToInt ( ) ) , tokenDenominator ) ,
2024-05-14 19:11:16 +00:00
new ( big . Float ) . SetFloat64 ( tokenPrice ) ) )
}
2024-07-25 12:15:30 +00:00
if path . ApprovalRequired {
// tx approval fee
approvalFeeInEth := gweiToEth ( weiToGwei ( path . ApprovalFee . ToInt ( ) ) )
pathCost . Add ( pathCost , new ( big . Float ) . Mul ( approvalFeeInEth , nativeTokenPrice ) )
2024-05-14 19:11:16 +00:00
2024-07-25 12:15:30 +00:00
if path . ApprovalL1Fee . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
approvalL1FeeInEth := gweiToEth ( weiToGwei ( path . ApprovalL1Fee . ToInt ( ) ) )
pathCost . Add ( pathCost , new ( big . Float ) . Mul ( approvalL1FeeInEth , nativeTokenPrice ) )
2024-05-14 19:11:16 +00:00
}
}
currentCost = new ( big . Float ) . Add ( currentCost , pathCost )
}
if currentCost . Cmp ( bestCost ) == - 1 {
best = route
bestCost = currentCost
}
}
return best
}
2024-06-05 07:56:02 +00:00
func validateInputData ( input * RouteInputParams ) error {
if input . SendType == ENSRegister {
if input . Username == "" || input . PublicKey == "" {
2024-07-02 10:25:30 +00:00
return ErrENSRegisterRequiresUsernameAndPubKey
2024-06-05 07:56:02 +00:00
}
2024-06-18 10:31:23 +00:00
if input . testnetMode {
2024-06-06 20:08:25 +00:00
if input . TokenID != pathprocessor . SttSymbol {
2024-07-02 10:25:30 +00:00
return ErrENSRegisterTestnetSTTOnly
2024-06-05 07:56:02 +00:00
}
} else {
2024-06-06 20:08:25 +00:00
if input . TokenID != pathprocessor . SntSymbol {
2024-07-02 10:25:30 +00:00
return ErrENSRegisterMainnetSNTOnly
2024-06-05 07:56:02 +00:00
}
}
return nil
}
2024-06-05 12:46:10 +00:00
if input . SendType == ENSRelease {
if input . Username == "" {
2024-07-02 10:25:30 +00:00
return ErrENSReleaseRequiresUsername
2024-06-05 12:46:10 +00:00
}
}
2024-06-06 20:08:25 +00:00
if input . SendType == ENSSetPubKey {
2024-07-02 10:25:30 +00:00
if input . Username == "" || input . PublicKey == "" {
return ErrENSSetPubKeyRequiresUsernameAndPubKey
}
if ens . ValidateENSUsername ( input . Username ) != nil {
return ErrENSSetPubKeyInvalidUsername
2024-06-06 20:08:25 +00:00
}
}
2024-06-11 08:00:17 +00:00
if input . SendType == StickersBuy {
if input . PackID == nil {
2024-07-02 10:25:30 +00:00
return ErrStickersBuyRequiresPackID
2024-06-11 08:00:17 +00:00
}
}
2024-06-12 20:14:30 +00:00
if input . SendType == Swap {
if input . ToTokenID == "" {
2024-07-02 10:25:30 +00:00
return ErrSwapRequiresToTokenID
2024-06-12 20:14:30 +00:00
}
if input . TokenID == input . ToTokenID {
2024-07-02 10:25:30 +00:00
return ErrSwapTokenIDMustBeDifferent
2024-06-12 20:14:30 +00:00
}
2024-07-02 14:59:11 +00:00
if input . AmountIn != nil &&
2024-06-12 20:14:30 +00:00
input . AmountOut != nil &&
2024-07-02 14:59:11 +00:00
input . AmountIn . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 &&
2024-06-12 20:14:30 +00:00
input . AmountOut . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
2024-07-02 10:25:30 +00:00
return ErrSwapAmountInAmountOutMustBeExclusive
2024-06-12 20:14:30 +00:00
}
2024-07-02 14:59:11 +00:00
if input . AmountIn != nil && input . AmountIn . ToInt ( ) . Sign ( ) < 0 {
2024-07-02 10:25:30 +00:00
return ErrSwapAmountInMustBePositive
2024-06-12 20:14:30 +00:00
}
if input . AmountOut != nil && input . AmountOut . ToInt ( ) . Sign ( ) < 0 {
2024-07-02 10:25:30 +00:00
return ErrSwapAmountOutMustBePositive
2024-06-12 20:14:30 +00:00
}
}
2024-07-03 09:30:17 +00:00
return validateFromLockedAmount ( input )
2024-06-10 15:15:41 +00:00
}
2024-07-03 09:30:17 +00:00
func validateFromLockedAmount ( input * RouteInputParams ) error {
if input . FromLockedAmount == nil || len ( input . FromLockedAmount ) == 0 {
2024-06-10 15:15:41 +00:00
return nil
}
2024-07-03 09:30:17 +00:00
var suppNetworks map [ uint64 ] bool
if input . testnetMode {
2024-07-25 12:15:30 +00:00
suppNetworks = copyMapGeneric ( supportedTestNetworks , nil ) . ( map [ uint64 ] bool )
2024-07-03 09:30:17 +00:00
} else {
2024-07-25 12:15:30 +00:00
suppNetworks = copyMapGeneric ( supportedNetworks , nil ) . ( map [ uint64 ] bool )
}
if suppNetworks == nil {
return ErrCannotCheckLockedAmounts
2024-07-03 09:30:17 +00:00
}
totalLockedAmount := big . NewInt ( 0 )
2024-06-10 15:15:41 +00:00
excludedChainCount := 0
2024-07-03 09:30:17 +00:00
for chainID , amount := range input . FromLockedAmount {
2024-08-23 14:01:49 +00:00
if arrayContainsElement ( chainID , input . DisabledFromChainIDs ) {
2024-07-03 09:30:17 +00:00
return ErrDisabledChainFoundAmongLockedNetworks
}
if input . testnetMode {
2024-06-10 15:15:41 +00:00
if ! supportedTestNetworks [ chainID ] {
2024-07-03 09:30:17 +00:00
return ErrLockedAmountNotSupportedForNetwork
2024-06-10 15:15:41 +00:00
}
} else {
if ! supportedNetworks [ chainID ] {
2024-07-03 09:30:17 +00:00
return ErrLockedAmountNotSupportedForNetwork
2024-06-10 15:15:41 +00:00
}
}
if amount == nil || amount . ToInt ( ) . Sign ( ) < 0 {
2024-07-03 09:30:17 +00:00
return ErrLockedAmountNotNegative
2024-06-10 15:15:41 +00:00
}
2024-07-03 09:30:17 +00:00
if ! ( amount . ToInt ( ) . Sign ( ) > 0 ) {
2024-06-10 15:15:41 +00:00
excludedChainCount ++
}
2024-07-03 09:30:17 +00:00
delete ( suppNetworks , chainID )
totalLockedAmount = new ( big . Int ) . Add ( totalLockedAmount , amount . ToInt ( ) )
2024-06-10 15:15:41 +00:00
}
2024-07-03 09:30:17 +00:00
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
2024-06-10 15:15:41 +00:00
}
2024-06-05 07:56:02 +00:00
return nil
}
2024-06-20 09:03:42 +00:00
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 ) {
2024-07-10 21:06:56 +00:00
routesResponse := SuggestedRoutesV2Response {
Uuid : input . Uuid ,
}
2024-06-20 09:03:42 +00:00
if err != nil {
2024-07-10 21:06:56 +00:00
errorResponse := errors . CreateErrorResponseFromError ( err )
routesResponse . ErrorResponse = errorResponse . ( * errors . ErrorResponse )
2024-06-20 09:03:42 +00:00
}
2024-07-10 21:06:56 +00:00
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 )
2024-06-20 09:03:42 +00:00
} )
}
func ( r * Router ) StopSuggestedRoutesV2AsyncCalcualtion ( ) {
r . scheduler . Stop ( )
}
2024-05-16 08:55:46 +00:00
func ( r * Router ) SuggestedRoutesV2 ( ctx context . Context , input * RouteInputParams ) ( * SuggestedRoutesV2 , error ) {
2024-06-20 09:03:42 +00:00
testnetMode , err := r . rpcClient . NetworkManager . GetTestNetworksEnabled ( )
if err != nil {
2024-07-03 09:30:17 +00:00
return nil , errors . CreateErrorResponseFromError ( err )
2024-06-20 09:03:42 +00:00
}
input . testnetMode = testnetMode
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-06-20 09:03:42 +00:00
err = validateInputData ( input )
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-07-04 10:48:14 +00:00
balanceMap , err := r . getBalanceMapForTokenOnChains ( 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-23 14:01:49 +00:00
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
}
2024-07-04 10:48:14 +00:00
}
2024-06-18 10:31:23 +00:00
2024-08-19 12:44:46 +00:00
candidates , processorErrors , err := r . resolveCandidates ( ctx , input , selectedFromChains , selectedToChains , balanceMap )
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-07-18 12:20:54 +00:00
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.
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 {
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 )
2024-07-04 10:48:14 +00:00
}
2024-05-14 19:11:16 +00:00
2024-07-04 10:48:14 +00:00
// 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 )
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
if input . SendType == ERC721Transfer {
tokenBalance = big . NewInt ( 1 )
} else if input . 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 {
balanceMap [ 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 {
balanceMap [ makeBalanceKey ( chain . ChainID , nativeToken . Symbol ) ] = nativeBalance
2024-07-04 10:48:14 +00:00
}
}
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 {
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
}
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 )
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-07-04 10:48:14 +00:00
if tokenBalance , ok = balanceMap [ makeBalanceKey ( chain . ChainID , input . TokenID ) ] ; ! ok {
2024-05-14 19:11:16 +00:00
continue
}
2024-07-04 10:48:14 +00:00
if tokenBalance . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
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 )
} 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
}
2024-05-14 19:11:16 +00:00
}
2024-07-04 10:48:14 +00:00
}
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 {
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
if amountToSend . Cmp ( pathprocessor . ZeroBigIntValue ) == 0 {
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-07-04 10:48:14 +00:00
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 {
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.
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 )
2024-05-14 19:11:16 +00:00
2024-07-04 10:48:14 +00:00
crossChainAmountOptions := r . getOptionsForAmoutToSplitAccrossChainsForProcessingChain ( input , amountToSend , selectedFromChain , selectedFromChains , balanceMap )
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-07-04 10:48:14 +00:00
func ( r * Router ) findOptionsForSendingAmount ( input * RouteInputParams , selectedFromChains [ ] * params . Network ,
balanceMap map [ string ] * big . Int ) ( map [ uint64 ] [ ] amountOption , error ) {
2024-06-05 07:56:02 +00:00
2024-07-04 10:48:14 +00:00
crossChainAmountOptions := r . getCrossChainsOptionsForSendingAmount ( input , selectedFromChains , balanceMap )
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-19 12:44:46 +00:00
func ( r * Router ) getSelectedChains ( input * 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 {
if network . IsTest != input . testnetMode {
continue
}
2024-05-14 19:11:16 +00:00
2024-08-23 14:01:49 +00:00
if ! arrayContainsElement ( network . ChainID , input . DisabledFromChainIDs ) {
2024-07-04 10:48:14 +00:00
selectedFromChains = append ( selectedFromChains , network )
}
2024-08-23 14:01:49 +00:00
if ! 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-07-04 10:48:14 +00:00
func ( r * Router ) resolveCandidates ( ctx context . Context , input * RouteInputParams , selectedFromChains [ ] * params . Network ,
2024-08-19 12:44:46 +00:00
selectedToChains [ ] * params . Network , balanceMap map [ string ] * big . Int ) ( candidates [ ] * PathV2 , processorErrors [ ] * ProcessorError , err error ) {
2024-07-04 10:48:14 +00:00
var (
testsMode = input . testsMode && input . testParams != nil
group = async . NewAtomicGroup ( ctx )
mu sync . Mutex
)
crossChainAmountOptions , err := r . findOptionsForSendingAmount ( input , selectedFromChains , balanceMap )
if err != nil {
2024-07-18 12:20:54 +00:00
return nil , nil , errors . CreateErrorResponseFromError ( err )
}
2024-08-23 14:01:49 +00:00
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 )
2024-07-18 12:20:54 +00:00
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 )
2024-07-04 10:48:14 +00:00
}
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
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
if isSingleChainOperation ( selectedFromChains , selectedToChains ) && pathprocessor . IsProcessorBridge ( pProcessor . Name ( ) ) {
2024-08-23 14:01:49 +00:00
continue
}
2024-07-23 10:15:29 +00:00
if ! input . SendType . processZeroAmountInProcessor ( amountOption . amount , input . AmountOut . ToInt ( ) , pProcessor . Name ( ) ) {
continue
}
2024-08-19 12:44:46 +00:00
for _ , dest := range selectedToChains {
2024-07-04 10:48:14 +00:00
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 )
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
}
approvalRequired , approvalAmountRequired , approvalGasLimit , l1ApprovalFee , err := r . requireApproval ( ctx , input . SendType , & approvalContractAddress , 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
}
// 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 {
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
}
maxFeesPerGas := fees . feeFor ( input . GasFeeMode )
estimatedTime := r . feesManager . TransactionEstimatedTime ( ctx , network . ChainID , maxFeesPerGas )
if approvalRequired && estimatedTime < MoreThanFiveMinutes {
estimatedTime += 1
}
2024-07-25 12:15:30 +00:00
// 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 )
}
2024-07-18 12:20:54 +00:00
appendPathFn ( & PathV2 {
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 ) ,
SuggestedLevelsForMaxFeesPerGas : fees . MaxFeesLevels ,
2024-07-25 12:15:30 +00:00
MaxFeesPerGas : ( * hexutil . Big ) ( maxFeesPerGas ) ,
2024-07-04 10:48:14 +00:00
TxBaseFee : ( * hexutil . Big ) ( fees . BaseFee ) ,
TxPriorityFee : ( * hexutil . Big ) ( fees . MaxPriorityFeePerGas ) ,
TxGasAmount : gasLimit ,
TxBonderFees : ( * hexutil . Big ) ( bonderFees ) ,
TxTokenFees : ( * hexutil . Big ) ( tokenFees ) ,
2024-07-25 12:15:30 +00:00
TxFee : ( * hexutil . Big ) ( txFeeInWei ) ,
TxL1Fee : ( * hexutil . Big ) ( txL1FeeInWei ) ,
2024-07-04 10:48:14 +00:00
ApprovalRequired : approvalRequired ,
ApprovalAmountRequired : ( * hexutil . Big ) ( approvalAmountRequired ) ,
ApprovalContractAddress : & approvalContractAddress ,
ApprovalBaseFee : ( * hexutil . Big ) ( fees . BaseFee ) ,
ApprovalPriorityFee : ( * hexutil . Big ) ( fees . MaxPriorityFeePerGas ) ,
ApprovalGasAmount : approvalGasLimit ,
2024-07-25 12:15:30 +00:00
ApprovalFee : ( * hexutil . Big ) ( approvalFeeInWei ) ,
ApprovalL1Fee : ( * hexutil . Big ) ( approvalL1FeeInWei ) ,
TxTotalFee : ( * hexutil . Big ) ( ethTotalFees ) ,
2024-07-04 10:48:14 +00:00
EstimatedTime : estimatedTime ,
2024-07-25 12:15:30 +00:00
subtractFees : amountOption . subtractFees ,
requiredTokenBalance : requiredTokenBalance ,
requiredNativeBalance : requiredNativeBalance ,
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-23 14:01:49 +00:00
func ( r * Router ) checkBalancesForTheBestRoute ( ctx context . Context , bestRoute [ ] * PathV2 , balanceMap map [ string ] * big . Int ) ( hasPositiveBalance bool , err error ) {
2024-07-25 12:15:30 +00:00
balanceMapCopy := copyMapGeneric ( balanceMap , func ( v interface { } ) interface { } {
return new ( big . Int ) . Set ( v . ( * big . Int ) )
} ) . ( map [ string ] * big . Int )
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 {
if tokenBalance . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
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-06-06 20:08:25 +00:00
if path . requiredTokenBalance != nil && path . requiredTokenBalance . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
2024-07-31 20:40:15 +00:00
if tokenBalance , ok := balanceMapCopy [ tokenKey ] ; ok {
2024-07-04 10:48:14 +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-07-31 20:40:15 +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-07-04 10:48:14 +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-07-31 20:40:15 +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
}
func removeBestRouteFromAllRouters ( allRoutes [ ] [ ] * PathV2 , best [ ] * PathV2 ) [ ] [ ] * PathV2 {
2024-07-25 12:15:30 +00:00
for i := len ( allRoutes ) - 1 ; i >= 0 ; i -- {
route := allRoutes [ i ]
2024-06-30 20:44:21 +00:00
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
}
2024-07-29 10:54:59 +00:00
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
}
2024-07-04 10:48:14 +00:00
func ( r * Router ) resolveRoutes ( ctx context . Context , input * RouteInputParams , candidates [ ] * PathV2 , balanceMap map [ string ] * big . Int ) ( suggestedRoutes * SuggestedRoutesV2 , err error ) {
2024-06-30 20:44:21 +00:00
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 {
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
var allRoutes [ ] [ ] * PathV2
2024-07-25 12:15:30 +00:00
suggestedRoutes , allRoutes = newSuggestedRoutesV2 ( input . Uuid , input . AmountIn . ToInt ( ) , candidates , input . FromLockedAmount , tokenPrice , nativeTokenPrice )
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 (
bestRoute [ ] * PathV2
lastBestRouteWithPositiveBalance [ ] * PathV2
lastBestRouteErr error
)
2024-06-30 20:44:21 +00:00
for len ( allRoutes ) > 0 {
2024-07-31 20:40:15 +00:00
bestRoute = findBestV2 ( allRoutes , tokenPrice , nativeTokenPrice )
var hasPositiveBalance bool
2024-08-23 14:01:49 +00:00
hasPositiveBalance , err = r . checkBalancesForTheBestRoute ( ctx , bestRoute , balanceMap )
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-07-31 20:40:15 +00:00
if input . SendType == Transfer ||
input . SendType == Bridge {
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 {
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 ( ) )
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
}