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/eth-node/types"
|
||||||
"github.com/status-im/status-go/protocol/protobuf"
|
"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/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"
|
"github.com/status-im/status-go/transactions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommunityTokenFees struct {
|
type CommunityTokenFees struct {
|
||||||
GasUnits uint64 `json:"gasUnits"`
|
GasUnits uint64 `json:"gasUnits"`
|
||||||
SuggestedFees *router.SuggestedFeesGwei `json:"suggestedFees"`
|
SuggestedFees *fees.SuggestedFeesGwei `json:"suggestedFees"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func weiToGwei(val *big.Int) *big.Float {
|
func weiToGwei(val *big.Int) *big.Float {
|
||||||
|
@ -373,7 +373,7 @@ func (s *Service) prepareCommunityTokenFees(ctx context.Context, from common.Add
|
||||||
}, nil
|
}, 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 := transactions.SendTxArgs{}
|
||||||
sendArgs.From = types.Address(from)
|
sendArgs.From = types.Address(from)
|
||||||
sendArgs.To = (*types.Address)(to)
|
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/utils"
|
||||||
"github.com/status-im/status-go/services/wallet/bigint"
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
wcommon "github.com/status-im/status-go/services/wallet/common"
|
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/services/wallet/walletevent"
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
"github.com/status-im/status-go/transactions"
|
"github.com/status-im/status-go/transactions"
|
||||||
|
@ -49,7 +49,7 @@ type Service struct {
|
||||||
walletFeed *event.Feed
|
walletFeed *event.Feed
|
||||||
walletWatcher *walletevent.Watcher
|
walletWatcher *walletevent.Watcher
|
||||||
transactor *transactions.Transactor
|
transactor *transactions.Transactor
|
||||||
feeManager *router.FeeManager
|
feeManager *fees.FeeManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a new Collectibles Service.
|
// Returns a new Collectibles Service.
|
||||||
|
@ -63,7 +63,7 @@ func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pen
|
||||||
db: communitytokensdatabase.NewCommunityTokensDatabase(appDb),
|
db: communitytokensdatabase.NewCommunityTokensDatabase(appDb),
|
||||||
walletFeed: walletFeed,
|
walletFeed: walletFeed,
|
||||||
transactor: transactor,
|
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/onramp"
|
||||||
"github.com/status-im/status-go/services/wallet/requests"
|
"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"
|
||||||
|
"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/pathprocessor"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty"
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
"github.com/status-im/status-go/services/wallet/token"
|
"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)
|
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")
|
log.Debug("call to GetSuggestedFees")
|
||||||
return api.router.GetFeesManager().SuggestedFeesGwei(ctx, chainID)
|
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)
|
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")
|
log.Debug("call to getTransactionEstimatedTime")
|
||||||
return api.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil
|
return api.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil
|
||||||
}
|
}
|
||||||
|
@ -488,13 +489,13 @@ func gweiToWei(val *big.Float) *big.Int {
|
||||||
return res
|
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")
|
log.Debug("call to GetSuggestedRoutes")
|
||||||
|
|
||||||
return api.router.SuggestedRoutes(ctx, input)
|
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")
|
log.Debug("call to GetSuggestedRoutesAsync")
|
||||||
|
|
||||||
api.router.SuggestedRoutesAsync(input)
|
api.router.SuggestedRoutesAsync(input)
|
||||||
|
|
|
@ -11,6 +11,7 @@ type MultiTransactionIDType int64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NoMultiTransactionID = MultiTransactionIDType(0)
|
NoMultiTransactionID = MultiTransactionIDType(0)
|
||||||
|
HexAddressLength = 42
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChainID uint64
|
type ChainID uint64
|
||||||
|
@ -32,6 +33,18 @@ const (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ZeroAddress = ethCommon.HexToAddress("0x0000000000000000000000000000000000000000")
|
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
|
type ContractType byte
|
||||||
|
|
|
@ -2,7 +2,10 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
gethParams "github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,3 +27,51 @@ func NetworksToChainIDs(networks []*params.Network) []uint64 {
|
||||||
|
|
||||||
return chainIDs
|
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
|
package router
|
||||||
|
|
||||||
import (
|
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 {
|
func removeBestRouteFromAllRouters(allRoutes [][]*Path, best []*Path) [][]*Path {
|
||||||
for _, e := range arr {
|
for i := len(allRoutes) - 1; i >= 0; i-- {
|
||||||
if e == el {
|
route := allRoutes[i]
|
||||||
return true
|
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 false
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func arraysWithSameElements[T comparable](ar1 []T, ar2 []T, isEqual func(T, T) bool) bool {
|
func getChainPriority(chainID uint64) int {
|
||||||
if len(ar1) != len(ar2) {
|
switch chainID {
|
||||||
return false
|
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 {
|
func getRoutePriority(route []*Path) int {
|
||||||
return len(fromChains) == 1 &&
|
priority := 0
|
||||||
len(toChains) == 1 &&
|
for _, path := range route {
|
||||||
fromChains[0].ChainID == toChains[0].ChainID
|
priority += getChainPriority(path.FromChain.ChainID)
|
||||||
|
}
|
||||||
|
return priority
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,31 +6,12 @@ import (
|
||||||
|
|
||||||
// Abbreviation `WR` for the error code stands for Wallet Router
|
// Abbreviation `WR` for the error code stands for Wallet Router
|
||||||
var (
|
var (
|
||||||
ErrENSRegisterRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WR-001"), Details: "username and public key are required for ENSRegister"}
|
ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-001"), Details: "not enough token balance, token: %s, chainId: %d"}
|
||||||
ErrENSRegisterTestnetSTTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WR-002"), Details: "only STT is supported for ENSRegister on testnet"}
|
ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-002"), Details: "not enough native balance, token: %s, chainId: %d"}
|
||||||
ErrENSRegisterMainnetSNTOnly = &errors.ErrorResponse{Code: errors.ErrorCode("WR-003"), Details: "only SNT is supported for ENSRegister on mainnet"}
|
ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-003"), Details: "native token not found"}
|
||||||
ErrENSReleaseRequiresUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WR-004"), Details: "username is required for ENSRelease"}
|
ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-004"), Details: "token not found"}
|
||||||
ErrENSSetPubKeyRequiresUsernameAndPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WR-005"), Details: "username and public key are required for ENSSetPubKey"}
|
ErrNoBestRouteFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-005"), Details: "no best route found"}
|
||||||
ErrStickersBuyRequiresPackID = &errors.ErrorResponse{Code: errors.ErrorCode("WR-006"), Details: "packID is required for StickersBuy"}
|
ErrCannotCheckBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-006"), Details: "cannot check balance"}
|
||||||
ErrSwapRequiresToTokenID = &errors.ErrorResponse{Code: errors.ErrorCode("WR-007"), Details: "toTokenID is required for Swap"}
|
ErrLowAmountInForHopBridge = &errors.ErrorResponse{Code: errors.ErrorCode("WR-007"), Details: "bonder fee greater than estimated received, a higher amount is needed to cover fees"}
|
||||||
ErrSwapTokenIDMustBeDifferent = &errors.ErrorResponse{Code: errors.ErrorCode("WR-008"), Details: "tokenID and toTokenID must be different"}
|
ErrNoPositiveBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-008"), Details: "no positive balance"}
|
||||||
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"}
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package router
|
package fees
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -54,7 +54,7 @@ type SuggestedFeesGwei struct {
|
||||||
EIP1559Enabled bool `json:"eip1559Enabled"`
|
EIP1559Enabled bool `json:"eip1559Enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MaxFeesLevels) feeFor(mode GasFeeMode) *big.Int {
|
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) *big.Int {
|
||||||
if mode == GasFeeLow {
|
if mode == GasFeeLow {
|
||||||
return m.Low.ToInt()
|
return m.Low.ToInt()
|
||||||
}
|
}
|
||||||
|
@ -66,8 +66,8 @@ func (m *MaxFeesLevels) feeFor(mode GasFeeMode) *big.Int {
|
||||||
return m.Medium.ToInt()
|
return m.Medium.ToInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SuggestedFees) feeFor(mode GasFeeMode) *big.Int {
|
func (s *SuggestedFees) FeeFor(mode GasFeeMode) *big.Int {
|
||||||
return s.MaxFeesLevels.feeFor(mode)
|
return s.MaxFeesLevels.FeeFor(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
const inclusionThreshold = 0.95
|
const inclusionThreshold = 0.95
|
||||||
|
@ -90,20 +90,6 @@ type FeeManager struct {
|
||||||
RPCClient *rpc.Client
|
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) {
|
func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {
|
||||||
backend, err := f.RPCClient.EthClient(chainID)
|
backend, err := f.RPCClient.EthClient(chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -151,12 +137,12 @@ func (f *FeeManager) SuggestedFeesGwei(ctx context.Context, chainID uint64) (*Su
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &SuggestedFeesGwei{
|
return &SuggestedFeesGwei{
|
||||||
GasPrice: weiToGwei(fees.GasPrice),
|
GasPrice: common.WeiToGwei(fees.GasPrice),
|
||||||
BaseFee: weiToGwei(fees.BaseFee),
|
BaseFee: common.WeiToGwei(fees.BaseFee),
|
||||||
MaxPriorityFeePerGas: weiToGwei(fees.MaxPriorityFeePerGas),
|
MaxPriorityFeePerGas: common.WeiToGwei(fees.MaxPriorityFeePerGas),
|
||||||
MaxFeePerGasLow: weiToGwei(fees.MaxFeesLevels.Low.ToInt()),
|
MaxFeePerGasLow: common.WeiToGwei(fees.MaxFeesLevels.Low.ToInt()),
|
||||||
MaxFeePerGasMedium: weiToGwei(fees.MaxFeesLevels.Medium.ToInt()),
|
MaxFeePerGasMedium: common.WeiToGwei(fees.MaxFeesLevels.Medium.ToInt()),
|
||||||
MaxFeePerGasHigh: weiToGwei(fees.MaxFeesLevels.High.ToInt()),
|
MaxFeePerGasHigh: common.WeiToGwei(fees.MaxFeesLevels.High.ToInt()),
|
||||||
EIP1559Enabled: fees.EIP1559Enabled,
|
EIP1559Enabled: fees.EIP1559Enabled,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
|
@ -2,9 +2,9 @@ package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/status-im/status-go/services/wallet/common"
|
||||||
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"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
|
// 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)
|
filteredRoutes = append(filteredRoutes, route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,22 +162,3 @@ func calculateRestAmountIn(route []*Path, excludePath *Path) *big.Int {
|
||||||
}
|
}
|
||||||
return restAmountIn
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -21,17 +20,16 @@ import (
|
||||||
"github.com/status-im/status-go/services/wallet/collectibles"
|
"github.com/status-im/status-go/services/wallet/collectibles"
|
||||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
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/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/pathprocessor"
|
||||||
|
"github.com/status-im/status-go/services/wallet/router/sendtype"
|
||||||
"github.com/status-im/status-go/services/wallet/token"
|
"github.com/status-im/status-go/services/wallet/token"
|
||||||
walletToken "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/signal"
|
||||||
"github.com/status-im/status-go/transactions"
|
"github.com/status-im/status-go/transactions"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
hexAddressLength = 42
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
routerTask = async.TaskType{
|
routerTask = async.TaskType{
|
||||||
ID: 1,
|
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 {
|
type amountOption struct {
|
||||||
amount *big.Int
|
amount *big.Int
|
||||||
locked bool
|
locked bool
|
||||||
|
@ -101,56 +47,11 @@ func makeBalanceKey(chainID uint64, symbol string) string {
|
||||||
return fmt.Sprintf("%d-%s", chainID, symbol)
|
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 {
|
type ProcessorError struct {
|
||||||
ProcessorName string
|
ProcessorName string
|
||||||
Error error
|
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 {
|
type SuggestedRoutes struct {
|
||||||
Uuid string
|
Uuid string
|
||||||
Best []*Path
|
Best []*Path
|
||||||
|
@ -168,13 +69,6 @@ type SuggestedRoutesResponse struct {
|
||||||
ErrorResponse *errors.ErrorResponse `json:"ErrorResponse,omitempty"`
|
ErrorResponse *errors.ErrorResponse `json:"ErrorResponse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Graph []*Node
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
Path *Path
|
|
||||||
Children Graph
|
|
||||||
}
|
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
rpcClient *rpc.Client
|
rpcClient *rpc.Client
|
||||||
tokenManager *token.Manager
|
tokenManager *token.Manager
|
||||||
|
@ -183,7 +77,7 @@ type Router struct {
|
||||||
collectiblesManager *collectibles.Manager
|
collectiblesManager *collectibles.Manager
|
||||||
ensService *ens.Service
|
ensService *ens.Service
|
||||||
stickersService *stickers.Service
|
stickersService *stickers.Service
|
||||||
feesManager *FeeManager
|
feesManager *fees.FeeManager
|
||||||
pathProcessors map[string]pathprocessor.PathProcessor
|
pathProcessors map[string]pathprocessor.PathProcessor
|
||||||
scheduler *async.Scheduler
|
scheduler *async.Scheduler
|
||||||
}
|
}
|
||||||
|
@ -200,9 +94,11 @@ func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, token
|
||||||
collectiblesManager: collectiblesManager,
|
collectiblesManager: collectiblesManager,
|
||||||
ensService: ensService,
|
ensService: ensService,
|
||||||
stickersService: stickersService,
|
stickersService: stickersService,
|
||||||
feesManager: &FeeManager{rpcClient},
|
feesManager: &fees.FeeManager{
|
||||||
pathProcessors: processors,
|
RPCClient: rpcClient,
|
||||||
scheduler: async.NewScheduler(),
|
},
|
||||||
|
pathProcessors: processors,
|
||||||
|
scheduler: async.NewScheduler(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +110,7 @@ func (r *Router) Stop() {
|
||||||
r.scheduler.Stop()
|
r.scheduler.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) GetFeesManager() *FeeManager {
|
func (r *Router) GetFeesManager() *fees.FeeManager {
|
||||||
return r.feesManager
|
return r.feesManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,252 +154,7 @@ func newSuggestedRoutes(
|
||||||
return suggestedRoutes, allRoutes
|
return suggestedRoutes, allRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNode(path *Path) *Node {
|
func (r *Router) SuggestedRoutesAsync(input *requests.RouteInputParams) {
|
||||||
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) {
|
|
||||||
r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) {
|
r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) {
|
||||||
return r.SuggestedRoutes(ctx, input)
|
return r.SuggestedRoutes(ctx, input)
|
||||||
}, func(result interface{}, taskType async.TaskType, err error) {
|
}, func(result interface{}, taskType async.TaskType, err error) {
|
||||||
|
@ -531,13 +182,13 @@ func (r *Router) StopSuggestedRoutesAsyncCalculation() {
|
||||||
r.scheduler.Stop()
|
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()
|
testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.CreateErrorResponseFromError(err)
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
input.testnetMode = testnetMode
|
input.TestnetMode = testnetMode
|
||||||
|
|
||||||
// clear all processors
|
// clear all processors
|
||||||
for _, processor := range r.pathProcessors {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.CreateErrorResponseFromError(err)
|
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 "
|
pattern := "insufficient funds for gas * price + value: address "
|
||||||
addressIndex := strings.Index(errors.DetailsFromError(err), pattern)
|
addressIndex := strings.Index(errors.DetailsFromError(err), pattern)
|
||||||
if addressIndex != -1 {
|
if addressIndex != -1 {
|
||||||
addressIndex += len(pattern) + hexAddressLength
|
addressIndex += len(pattern) + walletCommon.HexAddressLength
|
||||||
return errors.CreateErrorResponseFromError(&errors.ErrorResponse{
|
return errors.CreateErrorResponseFromError(&errors.ErrorResponse{
|
||||||
Code: errors.ErrorCodeFromError(err),
|
Code: errors.ErrorCodeFromError(err),
|
||||||
Details: errors.DetailsFromError(err)[:addressIndex],
|
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
|
// 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.
|
// 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) {
|
func (r *Router) getBalanceMapForTokenOnChains(ctx context.Context, input *requests.RouteInputParams, selectedFromChains []*params.Network) (balanceMap map[string]*big.Int, err error) {
|
||||||
if input.testsMode {
|
if input.TestsMode {
|
||||||
return input.testParams.balanceMap, nil
|
return input.TestParams.BalanceMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
balanceMap = make(map[string]*big.Int)
|
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
|
// add token balance for the chain
|
||||||
var tokenBalance *big.Int
|
var tokenBalance *big.Int
|
||||||
if input.SendType == ERC721Transfer {
|
if input.SendType == sendtype.ERC721Transfer {
|
||||||
tokenBalance = big.NewInt(1)
|
tokenBalance = big.NewInt(1)
|
||||||
} else if input.SendType == ERC1155Transfer {
|
} else if input.SendType == sendtype.ERC1155Transfer {
|
||||||
tokenBalance, err = r.getERC1155Balance(ctx, chain, token, input.AddrFrom)
|
tokenBalance, err = r.getERC1155Balance(ctx, chain, token, input.AddrFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err))
|
chainError(chain.ChainID, token.Symbol, errors.CreateErrorResponseFromError(err))
|
||||||
|
@ -689,7 +340,7 @@ func (r *Router) getBalanceMapForTokenOnChains(ctx context.Context, input *Route
|
||||||
return
|
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
|
selectedButNotLockedChains := []*params.Network{processingChain} // always add the processing chain at the beginning
|
||||||
for _, net := range selectedFromChains {
|
for _, net := range selectedFromChains {
|
||||||
if net.ChainID == processingChain.ChainID {
|
if net.ChainID == processingChain.ChainID {
|
||||||
|
@ -702,7 +353,7 @@ func (r *Router) getSelectedUnlockedChains(input *RouteInputParams, processingCh
|
||||||
return selectedButNotLockedChains
|
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 {
|
selectedFromChains []*params.Network, balanceMap map[string]*big.Int) map[uint64][]amountOption {
|
||||||
selectedButNotLockedChains := r.getSelectedUnlockedChains(input, processingChain, selectedFromChains)
|
selectedButNotLockedChains := r.getSelectedUnlockedChains(input, processingChain, selectedFromChains)
|
||||||
|
|
||||||
|
@ -739,7 +390,7 @@ func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input
|
||||||
return crossChainAmountOptions
|
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 {
|
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
|
// 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
|
// 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
|
// 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.
|
// 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
|
// 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
|
// 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)
|
amountToSplitAccrossChains := new(big.Int).Set(amountToSend)
|
||||||
|
@ -814,7 +465,7 @@ func (r *Router) getCrossChainsOptionsForSendingAmount(input *RouteInputParams,
|
||||||
return finalCrossChainAmountOptions
|
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) {
|
balanceMap map[string]*big.Int) (map[uint64][]amountOption, error) {
|
||||||
|
|
||||||
crossChainAmountOptions := r.getCrossChainsOptionsForSendingAmount(input, selectedFromChains, balanceMap)
|
crossChainAmountOptions := r.getCrossChainsOptionsForSendingAmount(input, selectedFromChains, balanceMap)
|
||||||
|
@ -835,7 +486,7 @@ func (r *Router) findOptionsForSendingAmount(input *RouteInputParams, selectedFr
|
||||||
return crossChainAmountOptions, nil
|
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
|
var networks []*params.Network
|
||||||
networks, err = r.rpcClient.NetworkManager.Get(false)
|
networks, err = r.rpcClient.NetworkManager.Get(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -843,15 +494,15 @@ func (r *Router) getSelectedChains(input *RouteInputParams) (selectedFromChains
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, network := range networks {
|
for _, network := range networks {
|
||||||
if network.IsTest != input.testnetMode {
|
if network.IsTest != input.TestnetMode {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !arrayContainsElement(network.ChainID, input.DisabledFromChainIDs) {
|
if !walletCommon.ArrayContainsElement(network.ChainID, input.DisabledFromChainIDs) {
|
||||||
selectedFromChains = append(selectedFromChains, network)
|
selectedFromChains = append(selectedFromChains, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !arrayContainsElement(network.ChainID, input.DisabledToChainIDs) {
|
if !walletCommon.ArrayContainsElement(network.ChainID, input.DisabledToChainIDs) {
|
||||||
selectedToChains = append(selectedToChains, network)
|
selectedToChains = append(selectedToChains, network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -859,10 +510,10 @@ func (r *Router) getSelectedChains(input *RouteInputParams) (selectedFromChains
|
||||||
return selectedFromChains, selectedToChains, nil
|
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) {
|
selectedToChains []*params.Network, balanceMap map[string]*big.Int) (candidates []*Path, processorErrors []*ProcessorError, err error) {
|
||||||
var (
|
var (
|
||||||
testsMode = input.testsMode && input.testParams != nil
|
testsMode = input.TestsMode && input.TestParams != nil
|
||||||
group = async.NewAtomicGroup(ctx)
|
group = async.NewAtomicGroup(ctx)
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
)
|
)
|
||||||
|
@ -872,7 +523,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
||||||
return nil, nil, errors.CreateErrorResponseFromError(err)
|
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)
|
log.Error("router.resolveCandidates error", "processor", processorName, "sendType", sendType, "fromChainId: ", fromChainID, "toChainId", toChainID, "amount", amount, "err", err)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
@ -891,7 +542,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
||||||
for networkIdx := range selectedFromChains {
|
for networkIdx := range selectedFromChains {
|
||||||
network := selectedFromChains[networkIdx]
|
network := selectedFromChains[networkIdx]
|
||||||
|
|
||||||
if !input.SendType.isAvailableFor(network) {
|
if !input.SendType.IsAvailableFor(network) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -901,7 +552,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
||||||
)
|
)
|
||||||
|
|
||||||
if testsMode {
|
if testsMode {
|
||||||
token = input.testParams.tokenFrom
|
token = input.TestParams.TokenFrom
|
||||||
} else {
|
} else {
|
||||||
token = input.SendType.FindToken(r.tokenManager, r.collectiblesService, input.AddrFrom, network, input.TokenID)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.SendType == Swap {
|
if input.SendType == sendtype.Swap {
|
||||||
toToken = input.SendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID)
|
toToken = input.SendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fees *SuggestedFees
|
var fetchedFees *fees.SuggestedFees
|
||||||
if testsMode {
|
if testsMode {
|
||||||
fees = input.testParams.suggestedFees
|
fetchedFees = input.TestParams.SuggestedFees
|
||||||
} else {
|
} else {
|
||||||
fees, err = r.feesManager.SuggestedFees(ctx, network.ChainID)
|
fetchedFees, err = r.feesManager.SuggestedFees(ctx, network.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -942,26 +593,26 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
||||||
// 6. ...
|
// 6. ...
|
||||||
//
|
//
|
||||||
// With the current routing algorithm atm we're not able to generate all possible routes.
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're doing a single chain operation, we can skip bridge processors
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !input.SendType.processZeroAmountInProcessor(amountOption.amount, input.AmountOut.ToInt(), pProcessor.Name()) {
|
if !input.SendType.ProcessZeroAmountInProcessor(amountOption.amount, input.AmountOut.ToInt(), pProcessor.Name()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dest := range selectedToChains {
|
for _, dest := range selectedToChains {
|
||||||
|
|
||||||
if !input.SendType.isAvailableFor(network) {
|
if !input.SendType.IsAvailableFor(network) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !input.SendType.isAvailableBetween(network, dest) {
|
if !input.SendType.IsAvailableBetween(network, dest) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -979,12 +630,12 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
||||||
PublicKey: input.PublicKey,
|
PublicKey: input.PublicKey,
|
||||||
PackID: input.PackID.ToInt(),
|
PackID: input.PackID.ToInt(),
|
||||||
}
|
}
|
||||||
if input.testsMode {
|
if input.TestsMode {
|
||||||
processorInputParams.TestsMode = input.testsMode
|
processorInputParams.TestsMode = input.TestsMode
|
||||||
processorInputParams.TestEstimationMap = input.testParams.estimationMap
|
processorInputParams.TestEstimationMap = input.TestParams.EstimationMap
|
||||||
processorInputParams.TestBonderFeeMap = input.testParams.bonderFeeMap
|
processorInputParams.TestBonderFeeMap = input.TestParams.BonderFeeMap
|
||||||
processorInputParams.TestApprovalGasEstimation = input.testParams.approvalGasEstimation
|
processorInputParams.TestApprovalGasEstimation = input.TestParams.ApprovalGasEstimation
|
||||||
processorInputParams.TestApprovalL1Fee = input.testParams.approvalL1Fee
|
processorInputParams.TestApprovalL1Fee = input.TestParams.ApprovalL1Fee
|
||||||
}
|
}
|
||||||
|
|
||||||
can, err := pProcessor.AvailableFor(processorInputParams)
|
can, err := pProcessor.AvailableFor(processorInputParams)
|
||||||
|
@ -1036,10 +687,10 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
maxFeesPerGas := fees.feeFor(input.GasFeeMode)
|
maxFeesPerGas := fetchedFees.FeeFor(input.GasFeeMode)
|
||||||
|
|
||||||
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas)
|
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas)
|
||||||
if approvalRequired && estimatedTime < MoreThanFiveMinutes {
|
if approvalRequired && estimatedTime < fees.MoreThanFiveMinutes {
|
||||||
estimatedTime += 1
|
estimatedTime += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1090,11 +741,11 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
||||||
AmountInLocked: amountOption.locked,
|
AmountInLocked: amountOption.locked,
|
||||||
AmountOut: (*hexutil.Big)(amountOut),
|
AmountOut: (*hexutil.Big)(amountOut),
|
||||||
|
|
||||||
SuggestedLevelsForMaxFeesPerGas: fees.MaxFeesLevels,
|
SuggestedLevelsForMaxFeesPerGas: fetchedFees.MaxFeesLevels,
|
||||||
MaxFeesPerGas: (*hexutil.Big)(maxFeesPerGas),
|
MaxFeesPerGas: (*hexutil.Big)(maxFeesPerGas),
|
||||||
|
|
||||||
TxBaseFee: (*hexutil.Big)(fees.BaseFee),
|
TxBaseFee: (*hexutil.Big)(fetchedFees.BaseFee),
|
||||||
TxPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
TxPriorityFee: (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas),
|
||||||
TxGasAmount: gasLimit,
|
TxGasAmount: gasLimit,
|
||||||
TxBonderFees: (*hexutil.Big)(bonderFees),
|
TxBonderFees: (*hexutil.Big)(bonderFees),
|
||||||
TxTokenFees: (*hexutil.Big)(tokenFees),
|
TxTokenFees: (*hexutil.Big)(tokenFees),
|
||||||
|
@ -1105,8 +756,8 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
||||||
ApprovalRequired: approvalRequired,
|
ApprovalRequired: approvalRequired,
|
||||||
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
|
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
|
||||||
ApprovalContractAddress: &approvalContractAddress,
|
ApprovalContractAddress: &approvalContractAddress,
|
||||||
ApprovalBaseFee: (*hexutil.Big)(fees.BaseFee),
|
ApprovalBaseFee: (*hexutil.Big)(fetchedFees.BaseFee),
|
||||||
ApprovalPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
ApprovalPriorityFee: (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas),
|
||||||
ApprovalGasAmount: approvalGasLimit,
|
ApprovalGasAmount: approvalGasLimit,
|
||||||
|
|
||||||
ApprovalFee: (*hexutil.Big)(approvalFeeInWei),
|
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) {
|
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))
|
return new(big.Int).Set(v.(*big.Int))
|
||||||
}).(map[string]*big.Int)
|
}).(map[string]*big.Int)
|
||||||
if balanceMapCopy == nil {
|
if balanceMapCopy == nil {
|
||||||
|
@ -1193,59 +844,10 @@ func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*
|
||||||
return hasPositiveBalance, nil
|
return hasPositiveBalance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeBestRouteFromAllRouters(allRoutes [][]*Path, best []*Path) [][]*Path {
|
func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputParams, candidates []*Path, balanceMap map[string]*big.Int) (suggestedRoutes *SuggestedRoutes, err error) {
|
||||||
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) {
|
|
||||||
var prices map[string]float64
|
var prices map[string]float64
|
||||||
if input.testsMode {
|
if input.TestsMode {
|
||||||
prices = input.testParams.tokenPrices
|
prices = input.TestParams.TokenPrices
|
||||||
} else {
|
} else {
|
||||||
prices, err = input.SendType.FetchPrices(r.marketManager, input.TokenID)
|
prices, err = input.SendType.FetchPrices(r.marketManager, input.TokenID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1283,8 +885,8 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
|
||||||
if err != nil {
|
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
|
// 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
|
// we shold check other routes even though there are not the cheapest ones
|
||||||
if input.SendType == Transfer ||
|
if input.SendType == sendtype.Transfer ||
|
||||||
input.SendType == Bridge {
|
input.SendType == sendtype.Bridge {
|
||||||
if hasPositiveBalance {
|
if hasPositiveBalance {
|
||||||
lastBestRouteWithPositiveBalance = bestRoute
|
lastBestRouteWithPositiveBalance = bestRoute
|
||||||
lastBestRouteErr = err
|
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"
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
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/pathprocessor"
|
||||||
|
"github.com/status-im/status-go/services/wallet/router/sendtype"
|
||||||
"github.com/status-im/status-go/services/wallet/token"
|
"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) {
|
bool, *big.Int, uint64, uint64, error) {
|
||||||
if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() {
|
if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() {
|
||||||
return false, nil, 0, 0, nil
|
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)
|
selectedFromChains, _, err := router.getSelectedChains(tt.input)
|
||||||
assert.NoError(t, err)
|
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.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, len(tt.expectedAmountOptions), len(amountOptions))
|
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 (
|
import (
|
||||||
"math/big"
|
"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
|
// 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()
|
pathProcessorName := p.Name()
|
||||||
switch s {
|
switch s {
|
||||||
case Transfer:
|
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 amountIn.Cmp(pathprocessor.ZeroBigIntValue) == 0 {
|
||||||
if s == Transfer {
|
if s == Transfer {
|
||||||
if processorName != pathprocessor.ProcessorTransferName {
|
if processorName != pathprocessor.ProcessorTransferName {
|
||||||
|
@ -129,7 +129,7 @@ func (s SendType) processZeroAmountInProcessor(amountIn *big.Int, amountOut *big
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SendType) isAvailableBetween(from, to *params.Network) bool {
|
func (s SendType) IsAvailableBetween(from, to *params.Network) bool {
|
||||||
if s.IsCollectiblesTransfer() ||
|
if s.IsCollectiblesTransfer() ||
|
||||||
s.IsEnsTransfer() ||
|
s.IsEnsTransfer() ||
|
||||||
s.IsStickersTransfer() ||
|
s.IsStickersTransfer() ||
|
||||||
|
@ -144,7 +144,7 @@ func (s SendType) isAvailableBetween(from, to *params.Network) bool {
|
||||||
return true
|
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
|
// Set of network ChainIDs allowed for any type of transaction
|
||||||
allAllowedNetworks := map[uint64]bool{
|
allAllowedNetworks := map[uint64]bool{
|
||||||
walletCommon.EthereumMainnet: true,
|
walletCommon.EthereumMainnet: true,
|
Loading…
Reference in New Issue