2024-05-16 08:55:46 +00:00
package router
2024-05-14 19:11:16 +00:00
import (
"context"
2024-06-20 16:21:30 +00:00
"errors"
2024-06-30 20:44:21 +00:00
"fmt"
2024-05-14 19:11:16 +00:00
"math"
"math/big"
"sort"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
2024-06-10 15:15:41 +00:00
sErrors "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"
)
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
estimationMap map [ string ] uint64 // [processor-name, estimated-value]
bonderFeeMap map [ string ] * big . Int // [token-symbol, bonder-fee]
suggestedFees * SuggestedFees
baseFee * big . Int
balanceMap map [ string ] * big . Int // [token-symbol, balance]
approvalGasEstimation uint64
approvalL1Fee uint64
2024-05-16 07:37:36 +00:00
}
2024-06-30 20:44:21 +00:00
func makeTestBalanceKey ( chainID uint64 , symbol string ) string {
return fmt . Sprintf ( "%d-%s" , chainID , symbol )
}
func ( rt routerTestParams ) getTestBalance ( chainID uint64 , symbol string ) * big . Int {
if val , ok := rt . balanceMap [ makeTestBalanceKey ( chainID , symbol ) ] ; ok {
return val
}
return big . NewInt ( 0 )
}
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-06-19 09:20:41 +00:00
SuggestedLevelsForMaxFeesPerGas * MaxFeesLevels // Suggested max fees for the transaction
2024-05-14 19:11:16 +00:00
TxBaseFee * hexutil . Big // Base fee for the transaction
2024-06-19 09:20:41 +00:00
TxPriorityFee * hexutil . Big // Priority fee for the transaction
2024-05-14 19:11:16 +00:00
TxGasAmount uint64 // Gas used for the transaction
TxBonderFees * hexutil . Big // Bonder fees for the transaction - used for Hop bridge
TxTokenFees * hexutil . Big // Token fees for the transaction - used for bridges (represent the difference between the amount in and the amount out)
TxL1Fee * hexutil . Big // L1 fee for the transaction - used for for transactions placed on L2 chains
ApprovalRequired bool // Is approval required for the transaction
ApprovalAmountRequired * hexutil . Big // Amount required for the approval transaction
ApprovalContractAddress * common . Address // Address of the contract that needs to be approved
ApprovalBaseFee * hexutil . Big // Base fee for the approval transaction
ApprovalPriorityFee * hexutil . Big // Priority fee for the approval transaction
ApprovalGasAmount uint64 // Gas used for the approval transaction
ApprovalL1Fee * hexutil . Big // L1 fee for the approval transaction - used for for transactions placed on L2 chains
EstimatedTime TransactionEstimation
requiredTokenBalance * big . Int
requiredNativeBalance * big . Int
}
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-06-20 09:03:42 +00:00
type ErrorResponseWithUUID struct {
Uuid string
ErrorResponse error
}
2024-05-14 19:11:16 +00:00
type GraphV2 = [ ] * NodeV2
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 ,
Best : 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-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
}
func findBestV2 ( routes [ ] [ ] * PathV2 , tokenPrice float64 , nativeChainTokenPrice float64 ) [ ] * PathV2 {
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-06-14 11:27:53 +00:00
path . requiredTokenBalance = big . NewInt ( 0 )
2024-05-21 14:33:36 +00:00
path . requiredNativeBalance = big . NewInt ( 0 )
2024-06-14 11:27:53 +00:00
if path . FromToken . IsNative ( ) {
path . requiredNativeBalance . Add ( path . requiredNativeBalance , path . AmountIn . ToInt ( ) )
} else {
path . requiredTokenBalance . Add ( path . requiredTokenBalance , path . AmountIn . ToInt ( ) )
}
2024-05-14 19:11:16 +00:00
// ecaluate the cost of the path
pathCost := big . NewFloat ( 0 )
nativeTokenPrice := new ( big . Float ) . SetFloat64 ( nativeChainTokenPrice )
if path . TxBaseFee != nil && path . TxPriorityFee != nil {
feePerGas := new ( big . Int ) . Add ( path . TxBaseFee . ToInt ( ) , path . TxPriorityFee . ToInt ( ) )
txFeeInWei := new ( big . Int ) . Mul ( feePerGas , big . NewInt ( int64 ( path . TxGasAmount ) ) )
txFeeInEth := gweiToEth ( weiToGwei ( txFeeInWei ) )
path . requiredNativeBalance . Add ( path . requiredNativeBalance , txFeeInWei )
pathCost = new ( big . Float ) . Mul ( txFeeInEth , nativeTokenPrice )
}
2024-06-06 20:08:25 +00:00
if path . TxBonderFees != nil && path . TxBonderFees . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
2024-06-14 11:27:53 +00:00
if path . FromToken . IsNative ( ) {
path . requiredNativeBalance . Add ( path . requiredNativeBalance , path . TxBonderFees . ToInt ( ) )
} else {
path . requiredTokenBalance . Add ( path . requiredTokenBalance , path . TxBonderFees . ToInt ( ) )
}
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 . TxL1Fee != nil && path . TxL1Fee . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
2024-05-14 19:11:16 +00:00
l1FeeInWei := path . TxL1Fee . ToInt ( )
l1FeeInEth := gweiToEth ( weiToGwei ( l1FeeInWei ) )
path . requiredNativeBalance . Add ( path . requiredNativeBalance , l1FeeInWei )
pathCost . Add ( pathCost , new ( big . Float ) . Mul ( l1FeeInEth , nativeTokenPrice ) )
}
2024-06-06 20:08:25 +00:00
if path . TxTokenFees != nil && path . TxTokenFees . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 && path . FromToken != nil {
2024-06-14 11:27:53 +00:00
if path . FromToken . IsNative ( ) {
path . requiredNativeBalance . Add ( path . requiredNativeBalance , path . TxTokenFees . ToInt ( ) )
} else {
path . requiredTokenBalance . Add ( path . requiredTokenBalance , path . TxTokenFees . ToInt ( ) )
}
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 ) ) )
}
if path . ApprovalRequired {
if path . ApprovalBaseFee != nil && path . ApprovalPriorityFee != nil {
feePerGas := new ( big . Int ) . Add ( path . ApprovalBaseFee . ToInt ( ) , path . ApprovalPriorityFee . ToInt ( ) )
txFeeInWei := new ( big . Int ) . Mul ( feePerGas , big . NewInt ( int64 ( path . ApprovalGasAmount ) ) )
txFeeInEth := gweiToEth ( weiToGwei ( txFeeInWei ) )
path . requiredNativeBalance . Add ( path . requiredNativeBalance , txFeeInWei )
pathCost . Add ( pathCost , new ( big . Float ) . Mul ( txFeeInEth , nativeTokenPrice ) )
}
if path . ApprovalL1Fee != nil {
l1FeeInWei := path . ApprovalL1Fee . ToInt ( )
l1FeeInEth := gweiToEth ( weiToGwei ( l1FeeInWei ) )
path . requiredNativeBalance . Add ( path . requiredNativeBalance , l1FeeInWei )
pathCost . Add ( pathCost , new ( big . Float ) . Mul ( l1FeeInEth , nativeTokenPrice ) )
}
}
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
}
// we can do this check, cause AmountIn is required in `RouteInputParams`
if input . AmountIn . ToInt ( ) . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 &&
input . AmountOut != nil &&
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
}
if 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-06-05 07:56:02 +00:00
if input . FromLockedAmount != nil && len ( input . FromLockedAmount ) > 0 {
2024-06-14 11:27:53 +00:00
suppNetworks := copyMap ( supportedNetworks )
2024-06-18 10:31:23 +00:00
if input . testnetMode {
2024-06-14 11:27:53 +00:00
suppNetworks = copyMap ( supportedTestNetworks )
}
totalLockedAmount := big . NewInt ( 0 )
2024-06-05 07:56:02 +00:00
for chainID , amount := range input . FromLockedAmount {
2024-06-20 09:03:42 +00:00
if containsNetworkChainID ( chainID , input . DisabledFromChainIDs ) {
return ErrDisabledChainFoundAmongLockedNetworks
}
2024-06-18 10:31:23 +00:00
if input . testnetMode {
2024-06-05 07:56:02 +00:00
if ! supportedTestNetworks [ chainID ] {
2024-06-18 10:31:23 +00:00
return ErrLockedAmountNotSupportedForNetwork
2024-06-05 07:56:02 +00:00
}
} else {
if ! supportedNetworks [ chainID ] {
2024-06-18 10:31:23 +00:00
return ErrLockedAmountNotSupportedForNetwork
2024-06-05 07:56:02 +00:00
}
}
2024-06-14 11:27:53 +00:00
delete ( suppNetworks , chainID )
totalLockedAmount = new ( big . Int ) . Add ( totalLockedAmount , amount . ToInt ( ) )
2024-06-05 07:56:02 +00:00
if amount == nil || amount . ToInt ( ) . Sign ( ) < 0 {
2024-07-02 10:25:30 +00:00
return ErrLockedAmountNotNegative
2024-06-05 07:56:02 +00:00
}
}
2024-06-14 11:27:53 +00:00
if totalLockedAmount . Cmp ( input . AmountIn . ToInt ( ) ) > 0 {
2024-06-18 10:31:23 +00:00
return ErrLockedAmountExceedsTotalSendAmount
2024-06-14 11:27:53 +00:00
} else if totalLockedAmount . Cmp ( input . AmountIn . ToInt ( ) ) < 0 && len ( suppNetworks ) == 0 {
2024-06-18 10:31:23 +00:00
return ErrLockedAmountLessThanSendAmountAllNetworks
2024-06-14 11:27:53 +00:00
}
2024-06-05 07:56:02 +00:00
}
2024-06-10 15:15:41 +00:00
return validateFromLockedAmount ( input . FromLockedAmount , input . testnetMode )
}
func validateFromLockedAmount ( fromLockedAmount map [ uint64 ] * hexutil . Big , isTestnetMode bool ) error {
if fromLockedAmount == nil || len ( fromLockedAmount ) == 0 {
return nil
}
chainIDSet := make ( map [ uint64 ] bool )
excludedChainCount := 0
for chainID , amount := range fromLockedAmount {
if isTestnetMode {
if ! supportedTestNetworks [ chainID ] {
2024-06-20 16:21:30 +00:00
return errors . New ( "locked amount is not supported for the selected network" )
2024-06-10 15:15:41 +00:00
}
} else {
if ! supportedNetworks [ chainID ] {
2024-06-20 16:21:30 +00:00
return errors . New ( "locked amount is not supported for the selected network" )
2024-06-10 15:15:41 +00:00
}
}
// Check locked amount is not negative
if amount == nil || amount . ToInt ( ) . Sign ( ) < 0 {
2024-06-20 16:21:30 +00:00
return errors . New ( "locked amount must not be negative" )
2024-06-10 15:15:41 +00:00
}
// Check if locked chain ID is a duplicate
if _ , exists := chainIDSet [ chainID ] ; exists {
// Handle duplicate chain ID
return fmt . Errorf ( "a chain ID may only appear once, duplicate chain ID found '%d'" , chainID )
}
chainIDSet [ chainID ] = amount . ToInt ( ) . Sign ( ) > 0
if ! chainIDSet [ chainID ] {
excludedChainCount ++
}
}
if ( ! isTestnetMode && excludedChainCount == len ( supportedNetworks ) ) ||
( isTestnetMode && excludedChainCount == len ( supportedTestNetworks ) ) {
2024-06-20 16:21:30 +00:00
return errors . New ( "all supported chains are excluded, routing impossible" )
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 ) {
if err != nil {
errResponse := & ErrorResponseWithUUID {
Uuid : input . Uuid ,
2024-06-10 15:15:41 +00:00
ErrorResponse : sErrors . CreateErrorResponseFromError ( err ) ,
2024-06-20 09:03:42 +00:00
}
signal . SendWalletEvent ( signal . SuggestedRoutes , errResponse )
return
}
signal . SendWalletEvent ( signal . SuggestedRoutes , result )
} )
}
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-06-10 15:15:41 +00:00
return nil , sErrors . 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-06-10 15:15:41 +00:00
return nil , sErrors . CreateErrorResponseFromError ( err )
2024-06-18 10:31:23 +00:00
}
2024-06-14 11:27:53 +00:00
candidates , err := r . resolveCandidates ( ctx , input )
if err != nil {
2024-06-10 15:15:41 +00:00
return nil , sErrors . CreateErrorResponseFromError ( err )
2024-06-14 11:27:53 +00:00
}
return r . resolveRoutes ( ctx , input , candidates )
}
func ( r * Router ) resolveCandidates ( ctx context . Context , input * RouteInputParams ) ( candidates [ ] * PathV2 , err error ) {
2024-06-18 10:31:23 +00:00
var (
testsMode = input . testsMode && input . testParams != nil
networks [ ] * params . Network
)
networks , err = r . rpcClient . NetworkManager . Get ( false )
2024-05-14 19:11:16 +00:00
if err != nil {
2024-06-10 15:15:41 +00:00
return nil , sErrors . CreateErrorResponseFromError ( err )
2024-05-14 19:11:16 +00:00
}
var (
2024-06-14 11:27:53 +00:00
group = async . NewAtomicGroup ( ctx )
mu sync . Mutex
2024-05-14 19:11:16 +00:00
)
for networkIdx := range networks {
network := networks [ networkIdx ]
2024-06-18 10:31:23 +00:00
if network . IsTest != input . testnetMode {
2024-05-14 19:11:16 +00:00
continue
}
2024-06-20 09:03:42 +00:00
if containsNetworkChainID ( network . ChainID , input . DisabledFromChainIDs ) {
2024-05-14 19:11:16 +00:00
continue
}
2024-05-16 07:37:36 +00:00
if ! input . SendType . isAvailableFor ( network ) {
2024-05-14 19:11:16 +00:00
continue
}
2024-06-05 07:56:02 +00:00
var (
token * walletToken . Token
toToken * walletToken . Token
)
2024-06-18 10:31:23 +00:00
if testsMode {
token = input . testParams . tokenFrom
} else {
token = input . SendType . FindToken ( r . tokenManager , r . collectiblesService , input . AddrFrom , network , input . TokenID )
}
2024-05-14 19:11:16 +00:00
if token == nil {
continue
}
2024-05-16 07:37:36 +00:00
if input . SendType == Swap {
2024-05-16 08:55:46 +00:00
toToken = input . SendType . FindToken ( r . tokenManager , r . collectiblesService , common . Address { } , network , input . ToTokenID )
2024-05-14 19:11:16 +00:00
}
amountLocked := false
2024-05-16 07:37:36 +00:00
amountToSend := input . AmountIn . ToInt ( )
if lockedAmount , ok := input . FromLockedAmount [ network . ChainID ] ; ok {
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-05-14 19:11:16 +00:00
if chainID == network . ChainID {
continue
}
amountToSend = new ( big . Int ) . Sub ( amountToSend , lockedAmount . ToInt ( ) )
}
}
2024-06-20 09:03:42 +00:00
var fees * SuggestedFees
if testsMode {
fees = input . testParams . suggestedFees
} else {
fees , err = r . feesManager . SuggestedFees ( ctx , network . ChainID )
2024-05-14 19:11:16 +00:00
if err != nil {
2024-06-20 09:03:42 +00:00
continue
2024-05-14 19:11:16 +00:00
}
2024-06-20 09:03:42 +00:00
}
2024-05-14 19:11:16 +00:00
2024-06-20 09:03:42 +00:00
group . Add ( func ( c context . Context ) error {
2024-06-06 20:08:25 +00:00
for _ , pProcessor := range r . pathProcessors {
2024-06-12 20:14:30 +00:00
// 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-06-06 20:08:25 +00:00
if ! input . SendType . canUseProcessor ( pProcessor ) {
2024-05-14 19:11:16 +00:00
continue
}
for _ , dest := range networks {
2024-06-18 10:31:23 +00:00
if dest . IsTest != input . testnetMode {
2024-05-14 19:11:16 +00:00
continue
}
2024-05-16 10:22:32 +00:00
if ! input . SendType . isAvailableFor ( network ) {
continue
}
if ! input . SendType . isAvailableBetween ( network , dest ) {
continue
}
2024-06-20 09:03:42 +00:00
if containsNetworkChainID ( dest . ChainID , input . DisabledToChainIDs ) {
2024-05-14 19:11:16 +00:00
continue
}
2024-06-12 20:14:30 +00:00
processorInputParams := pathprocessor . ProcessorInputParams {
2024-06-05 07:56:02 +00:00
FromChain : network ,
ToChain : dest ,
FromToken : token ,
ToToken : toToken ,
ToAddr : input . AddrTo ,
FromAddr : input . AddrFrom ,
AmountIn : amountToSend ,
2024-06-20 09:03:42 +00:00
AmountOut : input . AmountOut . ToInt ( ) ,
2024-06-05 07:56:02 +00:00
Username : input . Username ,
PublicKey : input . PublicKey ,
2024-06-13 13:38:08 +00:00
PackID : input . PackID . ToInt ( ) ,
2024-06-05 07:56:02 +00:00
}
2024-06-18 10:31:23 +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-06-05 07:56:02 +00:00
2024-06-12 20:14:30 +00:00
can , err := pProcessor . AvailableFor ( processorInputParams )
2024-05-14 19:11:16 +00:00
if err != nil || ! can {
continue
}
2024-06-12 20:14:30 +00:00
bonderFees , tokenFees , err := pProcessor . CalculateFees ( processorInputParams )
2024-05-14 19:11:16 +00:00
if err != nil {
continue
}
2024-06-12 20:14:30 +00:00
gasLimit , err := pProcessor . EstimateGas ( processorInputParams )
2024-06-05 07:56:02 +00:00
if err != nil {
continue
2024-05-14 19:11:16 +00:00
}
2024-06-12 20:14:30 +00:00
approvalContractAddress , err := pProcessor . GetContractAddress ( processorInputParams )
2024-05-23 12:38:39 +00:00
if err != nil {
continue
}
2024-06-14 11:27:53 +00:00
approvalRequired , approvalAmountRequired , approvalGasLimit , l1ApprovalFee , err := r . requireApproval ( ctx , input . SendType , & approvalContractAddress , processorInputParams )
2024-05-14 19:11:16 +00:00
if err != nil {
continue
}
var l1FeeWei uint64
2024-05-16 07:37:36 +00:00
if input . SendType . needL1Fee ( ) {
2024-06-12 20:14:30 +00:00
txInputData , err := pProcessor . PackTxInputData ( processorInputParams )
2024-05-14 19:11:16 +00:00
if err != nil {
continue
}
2024-05-21 14:33:36 +00:00
l1FeeWei , _ = r . feesManager . GetL1Fee ( ctx , network . ChainID , txInputData )
2024-05-14 19:11:16 +00:00
}
2024-06-12 20:14:30 +00:00
amountOut , err := pProcessor . CalculateAmountOut ( processorInputParams )
2024-05-14 19:11:16 +00:00
if err != nil {
continue
}
2024-06-19 09:20:41 +00:00
maxFeesPerGas := fees . feeFor ( input . GasFeeMode )
2024-05-16 08:55:46 +00:00
estimatedTime := r . feesManager . TransactionEstimatedTime ( ctx , network . ChainID , maxFeesPerGas )
2024-05-14 19:11:16 +00:00
if approvalRequired && estimatedTime < MoreThanFiveMinutes {
estimatedTime += 1
}
mu . Lock ( )
candidates = append ( candidates , & PathV2 {
2024-06-06 20:08:25 +00:00
ProcessorName : pProcessor . Name ( ) ,
2024-06-05 08:36:27 +00:00
FromChain : network ,
2024-06-14 08:17:19 +00:00
ToChain : dest ,
2024-05-14 19:11:16 +00:00
FromToken : token ,
2024-06-30 21:38:32 +00:00
ToToken : toToken ,
2024-05-14 19:11:16 +00:00
AmountIn : ( * hexutil . Big ) ( amountToSend ) ,
AmountInLocked : amountLocked ,
AmountOut : ( * hexutil . Big ) ( amountOut ) ,
2024-06-19 09:20:41 +00:00
SuggestedLevelsForMaxFeesPerGas : fees . MaxFeesLevels ,
2024-05-14 19:11:16 +00:00
2024-06-19 09:20:41 +00:00
TxBaseFee : ( * hexutil . Big ) ( fees . BaseFee ) ,
TxPriorityFee : ( * hexutil . Big ) ( fees . MaxPriorityFeePerGas ) ,
2024-05-14 19:11:16 +00:00
TxGasAmount : gasLimit ,
TxBonderFees : ( * hexutil . Big ) ( bonderFees ) ,
TxTokenFees : ( * hexutil . Big ) ( tokenFees ) ,
TxL1Fee : ( * hexutil . Big ) ( big . NewInt ( int64 ( l1FeeWei ) ) ) ,
ApprovalRequired : approvalRequired ,
ApprovalAmountRequired : ( * hexutil . Big ) ( approvalAmountRequired ) ,
2024-05-23 12:38:39 +00:00
ApprovalContractAddress : & approvalContractAddress ,
2024-06-19 09:20:41 +00:00
ApprovalBaseFee : ( * hexutil . Big ) ( fees . BaseFee ) ,
ApprovalPriorityFee : ( * hexutil . Big ) ( fees . MaxPriorityFeePerGas ) ,
2024-05-14 19:11:16 +00:00
ApprovalGasAmount : approvalGasLimit ,
ApprovalL1Fee : ( * hexutil . Big ) ( big . NewInt ( int64 ( l1ApprovalFee ) ) ) ,
EstimatedTime : estimatedTime ,
2024-06-19 09:20:41 +00:00
} )
2024-05-14 19:11:16 +00:00
mu . Unlock ( )
}
}
return nil
} )
}
group . Wait ( )
2024-06-14 11:27:53 +00:00
return candidates , nil
}
2024-05-14 19:11:16 +00:00
2024-06-30 20:44:21 +00:00
func ( r * Router ) checkBalancesForTheBestRoute ( ctx context . Context , bestRoute [ ] * PathV2 , input * RouteInputParams ) ( err error ) {
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-06-06 20:08:25 +00:00
if path . requiredTokenBalance != nil && path . requiredTokenBalance . Cmp ( pathprocessor . ZeroBigIntValue ) > 0 {
2024-05-14 19:11:16 +00:00
tokenBalance := big . NewInt ( 1 )
2024-06-18 10:31:23 +00:00
if input . testsMode {
2024-06-30 20:44:21 +00:00
tokenBalance = input . testParams . getTestBalance ( path . FromChain . ChainID , path . FromToken . Symbol )
2024-06-18 10:31:23 +00:00
} else {
if input . SendType == ERC1155Transfer {
tokenBalance , err = r . getERC1155Balance ( ctx , path . FromChain , path . FromToken , input . AddrFrom )
if err != nil {
2024-06-10 15:15:41 +00:00
return sErrors . CreateErrorResponseFromError ( err )
2024-06-18 10:31:23 +00:00
}
} else if input . SendType != ERC721Transfer {
tokenBalance , err = r . getBalance ( ctx , path . FromChain , path . FromToken , input . AddrFrom )
if err != nil {
2024-06-10 15:15:41 +00:00
return sErrors . CreateErrorResponseFromError ( err )
2024-06-18 10:31:23 +00:00
}
2024-05-14 19:11:16 +00:00
}
}
if tokenBalance . Cmp ( path . requiredTokenBalance ) == - 1 {
2024-06-30 20:44:21 +00:00
return ErrNotEnoughTokenBalance
2024-05-14 19:11:16 +00:00
}
}
2024-06-30 20:44:21 +00:00
var nativeBalance * big . Int
2024-06-18 10:31:23 +00:00
if input . testsMode {
2024-06-30 20:44:21 +00:00
nativeBalance = input . testParams . getTestBalance ( path . FromChain . ChainID , pathprocessor . EthSymbol )
2024-06-18 10:31:23 +00:00
} else {
nativeToken := r . tokenManager . FindToken ( path . FromChain , path . FromChain . NativeCurrencySymbol )
if nativeToken == nil {
2024-06-30 20:44:21 +00:00
return ErrNativeTokenNotFound
2024-06-18 10:31:23 +00:00
}
2024-05-14 19:11:16 +00:00
2024-06-18 10:31:23 +00:00
nativeBalance , err = r . getBalance ( ctx , path . FromChain , nativeToken , input . AddrFrom )
if err != nil {
2024-06-10 15:15:41 +00:00
return sErrors . CreateErrorResponseFromError ( err )
2024-06-18 10:31:23 +00:00
}
2024-05-14 19:11:16 +00:00
}
if nativeBalance . Cmp ( path . requiredNativeBalance ) == - 1 {
2024-06-30 20:44:21 +00:00
return ErrNotEnoughNativeBalance
}
}
return nil
}
func removeBestRouteFromAllRouters ( allRoutes [ ] [ ] * PathV2 , best [ ] * PathV2 ) [ ] [ ] * PathV2 {
for i , route := range allRoutes {
routeFound := true
for _ , p := range route {
found := false
for _ , b := range best {
if p . ProcessorName == b . ProcessorName &&
( p . FromChain == nil && b . FromChain == nil || p . FromChain . ChainID == b . FromChain . ChainID ) &&
( p . ToChain == nil && b . ToChain == nil || p . ToChain . ChainID == b . ToChain . ChainID ) &&
( p . FromToken == nil && b . FromToken == nil || p . FromToken . Symbol == b . FromToken . Symbol ) {
found = true
break
}
}
if ! found {
routeFound = false
break
}
}
if routeFound {
return append ( allRoutes [ : i ] , allRoutes [ i + 1 : ] ... )
}
}
return nil
}
func ( r * Router ) resolveRoutes ( ctx context . Context , input * RouteInputParams , candidates [ ] * PathV2 ) ( suggestedRoutes * SuggestedRoutesV2 , err error ) {
var prices map [ string ] float64
if input . testsMode {
prices = input . testParams . tokenPrices
} else {
prices , err = input . SendType . FetchPrices ( r . marketManager , input . TokenID )
if err != nil {
2024-06-10 15:15:41 +00:00
return nil , sErrors . CreateErrorResponseFromError ( err )
2024-06-30 20:44:21 +00:00
}
}
tokenPrice := prices [ input . TokenID ]
nativeChainTokenPrice := prices [ pathprocessor . EthSymbol ]
var allRoutes [ ] [ ] * PathV2
suggestedRoutes , allRoutes = newSuggestedRoutesV2 ( input . Uuid , input . AmountIn . ToInt ( ) , candidates , input . FromLockedAmount , tokenPrice , nativeChainTokenPrice )
for len ( allRoutes ) > 0 {
best := findBestV2 ( allRoutes , tokenPrice , nativeChainTokenPrice )
err := r . checkBalancesForTheBestRoute ( ctx , best , input )
if err != nil {
// If it's about transfer or bridge and there is more routes, but on the best (cheapest) one there is not enugh balance
// we shold check other routes even though there are not the cheapest ones
if ( input . SendType == Transfer ||
input . SendType == Bridge ) &&
len ( allRoutes ) > 1 {
allRoutes = removeBestRouteFromAllRouters ( allRoutes , best )
continue
} else {
2024-06-10 15:15:41 +00:00
return suggestedRoutes , sErrors . CreateErrorResponseFromError ( err )
2024-06-30 20:44:21 +00:00
}
}
if len ( best ) > 0 {
sort . Slice ( best , func ( i , j int ) bool {
return best [ i ] . AmountInLocked
} )
2024-05-14 19:11:16 +00:00
}
2024-06-30 20:44:21 +00:00
suggestedRoutes . Best = best
break
2024-05-14 19:11:16 +00:00
}
return suggestedRoutes , nil
}