chore!: router code organization improvements
It's a breaking change due to errors' changes, the list of mapped old error codes: - `"WR-001"` is now `"WRR-001"` - `"WR-002"` is now `"WRR-002"` - `"WR-003"` is now `"WRR-003"` - `"WR-004"` is now `"WRR-004"` - `"WR-005"` is now `"WRR-005"` - `"WR-006"` is now `"WRR-006"` - `"WR-007"` is now `"WRR-007"` - `"WR-008"` is now `"WRR-008"` - `"WR-009"` is now `"WRR-009"` - `"WR-010"` is now `"WRR-010"` - `"WR-011"` is now `"WRR-011"` - `"WR-012"` is now `"WRR-012"` - `"WR-013"` is now `"WRR-013"` - `"WR-014"` is now `"WRR-014"` - `"WR-015"` is now `"WRR-015"` - `"WR-019"` is now `"WRR-016"` - `"WR-020"` is now `"WRR-017"` - `"WR-021"` is now `"WRR-018"` - `"WR-025"` is now `"WRR-019"` - `"WR-016"` is now `"WR-001"` - `"WR-017"` is now `"WR-002"` - `"WR-018"` is now `"WR-003"` - `"WR-022"` is now `"WR-004"` - `"WR-023"` is now `"WR-005"` - `"WR-024"` is now `"WR-006"` - `"WR-026"` is now `"WR-007"` - `"WR-027"` is now `"WR-008"` Other changes: - `RouteInputParams` type moved to `requests` package and code updated accordingly - `SuggestedFees` type moved to a new `fees` package and code updated accordingly - `SendType` type moved to a new `sendtype` package and code updated accordingly - the following functions moved to `common` package - `ArrayContainsElement` - `ArraysWithSameElements` - `SameSingleChainTransfer` - `CopyMapGeneric` - `GweiToEth` - `WeiToGwei` - the following consts moved to `common` package - `HexAddressLength` - `SupportedNetworks` - `SupportedTestNetworks`
This commit is contained in:
parent
d10d492515
commit
00559692bc
|
@ -18,13 +18,13 @@ import (
|
|||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
"github.com/status-im/status-go/services/wallet/router"
|
||||
"github.com/status-im/status-go/services/wallet/router/fees"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
type CommunityTokenFees struct {
|
||||
GasUnits uint64 `json:"gasUnits"`
|
||||
SuggestedFees *router.SuggestedFeesGwei `json:"suggestedFees"`
|
||||
SuggestedFees *fees.SuggestedFeesGwei `json:"suggestedFees"`
|
||||
}
|
||||
|
||||
func weiToGwei(val *big.Int) *big.Float {
|
||||
|
@ -373,7 +373,7 @@ func (s *Service) prepareCommunityTokenFees(ctx context.Context, from common.Add
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) suggestedFeesToSendTxArgs(from common.Address, to *common.Address, gas uint64, suggestedFees *router.SuggestedFeesGwei) transactions.SendTxArgs {
|
||||
func (s *Service) suggestedFeesToSendTxArgs(from common.Address, to *common.Address, gas uint64, suggestedFees *fees.SuggestedFeesGwei) transactions.SendTxArgs {
|
||||
sendArgs := transactions.SendTxArgs{}
|
||||
sendArgs.From = types.Address(from)
|
||||
sendArgs.To = (*types.Address)(to)
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
"github.com/status-im/status-go/services/utils"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
wcommon "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/router"
|
||||
"github.com/status-im/status-go/services/wallet/router/fees"
|
||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
|
@ -49,7 +49,7 @@ type Service struct {
|
|||
walletFeed *event.Feed
|
||||
walletWatcher *walletevent.Watcher
|
||||
transactor *transactions.Transactor
|
||||
feeManager *router.FeeManager
|
||||
feeManager *fees.FeeManager
|
||||
}
|
||||
|
||||
// Returns a new Collectibles Service.
|
||||
|
@ -63,7 +63,7 @@ func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pen
|
|||
db: communitytokensdatabase.NewCommunityTokensDatabase(appDb),
|
||||
walletFeed: walletFeed,
|
||||
transactor: transactor,
|
||||
feeManager: &router.FeeManager{RPCClient: rpcClient},
|
||||
feeManager: &fees.FeeManager{RPCClient: rpcClient},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/status-im/status-go/services/wallet/onramp"
|
||||
"github.com/status-im/status-go/services/wallet/requests"
|
||||
"github.com/status-im/status-go/services/wallet/router"
|
||||
"github.com/status-im/status-go/services/wallet/router/fees"
|
||||
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
||||
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||
"github.com/status-im/status-go/services/wallet/token"
|
||||
|
@ -468,7 +469,7 @@ func (api *API) FetchTokenDetails(ctx context.Context, symbols []string) (map[st
|
|||
return api.s.marketManager.FetchTokenDetails(symbols)
|
||||
}
|
||||
|
||||
func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*router.SuggestedFeesGwei, error) {
|
||||
func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*fees.SuggestedFeesGwei, error) {
|
||||
log.Debug("call to GetSuggestedFees")
|
||||
return api.router.GetFeesManager().SuggestedFeesGwei(ctx, chainID)
|
||||
}
|
||||
|
@ -478,7 +479,7 @@ func (api *API) GetEstimatedLatestBlockNumber(ctx context.Context, chainID uint6
|
|||
return api.s.blockChainState.GetEstimatedLatestBlockNumber(ctx, chainID)
|
||||
}
|
||||
|
||||
func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Float) (router.TransactionEstimation, error) {
|
||||
func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Float) (fees.TransactionEstimation, error) {
|
||||
log.Debug("call to getTransactionEstimatedTime")
|
||||
return api.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil
|
||||
}
|
||||
|
@ -488,13 +489,13 @@ func gweiToWei(val *big.Float) *big.Int {
|
|||
return res
|
||||
}
|
||||
|
||||
func (api *API) GetSuggestedRoutes(ctx context.Context, input *router.RouteInputParams) (*router.SuggestedRoutes, error) {
|
||||
func (api *API) GetSuggestedRoutes(ctx context.Context, input *requests.RouteInputParams) (*router.SuggestedRoutes, error) {
|
||||
log.Debug("call to GetSuggestedRoutes")
|
||||
|
||||
return api.router.SuggestedRoutes(ctx, input)
|
||||
}
|
||||
|
||||
func (api *API) GetSuggestedRoutesAsync(ctx context.Context, input *router.RouteInputParams) {
|
||||
func (api *API) GetSuggestedRoutesAsync(ctx context.Context, input *requests.RouteInputParams) {
|
||||
log.Debug("call to GetSuggestedRoutesAsync")
|
||||
|
||||
api.router.SuggestedRoutesAsync(input)
|
||||
|
|
|
@ -11,6 +11,7 @@ type MultiTransactionIDType int64
|
|||
|
||||
const (
|
||||
NoMultiTransactionID = MultiTransactionIDType(0)
|
||||
HexAddressLength = 42
|
||||
)
|
||||
|
||||
type ChainID uint64
|
||||
|
@ -32,6 +33,18 @@ const (
|
|||
|
||||
var (
|
||||
ZeroAddress = ethCommon.HexToAddress("0x0000000000000000000000000000000000000000")
|
||||
|
||||
SupportedNetworks = map[uint64]bool{
|
||||
EthereumMainnet: true,
|
||||
OptimismMainnet: true,
|
||||
ArbitrumMainnet: true,
|
||||
}
|
||||
|
||||
SupportedTestNetworks = map[uint64]bool{
|
||||
EthereumSepolia: true,
|
||||
OptimismSepolia: true,
|
||||
ArbitrumSepolia: true,
|
||||
}
|
||||
)
|
||||
|
||||
type ContractType byte
|
||||
|
|
|
@ -2,7 +2,10 @@ package common
|
|||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
gethParams "github.com/ethereum/go-ethereum/params"
|
||||
"github.com/status-im/status-go/params"
|
||||
)
|
||||
|
||||
|
@ -24,3 +27,51 @@ func NetworksToChainIDs(networks []*params.Network) []uint64 {
|
|||
|
||||
return chainIDs
|
||||
}
|
||||
|
||||
func ArrayContainsElement[T comparable](el T, arr []T) bool {
|
||||
for _, e := range arr {
|
||||
if e == el {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsSingleChainOperation(fromChains []*params.Network, toChains []*params.Network) bool {
|
||||
return len(fromChains) == 1 &&
|
||||
len(toChains) == 1 &&
|
||||
fromChains[0].ChainID == toChains[0].ChainID
|
||||
}
|
||||
|
||||
// CopyMapGeneric creates a copy of any map, if the deepCopyValue function is provided, it will be used to copy values.
|
||||
func CopyMapGeneric(original interface{}, deepCopyValueFn func(interface{}) interface{}) interface{} {
|
||||
originalVal := reflect.ValueOf(original)
|
||||
if originalVal.Kind() != reflect.Map {
|
||||
return nil
|
||||
}
|
||||
|
||||
newMap := reflect.MakeMap(originalVal.Type())
|
||||
for iter := originalVal.MapRange(); iter.Next(); {
|
||||
if deepCopyValueFn != nil {
|
||||
newMap.SetMapIndex(iter.Key(), reflect.ValueOf(deepCopyValueFn(iter.Value().Interface())))
|
||||
} else {
|
||||
newMap.SetMapIndex(iter.Key(), iter.Value())
|
||||
}
|
||||
}
|
||||
|
||||
return newMap.Interface()
|
||||
}
|
||||
|
||||
func GweiToEth(val *big.Float) *big.Float {
|
||||
return new(big.Float).Quo(val, big.NewFloat(1000000000))
|
||||
}
|
||||
|
||||
func WeiToGwei(val *big.Int) *big.Float {
|
||||
result := new(big.Float)
|
||||
result.SetInt(val)
|
||||
|
||||
unit := new(big.Int)
|
||||
unit.SetInt64(gethParams.GWei)
|
||||
|
||||
return result.Quo(result, new(big.Float).SetInt(unit))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/errors"
|
||||
"github.com/status-im/status-go/services/ens"
|
||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/router/fees"
|
||||
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
||||
"github.com/status-im/status-go/services/wallet/router/sendtype"
|
||||
"github.com/status-im/status-go/services/wallet/token"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrENSRegisterRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-001"), Details: "username and public key are required for ENSRegister"}
|
||||
ErrENSRegisterTestnetSTTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-002"), Details: "only STT is supported for ENSRegister on testnet"}
|
||||
ErrENSRegisterMainnetSNTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-003"), Details: "only SNT is supported for ENSRegister on mainnet"}
|
||||
ErrENSReleaseRequiresUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-004"), Details: "username is required for ENSRelease"}
|
||||
ErrENSSetPubKeyRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-005"), Details: "username and public key are required for ENSSetPubKey"}
|
||||
ErrStickersBuyRequiresPackID = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-006"), Details: "packID is required for StickersBuy"}
|
||||
ErrSwapRequiresToTokenID = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-007"), Details: "toTokenID is required for Swap"}
|
||||
ErrSwapTokenIDMustBeDifferent = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-008"), Details: "tokenID and toTokenID must be different"}
|
||||
ErrSwapAmountInAmountOutMustBeExclusive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-009"), Details: "only one of amountIn or amountOut can be set"}
|
||||
ErrSwapAmountInMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-010"), Details: "amountIn must be positive"}
|
||||
ErrSwapAmountOutMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-011"), Details: "amountOut must be positive"}
|
||||
ErrLockedAmountNotSupportedForNetwork = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-012"), Details: "locked amount is not supported for the selected network"}
|
||||
ErrLockedAmountNotNegative = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-013"), Details: "locked amount must not be negative"}
|
||||
ErrLockedAmountExceedsTotalSendAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-014"), Details: "locked amount exceeds the total amount to send"}
|
||||
ErrLockedAmountLessThanSendAmountAllNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-015"), Details: "locked amount is less than the total amount to send, but all networks are locked"}
|
||||
ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-016"), Details: "disabled chain found among locked networks"}
|
||||
ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-017"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"}
|
||||
ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-018"), Details: "all supported chains are excluded, routing impossible"}
|
||||
ErrCannotCheckLockedAmounts = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-019"), Details: "cannot check locked amounts"}
|
||||
)
|
||||
|
||||
type RouteInputParams struct {
|
||||
Uuid string `json:"uuid"`
|
||||
SendType sendtype.SendType `json:"sendType" validate:"required"`
|
||||
AddrFrom common.Address `json:"addrFrom" validate:"required"`
|
||||
AddrTo common.Address `json:"addrTo" validate:"required"`
|
||||
AmountIn *hexutil.Big `json:"amountIn" validate:"required"`
|
||||
AmountOut *hexutil.Big `json:"amountOut"`
|
||||
TokenID string `json:"tokenID" validate:"required"`
|
||||
ToTokenID string `json:"toTokenID"`
|
||||
DisabledFromChainIDs []uint64 `json:"disabledFromChainIDs"`
|
||||
DisabledToChainIDs []uint64 `json:"disabledToChainIDs"`
|
||||
GasFeeMode fees.GasFeeMode `json:"gasFeeMode" validate:"required"`
|
||||
FromLockedAmount map[uint64]*hexutil.Big `json:"fromLockedAmount"`
|
||||
TestnetMode bool
|
||||
|
||||
// For send types like EnsRegister, EnsRelease, EnsSetPubKey, StickersBuy
|
||||
Username string `json:"username"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
PackID *hexutil.Big `json:"packID"`
|
||||
|
||||
// TODO: Remove two fields below once we implement a better solution for tests
|
||||
// Currently used for tests only
|
||||
TestsMode bool
|
||||
TestParams *RouterTestParams
|
||||
}
|
||||
|
||||
type RouterTestParams struct {
|
||||
TokenFrom *token.Token
|
||||
TokenPrices map[string]float64
|
||||
EstimationMap map[string]pathprocessor.Estimation // [processor-name, estimation]
|
||||
BonderFeeMap map[string]*big.Int // [token-symbol, bonder-fee]
|
||||
SuggestedFees *fees.SuggestedFees
|
||||
BaseFee *big.Int
|
||||
BalanceMap map[string]*big.Int // [token-symbol, balance]
|
||||
ApprovalGasEstimation uint64
|
||||
ApprovalL1Fee uint64
|
||||
}
|
||||
|
||||
func (i *RouteInputParams) Validate() error {
|
||||
if i.SendType == sendtype.ENSRegister {
|
||||
if i.Username == "" || i.PublicKey == "" {
|
||||
return ErrENSRegisterRequiresUsernameAndPubKey
|
||||
}
|
||||
if i.TestnetMode {
|
||||
if i.TokenID != pathprocessor.SttSymbol {
|
||||
return ErrENSRegisterTestnetSTTOnly
|
||||
}
|
||||
} else {
|
||||
if i.TokenID != pathprocessor.SntSymbol {
|
||||
return ErrENSRegisterMainnetSNTOnly
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.SendType == sendtype.ENSRelease {
|
||||
if i.Username == "" {
|
||||
return ErrENSReleaseRequiresUsername
|
||||
}
|
||||
}
|
||||
|
||||
if i.SendType == sendtype.ENSSetPubKey {
|
||||
if i.Username == "" || i.PublicKey == "" {
|
||||
return ErrENSSetPubKeyRequiresUsernameAndPubKey
|
||||
}
|
||||
|
||||
if ens.ValidateENSUsername(i.Username) != nil {
|
||||
return ErrENSSetPubKeyInvalidUsername
|
||||
}
|
||||
}
|
||||
|
||||
if i.SendType == sendtype.StickersBuy {
|
||||
if i.PackID == nil {
|
||||
return ErrStickersBuyRequiresPackID
|
||||
}
|
||||
}
|
||||
|
||||
if i.SendType == sendtype.Swap {
|
||||
if i.ToTokenID == "" {
|
||||
return ErrSwapRequiresToTokenID
|
||||
}
|
||||
if i.TokenID == i.ToTokenID {
|
||||
return ErrSwapTokenIDMustBeDifferent
|
||||
}
|
||||
|
||||
if i.AmountIn != nil &&
|
||||
i.AmountOut != nil &&
|
||||
i.AmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 &&
|
||||
i.AmountOut.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
return ErrSwapAmountInAmountOutMustBeExclusive
|
||||
}
|
||||
|
||||
if i.AmountIn != nil && i.AmountIn.ToInt().Sign() < 0 {
|
||||
return ErrSwapAmountInMustBePositive
|
||||
}
|
||||
|
||||
if i.AmountOut != nil && i.AmountOut.ToInt().Sign() < 0 {
|
||||
return ErrSwapAmountOutMustBePositive
|
||||
}
|
||||
}
|
||||
|
||||
return i.validateFromLockedAmount()
|
||||
}
|
||||
|
||||
func (i *RouteInputParams) validateFromLockedAmount() error {
|
||||
if i.FromLockedAmount == nil || len(i.FromLockedAmount) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var suppNetworks map[uint64]bool
|
||||
if i.TestnetMode {
|
||||
suppNetworks = walletCommon.CopyMapGeneric(walletCommon.SupportedTestNetworks, nil).(map[uint64]bool)
|
||||
} else {
|
||||
suppNetworks = walletCommon.CopyMapGeneric(walletCommon.SupportedNetworks, nil).(map[uint64]bool)
|
||||
}
|
||||
|
||||
if suppNetworks == nil {
|
||||
return ErrCannotCheckLockedAmounts
|
||||
}
|
||||
|
||||
totalLockedAmount := big.NewInt(0)
|
||||
excludedChainCount := 0
|
||||
|
||||
for chainID, amount := range i.FromLockedAmount {
|
||||
if walletCommon.ArrayContainsElement(chainID, i.DisabledFromChainIDs) {
|
||||
return ErrDisabledChainFoundAmongLockedNetworks
|
||||
}
|
||||
|
||||
if i.TestnetMode {
|
||||
if !walletCommon.SupportedTestNetworks[chainID] {
|
||||
return ErrLockedAmountNotSupportedForNetwork
|
||||
}
|
||||
} else {
|
||||
if !walletCommon.SupportedNetworks[chainID] {
|
||||
return ErrLockedAmountNotSupportedForNetwork
|
||||
}
|
||||
}
|
||||
|
||||
if amount == nil || amount.ToInt().Sign() < 0 {
|
||||
return ErrLockedAmountNotNegative
|
||||
}
|
||||
|
||||
if !(amount.ToInt().Sign() > 0) {
|
||||
excludedChainCount++
|
||||
}
|
||||
delete(suppNetworks, chainID)
|
||||
totalLockedAmount = new(big.Int).Add(totalLockedAmount, amount.ToInt())
|
||||
}
|
||||
|
||||
if (!i.TestnetMode && excludedChainCount == len(walletCommon.SupportedNetworks)) ||
|
||||
(i.TestnetMode && excludedChainCount == len(walletCommon.SupportedTestNetworks)) {
|
||||
return ErrLockedAmountExcludesAllSupported
|
||||
}
|
||||
|
||||
if totalLockedAmount.Cmp(i.AmountIn.ToInt()) > 0 {
|
||||
return ErrLockedAmountExceedsTotalSendAmount
|
||||
}
|
||||
if totalLockedAmount.Cmp(i.AmountIn.ToInt()) < 0 && len(suppNetworks) == 0 {
|
||||
return ErrLockedAmountLessThanSendAmountAllNetworks
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,32 +1,54 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/services/wallet/common"
|
||||
)
|
||||
|
||||
func arrayContainsElement[T comparable](el T, arr []T) bool {
|
||||
for _, e := range arr {
|
||||
if e == el {
|
||||
return true
|
||||
func removeBestRouteFromAllRouters(allRoutes [][]*Path, best []*Path) [][]*Path {
|
||||
for i := len(allRoutes) - 1; i >= 0; i-- {
|
||||
route := allRoutes[i]
|
||||
routeFound := true
|
||||
for _, p := range route {
|
||||
found := false
|
||||
for _, b := range best {
|
||||
if p.ProcessorName == b.ProcessorName &&
|
||||
(p.FromChain == nil && b.FromChain == nil || p.FromChain.ChainID == b.FromChain.ChainID) &&
|
||||
(p.ToChain == nil && b.ToChain == nil || p.ToChain.ChainID == b.ToChain.ChainID) &&
|
||||
(p.FromToken == nil && b.FromToken == nil || p.FromToken.Symbol == b.FromToken.Symbol) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
if !found {
|
||||
routeFound = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if routeFound {
|
||||
return append(allRoutes[:i], allRoutes[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func arraysWithSameElements[T comparable](ar1 []T, ar2 []T, isEqual func(T, T) bool) bool {
|
||||
if len(ar1) != len(ar2) {
|
||||
return false
|
||||
func getChainPriority(chainID uint64) int {
|
||||
switch chainID {
|
||||
case common.EthereumMainnet, common.EthereumSepolia:
|
||||
return 1
|
||||
case common.OptimismMainnet, common.OptimismSepolia:
|
||||
return 2
|
||||
case common.ArbitrumMainnet, common.ArbitrumSepolia:
|
||||
return 3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
for _, el := range ar1 {
|
||||
if !arrayContainsElement(el, ar2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isSingleChainOperation(fromChains []*params.Network, toChains []*params.Network) bool {
|
||||
return len(fromChains) == 1 &&
|
||||
len(toChains) == 1 &&
|
||||
fromChains[0].ChainID == toChains[0].ChainID
|
||||
func getRoutePriority(route []*Path) int {
|
||||
priority := 0
|
||||
for _, path := range route {
|
||||
priority += getChainPriority(path.FromChain.ChainID)
|
||||
}
|
||||
return priority
|
||||
}
|
||||
|
|
|
@ -6,31 +6,12 @@ import (
|
|||
|
||||
// Abbreviation `WR` for the error code stands for Wallet Router
|
||||
var (
|
||||
ErrENSRegisterRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WR-001"), Details: "username and public key are required for ENSRegister"}
|
||||
ErrENSRegisterTestnetSTTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WR-002"), Details: "only STT is supported for ENSRegister on testnet"}
|
||||
ErrENSRegisterMainnetSNTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WR-003"), Details: "only SNT is supported for ENSRegister on mainnet"}
|
||||
ErrENSReleaseRequiresUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WR-004"), Details: "username is required for ENSRelease"}
|
||||
ErrENSSetPubKeyRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WR-005"), Details: "username and public key are required for ENSSetPubKey"}
|
||||
ErrStickersBuyRequiresPackID = &errors.ErrorResponse{Code: errors.ErrorCode("WR-006"), Details: "packID is required for StickersBuy"}
|
||||
ErrSwapRequiresToTokenID = &errors.ErrorResponse{Code: errors.ErrorCode("WR-007"), Details: "toTokenID is required for Swap"}
|
||||
ErrSwapTokenIDMustBeDifferent = &errors.ErrorResponse{Code: errors.ErrorCode("WR-008"), Details: "tokenID and toTokenID must be different"}
|
||||
ErrSwapAmountInAmountOutMustBeExclusive = &errors.ErrorResponse{Code: errors.ErrorCode("WR-009"), Details: "only one of amountIn or amountOut can be set"}
|
||||
ErrSwapAmountInMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WR-010"), Details: "amountIn must be positive"}
|
||||
ErrSwapAmountOutMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WR-011"), Details: "amountOut must be positive"}
|
||||
ErrLockedAmountNotSupportedForNetwork = &errors.ErrorResponse{Code: errors.ErrorCode("WR-012"), Details: "locked amount is not supported for the selected network"}
|
||||
ErrLockedAmountNotNegative = &errors.ErrorResponse{Code: errors.ErrorCode("WR-013"), Details: "locked amount must not be negative"}
|
||||
ErrLockedAmountExceedsTotalSendAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WR-014"), Details: "locked amount exceeds the total amount to send"}
|
||||
ErrLockedAmountLessThanSendAmountAllNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-015"), Details: "locked amount is less than the total amount to send, but all networks are locked"}
|
||||
ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-016"), Details: "not enough token balance, token: %s, chainId: %d"}
|
||||
ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-017"), Details: "not enough native balance, token: %s, chainId: %d"}
|
||||
ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-018"), Details: "native token not found"}
|
||||
ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-019"), Details: "disabled chain found among locked networks"}
|
||||
ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WR-020"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"}
|
||||
ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WR-021"), Details: "all supported chains are excluded, routing impossible"}
|
||||
ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-022"), Details: "token not found"}
|
||||
ErrNoBestRouteFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-023"), Details: "no best route found"}
|
||||
ErrCannotCheckBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-024"), Details: "cannot check balance"}
|
||||
ErrCannotCheckLockedAmounts = &errors.ErrorResponse{Code: errors.ErrorCode("WR-025"), Details: "cannot check locked amounts"}
|
||||
ErrLowAmountInForHopBridge = &errors.ErrorResponse{Code: errors.ErrorCode("WR-026"), Details: "bonder fee greater than estimated received, a higher amount is needed to cover fees"}
|
||||
ErrNoPositiveBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-027"), Details: "no positive balance"}
|
||||
ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-001"), Details: "not enough token balance, token: %s, chainId: %d"}
|
||||
ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-002"), Details: "not enough native balance, token: %s, chainId: %d"}
|
||||
ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-003"), Details: "native token not found"}
|
||||
ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-004"), Details: "token not found"}
|
||||
ErrNoBestRouteFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-005"), Details: "no best route found"}
|
||||
ErrCannotCheckBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-006"), Details: "cannot check balance"}
|
||||
ErrLowAmountInForHopBridge = &errors.ErrorResponse{Code: errors.ErrorCode("WR-007"), Details: "bonder fee greater than estimated received, a higher amount is needed to cover fees"}
|
||||
ErrNoPositiveBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-008"), Details: "no positive balance"}
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package router
|
||||
package fees
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -54,7 +54,7 @@ type SuggestedFeesGwei struct {
|
|||
EIP1559Enabled bool `json:"eip1559Enabled"`
|
||||
}
|
||||
|
||||
func (m *MaxFeesLevels) feeFor(mode GasFeeMode) *big.Int {
|
||||
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) *big.Int {
|
||||
if mode == GasFeeLow {
|
||||
return m.Low.ToInt()
|
||||
}
|
||||
|
@ -66,8 +66,8 @@ func (m *MaxFeesLevels) feeFor(mode GasFeeMode) *big.Int {
|
|||
return m.Medium.ToInt()
|
||||
}
|
||||
|
||||
func (s *SuggestedFees) feeFor(mode GasFeeMode) *big.Int {
|
||||
return s.MaxFeesLevels.feeFor(mode)
|
||||
func (s *SuggestedFees) FeeFor(mode GasFeeMode) *big.Int {
|
||||
return s.MaxFeesLevels.FeeFor(mode)
|
||||
}
|
||||
|
||||
const inclusionThreshold = 0.95
|
||||
|
@ -90,20 +90,6 @@ type FeeManager struct {
|
|||
RPCClient *rpc.Client
|
||||
}
|
||||
|
||||
func weiToGwei(val *big.Int) *big.Float {
|
||||
result := new(big.Float)
|
||||
result.SetInt(val)
|
||||
|
||||
unit := new(big.Int)
|
||||
unit.SetInt64(params.GWei)
|
||||
|
||||
return result.Quo(result, new(big.Float).SetInt(unit))
|
||||
}
|
||||
|
||||
func gweiToEth(val *big.Float) *big.Float {
|
||||
return new(big.Float).Quo(val, big.NewFloat(1000000000))
|
||||
}
|
||||
|
||||
func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {
|
||||
backend, err := f.RPCClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
|
@ -151,12 +137,12 @@ func (f *FeeManager) SuggestedFeesGwei(ctx context.Context, chainID uint64) (*Su
|
|||
return nil, err
|
||||
}
|
||||
return &SuggestedFeesGwei{
|
||||
GasPrice: weiToGwei(fees.GasPrice),
|
||||
BaseFee: weiToGwei(fees.BaseFee),
|
||||
MaxPriorityFeePerGas: weiToGwei(fees.MaxPriorityFeePerGas),
|
||||
MaxFeePerGasLow: weiToGwei(fees.MaxFeesLevels.Low.ToInt()),
|
||||
MaxFeePerGasMedium: weiToGwei(fees.MaxFeesLevels.Medium.ToInt()),
|
||||
MaxFeePerGasHigh: weiToGwei(fees.MaxFeesLevels.High.ToInt()),
|
||||
GasPrice: common.WeiToGwei(fees.GasPrice),
|
||||
BaseFee: common.WeiToGwei(fees.BaseFee),
|
||||
MaxPriorityFeePerGas: common.WeiToGwei(fees.MaxPriorityFeePerGas),
|
||||
MaxFeePerGasLow: common.WeiToGwei(fees.MaxFeesLevels.Low.ToInt()),
|
||||
MaxFeePerGasMedium: common.WeiToGwei(fees.MaxFeesLevels.Medium.ToInt()),
|
||||
MaxFeePerGasHigh: common.WeiToGwei(fees.MaxFeesLevels.High.ToInt()),
|
||||
EIP1559Enabled: fees.EIP1559Enabled,
|
||||
}, nil
|
||||
}
|
|
@ -2,9 +2,9 @@ package router
|
|||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
@ -57,7 +57,7 @@ func filterNetworkCompliance(routes [][]*Path, fromLockedAmount map[uint64]*hexu
|
|||
}
|
||||
|
||||
// Create fresh copies of the maps for each route check, because they are manipulated
|
||||
if isValidForNetworkCompliance(route, copyMapGeneric(fromIncluded, nil).(map[uint64]bool), copyMapGeneric(fromExcluded, nil).(map[uint64]bool)) {
|
||||
if isValidForNetworkCompliance(route, common.CopyMapGeneric(fromIncluded, nil).(map[uint64]bool), common.CopyMapGeneric(fromExcluded, nil).(map[uint64]bool)) {
|
||||
filteredRoutes = append(filteredRoutes, route)
|
||||
}
|
||||
}
|
||||
|
@ -162,22 +162,3 @@ func calculateRestAmountIn(route []*Path, excludePath *Path) *big.Int {
|
|||
}
|
||||
return restAmountIn
|
||||
}
|
||||
|
||||
// copyMapGeneric creates a copy of any map, if the deepCopyValue function is provided, it will be used to copy values.
|
||||
func copyMapGeneric(original interface{}, deepCopyValueFn func(interface{}) interface{}) interface{} {
|
||||
originalVal := reflect.ValueOf(original)
|
||||
if originalVal.Kind() != reflect.Map {
|
||||
return nil
|
||||
}
|
||||
|
||||
newMap := reflect.MakeMap(originalVal.Type())
|
||||
for iter := originalVal.MapRange(); iter.Next(); {
|
||||
if deepCopyValueFn != nil {
|
||||
newMap.SetMapIndex(iter.Key(), reflect.ValueOf(deepCopyValueFn(iter.Value().Interface())))
|
||||
} else {
|
||||
newMap.SetMapIndex(iter.Key(), iter.Value())
|
||||
}
|
||||
}
|
||||
|
||||
return newMap.Interface()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package router
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -21,17 +20,16 @@ import (
|
|||
"github.com/status-im/status-go/services/wallet/collectibles"
|
||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/market"
|
||||
"github.com/status-im/status-go/services/wallet/requests"
|
||||
"github.com/status-im/status-go/services/wallet/router/fees"
|
||||
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
||||
"github.com/status-im/status-go/services/wallet/router/sendtype"
|
||||
"github.com/status-im/status-go/services/wallet/token"
|
||||
walletToken "github.com/status-im/status-go/services/wallet/token"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
const (
|
||||
hexAddressLength = 42
|
||||
)
|
||||
|
||||
var (
|
||||
routerTask = async.TaskType{
|
||||
ID: 1,
|
||||
|
@ -39,58 +37,6 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
var (
|
||||
supportedNetworks = map[uint64]bool{
|
||||
walletCommon.EthereumMainnet: true,
|
||||
walletCommon.OptimismMainnet: true,
|
||||
walletCommon.ArbitrumMainnet: true,
|
||||
}
|
||||
|
||||
supportedTestNetworks = map[uint64]bool{
|
||||
walletCommon.EthereumSepolia: true,
|
||||
walletCommon.OptimismSepolia: true,
|
||||
walletCommon.ArbitrumSepolia: true,
|
||||
}
|
||||
)
|
||||
|
||||
type RouteInputParams struct {
|
||||
Uuid string `json:"uuid"`
|
||||
SendType SendType `json:"sendType" validate:"required"`
|
||||
AddrFrom common.Address `json:"addrFrom" validate:"required"`
|
||||
AddrTo common.Address `json:"addrTo" validate:"required"`
|
||||
AmountIn *hexutil.Big `json:"amountIn" validate:"required"`
|
||||
AmountOut *hexutil.Big `json:"amountOut"`
|
||||
TokenID string `json:"tokenID" validate:"required"`
|
||||
ToTokenID string `json:"toTokenID"`
|
||||
DisabledFromChainIDs []uint64 `json:"disabledFromChainIDs"`
|
||||
DisabledToChainIDs []uint64 `json:"disabledToChainIDs"`
|
||||
GasFeeMode GasFeeMode `json:"gasFeeMode" validate:"required"`
|
||||
FromLockedAmount map[uint64]*hexutil.Big `json:"fromLockedAmount"`
|
||||
testnetMode bool
|
||||
|
||||
// For send types like EnsRegister, EnsRelease, EnsSetPubKey, StickersBuy
|
||||
Username string `json:"username"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
PackID *hexutil.Big `json:"packID"`
|
||||
|
||||
// TODO: Remove two fields below once we implement a better solution for tests
|
||||
// Currently used for tests only
|
||||
testsMode bool
|
||||
testParams *routerTestParams
|
||||
}
|
||||
|
||||
type routerTestParams struct {
|
||||
tokenFrom *walletToken.Token
|
||||
tokenPrices map[string]float64
|
||||
estimationMap map[string]pathprocessor.Estimation // [processor-name, estimation]
|
||||
bonderFeeMap map[string]*big.Int // [token-symbol, bonder-fee]
|
||||
suggestedFees *SuggestedFees
|
||||
baseFee *big.Int
|
||||
balanceMap map[string]*big.Int // [token-symbol, balance]
|
||||
approvalGasEstimation uint64
|
||||
approvalL1Fee uint64
|
||||
}
|
||||
|
||||
type amountOption struct {
|
||||
amount *big.Int
|
||||
locked bool
|
||||
|
@ -101,56 +47,11 @@ func makeBalanceKey(chainID uint64, symbol string) string {
|
|||
return fmt.Sprintf("%d-%s", chainID, symbol)
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
ProcessorName string
|
||||
FromChain *params.Network // Source chain
|
||||
ToChain *params.Network // Destination chain
|
||||
FromToken *walletToken.Token // Source token
|
||||
ToToken *walletToken.Token // Destination token, set if applicable
|
||||
AmountIn *hexutil.Big // Amount that will be sent from the source chain
|
||||
AmountInLocked bool // Is the amount locked
|
||||
AmountOut *hexutil.Big // Amount that will be received on the destination chain
|
||||
|
||||
SuggestedLevelsForMaxFeesPerGas *MaxFeesLevels // Suggested max fees for the transaction (in ETH WEI)
|
||||
MaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI)
|
||||
|
||||
TxBaseFee *hexutil.Big // Base fee for the transaction (in ETH WEI)
|
||||
TxPriorityFee *hexutil.Big // Priority fee for the transaction (in ETH WEI)
|
||||
TxGasAmount uint64 // Gas used for the transaction
|
||||
TxBonderFees *hexutil.Big // Bonder fees for the transaction - used for Hop bridge (in selected token)
|
||||
TxTokenFees *hexutil.Big // Token fees for the transaction - used for bridges (represent the difference between the amount in and the amount out, in selected token)
|
||||
|
||||
TxFee *hexutil.Big // fee for the transaction (includes tx fee only, doesn't include approval fees, l1 fees, l1 approval fees, token fees or bonders fees, in ETH WEI)
|
||||
TxL1Fee *hexutil.Big // L1 fee for the transaction - used for for transactions placed on L2 chains (in ETH WEI)
|
||||
|
||||
ApprovalRequired bool // Is approval required for the transaction
|
||||
ApprovalAmountRequired *hexutil.Big // Amount required for the approval transaction
|
||||
ApprovalContractAddress *common.Address // Address of the contract that needs to be approved
|
||||
ApprovalBaseFee *hexutil.Big // Base fee for the approval transaction (in ETH WEI)
|
||||
ApprovalPriorityFee *hexutil.Big // Priority fee for the approval transaction (in ETH WEI)
|
||||
ApprovalGasAmount uint64 // Gas used for the approval transaction
|
||||
|
||||
ApprovalFee *hexutil.Big // Total fee for the approval transaction (includes approval tx fees only, doesn't include approval l1 fees, in ETH WEI)
|
||||
ApprovalL1Fee *hexutil.Big // L1 fee for the approval transaction - used for for transactions placed on L2 chains (in ETH WEI)
|
||||
|
||||
TxTotalFee *hexutil.Big // Total fee for the transaction (includes tx fees, approval fees, l1 fees, l1 approval fees, in ETH WEI)
|
||||
|
||||
EstimatedTime TransactionEstimation
|
||||
|
||||
requiredTokenBalance *big.Int // (in selected token)
|
||||
requiredNativeBalance *big.Int // (in ETH WEI)
|
||||
subtractFees bool
|
||||
}
|
||||
|
||||
type ProcessorError struct {
|
||||
ProcessorName string
|
||||
Error error
|
||||
}
|
||||
|
||||
func (p *Path) Equal(o *Path) bool {
|
||||
return p.FromChain.ChainID == o.FromChain.ChainID && p.ToChain.ChainID == o.ToChain.ChainID
|
||||
}
|
||||
|
||||
type SuggestedRoutes struct {
|
||||
Uuid string
|
||||
Best []*Path
|
||||
|
@ -168,13 +69,6 @@ type SuggestedRoutesResponse struct {
|
|||
ErrorResponse *errors.ErrorResponse `json:"ErrorResponse,omitempty"`
|
||||
}
|
||||
|
||||
type Graph []*Node
|
||||
|
||||
type Node struct {
|
||||
Path *Path
|
||||
Children Graph
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
rpcClient *rpc.Client
|
||||
tokenManager *token.Manager
|
||||
|
@ -183,7 +77,7 @@ type Router struct {
|
|||
collectiblesManager *collectibles.Manager
|
||||
ensService *ens.Service
|
||||
stickersService *stickers.Service
|
||||
feesManager *FeeManager
|
||||
feesManager *fees.FeeManager
|
||||
pathProcessors map[string]pathprocessor.PathProcessor
|
||||
scheduler *async.Scheduler
|
||||
}
|
||||
|
@ -200,7 +94,9 @@ func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, token
|
|||
collectiblesManager: collectiblesManager,
|
||||
ensService: ensService,
|
||||
stickersService: stickersService,
|
||||
feesManager: &FeeManager{rpcClient},
|
||||
feesManager: &fees.FeeManager{
|
||||
RPCClient: rpcClient,
|
||||
},
|
||||
pathProcessors: processors,
|
||||
scheduler: async.NewScheduler(),
|
||||
}
|
||||
|
@ -214,7 +110,7 @@ func (r *Router) Stop() {
|
|||
r.scheduler.Stop()
|
||||
}
|
||||
|
||||
func (r *Router) GetFeesManager() *FeeManager {
|
||||
func (r *Router) GetFeesManager() *fees.FeeManager {
|
||||
return r.feesManager
|
||||
}
|
||||
|
||||
|
@ -258,252 +154,7 @@ func newSuggestedRoutes(
|
|||
return suggestedRoutes, allRoutes
|
||||
}
|
||||
|
||||
func newNode(path *Path) *Node {
|
||||
return &Node{Path: path, Children: make(Graph, 0)}
|
||||
}
|
||||
|
||||
func buildGraph(AmountIn *big.Int, routes []*Path, level int, sourceChainIDs []uint64) Graph {
|
||||
graph := make(Graph, 0)
|
||||
for _, route := range routes {
|
||||
found := false
|
||||
for _, chainID := range sourceChainIDs {
|
||||
if chainID == route.FromChain.ChainID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
node := newNode(route)
|
||||
|
||||
newRoutes := make([]*Path, 0)
|
||||
for _, r := range routes {
|
||||
if route.Equal(r) {
|
||||
continue
|
||||
}
|
||||
newRoutes = append(newRoutes, r)
|
||||
}
|
||||
|
||||
newAmountIn := new(big.Int).Sub(AmountIn, route.AmountIn.ToInt())
|
||||
if newAmountIn.Sign() > 0 {
|
||||
newSourceChainIDs := make([]uint64, len(sourceChainIDs))
|
||||
copy(newSourceChainIDs, sourceChainIDs)
|
||||
newSourceChainIDs = append(newSourceChainIDs, route.FromChain.ChainID)
|
||||
node.Children = buildGraph(newAmountIn, newRoutes, level+1, newSourceChainIDs)
|
||||
|
||||
if len(node.Children) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
graph = append(graph, node)
|
||||
}
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
func (n Node) buildAllRoutes() [][]*Path {
|
||||
res := make([][]*Path, 0)
|
||||
|
||||
if len(n.Children) == 0 && n.Path != nil {
|
||||
res = append(res, []*Path{n.Path})
|
||||
}
|
||||
|
||||
for _, node := range n.Children {
|
||||
for _, route := range node.buildAllRoutes() {
|
||||
extendedRoute := route
|
||||
if n.Path != nil {
|
||||
extendedRoute = append([]*Path{n.Path}, route...)
|
||||
}
|
||||
res = append(res, extendedRoute)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func findBest(routes [][]*Path, tokenPrice float64, nativeTokenPrice float64) []*Path {
|
||||
var best []*Path
|
||||
bestCost := big.NewFloat(math.Inf(1))
|
||||
for _, route := range routes {
|
||||
currentCost := big.NewFloat(0)
|
||||
for _, path := range route {
|
||||
tokenDenominator := big.NewFloat(math.Pow(10, float64(path.FromToken.Decimals)))
|
||||
|
||||
// calculate the cost of the path
|
||||
nativeTokenPrice := new(big.Float).SetFloat64(nativeTokenPrice)
|
||||
|
||||
// tx fee
|
||||
txFeeInEth := gweiToEth(weiToGwei(path.TxFee.ToInt()))
|
||||
pathCost := new(big.Float).Mul(txFeeInEth, nativeTokenPrice)
|
||||
|
||||
if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
txL1FeeInEth := gweiToEth(weiToGwei(path.TxL1Fee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(txL1FeeInEth, nativeTokenPrice))
|
||||
}
|
||||
|
||||
if path.TxBonderFees != nil && path.TxBonderFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(
|
||||
new(big.Float).Quo(new(big.Float).SetInt(path.TxBonderFees.ToInt()), tokenDenominator),
|
||||
new(big.Float).SetFloat64(tokenPrice)))
|
||||
|
||||
}
|
||||
|
||||
if path.TxTokenFees != nil && path.TxTokenFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && path.FromToken != nil {
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(
|
||||
new(big.Float).Quo(new(big.Float).SetInt(path.TxTokenFees.ToInt()), tokenDenominator),
|
||||
new(big.Float).SetFloat64(tokenPrice)))
|
||||
}
|
||||
|
||||
if path.ApprovalRequired {
|
||||
// tx approval fee
|
||||
approvalFeeInEth := gweiToEth(weiToGwei(path.ApprovalFee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(approvalFeeInEth, nativeTokenPrice))
|
||||
|
||||
if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
approvalL1FeeInEth := gweiToEth(weiToGwei(path.ApprovalL1Fee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(approvalL1FeeInEth, nativeTokenPrice))
|
||||
}
|
||||
}
|
||||
|
||||
currentCost = new(big.Float).Add(currentCost, pathCost)
|
||||
}
|
||||
|
||||
if currentCost.Cmp(bestCost) == -1 {
|
||||
best = route
|
||||
bestCost = currentCost
|
||||
}
|
||||
}
|
||||
|
||||
return best
|
||||
}
|
||||
|
||||
func validateInputData(input *RouteInputParams) error {
|
||||
if input.SendType == ENSRegister {
|
||||
if input.Username == "" || input.PublicKey == "" {
|
||||
return ErrENSRegisterRequiresUsernameAndPubKey
|
||||
}
|
||||
if input.testnetMode {
|
||||
if input.TokenID != pathprocessor.SttSymbol {
|
||||
return ErrENSRegisterTestnetSTTOnly
|
||||
}
|
||||
} else {
|
||||
if input.TokenID != pathprocessor.SntSymbol {
|
||||
return ErrENSRegisterMainnetSNTOnly
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if input.SendType == ENSRelease {
|
||||
if input.Username == "" {
|
||||
return ErrENSReleaseRequiresUsername
|
||||
}
|
||||
}
|
||||
|
||||
if input.SendType == ENSSetPubKey {
|
||||
if input.Username == "" || input.PublicKey == "" {
|
||||
return ErrENSSetPubKeyRequiresUsernameAndPubKey
|
||||
}
|
||||
|
||||
if ens.ValidateENSUsername(input.Username) != nil {
|
||||
return ErrENSSetPubKeyInvalidUsername
|
||||
}
|
||||
}
|
||||
|
||||
if input.SendType == StickersBuy {
|
||||
if input.PackID == nil {
|
||||
return ErrStickersBuyRequiresPackID
|
||||
}
|
||||
}
|
||||
|
||||
if input.SendType == Swap {
|
||||
if input.ToTokenID == "" {
|
||||
return ErrSwapRequiresToTokenID
|
||||
}
|
||||
if input.TokenID == input.ToTokenID {
|
||||
return ErrSwapTokenIDMustBeDifferent
|
||||
}
|
||||
|
||||
if input.AmountIn != nil &&
|
||||
input.AmountOut != nil &&
|
||||
input.AmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 &&
|
||||
input.AmountOut.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
return ErrSwapAmountInAmountOutMustBeExclusive
|
||||
}
|
||||
|
||||
if input.AmountIn != nil && input.AmountIn.ToInt().Sign() < 0 {
|
||||
return ErrSwapAmountInMustBePositive
|
||||
}
|
||||
|
||||
if input.AmountOut != nil && input.AmountOut.ToInt().Sign() < 0 {
|
||||
return ErrSwapAmountOutMustBePositive
|
||||
}
|
||||
}
|
||||
|
||||
return validateFromLockedAmount(input)
|
||||
}
|
||||
|
||||
func validateFromLockedAmount(input *RouteInputParams) error {
|
||||
if input.FromLockedAmount == nil || len(input.FromLockedAmount) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var suppNetworks map[uint64]bool
|
||||
if input.testnetMode {
|
||||
suppNetworks = copyMapGeneric(supportedTestNetworks, nil).(map[uint64]bool)
|
||||
} else {
|
||||
suppNetworks = copyMapGeneric(supportedNetworks, nil).(map[uint64]bool)
|
||||
}
|
||||
|
||||
if suppNetworks == nil {
|
||||
return ErrCannotCheckLockedAmounts
|
||||
}
|
||||
|
||||
totalLockedAmount := big.NewInt(0)
|
||||
excludedChainCount := 0
|
||||
|
||||
for chainID, amount := range input.FromLockedAmount {
|
||||
if arrayContainsElement(chainID, input.DisabledFromChainIDs) {
|
||||
return ErrDisabledChainFoundAmongLockedNetworks
|
||||
}
|
||||
|
||||
if input.testnetMode {
|
||||
if !supportedTestNetworks[chainID] {
|
||||
return ErrLockedAmountNotSupportedForNetwork
|
||||
}
|
||||
} else {
|
||||
if !supportedNetworks[chainID] {
|
||||
return ErrLockedAmountNotSupportedForNetwork
|
||||
}
|
||||
}
|
||||
|
||||
if amount == nil || amount.ToInt().Sign() < 0 {
|
||||
return ErrLockedAmountNotNegative
|
||||
}
|
||||
|
||||
if !(amount.ToInt().Sign() > 0) {
|
||||
excludedChainCount++
|
||||
}
|
||||
delete(suppNetworks, chainID)
|
||||
totalLockedAmount = new(big.Int).Add(totalLockedAmount, amount.ToInt())
|
||||
}
|
||||
|
||||
if (!input.testnetMode && excludedChainCount == len(supportedNetworks)) ||
|
||||
(input.testnetMode && excludedChainCount == len(supportedTestNetworks)) {
|
||||
return ErrLockedAmountExcludesAllSupported
|
||||
}
|
||||
|
||||
if totalLockedAmount.Cmp(input.AmountIn.ToInt()) > 0 {
|
||||
return ErrLockedAmountExceedsTotalSendAmount
|
||||
} else if totalLockedAmount.Cmp(input.AmountIn.ToInt()) < 0 && len(suppNetworks) == 0 {
|
||||
return ErrLockedAmountLessThanSendAmountAllNetworks
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) SuggestedRoutesAsync(input *RouteInputParams) {
|
||||
func (r *Router) SuggestedRoutesAsync(input *requests.RouteInputParams) {
|
||||
r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) {
|
||||
return r.SuggestedRoutes(ctx, input)
|
||||
}, func(result interface{}, taskType async.TaskType, err error) {
|
||||
|
@ -531,13 +182,13 @@ func (r *Router) StopSuggestedRoutesAsyncCalculation() {
|
|||
r.scheduler.Stop()
|
||||
}
|
||||
|
||||
func (r *Router) SuggestedRoutes(ctx context.Context, input *RouteInputParams) (*SuggestedRoutes, error) {
|
||||
func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInputParams) (*SuggestedRoutes, error) {
|
||||
testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled()
|
||||
if err != nil {
|
||||
return nil, errors.CreateErrorResponseFromError(err)
|
||||
}
|
||||
|
||||
input.testnetMode = testnetMode
|
||||
input.TestnetMode = testnetMode
|
||||
|
||||
// clear all processors
|
||||
for _, processor := range r.pathProcessors {
|
||||
|
@ -546,7 +197,7 @@ func (r *Router) SuggestedRoutes(ctx context.Context, input *RouteInputParams) (
|
|||
}
|
||||
}
|
||||
|
||||
err = validateInputData(input)
|
||||
err = input.Validate()
|
||||
if err != nil {
|
||||
return nil, errors.CreateErrorResponseFromError(err)
|
||||
}
|
||||
|
@ -608,7 +259,7 @@ func (r *Router) SuggestedRoutes(ctx context.Context, input *RouteInputParams) (
|
|||
pattern := "insufficient funds for gas * price + value: address "
|
||||
addressIndex := strings.Index(errors.DetailsFromError(err), pattern)
|
||||
if addressIndex != -1 {
|
||||
addressIndex += len(pattern) + hexAddressLength
|
||||
addressIndex += len(pattern) + walletCommon.HexAddressLength
|
||||
return errors.CreateErrorResponseFromError(&errors.ErrorResponse{
|
||||
Code: errors.ErrorCodeFromError(err),
|
||||
Details: errors.DetailsFromError(err)[:addressIndex],
|
||||
|
@ -622,9 +273,9 @@ func (r *Router) SuggestedRoutes(ctx context.Context, input *RouteInputParams) (
|
|||
|
||||
// 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
|
||||
func (r *Router) getBalanceMapForTokenOnChains(ctx context.Context, input *requests.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)
|
||||
|
@ -653,9 +304,9 @@ func (r *Router) getBalanceMapForTokenOnChains(ctx context.Context, input *Route
|
|||
|
||||
// add token balance for the chain
|
||||
var tokenBalance *big.Int
|
||||
if input.SendType == ERC721Transfer {
|
||||
if input.SendType == sendtype.ERC721Transfer {
|
||||
tokenBalance = big.NewInt(1)
|
||||
} else if input.SendType == ERC1155Transfer {
|
||||
} else if input.SendType == sendtype.ERC1155Transfer {
|
||||
tokenBalance, err = r.getERC1155Balance(ctx, chain, token, input.AddrFrom)
|
||||
if err != nil {
|
||||
chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err))
|
||||
|
@ -689,7 +340,7 @@ func (r *Router) getBalanceMapForTokenOnChains(ctx context.Context, input *Route
|
|||
return
|
||||
}
|
||||
|
||||
func (r *Router) getSelectedUnlockedChains(input *RouteInputParams, processingChain *params.Network, selectedFromChains []*params.Network) []*params.Network {
|
||||
func (r *Router) getSelectedUnlockedChains(input *requests.RouteInputParams, processingChain *params.Network, selectedFromChains []*params.Network) []*params.Network {
|
||||
selectedButNotLockedChains := []*params.Network{processingChain} // always add the processing chain at the beginning
|
||||
for _, net := range selectedFromChains {
|
||||
if net.ChainID == processingChain.ChainID {
|
||||
|
@ -702,7 +353,7 @@ func (r *Router) getSelectedUnlockedChains(input *RouteInputParams, processingCh
|
|||
return selectedButNotLockedChains
|
||||
}
|
||||
|
||||
func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input *RouteInputParams, amountToSplit *big.Int, processingChain *params.Network,
|
||||
func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input *requests.RouteInputParams, amountToSplit *big.Int, processingChain *params.Network,
|
||||
selectedFromChains []*params.Network, balanceMap map[string]*big.Int) map[uint64][]amountOption {
|
||||
selectedButNotLockedChains := r.getSelectedUnlockedChains(input, processingChain, selectedFromChains)
|
||||
|
||||
|
@ -739,7 +390,7 @@ func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input
|
|||
return crossChainAmountOptions
|
||||
}
|
||||
|
||||
func (r *Router) getCrossChainsOptionsForSendingAmount(input *RouteInputParams, selectedFromChains []*params.Network,
|
||||
func (r *Router) getCrossChainsOptionsForSendingAmount(input *requests.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
|
||||
|
@ -786,7 +437,7 @@ func (r *Router) getCrossChainsOptionsForSendingAmount(input *RouteInputParams,
|
|||
|
||||
// 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 {
|
||||
if input.SendType == sendtype.Transfer && len(selectedFromChains) > 1 {
|
||||
// All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount
|
||||
// was properly set and if there is something unexpected it will return an error and we will not reach this point
|
||||
amountToSplitAccrossChains := new(big.Int).Set(amountToSend)
|
||||
|
@ -814,7 +465,7 @@ func (r *Router) getCrossChainsOptionsForSendingAmount(input *RouteInputParams,
|
|||
return finalCrossChainAmountOptions
|
||||
}
|
||||
|
||||
func (r *Router) findOptionsForSendingAmount(input *RouteInputParams, selectedFromChains []*params.Network,
|
||||
func (r *Router) findOptionsForSendingAmount(input *requests.RouteInputParams, selectedFromChains []*params.Network,
|
||||
balanceMap map[string]*big.Int) (map[uint64][]amountOption, error) {
|
||||
|
||||
crossChainAmountOptions := r.getCrossChainsOptionsForSendingAmount(input, selectedFromChains, balanceMap)
|
||||
|
@ -835,7 +486,7 @@ func (r *Router) findOptionsForSendingAmount(input *RouteInputParams, selectedFr
|
|||
return crossChainAmountOptions, nil
|
||||
}
|
||||
|
||||
func (r *Router) getSelectedChains(input *RouteInputParams) (selectedFromChains []*params.Network, selectedToChains []*params.Network, err error) {
|
||||
func (r *Router) getSelectedChains(input *requests.RouteInputParams) (selectedFromChains []*params.Network, selectedToChains []*params.Network, err error) {
|
||||
var networks []*params.Network
|
||||
networks, err = r.rpcClient.NetworkManager.Get(false)
|
||||
if err != nil {
|
||||
|
@ -843,15 +494,15 @@ func (r *Router) getSelectedChains(input *RouteInputParams) (selectedFromChains
|
|||
}
|
||||
|
||||
for _, network := range networks {
|
||||
if network.IsTest != input.testnetMode {
|
||||
if network.IsTest != input.TestnetMode {
|
||||
continue
|
||||
}
|
||||
|
||||
if !arrayContainsElement(network.ChainID, input.DisabledFromChainIDs) {
|
||||
if !walletCommon.ArrayContainsElement(network.ChainID, input.DisabledFromChainIDs) {
|
||||
selectedFromChains = append(selectedFromChains, network)
|
||||
}
|
||||
|
||||
if !arrayContainsElement(network.ChainID, input.DisabledToChainIDs) {
|
||||
if !walletCommon.ArrayContainsElement(network.ChainID, input.DisabledToChainIDs) {
|
||||
selectedToChains = append(selectedToChains, network)
|
||||
}
|
||||
}
|
||||
|
@ -859,10 +510,10 @@ func (r *Router) getSelectedChains(input *RouteInputParams) (selectedFromChains
|
|||
return selectedFromChains, selectedToChains, nil
|
||||
}
|
||||
|
||||
func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams, selectedFromChains []*params.Network,
|
||||
func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInputParams, selectedFromChains []*params.Network,
|
||||
selectedToChains []*params.Network, balanceMap map[string]*big.Int) (candidates []*Path, processorErrors []*ProcessorError, err error) {
|
||||
var (
|
||||
testsMode = input.testsMode && input.testParams != nil
|
||||
testsMode = input.TestsMode && input.TestParams != nil
|
||||
group = async.NewAtomicGroup(ctx)
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
@ -872,7 +523,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
return nil, nil, errors.CreateErrorResponseFromError(err)
|
||||
}
|
||||
|
||||
appendProcessorErrorFn := func(processorName string, sendType SendType, fromChainID uint64, toChainID uint64, amount *big.Int, err error) {
|
||||
appendProcessorErrorFn := func(processorName string, sendType sendtype.SendType, fromChainID uint64, toChainID uint64, amount *big.Int, err error) {
|
||||
log.Error("router.resolveCandidates error", "processor", processorName, "sendType", sendType, "fromChainId: ", fromChainID, "toChainId", toChainID, "amount", amount, "err", err)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
@ -891,7 +542,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
for networkIdx := range selectedFromChains {
|
||||
network := selectedFromChains[networkIdx]
|
||||
|
||||
if !input.SendType.isAvailableFor(network) {
|
||||
if !input.SendType.IsAvailableFor(network) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -901,7 +552,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
)
|
||||
|
||||
if testsMode {
|
||||
token = input.testParams.tokenFrom
|
||||
token = input.TestParams.TokenFrom
|
||||
} else {
|
||||
token = input.SendType.FindToken(r.tokenManager, r.collectiblesService, input.AddrFrom, network, input.TokenID)
|
||||
}
|
||||
|
@ -909,15 +560,15 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
continue
|
||||
}
|
||||
|
||||
if input.SendType == Swap {
|
||||
if input.SendType == sendtype.Swap {
|
||||
toToken = input.SendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID)
|
||||
}
|
||||
|
||||
var fees *SuggestedFees
|
||||
var fetchedFees *fees.SuggestedFees
|
||||
if testsMode {
|
||||
fees = input.testParams.suggestedFees
|
||||
fetchedFees = input.TestParams.SuggestedFees
|
||||
} else {
|
||||
fees, err = r.feesManager.SuggestedFees(ctx, network.ChainID)
|
||||
fetchedFees, err = r.feesManager.SuggestedFees(ctx, network.ChainID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@ -942,26 +593,26 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
// 6. ...
|
||||
//
|
||||
// With the current routing algorithm atm we're not able to generate all possible routes.
|
||||
if !input.SendType.canUseProcessor(pProcessor) {
|
||||
if !input.SendType.CanUseProcessor(pProcessor) {
|
||||
continue
|
||||
}
|
||||
|
||||
// if we're doing a single chain operation, we can skip bridge processors
|
||||
if isSingleChainOperation(selectedFromChains, selectedToChains) && pathprocessor.IsProcessorBridge(pProcessor.Name()) {
|
||||
if walletCommon.IsSingleChainOperation(selectedFromChains, selectedToChains) && pathprocessor.IsProcessorBridge(pProcessor.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !input.SendType.processZeroAmountInProcessor(amountOption.amount, input.AmountOut.ToInt(), pProcessor.Name()) {
|
||||
if !input.SendType.ProcessZeroAmountInProcessor(amountOption.amount, input.AmountOut.ToInt(), pProcessor.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, dest := range selectedToChains {
|
||||
|
||||
if !input.SendType.isAvailableFor(network) {
|
||||
if !input.SendType.IsAvailableFor(network) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !input.SendType.isAvailableBetween(network, dest) {
|
||||
if !input.SendType.IsAvailableBetween(network, dest) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -979,12 +630,12 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
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
|
||||
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)
|
||||
|
@ -1036,10 +687,10 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
continue
|
||||
}
|
||||
|
||||
maxFeesPerGas := fees.feeFor(input.GasFeeMode)
|
||||
maxFeesPerGas := fetchedFees.FeeFor(input.GasFeeMode)
|
||||
|
||||
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas)
|
||||
if approvalRequired && estimatedTime < MoreThanFiveMinutes {
|
||||
if approvalRequired && estimatedTime < fees.MoreThanFiveMinutes {
|
||||
estimatedTime += 1
|
||||
}
|
||||
|
||||
|
@ -1090,11 +741,11 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
AmountInLocked: amountOption.locked,
|
||||
AmountOut: (*hexutil.Big)(amountOut),
|
||||
|
||||
SuggestedLevelsForMaxFeesPerGas: fees.MaxFeesLevels,
|
||||
SuggestedLevelsForMaxFeesPerGas: fetchedFees.MaxFeesLevels,
|
||||
MaxFeesPerGas: (*hexutil.Big)(maxFeesPerGas),
|
||||
|
||||
TxBaseFee: (*hexutil.Big)(fees.BaseFee),
|
||||
TxPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
||||
TxBaseFee: (*hexutil.Big)(fetchedFees.BaseFee),
|
||||
TxPriorityFee: (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas),
|
||||
TxGasAmount: gasLimit,
|
||||
TxBonderFees: (*hexutil.Big)(bonderFees),
|
||||
TxTokenFees: (*hexutil.Big)(tokenFees),
|
||||
|
@ -1105,8 +756,8 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
ApprovalRequired: approvalRequired,
|
||||
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
|
||||
ApprovalContractAddress: &approvalContractAddress,
|
||||
ApprovalBaseFee: (*hexutil.Big)(fees.BaseFee),
|
||||
ApprovalPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
||||
ApprovalBaseFee: (*hexutil.Big)(fetchedFees.BaseFee),
|
||||
ApprovalPriorityFee: (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas),
|
||||
ApprovalGasAmount: approvalGasLimit,
|
||||
|
||||
ApprovalFee: (*hexutil.Big)(approvalFeeInWei),
|
||||
|
@ -1138,7 +789,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
}
|
||||
|
||||
func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*Path, balanceMap map[string]*big.Int) (hasPositiveBalance bool, err error) {
|
||||
balanceMapCopy := copyMapGeneric(balanceMap, func(v interface{}) interface{} {
|
||||
balanceMapCopy := walletCommon.CopyMapGeneric(balanceMap, func(v interface{}) interface{} {
|
||||
return new(big.Int).Set(v.(*big.Int))
|
||||
}).(map[string]*big.Int)
|
||||
if balanceMapCopy == nil {
|
||||
|
@ -1193,59 +844,10 @@ func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*
|
|||
return hasPositiveBalance, nil
|
||||
}
|
||||
|
||||
func removeBestRouteFromAllRouters(allRoutes [][]*Path, best []*Path) [][]*Path {
|
||||
for i := len(allRoutes) - 1; i >= 0; i-- {
|
||||
route := allRoutes[i]
|
||||
routeFound := true
|
||||
for _, p := range route {
|
||||
found := false
|
||||
for _, b := range best {
|
||||
if p.ProcessorName == b.ProcessorName &&
|
||||
(p.FromChain == nil && b.FromChain == nil || p.FromChain.ChainID == b.FromChain.ChainID) &&
|
||||
(p.ToChain == nil && b.ToChain == nil || p.ToChain.ChainID == b.ToChain.ChainID) &&
|
||||
(p.FromToken == nil && b.FromToken == nil || p.FromToken.Symbol == b.FromToken.Symbol) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
routeFound = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if routeFound {
|
||||
return append(allRoutes[:i], allRoutes[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getChainPriority(chainID uint64) int {
|
||||
switch chainID {
|
||||
case walletCommon.EthereumMainnet, walletCommon.EthereumSepolia:
|
||||
return 1
|
||||
case walletCommon.OptimismMainnet, walletCommon.OptimismSepolia:
|
||||
return 2
|
||||
case walletCommon.ArbitrumMainnet, walletCommon.ArbitrumSepolia:
|
||||
return 3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func getRoutePriority(route []*Path) int {
|
||||
priority := 0
|
||||
for _, path := range route {
|
||||
priority += getChainPriority(path.FromChain.ChainID)
|
||||
}
|
||||
return priority
|
||||
}
|
||||
|
||||
func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, candidates []*Path, balanceMap map[string]*big.Int) (suggestedRoutes *SuggestedRoutes, err error) {
|
||||
func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputParams, candidates []*Path, balanceMap map[string]*big.Int) (suggestedRoutes *SuggestedRoutes, err error) {
|
||||
var prices map[string]float64
|
||||
if input.testsMode {
|
||||
prices = input.testParams.tokenPrices
|
||||
if input.TestsMode {
|
||||
prices = input.TestParams.TokenPrices
|
||||
} else {
|
||||
prices, err = input.SendType.FetchPrices(r.marketManager, input.TokenID)
|
||||
if err != nil {
|
||||
|
@ -1283,8 +885,8 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
|
|||
if err != nil {
|
||||
// If it's about transfer or bridge and there is more routes, but on the best (cheapest) one there is not enugh balance
|
||||
// we shold check other routes even though there are not the cheapest ones
|
||||
if input.SendType == Transfer ||
|
||||
input.SendType == Bridge {
|
||||
if input.SendType == sendtype.Transfer ||
|
||||
input.SendType == sendtype.Bridge {
|
||||
if hasPositiveBalance {
|
||||
lastBestRouteWithPositiveBalance = bestRoute
|
||||
lastBestRouteErr = err
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
||||
)
|
||||
|
||||
type Graph []*Node
|
||||
|
||||
type Node struct {
|
||||
Path *Path
|
||||
Children Graph
|
||||
}
|
||||
|
||||
func newNode(path *Path) *Node {
|
||||
return &Node{Path: path, Children: make(Graph, 0)}
|
||||
}
|
||||
|
||||
func buildGraph(AmountIn *big.Int, routes []*Path, level int, sourceChainIDs []uint64) Graph {
|
||||
graph := make(Graph, 0)
|
||||
for _, route := range routes {
|
||||
found := false
|
||||
for _, chainID := range sourceChainIDs {
|
||||
if chainID == route.FromChain.ChainID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
node := newNode(route)
|
||||
|
||||
newRoutes := make([]*Path, 0)
|
||||
for _, r := range routes {
|
||||
if route.Equal(r) {
|
||||
continue
|
||||
}
|
||||
newRoutes = append(newRoutes, r)
|
||||
}
|
||||
|
||||
newAmountIn := new(big.Int).Sub(AmountIn, route.AmountIn.ToInt())
|
||||
if newAmountIn.Sign() > 0 {
|
||||
newSourceChainIDs := make([]uint64, len(sourceChainIDs))
|
||||
copy(newSourceChainIDs, sourceChainIDs)
|
||||
newSourceChainIDs = append(newSourceChainIDs, route.FromChain.ChainID)
|
||||
node.Children = buildGraph(newAmountIn, newRoutes, level+1, newSourceChainIDs)
|
||||
|
||||
if len(node.Children) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
graph = append(graph, node)
|
||||
}
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
func (n Node) buildAllRoutes() [][]*Path {
|
||||
res := make([][]*Path, 0)
|
||||
|
||||
if len(n.Children) == 0 && n.Path != nil {
|
||||
res = append(res, []*Path{n.Path})
|
||||
}
|
||||
|
||||
for _, node := range n.Children {
|
||||
for _, route := range node.buildAllRoutes() {
|
||||
extendedRoute := route
|
||||
if n.Path != nil {
|
||||
extendedRoute = append([]*Path{n.Path}, route...)
|
||||
}
|
||||
res = append(res, extendedRoute)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func findBest(routes [][]*Path, tokenPrice float64, nativeTokenPrice float64) []*Path {
|
||||
var best []*Path
|
||||
bestCost := big.NewFloat(math.Inf(1))
|
||||
for _, route := range routes {
|
||||
currentCost := big.NewFloat(0)
|
||||
for _, path := range route {
|
||||
tokenDenominator := big.NewFloat(math.Pow(10, float64(path.FromToken.Decimals)))
|
||||
|
||||
// calculate the cost of the path
|
||||
nativeTokenPrice := new(big.Float).SetFloat64(nativeTokenPrice)
|
||||
|
||||
// tx fee
|
||||
txFeeInEth := common.GweiToEth(common.WeiToGwei(path.TxFee.ToInt()))
|
||||
pathCost := new(big.Float).Mul(txFeeInEth, nativeTokenPrice)
|
||||
|
||||
if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
txL1FeeInEth := common.GweiToEth(common.WeiToGwei(path.TxL1Fee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(txL1FeeInEth, nativeTokenPrice))
|
||||
}
|
||||
|
||||
if path.TxBonderFees != nil && path.TxBonderFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(
|
||||
new(big.Float).Quo(new(big.Float).SetInt(path.TxBonderFees.ToInt()), tokenDenominator),
|
||||
new(big.Float).SetFloat64(tokenPrice)))
|
||||
|
||||
}
|
||||
|
||||
if path.TxTokenFees != nil && path.TxTokenFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && path.FromToken != nil {
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(
|
||||
new(big.Float).Quo(new(big.Float).SetInt(path.TxTokenFees.ToInt()), tokenDenominator),
|
||||
new(big.Float).SetFloat64(tokenPrice)))
|
||||
}
|
||||
|
||||
if path.ApprovalRequired {
|
||||
// tx approval fee
|
||||
approvalFeeInEth := common.GweiToEth(common.WeiToGwei(path.ApprovalFee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(approvalFeeInEth, nativeTokenPrice))
|
||||
|
||||
if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
approvalL1FeeInEth := common.GweiToEth(common.WeiToGwei(path.ApprovalL1Fee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(approvalL1FeeInEth, nativeTokenPrice))
|
||||
}
|
||||
}
|
||||
|
||||
currentCost = new(big.Float).Add(currentCost, pathCost)
|
||||
}
|
||||
|
||||
if currentCost.Cmp(bestCost) == -1 {
|
||||
best = route
|
||||
bestCost = currentCost
|
||||
}
|
||||
}
|
||||
|
||||
return best
|
||||
}
|
|
@ -17,10 +17,11 @@ import (
|
|||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
||||
"github.com/status-im/status-go/services/wallet/router/sendtype"
|
||||
"github.com/status-im/status-go/services/wallet/token"
|
||||
)
|
||||
|
||||
func (r *Router) requireApproval(ctx context.Context, sendType SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) (
|
||||
func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) (
|
||||
bool, *big.Int, uint64, uint64, error) {
|
||||
if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() {
|
||||
return false, nil, 0, 0, nil
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/services/wallet/router/fees"
|
||||
walletToken "github.com/status-im/status-go/services/wallet/token"
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
ProcessorName string
|
||||
FromChain *params.Network // Source chain
|
||||
ToChain *params.Network // Destination chain
|
||||
FromToken *walletToken.Token // Source token
|
||||
ToToken *walletToken.Token // Destination token, set if applicable
|
||||
AmountIn *hexutil.Big // Amount that will be sent from the source chain
|
||||
AmountInLocked bool // Is the amount locked
|
||||
AmountOut *hexutil.Big // Amount that will be received on the destination chain
|
||||
|
||||
SuggestedLevelsForMaxFeesPerGas *fees.MaxFeesLevels // Suggested max fees for the transaction (in ETH WEI)
|
||||
MaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI)
|
||||
|
||||
TxBaseFee *hexutil.Big // Base fee for the transaction (in ETH WEI)
|
||||
TxPriorityFee *hexutil.Big // Priority fee for the transaction (in ETH WEI)
|
||||
TxGasAmount uint64 // Gas used for the transaction
|
||||
TxBonderFees *hexutil.Big // Bonder fees for the transaction - used for Hop bridge (in selected token)
|
||||
TxTokenFees *hexutil.Big // Token fees for the transaction - used for bridges (represent the difference between the amount in and the amount out, in selected token)
|
||||
|
||||
TxFee *hexutil.Big // fee for the transaction (includes tx fee only, doesn't include approval fees, l1 fees, l1 approval fees, token fees or bonders fees, in ETH WEI)
|
||||
TxL1Fee *hexutil.Big // L1 fee for the transaction - used for for transactions placed on L2 chains (in ETH WEI)
|
||||
|
||||
ApprovalRequired bool // Is approval required for the transaction
|
||||
ApprovalAmountRequired *hexutil.Big // Amount required for the approval transaction
|
||||
ApprovalContractAddress *common.Address // Address of the contract that needs to be approved
|
||||
ApprovalBaseFee *hexutil.Big // Base fee for the approval transaction (in ETH WEI)
|
||||
ApprovalPriorityFee *hexutil.Big // Priority fee for the approval transaction (in ETH WEI)
|
||||
ApprovalGasAmount uint64 // Gas used for the approval transaction
|
||||
|
||||
ApprovalFee *hexutil.Big // Total fee for the approval transaction (includes approval tx fees only, doesn't include approval l1 fees, in ETH WEI)
|
||||
ApprovalL1Fee *hexutil.Big // L1 fee for the approval transaction - used for for transactions placed on L2 chains (in ETH WEI)
|
||||
|
||||
TxTotalFee *hexutil.Big // Total fee for the transaction (includes tx fees, approval fees, l1 fees, l1 approval fees, in ETH WEI)
|
||||
|
||||
EstimatedTime fees.TransactionEstimation
|
||||
|
||||
requiredTokenBalance *big.Int // (in selected token)
|
||||
requiredNativeBalance *big.Int // (in ETH WEI)
|
||||
subtractFees bool
|
||||
}
|
||||
|
||||
func (p *Path) Equal(o *Path) bool {
|
||||
return p.FromChain.ChainID == o.FromChain.ChainID && p.ToChain.ChainID == o.ToChain.ChainID
|
||||
}
|
|
@ -266,7 +266,7 @@ func TestAmountOptions(t *testing.T) {
|
|||
selectedFromChains, _, err := router.getSelectedChains(tt.input)
|
||||
assert.NoError(t, err)
|
||||
|
||||
amountOptions, err := router.findOptionsForSendingAmount(tt.input, selectedFromChains, tt.input.testParams.balanceMap)
|
||||
amountOptions, err := router.findOptionsForSendingAmount(tt.input, selectedFromChains, tt.input.TestParams.BalanceMap)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(tt.expectedAmountOptions), len(amountOptions))
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
package router
|
||||
package sendtype
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
@ -84,7 +84,7 @@ func (s SendType) FindToken(tokenManager *token.Manager, collectibles *collectib
|
|||
}
|
||||
|
||||
// canUseProcessor is used to check if certain SendType can be used with a given path processor
|
||||
func (s SendType) canUseProcessor(p pathprocessor.PathProcessor) bool {
|
||||
func (s SendType) CanUseProcessor(p pathprocessor.PathProcessor) bool {
|
||||
pathProcessorName := p.Name()
|
||||
switch s {
|
||||
case Transfer:
|
||||
|
@ -111,7 +111,7 @@ func (s SendType) canUseProcessor(p pathprocessor.PathProcessor) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (s SendType) processZeroAmountInProcessor(amountIn *big.Int, amountOut *big.Int, processorName string) bool {
|
||||
func (s SendType) ProcessZeroAmountInProcessor(amountIn *big.Int, amountOut *big.Int, processorName string) bool {
|
||||
if amountIn.Cmp(pathprocessor.ZeroBigIntValue) == 0 {
|
||||
if s == Transfer {
|
||||
if processorName != pathprocessor.ProcessorTransferName {
|
||||
|
@ -129,7 +129,7 @@ func (s SendType) processZeroAmountInProcessor(amountIn *big.Int, amountOut *big
|
|||
return true
|
||||
}
|
||||
|
||||
func (s SendType) isAvailableBetween(from, to *params.Network) bool {
|
||||
func (s SendType) IsAvailableBetween(from, to *params.Network) bool {
|
||||
if s.IsCollectiblesTransfer() ||
|
||||
s.IsEnsTransfer() ||
|
||||
s.IsStickersTransfer() ||
|
||||
|
@ -144,7 +144,7 @@ func (s SendType) isAvailableBetween(from, to *params.Network) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (s SendType) isAvailableFor(network *params.Network) bool {
|
||||
func (s SendType) IsAvailableFor(network *params.Network) bool {
|
||||
// Set of network ChainIDs allowed for any type of transaction
|
||||
allAllowedNetworks := map[uint64]bool{
|
||||
walletCommon.EthereumMainnet: true,
|
Loading…
Reference in New Issue