feat_: the router - add candidates, as potential paths, by taking the max amount on enabled chains
This commit is contained in:
parent
4b19845592
commit
378e5741b9
|
@ -27,4 +27,5 @@ var (
|
||||||
ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-019"), Details: "disabled chain found among locked networks"}
|
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"}
|
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"}
|
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"}
|
||||||
)
|
)
|
||||||
|
|
|
@ -414,8 +414,8 @@ func (r *Router) requireApproval(ctx context.Context, sendType SendType, approva
|
||||||
return true, params.AmountIn, estimate, l1Fee, nil
|
return true, params.AmountIn, estimate, l1Fee, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) getBalance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
|
func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.Token, account common.Address) (*big.Int, error) {
|
||||||
client, err := r.rpcClient.EthClient(network.ChainID)
|
client, err := r.rpcClient.EthClient(chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -521,7 +521,7 @@ func (r *Router) SuggestedRoutes(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if sendType != ERC721Transfer {
|
} else if sendType != ERC721Transfer {
|
||||||
balance, err = r.getBalance(ctx, network, token, addrFrom)
|
balance, err = r.getBalance(ctx, network.ChainID, token, addrFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -535,7 +535,7 @@ func (r *Router) SuggestedRoutes(
|
||||||
maxAmountIn = amount
|
maxAmountIn = amount
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeBalance, err := r.getBalance(ctx, network, nativeToken, addrFrom)
|
nativeBalance, err := r.getBalance(ctx, network.ChainID, nativeToken, addrFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,15 +79,13 @@ type routerTestParams struct {
|
||||||
approvalL1Fee uint64
|
approvalL1Fee uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTestBalanceKey(chainID uint64, symbol string) string {
|
type amountOption struct {
|
||||||
return fmt.Sprintf("%d-%s", chainID, symbol)
|
amount *big.Int
|
||||||
|
locked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt routerTestParams) getTestBalance(chainID uint64, symbol string) *big.Int {
|
func makeBalanceKey(chainID uint64, symbol string) string {
|
||||||
if val, ok := rt.balanceMap[makeTestBalanceKey(chainID, symbol)]; ok {
|
return fmt.Sprintf("%d-%s", chainID, symbol)
|
||||||
return val
|
|
||||||
}
|
|
||||||
return big.NewInt(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PathV2 struct {
|
type PathV2 struct {
|
||||||
|
@ -487,40 +485,246 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
|
||||||
return nil, errors.CreateErrorResponseFromError(err)
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates, err := r.resolveCandidates(ctx, input)
|
selectedFromChains, selectedTohains, err := r.getSelectedChains(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.CreateErrorResponseFromError(err)
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.resolveRoutes(ctx, input, candidates)
|
balanceMap, err := r.getBalanceMapForTokenOnChains(ctx, input, selectedFromChains)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates, err := r.resolveCandidates(ctx, input, selectedFromChains, selectedTohains, balanceMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.resolveRoutes(ctx, input, candidates, balanceMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams) (candidates []*PathV2, err error) {
|
// getBalanceMapForTokenOnChains returns the balance map for passed address, where the key is in format "chainID-tokenSymbol" and
|
||||||
var (
|
// value is the balance of the token. Native token (EHT) is always added to the balance map.
|
||||||
testsMode = input.testsMode && input.testParams != nil
|
func (r *Router) getBalanceMapForTokenOnChains(ctx context.Context, input *RouteInputParams, selectedFromChains []*params.Network) (balanceMap map[string]*big.Int, err error) {
|
||||||
networks []*params.Network
|
if input.testsMode {
|
||||||
)
|
return input.testParams.balanceMap, nil
|
||||||
|
|
||||||
networks, err = r.rpcClient.NetworkManager.Get(false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.CreateErrorResponseFromError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
balanceMap = make(map[string]*big.Int)
|
||||||
group = async.NewAtomicGroup(ctx)
|
|
||||||
mu sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
for networkIdx := range networks {
|
for _, chain := range selectedFromChains {
|
||||||
network := networks[networkIdx]
|
token := input.SendType.FindToken(r.tokenManager, r.collectiblesService, input.AddrFrom, chain, input.TokenID)
|
||||||
|
if token == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// add token balance for the chain
|
||||||
|
tokenBalance := big.NewInt(1)
|
||||||
|
if input.SendType == ERC1155Transfer {
|
||||||
|
tokenBalance, err = r.getERC1155Balance(ctx, chain, token, input.AddrFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
|
}
|
||||||
|
} else if input.SendType != ERC721Transfer {
|
||||||
|
tokenBalance, err = r.getBalance(ctx, chain.ChainID, token, input.AddrFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
balanceMap[makeBalanceKey(chain.ChainID, token.Symbol)] = tokenBalance
|
||||||
|
|
||||||
|
// add native token balance for the chain
|
||||||
|
nativeToken := r.tokenManager.FindToken(chain, chain.NativeCurrencySymbol)
|
||||||
|
if nativeToken == nil {
|
||||||
|
return nil, ErrNativeTokenNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeBalance, err := r.getBalance(ctx, chain.ChainID, nativeToken, input.AddrFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
|
}
|
||||||
|
balanceMap[makeBalanceKey(chain.ChainID, nativeToken.Symbol)] = nativeBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) getSelectedUnlockedChains(input *RouteInputParams, processingChain *params.Network, selectedFromChains []*params.Network) []*params.Network {
|
||||||
|
selectedButNotLockedChains := []*params.Network{processingChain} // always add the processing chain at the beginning
|
||||||
|
for _, net := range selectedFromChains {
|
||||||
|
if net.ChainID == processingChain.ChainID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := input.FromLockedAmount[net.ChainID]; !ok {
|
||||||
|
selectedButNotLockedChains = append(selectedButNotLockedChains, net)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedButNotLockedChains
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input *RouteInputParams, amountToSplit *big.Int, processingChain *params.Network,
|
||||||
|
selectedFromChains []*params.Network, balanceMap map[string]*big.Int) map[uint64][]amountOption {
|
||||||
|
selectedButNotLockedChains := r.getSelectedUnlockedChains(input, processingChain, selectedFromChains)
|
||||||
|
|
||||||
|
crossChainAmountOptions := make(map[uint64][]amountOption)
|
||||||
|
for _, chain := range selectedButNotLockedChains {
|
||||||
|
var (
|
||||||
|
ok bool
|
||||||
|
tokenBalance *big.Int
|
||||||
|
)
|
||||||
|
|
||||||
|
if tokenBalance, ok = balanceMap[makeBalanceKey(chain.ChainID, input.TokenID)]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||||
|
if tokenBalance.Cmp(amountToSplit) <= 0 {
|
||||||
|
crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{
|
||||||
|
amount: tokenBalance,
|
||||||
|
locked: false,
|
||||||
|
})
|
||||||
|
amountToSplit = new(big.Int).Sub(amountToSplit, tokenBalance)
|
||||||
|
} else if amountToSplit.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||||
|
crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{
|
||||||
|
amount: amountToSplit,
|
||||||
|
locked: false,
|
||||||
|
})
|
||||||
|
// break since amountToSplit is fully addressed and the rest is 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return crossChainAmountOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) getCrossChainsOptionsForSendingAmount(input *RouteInputParams, selectedFromChains []*params.Network,
|
||||||
|
balanceMap map[string]*big.Int) map[uint64][]amountOption {
|
||||||
|
// All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount
|
||||||
|
// was properly set and if there is something unexpected it will return an error and we will not reach this point
|
||||||
|
finalCrossChainAmountOptions := make(map[uint64][]amountOption) // represents all possible amounts that can be sent from the "from" chain
|
||||||
|
|
||||||
|
for _, selectedFromChain := range selectedFromChains {
|
||||||
|
|
||||||
|
amountLocked := false
|
||||||
|
amountToSend := input.AmountIn.ToInt()
|
||||||
|
lockedAmount, fromChainLocked := input.FromLockedAmount[selectedFromChain.ChainID]
|
||||||
|
if fromChainLocked {
|
||||||
|
amountToSend = lockedAmount.ToInt()
|
||||||
|
amountLocked = true
|
||||||
|
} else if len(input.FromLockedAmount) > 0 {
|
||||||
|
for chainID, lockedAmount := range input.FromLockedAmount {
|
||||||
|
if chainID == selectedFromChain.ChainID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
amountToSend = new(big.Int).Sub(amountToSend, lockedAmount.ToInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if amountToSend.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||||
|
// add full amount always, cause we want to check for balance errors at the end of the routing algorithm
|
||||||
|
// TODO: once we introduce bettwer error handling and start checking for the balance at the beginning of the routing algorithm
|
||||||
|
// we can remove this line and optimize the routing algorithm more
|
||||||
|
finalCrossChainAmountOptions[selectedFromChain.ChainID] = append(finalCrossChainAmountOptions[selectedFromChain.ChainID], amountOption{
|
||||||
|
amount: amountToSend,
|
||||||
|
locked: amountLocked,
|
||||||
|
})
|
||||||
|
|
||||||
|
if amountLocked {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the amount that need to be send is bigger than the balance on the chain, then we want to check options if that
|
||||||
|
// amount can be splitted and sent across multiple chains.
|
||||||
|
if input.SendType == Transfer && len(selectedFromChains) > 1 {
|
||||||
|
// All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount
|
||||||
|
// was properly set and if there is something unexpected it will return an error and we will not reach this point
|
||||||
|
amountToSplitAccrossChains := new(big.Int).Set(amountToSend)
|
||||||
|
|
||||||
|
crossChainAmountOptions := r.getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input, amountToSend, selectedFromChain, selectedFromChains, balanceMap)
|
||||||
|
|
||||||
|
// sum up all the allocated amounts accorss all chains
|
||||||
|
allocatedAmount := big.NewInt(0)
|
||||||
|
for _, amountOptions := range crossChainAmountOptions {
|
||||||
|
for _, amountOption := range amountOptions {
|
||||||
|
allocatedAmount = new(big.Int).Add(allocatedAmount, amountOption.amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the allocated amount is the same as the amount that need to be sent, then we can add the options to the finalCrossChainAmountOptions
|
||||||
|
if allocatedAmount.Cmp(amountToSplitAccrossChains) == 0 {
|
||||||
|
for cID, amountOptions := range crossChainAmountOptions {
|
||||||
|
finalCrossChainAmountOptions[cID] = append(finalCrossChainAmountOptions[cID], amountOptions...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalCrossChainAmountOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) findOptionsForSendingAmount(input *RouteInputParams, selectedFromChains []*params.Network,
|
||||||
|
balanceMap map[string]*big.Int) (map[uint64][]amountOption, error) {
|
||||||
|
|
||||||
|
crossChainAmountOptions := r.getCrossChainsOptionsForSendingAmount(input, selectedFromChains, balanceMap)
|
||||||
|
|
||||||
|
// filter out duplicates values for the same chain
|
||||||
|
for chainID, amountOptions := range crossChainAmountOptions {
|
||||||
|
uniqueAmountOptions := make(map[string]amountOption)
|
||||||
|
for _, amountOption := range amountOptions {
|
||||||
|
uniqueAmountOptions[amountOption.amount.String()] = amountOption
|
||||||
|
}
|
||||||
|
|
||||||
|
crossChainAmountOptions[chainID] = make([]amountOption, 0)
|
||||||
|
for _, amountOption := range uniqueAmountOptions {
|
||||||
|
crossChainAmountOptions[chainID] = append(crossChainAmountOptions[chainID], amountOption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return crossChainAmountOptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) getSelectedChains(input *RouteInputParams) (selectedFromChains []*params.Network, selectedTohains []*params.Network, err error) {
|
||||||
|
var networks []*params.Network
|
||||||
|
networks, err = r.rpcClient.NetworkManager.Get(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.CreateErrorResponseFromError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, network := range networks {
|
||||||
if network.IsTest != input.testnetMode {
|
if network.IsTest != input.testnetMode {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if containsNetworkChainID(network.ChainID, input.DisabledFromChainIDs) {
|
if !containsNetworkChainID(network.ChainID, input.DisabledFromChainIDs) {
|
||||||
continue
|
selectedFromChains = append(selectedFromChains, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !containsNetworkChainID(network.ChainID, input.DisabledToChainIDs) {
|
||||||
|
selectedTohains = append(selectedTohains, network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedFromChains, selectedTohains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams, selectedFromChains []*params.Network,
|
||||||
|
selectedTohains []*params.Network, balanceMap map[string]*big.Int) (candidates []*PathV2, err error) {
|
||||||
|
var (
|
||||||
|
testsMode = input.testsMode && input.testParams != nil
|
||||||
|
group = async.NewAtomicGroup(ctx)
|
||||||
|
mu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
crossChainAmountOptions, err := r.findOptionsForSendingAmount(input, selectedFromChains, balanceMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.CreateErrorResponseFromError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for networkIdx := range selectedFromChains {
|
||||||
|
network := selectedFromChains[networkIdx]
|
||||||
|
|
||||||
if !input.SendType.isAvailableFor(network) {
|
if !input.SendType.isAvailableFor(network) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -543,20 +747,6 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
amountLocked := false
|
|
||||||
amountToSend := input.AmountIn.ToInt()
|
|
||||||
if lockedAmount, ok := input.FromLockedAmount[network.ChainID]; ok {
|
|
||||||
amountToSend = lockedAmount.ToInt()
|
|
||||||
amountLocked = true
|
|
||||||
} else if len(input.FromLockedAmount) > 0 {
|
|
||||||
for chainID, lockedAmount := range input.FromLockedAmount {
|
|
||||||
if chainID == network.ChainID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
amountToSend = new(big.Int).Sub(amountToSend, lockedAmount.ToInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fees *SuggestedFees
|
var fees *SuggestedFees
|
||||||
if testsMode {
|
if testsMode {
|
||||||
fees = input.testParams.suggestedFees
|
fees = input.testParams.suggestedFees
|
||||||
|
@ -568,144 +758,139 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
group.Add(func(c context.Context) error {
|
group.Add(func(c context.Context) error {
|
||||||
for _, pProcessor := range r.pathProcessors {
|
for _, amountOption := range crossChainAmountOptions[network.ChainID] {
|
||||||
// With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route
|
for _, pProcessor := range r.pathProcessors {
|
||||||
// once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function.
|
// With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route
|
||||||
// This also applies to including another (Celer) bridge in the calculation.
|
// once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function.
|
||||||
// TODO:
|
// This also applies to including another (Celer) bridge in the calculation.
|
||||||
// this algorithm, includeing finding the best route, has to be updated to include more bridges and one (for now) or more swap options
|
// TODO:
|
||||||
// it means that candidates should not be treated linearly, but improve the logic to have multiple routes with different processors of the same type.
|
// this algorithm, includeing finding the best route, has to be updated to include more bridges and one (for now) or more swap options
|
||||||
// Example:
|
// it means that candidates should not be treated linearly, but improve the logic to have multiple routes with different processors of the same type.
|
||||||
// Routes for sending SNT from Ethereum to Optimism can be:
|
// Example:
|
||||||
// 1. Swap SNT(mainnet) to ETH(mainnet); then bridge via Hop ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination
|
// Routes for sending SNT from Ethereum to Optimism can be:
|
||||||
// 2. Swap SNT(mainnet) to ETH(mainnet); then bridge via Celer ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination
|
// 1. Swap SNT(mainnet) to ETH(mainnet); then bridge via Hop ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination
|
||||||
// 3. Swap SNT(mainnet) to USDC(mainnet); then bridge via Hop USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination
|
// 2. Swap SNT(mainnet) to ETH(mainnet); then bridge via Celer ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination
|
||||||
// 4. Swap SNT(mainnet) to USDC(mainnet); then bridge via Celer USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination
|
// 3. Swap SNT(mainnet) to USDC(mainnet); then bridge via Hop USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination
|
||||||
// 5. ...
|
// 4. Swap SNT(mainnet) to USDC(mainnet); then bridge via Celer USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination
|
||||||
// 6. ...
|
// 5. ...
|
||||||
//
|
// 6. ...
|
||||||
// With the current routing algorithm atm we're not able to generate all possible routes.
|
//
|
||||||
if !input.SendType.canUseProcessor(pProcessor) {
|
// With the current routing algorithm atm we're not able to generate all possible routes.
|
||||||
continue
|
if !input.SendType.canUseProcessor(pProcessor) {
|
||||||
}
|
|
||||||
|
|
||||||
for _, dest := range networks {
|
|
||||||
if dest.IsTest != input.testnetMode {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !input.SendType.isAvailableFor(network) {
|
for _, dest := range selectedTohains {
|
||||||
continue
|
|
||||||
|
if !input.SendType.isAvailableFor(network) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !input.SendType.isAvailableBetween(network, dest) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
processorInputParams := pathprocessor.ProcessorInputParams{
|
||||||
|
FromChain: network,
|
||||||
|
ToChain: dest,
|
||||||
|
FromToken: token,
|
||||||
|
ToToken: toToken,
|
||||||
|
ToAddr: input.AddrTo,
|
||||||
|
FromAddr: input.AddrFrom,
|
||||||
|
AmountIn: amountOption.amount,
|
||||||
|
AmountOut: input.AmountOut.ToInt(),
|
||||||
|
|
||||||
|
Username: input.Username,
|
||||||
|
PublicKey: input.PublicKey,
|
||||||
|
PackID: input.PackID.ToInt(),
|
||||||
|
}
|
||||||
|
if input.testsMode {
|
||||||
|
processorInputParams.TestsMode = input.testsMode
|
||||||
|
processorInputParams.TestEstimationMap = input.testParams.estimationMap
|
||||||
|
processorInputParams.TestBonderFeeMap = input.testParams.bonderFeeMap
|
||||||
|
processorInputParams.TestApprovalGasEstimation = input.testParams.approvalGasEstimation
|
||||||
|
processorInputParams.TestApprovalL1Fee = input.testParams.approvalL1Fee
|
||||||
|
}
|
||||||
|
|
||||||
|
can, err := pProcessor.AvailableFor(processorInputParams)
|
||||||
|
if err != nil || !can {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gasLimit, err := pProcessor.EstimateGas(processorInputParams)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees
|
||||||
|
var l1FeeWei uint64 = 0
|
||||||
|
// if input.SendType.needL1Fee() {
|
||||||
|
// txInputData, err := pProcessor.PackTxInputData(processorInputParams)
|
||||||
|
// if err != nil {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
|
||||||
|
// }
|
||||||
|
|
||||||
|
amountOut, err := pProcessor.CalculateAmountOut(processorInputParams)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
maxFeesPerGas := fees.feeFor(input.GasFeeMode)
|
||||||
|
|
||||||
|
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas)
|
||||||
|
if approvalRequired && estimatedTime < MoreThanFiveMinutes {
|
||||||
|
estimatedTime += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
candidates = append(candidates, &PathV2{
|
||||||
|
ProcessorName: pProcessor.Name(),
|
||||||
|
FromChain: network,
|
||||||
|
ToChain: dest,
|
||||||
|
FromToken: token,
|
||||||
|
ToToken: toToken,
|
||||||
|
AmountIn: (*hexutil.Big)(amountOption.amount),
|
||||||
|
AmountInLocked: amountOption.locked,
|
||||||
|
AmountOut: (*hexutil.Big)(amountOut),
|
||||||
|
|
||||||
|
SuggestedLevelsForMaxFeesPerGas: fees.MaxFeesLevels,
|
||||||
|
|
||||||
|
TxBaseFee: (*hexutil.Big)(fees.BaseFee),
|
||||||
|
TxPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
||||||
|
TxGasAmount: gasLimit,
|
||||||
|
TxBonderFees: (*hexutil.Big)(bonderFees),
|
||||||
|
TxTokenFees: (*hexutil.Big)(tokenFees),
|
||||||
|
TxL1Fee: (*hexutil.Big)(big.NewInt(int64(l1FeeWei))),
|
||||||
|
|
||||||
|
ApprovalRequired: approvalRequired,
|
||||||
|
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
|
||||||
|
ApprovalContractAddress: &approvalContractAddress,
|
||||||
|
ApprovalBaseFee: (*hexutil.Big)(fees.BaseFee),
|
||||||
|
ApprovalPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
||||||
|
ApprovalGasAmount: approvalGasLimit,
|
||||||
|
ApprovalL1Fee: (*hexutil.Big)(big.NewInt(int64(l1ApprovalFee))),
|
||||||
|
|
||||||
|
EstimatedTime: estimatedTime,
|
||||||
|
})
|
||||||
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !input.SendType.isAvailableBetween(network, dest) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if containsNetworkChainID(dest.ChainID, input.DisabledToChainIDs) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
processorInputParams := pathprocessor.ProcessorInputParams{
|
|
||||||
FromChain: network,
|
|
||||||
ToChain: dest,
|
|
||||||
FromToken: token,
|
|
||||||
ToToken: toToken,
|
|
||||||
ToAddr: input.AddrTo,
|
|
||||||
FromAddr: input.AddrFrom,
|
|
||||||
AmountIn: amountToSend,
|
|
||||||
AmountOut: input.AmountOut.ToInt(),
|
|
||||||
|
|
||||||
Username: input.Username,
|
|
||||||
PublicKey: input.PublicKey,
|
|
||||||
PackID: input.PackID.ToInt(),
|
|
||||||
}
|
|
||||||
if input.testsMode {
|
|
||||||
processorInputParams.TestsMode = input.testsMode
|
|
||||||
processorInputParams.TestEstimationMap = input.testParams.estimationMap
|
|
||||||
processorInputParams.TestBonderFeeMap = input.testParams.bonderFeeMap
|
|
||||||
processorInputParams.TestApprovalGasEstimation = input.testParams.approvalGasEstimation
|
|
||||||
processorInputParams.TestApprovalL1Fee = input.testParams.approvalL1Fee
|
|
||||||
}
|
|
||||||
|
|
||||||
can, err := pProcessor.AvailableFor(processorInputParams)
|
|
||||||
if err != nil || !can {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
gasLimit, err := pProcessor.EstimateGas(processorInputParams)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees
|
|
||||||
var l1FeeWei uint64 = 0
|
|
||||||
// if input.SendType.needL1Fee() {
|
|
||||||
// txInputData, err := pProcessor.PackTxInputData(processorInputParams)
|
|
||||||
// if err != nil {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
|
|
||||||
// }
|
|
||||||
|
|
||||||
amountOut, err := pProcessor.CalculateAmountOut(processorInputParams)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
maxFeesPerGas := fees.feeFor(input.GasFeeMode)
|
|
||||||
|
|
||||||
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas)
|
|
||||||
if approvalRequired && estimatedTime < MoreThanFiveMinutes {
|
|
||||||
estimatedTime += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
mu.Lock()
|
|
||||||
candidates = append(candidates, &PathV2{
|
|
||||||
ProcessorName: pProcessor.Name(),
|
|
||||||
FromChain: network,
|
|
||||||
ToChain: dest,
|
|
||||||
FromToken: token,
|
|
||||||
ToToken: toToken,
|
|
||||||
AmountIn: (*hexutil.Big)(amountToSend),
|
|
||||||
AmountInLocked: amountLocked,
|
|
||||||
AmountOut: (*hexutil.Big)(amountOut),
|
|
||||||
|
|
||||||
SuggestedLevelsForMaxFeesPerGas: fees.MaxFeesLevels,
|
|
||||||
|
|
||||||
TxBaseFee: (*hexutil.Big)(fees.BaseFee),
|
|
||||||
TxPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
|
||||||
TxGasAmount: gasLimit,
|
|
||||||
TxBonderFees: (*hexutil.Big)(bonderFees),
|
|
||||||
TxTokenFees: (*hexutil.Big)(tokenFees),
|
|
||||||
TxL1Fee: (*hexutil.Big)(big.NewInt(int64(l1FeeWei))),
|
|
||||||
|
|
||||||
ApprovalRequired: approvalRequired,
|
|
||||||
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
|
|
||||||
ApprovalContractAddress: &approvalContractAddress,
|
|
||||||
ApprovalBaseFee: (*hexutil.Big)(fees.BaseFee),
|
|
||||||
ApprovalPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
|
||||||
ApprovalGasAmount: approvalGasLimit,
|
|
||||||
ApprovalL1Fee: (*hexutil.Big)(big.NewInt(int64(l1ApprovalFee))),
|
|
||||||
|
|
||||||
EstimatedTime: estimatedTime,
|
|
||||||
})
|
|
||||||
mu.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -716,49 +901,25 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
|
||||||
return candidates, nil
|
return candidates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*PathV2, input *RouteInputParams) (err error) {
|
func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*PathV2, input *RouteInputParams, balanceMap map[string]*big.Int) (err error) {
|
||||||
// check the best route for the required balances
|
// check the best route for the required balances
|
||||||
for _, path := range bestRoute {
|
for _, path := range bestRoute {
|
||||||
if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||||
tokenBalance := big.NewInt(1)
|
if tokenBalance, ok := balanceMap[makeBalanceKey(path.FromChain.ChainID, path.FromToken.Symbol)]; ok {
|
||||||
if input.testsMode {
|
if tokenBalance.Cmp(path.requiredTokenBalance) == -1 {
|
||||||
tokenBalance = input.testParams.getTestBalance(path.FromChain.ChainID, path.FromToken.Symbol)
|
return ErrNotEnoughTokenBalance
|
||||||
} else {
|
|
||||||
if input.SendType == ERC1155Transfer {
|
|
||||||
tokenBalance, err = r.getERC1155Balance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
|
|
||||||
if err != nil {
|
|
||||||
return errors.CreateErrorResponseFromError(err)
|
|
||||||
}
|
|
||||||
} else if input.SendType != ERC721Transfer {
|
|
||||||
tokenBalance, err = r.getBalance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
|
|
||||||
if err != nil {
|
|
||||||
return errors.CreateErrorResponseFromError(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
return ErrTokenNotFound
|
||||||
if tokenBalance.Cmp(path.requiredTokenBalance) == -1 {
|
|
||||||
return ErrNotEnoughTokenBalance
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var nativeBalance *big.Int
|
if nativeBalance, ok := balanceMap[makeBalanceKey(path.FromChain.ChainID, pathprocessor.EthSymbol)]; ok {
|
||||||
if input.testsMode {
|
if nativeBalance.Cmp(path.requiredNativeBalance) == -1 {
|
||||||
nativeBalance = input.testParams.getTestBalance(path.FromChain.ChainID, pathprocessor.EthSymbol)
|
return ErrNotEnoughNativeBalance
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
nativeToken := r.tokenManager.FindToken(path.FromChain, path.FromChain.NativeCurrencySymbol)
|
return ErrNativeTokenNotFound
|
||||||
if nativeToken == nil {
|
|
||||||
return ErrNativeTokenNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
nativeBalance, err = r.getBalance(ctx, path.FromChain, nativeToken, input.AddrFrom)
|
|
||||||
if err != nil {
|
|
||||||
return errors.CreateErrorResponseFromError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nativeBalance.Cmp(path.requiredNativeBalance) == -1 {
|
|
||||||
return ErrNotEnoughNativeBalance
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,7 +953,7 @@ func removeBestRouteFromAllRouters(allRoutes [][]*PathV2, best []*PathV2) [][]*P
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, candidates []*PathV2) (suggestedRoutes *SuggestedRoutesV2, err error) {
|
func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, candidates []*PathV2, balanceMap map[string]*big.Int) (suggestedRoutes *SuggestedRoutesV2, 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
|
||||||
|
@ -812,7 +973,7 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
|
||||||
for len(allRoutes) > 0 {
|
for len(allRoutes) > 0 {
|
||||||
best := findBestV2(allRoutes, tokenPrice, nativeChainTokenPrice)
|
best := findBestV2(allRoutes, tokenPrice, nativeChainTokenPrice)
|
||||||
|
|
||||||
err := r.checkBalancesForTheBestRoute(ctx, best, input)
|
err := r.checkBalancesForTheBestRoute(ctx, best, input, balanceMap)
|
||||||
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
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue