mirror of
https://github.com/status-im/status-go.git
synced 2025-01-25 22:19:51 +00:00
9b9a91f654
This commit maps this kind of errors `status-proxy-0.error: failed with 50011064 gas: insufficient funds for gas * price + value: address 0x4eeB09cf0076F840b38511D808464eE48efD4305 have 0 want 10000000000000` to this form: `status-proxy-0.error: failed with 50011064 gas: insufficient funds for gas * price + value: address 0x4eeB09cf0076F840b38511D808464eE48efD4305` which means that we don't want to display to a user details of how much they have and how much is needed for fees, cause those data are very often misleading, referring mostly to "how much user has". New error added in case there is no positive balances across all enabled chains.
752 lines
21 KiB
Go
752 lines
21 KiB
Go
package router
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"math/big"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum"
|
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/status-im/status-go/contracts"
|
|
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
|
|
"github.com/status-im/status-go/contracts/ierc20"
|
|
"github.com/status-im/status-go/params"
|
|
"github.com/status-im/status-go/rpc"
|
|
"github.com/status-im/status-go/services/ens"
|
|
"github.com/status-im/status-go/services/stickers"
|
|
"github.com/status-im/status-go/services/wallet/async"
|
|
"github.com/status-im/status-go/services/wallet/bigint"
|
|
"github.com/status-im/status-go/services/wallet/collectibles"
|
|
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
|
"github.com/status-im/status-go/services/wallet/market"
|
|
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
|
"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/transactions"
|
|
)
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// TODO: once new router is in place, remove this `router.go` file,
|
|
// rename and make `router_v2.go` file the main and only file
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
// TODO: remove the following two consts once we fully move to routerV2
|
|
const EstimateUsername = "RandomUsername"
|
|
const EstimatePubKey = "0x04bb2024ce5d72e45d4a4f8589ae657ef9745855006996115a23a1af88d536cf02c0524a585fce7bfa79d6a9669af735eda6205d6c7e5b3cdc2b8ff7b2fa1f0b56"
|
|
|
|
type Path struct {
|
|
BridgeName string
|
|
From *params.Network
|
|
To *params.Network
|
|
MaxAmountIn *hexutil.Big
|
|
AmountIn *hexutil.Big
|
|
AmountInLocked bool
|
|
AmountOut *hexutil.Big
|
|
GasAmount uint64
|
|
GasFees *SuggestedFeesGwei
|
|
BonderFees *hexutil.Big
|
|
TokenFees *big.Float
|
|
Cost *big.Float
|
|
EstimatedTime TransactionEstimation
|
|
ApprovalRequired bool
|
|
ApprovalGasFees *big.Float
|
|
ApprovalAmountRequired *hexutil.Big
|
|
ApprovalContractAddress *common.Address
|
|
}
|
|
|
|
func (p *Path) Equal(o *Path) bool {
|
|
return p.From.ChainID == o.From.ChainID && p.To.ChainID == o.To.ChainID
|
|
}
|
|
|
|
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.From.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.MaxAmountIn.ToInt())
|
|
if newAmountIn.Sign() > 0 {
|
|
newSourceChainIDs := make([]uint64, len(sourceChainIDs))
|
|
copy(newSourceChainIDs, sourceChainIDs)
|
|
newSourceChainIDs = append(newSourceChainIDs, route.From.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 filterRoutes(routes [][]*Path, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) [][]*Path {
|
|
if len(fromLockedAmount) == 0 {
|
|
return routes
|
|
}
|
|
|
|
filteredRoutesLevel1 := make([][]*Path, 0)
|
|
for _, route := range routes {
|
|
routeOk := true
|
|
fromIncluded := make(map[uint64]bool)
|
|
fromExcluded := make(map[uint64]bool)
|
|
for chainID, amount := range fromLockedAmount {
|
|
if amount.ToInt().Cmp(pathprocessor.ZeroBigIntValue) == 0 {
|
|
fromExcluded[chainID] = false
|
|
} else {
|
|
fromIncluded[chainID] = false
|
|
}
|
|
|
|
}
|
|
for _, path := range route {
|
|
if _, ok := fromExcluded[path.From.ChainID]; ok {
|
|
routeOk = false
|
|
break
|
|
}
|
|
if _, ok := fromIncluded[path.From.ChainID]; ok {
|
|
fromIncluded[path.From.ChainID] = true
|
|
}
|
|
}
|
|
for _, value := range fromIncluded {
|
|
if !value {
|
|
routeOk = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if routeOk {
|
|
filteredRoutesLevel1 = append(filteredRoutesLevel1, route)
|
|
}
|
|
}
|
|
|
|
filteredRoutesLevel2 := make([][]*Path, 0)
|
|
for _, route := range filteredRoutesLevel1 {
|
|
routeOk := true
|
|
for _, path := range route {
|
|
if amount, ok := fromLockedAmount[path.From.ChainID]; ok {
|
|
requiredAmountIn := new(big.Int).Sub(amountIn, amount.ToInt())
|
|
restAmountIn := big.NewInt(0)
|
|
|
|
for _, otherPath := range route {
|
|
if path.Equal(otherPath) {
|
|
continue
|
|
}
|
|
restAmountIn = new(big.Int).Add(otherPath.MaxAmountIn.ToInt(), restAmountIn)
|
|
}
|
|
if restAmountIn.Cmp(requiredAmountIn) >= 0 {
|
|
path.AmountIn = amount
|
|
path.AmountInLocked = true
|
|
} else {
|
|
routeOk = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if routeOk {
|
|
filteredRoutesLevel2 = append(filteredRoutesLevel2, route)
|
|
}
|
|
}
|
|
|
|
return filteredRoutesLevel2
|
|
}
|
|
|
|
func findBest(routes [][]*Path) []*Path {
|
|
var best []*Path
|
|
bestCost := big.NewFloat(math.Inf(1))
|
|
for _, route := range routes {
|
|
currentCost := big.NewFloat(0)
|
|
for _, path := range route {
|
|
currentCost = new(big.Float).Add(currentCost, path.Cost)
|
|
}
|
|
|
|
if currentCost.Cmp(bestCost) == -1 {
|
|
best = route
|
|
bestCost = currentCost
|
|
}
|
|
}
|
|
|
|
return best
|
|
}
|
|
|
|
type SuggestedRoutes struct {
|
|
Best []*Path
|
|
Candidates []*Path
|
|
TokenPrice float64
|
|
NativeChainTokenPrice float64
|
|
}
|
|
|
|
func newSuggestedRoutes(
|
|
amountIn *big.Int,
|
|
candidates []*Path,
|
|
fromLockedAmount map[uint64]*hexutil.Big,
|
|
) *SuggestedRoutes {
|
|
if len(candidates) == 0 {
|
|
return &SuggestedRoutes{
|
|
Candidates: candidates,
|
|
Best: candidates,
|
|
}
|
|
}
|
|
|
|
node := &Node{
|
|
Path: nil,
|
|
Children: buildGraph(amountIn, candidates, 0, []uint64{}),
|
|
}
|
|
routes := node.buildAllRoutes()
|
|
routes = filterRoutes(routes, amountIn, fromLockedAmount)
|
|
best := findBest(routes)
|
|
|
|
if len(best) > 0 {
|
|
sort.Slice(best, func(i, j int) bool {
|
|
return best[i].AmountInLocked
|
|
})
|
|
rest := new(big.Int).Set(amountIn)
|
|
for _, path := range best {
|
|
diff := new(big.Int).Sub(rest, path.MaxAmountIn.ToInt())
|
|
if diff.Cmp(pathprocessor.ZeroBigIntValue) >= 0 {
|
|
path.AmountIn = (*hexutil.Big)(path.MaxAmountIn.ToInt())
|
|
} else {
|
|
path.AmountIn = (*hexutil.Big)(new(big.Int).Set(rest))
|
|
}
|
|
rest.Sub(rest, path.AmountIn.ToInt())
|
|
}
|
|
}
|
|
|
|
return &SuggestedRoutes{
|
|
Candidates: candidates,
|
|
Best: best,
|
|
}
|
|
}
|
|
|
|
func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *token.Manager, marketManager *market.Manager,
|
|
collectibles *collectibles.Service, collectiblesManager *collectibles.Manager, ensService *ens.Service, stickersService *stickers.Service) *Router {
|
|
processors := make(map[string]pathprocessor.PathProcessor)
|
|
|
|
return &Router{
|
|
rpcClient: rpcClient,
|
|
tokenManager: tokenManager,
|
|
marketManager: marketManager,
|
|
collectiblesService: collectibles,
|
|
collectiblesManager: collectiblesManager,
|
|
ensService: ensService,
|
|
stickersService: stickersService,
|
|
feesManager: &FeeManager{rpcClient},
|
|
pathProcessors: processors,
|
|
scheduler: async.NewScheduler(),
|
|
}
|
|
}
|
|
|
|
func (r *Router) AddPathProcessor(processor pathprocessor.PathProcessor) {
|
|
r.pathProcessors[processor.Name()] = processor
|
|
}
|
|
|
|
func (r *Router) Stop() {
|
|
r.scheduler.Stop()
|
|
}
|
|
|
|
func (r *Router) GetFeesManager() *FeeManager {
|
|
return r.feesManager
|
|
}
|
|
|
|
func (r *Router) GetPathProcessors() map[string]pathprocessor.PathProcessor {
|
|
return r.pathProcessors
|
|
}
|
|
|
|
func arrayContainsElement[T comparable](el T, arr []T) bool {
|
|
for _, e := range arr {
|
|
if e == el {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func arraysWithSameElements[T comparable](ar1 []T, ar2 []T, isEqual func(T, T) bool) bool {
|
|
if len(ar1) != len(ar2) {
|
|
return false
|
|
}
|
|
for _, el := range ar1 {
|
|
if !arrayContainsElement(el, ar2) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func sameSingleChainTransfer(fromChains []*params.Network, toChains []*params.Network) bool {
|
|
return len(fromChains) == 1 &&
|
|
len(toChains) == 1 &&
|
|
arraysWithSameElements(fromChains, toChains, func(a, b *params.Network) bool {
|
|
return a.ChainID == b.ChainID
|
|
})
|
|
}
|
|
|
|
type Router struct {
|
|
rpcClient *rpc.Client
|
|
tokenManager *token.Manager
|
|
marketManager *market.Manager
|
|
collectiblesService *collectibles.Service
|
|
collectiblesManager *collectibles.Manager
|
|
ensService *ens.Service
|
|
stickersService *stickers.Service
|
|
feesManager *FeeManager
|
|
pathProcessors map[string]pathprocessor.PathProcessor
|
|
scheduler *async.Scheduler
|
|
}
|
|
|
|
func (r *Router) requireApproval(ctx context.Context, sendType SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) (
|
|
bool, *big.Int, uint64, uint64, error) {
|
|
if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() {
|
|
return false, nil, 0, 0, nil
|
|
}
|
|
|
|
if params.FromToken.IsNative() {
|
|
return false, nil, 0, 0, nil
|
|
}
|
|
|
|
contractMaker, err := contracts.NewContractMaker(r.rpcClient)
|
|
if err != nil {
|
|
return false, nil, 0, 0, err
|
|
}
|
|
|
|
contract, err := contractMaker.NewERC20(params.FromChain.ChainID, params.FromToken.Address)
|
|
if err != nil {
|
|
return false, nil, 0, 0, err
|
|
}
|
|
|
|
if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress {
|
|
return false, nil, 0, 0, nil
|
|
}
|
|
|
|
if params.TestsMode {
|
|
return true, params.AmountIn, params.TestApprovalGasEstimation, params.TestApprovalL1Fee, nil
|
|
}
|
|
|
|
allowance, err := contract.Allowance(&bind.CallOpts{
|
|
Context: ctx,
|
|
}, params.FromAddr, *approvalContractAddress)
|
|
|
|
if err != nil {
|
|
return false, nil, 0, 0, err
|
|
}
|
|
|
|
if allowance.Cmp(params.AmountIn) >= 0 {
|
|
return false, nil, 0, 0, nil
|
|
}
|
|
|
|
ethClient, err := r.rpcClient.EthClient(params.FromChain.ChainID)
|
|
if err != nil {
|
|
return false, nil, 0, 0, err
|
|
}
|
|
|
|
erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
|
|
if err != nil {
|
|
return false, nil, 0, 0, err
|
|
}
|
|
|
|
data, err := erc20ABI.Pack("approve", approvalContractAddress, params.AmountIn)
|
|
if err != nil {
|
|
return false, nil, 0, 0, err
|
|
}
|
|
|
|
estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
|
|
From: params.FromAddr,
|
|
To: ¶ms.FromToken.Address,
|
|
Value: pathprocessor.ZeroBigIntValue,
|
|
Data: data,
|
|
})
|
|
if err != nil {
|
|
return false, nil, 0, 0, err
|
|
}
|
|
|
|
// fetching l1 fee
|
|
var l1Fee uint64
|
|
oracleContractAddress, err := gaspriceoracle.ContractAddress(params.FromChain.ChainID)
|
|
if err == nil {
|
|
oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
|
|
if err != nil {
|
|
return false, nil, 0, 0, err
|
|
}
|
|
|
|
callOpt := &bind.CallOpts{}
|
|
|
|
l1FeeResult, _ := oracleContract.GetL1Fee(callOpt, data)
|
|
l1Fee = l1FeeResult.Uint64()
|
|
}
|
|
|
|
return true, params.AmountIn, estimate, l1Fee, nil
|
|
}
|
|
|
|
func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.Token, account common.Address) (*big.Int, error) {
|
|
client, err := r.rpcClient.EthClient(chainID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return r.tokenManager.GetBalance(ctx, client, account, token.Address)
|
|
}
|
|
|
|
func (r *Router) getERC1155Balance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
|
|
tokenID, success := new(big.Int).SetString(token.Symbol, 10)
|
|
if !success {
|
|
return nil, errors.New("failed to convert token symbol to big.Int")
|
|
}
|
|
|
|
balances, err := r.collectiblesManager.FetchERC1155Balances(
|
|
ctx,
|
|
account,
|
|
walletCommon.ChainID(network.ChainID),
|
|
token.Address,
|
|
[]*bigint.BigInt{&bigint.BigInt{Int: tokenID}},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(balances) != 1 || balances[0] == nil {
|
|
return nil, errors.New("invalid ERC1155 balance fetch response")
|
|
}
|
|
|
|
return balances[0].Int, nil
|
|
}
|
|
|
|
func (r *Router) SuggestedRoutes(
|
|
ctx context.Context,
|
|
sendType SendType,
|
|
addrFrom common.Address,
|
|
addrTo common.Address,
|
|
amountIn *big.Int,
|
|
tokenID string,
|
|
toTokenID string,
|
|
disabledFromChainIDs,
|
|
disabledToChainIDs,
|
|
preferedChainIDs []uint64,
|
|
gasFeeMode GasFeeMode,
|
|
fromLockedAmount map[uint64]*hexutil.Big,
|
|
testnetMode bool,
|
|
) (*SuggestedRoutes, error) {
|
|
|
|
networks, err := r.rpcClient.NetworkManager.Get(false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prices, err := sendType.FetchPrices(r.marketManager, tokenID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var (
|
|
group = async.NewAtomicGroup(ctx)
|
|
mu sync.Mutex
|
|
candidates = make([]*Path, 0)
|
|
)
|
|
|
|
for networkIdx := range networks {
|
|
network := networks[networkIdx]
|
|
if network.IsTest != testnetMode {
|
|
continue
|
|
}
|
|
|
|
if arrayContainsElement(network.ChainID, disabledFromChainIDs) {
|
|
continue
|
|
}
|
|
|
|
if !sendType.isAvailableFor(network) {
|
|
continue
|
|
}
|
|
|
|
token := sendType.FindToken(r.tokenManager, r.collectiblesService, addrFrom, network, tokenID)
|
|
if token == nil {
|
|
continue
|
|
}
|
|
|
|
var toToken *walletToken.Token
|
|
if sendType == Swap {
|
|
toToken = sendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, toTokenID)
|
|
}
|
|
|
|
nativeToken := r.tokenManager.FindToken(network, network.NativeCurrencySymbol)
|
|
if nativeToken == nil {
|
|
continue
|
|
}
|
|
|
|
group.Add(func(c context.Context) error {
|
|
gasFees, err := r.feesManager.SuggestedFeesGwei(ctx, network.ChainID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Default value is 1 as in case of erc721 as we built the token we are sure the account owns it
|
|
balance := big.NewInt(1)
|
|
if sendType == ERC1155Transfer {
|
|
balance, err = r.getERC1155Balance(ctx, network, token, addrFrom)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if sendType != ERC721Transfer {
|
|
balance, err = r.getBalance(ctx, network.ChainID, token, addrFrom)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
maxAmountIn := (*hexutil.Big)(balance)
|
|
if amount, ok := fromLockedAmount[network.ChainID]; ok {
|
|
if amount.ToInt().Cmp(balance) == 1 {
|
|
return errors.New("locked amount cannot be bigger than balance")
|
|
}
|
|
maxAmountIn = amount
|
|
}
|
|
|
|
nativeBalance, err := r.getBalance(ctx, network.ChainID, nativeToken, addrFrom)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
maxFees := gasFees.feeFor(gasFeeMode)
|
|
|
|
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, gweiToWei(maxFees))
|
|
for _, pProcessor := range r.pathProcessors {
|
|
// Skip processors that are added because of the Router V2, to not break the current functionality
|
|
if pProcessor.Name() == pathprocessor.ProcessorENSRegisterName ||
|
|
pProcessor.Name() == pathprocessor.ProcessorENSReleaseName ||
|
|
pProcessor.Name() == pathprocessor.ProcessorENSPublicKeyName ||
|
|
pProcessor.Name() == pathprocessor.ProcessorStickersBuyName {
|
|
continue
|
|
}
|
|
|
|
if !sendType.canUseProcessor(pProcessor) {
|
|
continue
|
|
}
|
|
|
|
for _, dest := range networks {
|
|
if dest.IsTest != testnetMode {
|
|
continue
|
|
}
|
|
|
|
if !sendType.isAvailableFor(network) {
|
|
continue
|
|
}
|
|
|
|
if !sendType.isAvailableBetween(network, dest) {
|
|
continue
|
|
}
|
|
|
|
if len(preferedChainIDs) > 0 && !arrayContainsElement(dest.ChainID, preferedChainIDs) {
|
|
continue
|
|
}
|
|
if arrayContainsElement(dest.ChainID, disabledToChainIDs) {
|
|
continue
|
|
}
|
|
|
|
processorInputParams := pathprocessor.ProcessorInputParams{
|
|
FromChain: network,
|
|
ToChain: dest,
|
|
FromToken: token,
|
|
ToToken: toToken,
|
|
ToAddr: addrTo,
|
|
FromAddr: addrFrom,
|
|
AmountIn: amountIn,
|
|
}
|
|
|
|
can, err := pProcessor.AvailableFor(processorInputParams)
|
|
if err != nil || !can {
|
|
continue
|
|
}
|
|
if maxAmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) == 0 {
|
|
continue
|
|
}
|
|
|
|
bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if bonderFees.Cmp(pathprocessor.ZeroBigIntValue) != 0 {
|
|
if maxAmountIn.ToInt().Cmp(amountIn) >= 0 {
|
|
if bonderFees.Cmp(amountIn) >= 0 {
|
|
continue
|
|
}
|
|
} else {
|
|
if bonderFees.Cmp(maxAmountIn.ToInt()) >= 0 {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
gasLimit := uint64(0)
|
|
if sendType.isTransfer(false) {
|
|
gasLimit, err = pProcessor.EstimateGas(processorInputParams)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
} else {
|
|
gasLimit = sendType.EstimateGas(r.ensService, r.stickersService, network, addrFrom, tokenID)
|
|
}
|
|
|
|
approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, sendType, &approvalContractAddress, processorInputParams)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
var l1GasFeeWei uint64
|
|
if sendType.needL1Fee() {
|
|
txInputData, err := pProcessor.PackTxInputData(processorInputParams)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
l1GasFeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
|
|
l1GasFeeWei += l1ApprovalFee
|
|
}
|
|
|
|
gasFees.L1GasFee = weiToGwei(big.NewInt(int64(l1GasFeeWei)))
|
|
|
|
requiredNativeBalance := new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(gasLimit)))
|
|
requiredNativeBalance.Add(requiredNativeBalance, new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(approvalGasLimit))))
|
|
requiredNativeBalance.Add(requiredNativeBalance, big.NewInt(int64(l1GasFeeWei))) // add l1Fee to requiredNativeBalance, in case of L1 chain l1Fee is 0
|
|
|
|
if nativeBalance.Cmp(requiredNativeBalance) <= 0 {
|
|
continue
|
|
}
|
|
|
|
// Removed the required fees from maxAMount in case of native token tx
|
|
if token.IsNative() {
|
|
maxAmountIn = (*hexutil.Big)(new(big.Int).Sub(maxAmountIn.ToInt(), requiredNativeBalance))
|
|
}
|
|
|
|
ethPrice := big.NewFloat(prices["ETH"])
|
|
|
|
approvalGasFees := new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat((float64(approvalGasLimit))))
|
|
|
|
approvalGasCost := new(big.Float)
|
|
approvalGasCost.Mul(approvalGasFees, ethPrice)
|
|
|
|
l1GasCost := new(big.Float)
|
|
l1GasCost.Mul(gasFees.L1GasFee, ethPrice)
|
|
|
|
gasCost := new(big.Float)
|
|
gasCost.Mul(new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat(float64(gasLimit))), ethPrice)
|
|
|
|
tokenFeesAsFloat := new(big.Float).Quo(
|
|
new(big.Float).SetInt(tokenFees),
|
|
big.NewFloat(math.Pow(10, float64(token.Decimals))),
|
|
)
|
|
tokenCost := new(big.Float)
|
|
tokenCost.Mul(tokenFeesAsFloat, big.NewFloat(prices[tokenID]))
|
|
|
|
cost := new(big.Float)
|
|
cost.Add(tokenCost, gasCost)
|
|
cost.Add(cost, approvalGasCost)
|
|
cost.Add(cost, l1GasCost)
|
|
mu.Lock()
|
|
candidates = append(candidates, &Path{
|
|
BridgeName: pProcessor.Name(),
|
|
From: network,
|
|
To: dest,
|
|
MaxAmountIn: maxAmountIn,
|
|
AmountIn: (*hexutil.Big)(pathprocessor.ZeroBigIntValue),
|
|
AmountOut: (*hexutil.Big)(pathprocessor.ZeroBigIntValue),
|
|
GasAmount: gasLimit,
|
|
GasFees: gasFees,
|
|
BonderFees: (*hexutil.Big)(bonderFees),
|
|
TokenFees: tokenFeesAsFloat,
|
|
Cost: cost,
|
|
EstimatedTime: estimatedTime,
|
|
ApprovalRequired: approvalRequired,
|
|
ApprovalGasFees: approvalGasFees,
|
|
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
|
|
ApprovalContractAddress: &approvalContractAddress,
|
|
})
|
|
mu.Unlock()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
group.Wait()
|
|
|
|
suggestedRoutes := newSuggestedRoutes(amountIn, candidates, fromLockedAmount)
|
|
suggestedRoutes.TokenPrice = prices[tokenID]
|
|
suggestedRoutes.NativeChainTokenPrice = prices["ETH"]
|
|
for _, path := range suggestedRoutes.Best {
|
|
processorInputParams := pathprocessor.ProcessorInputParams{
|
|
FromChain: path.From,
|
|
ToChain: path.To,
|
|
AmountIn: path.AmountIn.ToInt(),
|
|
FromToken: &token.Token{
|
|
Symbol: tokenID,
|
|
},
|
|
ToToken: &token.Token{
|
|
Symbol: toTokenID,
|
|
},
|
|
}
|
|
|
|
amountOut, err := r.pathProcessors[path.BridgeName].CalculateAmountOut(processorInputParams)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
path.AmountOut = (*hexutil.Big)(amountOut)
|
|
}
|
|
|
|
return suggestedRoutes, nil
|
|
}
|