status-go/services/wallet/router.go

834 lines
22 KiB
Go
Raw Normal View History

package wallet
import (
"context"
2022-11-23 17:49:23 +00:00
"errors"
2022-09-13 07:10:59 +00:00
"fmt"
"math"
"math/big"
2022-11-23 17:49:23 +00:00
"sort"
2022-12-19 12:37:37 +00:00
"strings"
"sync"
2022-12-19 12:37:37 +00:00
"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"
2022-09-13 07:10:59 +00:00
"github.com/ethereum/go-ethereum/common/hexutil"
2022-12-19 12:37:37 +00:00
"github.com/status-im/status-go/contracts"
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
2022-12-19 12:37:37 +00:00
"github.com/status-im/status-go/contracts/ierc20"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
2022-12-19 12:37:37 +00:00
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/async"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/bridge"
2023-08-24 08:45:14 +00:00
walletCommon "github.com/status-im/status-go/services/wallet/common"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/services/wallet/token"
2024-04-01 13:39:17 +00:00
walletToken "github.com/status-im/status-go/services/wallet/token"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/transactions"
)
2023-08-24 08:45:14 +00:00
const EstimateUsername = "RandomUsername"
const EstimatePubKey = "0x04bb2024ce5d72e45d4a4f8589ae657ef9745855006996115a23a1af88d536cf02c0524a585fce7bfa79d6a9669af735eda6205d6c7e5b3cdc2b8ff7b2fa1f0b56"
const ERC721TransferString = "ERC721Transfer"
2024-02-06 11:36:25 +00:00
const ERC1155TransferString = "ERC1155Transfer"
2023-08-24 08:45:14 +00:00
2022-09-13 07:10:59 +00:00
type SendType int
2022-09-13 07:10:59 +00:00
const (
Transfer SendType = iota
ENSRegister
ENSRelease
ENSSetPubKey
StickersBuy
2022-11-24 12:58:53 +00:00
Bridge
2023-08-24 08:45:14 +00:00
ERC721Transfer
2024-02-06 11:36:25 +00:00
ERC1155Transfer
2024-04-01 13:39:17 +00:00
Swap
2022-09-13 07:10:59 +00:00
)
2023-08-24 08:45:14 +00:00
2024-02-06 11:36:25 +00:00
func (s SendType) IsCollectiblesTransfer() bool {
return s == ERC721Transfer || s == ERC1155Transfer
}
2023-08-24 08:45:14 +00:00
func (s SendType) FetchPrices(service *Service, tokenID string) (map[string]float64, error) {
symbols := []string{tokenID, "ETH"}
2024-02-06 11:36:25 +00:00
if s.IsCollectiblesTransfer() {
2023-08-24 08:45:14 +00:00
symbols = []string{"ETH"}
}
pricesMap, err := service.marketManager.FetchPrices(symbols, []string{"USD"})
if err != nil {
return nil, err
}
prices := make(map[string]float64, 0)
for symbol, pricePerCurrency := range pricesMap {
prices[symbol] = pricePerCurrency["USD"]
}
2024-02-06 11:36:25 +00:00
if s.IsCollectiblesTransfer() {
2023-08-24 08:45:14 +00:00
prices[tokenID] = 0
}
return prices, nil
}
func (s SendType) FindToken(service *Service, account common.Address, network *params.Network, tokenID string) *token.Token {
2024-02-06 11:36:25 +00:00
if !s.IsCollectiblesTransfer() {
2023-08-24 08:45:14 +00:00
return service.tokenManager.FindToken(network, tokenID)
}
parts := strings.Split(tokenID, ":")
contractAddress := common.HexToAddress(parts[0])
collectibleTokenID, success := new(big.Int).SetString(parts[1], 10)
if !success {
return nil
}
uniqueID, err := service.collectibles.GetOwnedCollectible(walletCommon.ChainID(network.ChainID), account, contractAddress, collectibleTokenID)
if err != nil || uniqueID == nil {
return nil
}
return &token.Token{
Address: contractAddress,
Symbol: collectibleTokenID.String(),
Decimals: 0,
ChainID: network.ChainID,
}
}
2022-09-13 07:10:59 +00:00
func (s SendType) isTransfer() bool {
2024-04-01 13:39:17 +00:00
return s == Transfer || s == Swap || s.IsCollectiblesTransfer()
}
func (s SendType) needL1Fee() bool {
return s != ENSRegister && s != ENSRelease && s != ENSSetPubKey && s != StickersBuy
}
2022-11-24 12:58:53 +00:00
func (s SendType) isAvailableBetween(from, to *params.Network) bool {
2024-02-06 11:36:25 +00:00
if s.IsCollectiblesTransfer() {
2023-08-24 08:45:14 +00:00
return from.ChainID == to.ChainID
}
if s == Bridge {
return from.ChainID != to.ChainID
2022-11-24 12:58:53 +00:00
}
2024-04-01 13:39:17 +00:00
if s == Swap {
return from.ChainID == to.ChainID
}
2023-08-24 08:45:14 +00:00
return true
}
func (s SendType) canUseBridge(b bridge.Bridge) bool {
if s == ERC721Transfer && b.Name() != ERC721TransferString {
return false
}
if s != ERC721Transfer && b.Name() == ERC721TransferString {
return false
}
2024-02-06 11:36:25 +00:00
if s == ERC1155Transfer && b.Name() != ERC1155TransferString {
return false
}
if s != ERC1155Transfer && b.Name() == ERC1155TransferString {
return false
}
2023-08-24 08:45:14 +00:00
return true
2022-11-24 12:58:53 +00:00
}
2022-09-13 07:10:59 +00:00
func (s SendType) isAvailableFor(network *params.Network) bool {
2024-04-01 13:39:17 +00:00
if s == Swap {
return network.ChainID == walletCommon.EthereumMainnet ||
network.ChainID == walletCommon.OptimismMainnet ||
network.ChainID == walletCommon.ArbitrumMainnet
}
2024-02-06 11:36:25 +00:00
if s == Transfer || s == Bridge || s.IsCollectiblesTransfer() {
2022-09-13 07:10:59 +00:00
return true
}
2024-04-01 13:39:17 +00:00
if network.ChainID == walletCommon.EthereumMainnet ||
network.ChainID == walletCommon.EthereumGoerli ||
network.ChainID == walletCommon.EthereumSepolia {
2022-09-13 07:10:59 +00:00
return true
}
return false
}
2023-08-24 08:45:14 +00:00
func (s SendType) EstimateGas(service *Service, network *params.Network, from common.Address, tokenID string) uint64 {
2022-09-13 07:10:59 +00:00
tx := transactions.SendTxArgs{
2023-08-24 08:45:14 +00:00
From: (types.Address)(from),
2022-10-25 14:25:08 +00:00
Value: (*hexutil.Big)(zero),
2022-09-13 07:10:59 +00:00
}
if s == ENSRegister {
estimate, err := service.ens.API().RegisterEstimate(context.Background(), network.ChainID, tx, EstimateUsername, EstimatePubKey)
if err != nil {
return 400000
}
2022-09-13 07:10:59 +00:00
return estimate
}
2022-09-13 07:10:59 +00:00
if s == ENSRelease {
estimate, err := service.ens.API().ReleaseEstimate(context.Background(), network.ChainID, tx, EstimateUsername)
if err != nil {
2022-09-13 07:10:59 +00:00
return 200000
}
2022-09-13 07:10:59 +00:00
return estimate
}
2022-09-13 07:10:59 +00:00
if s == ENSSetPubKey {
estimate, err := service.ens.API().SetPubKeyEstimate(context.Background(), network.ChainID, tx, fmt.Sprint(EstimateUsername, ".stateofus.eth"), EstimatePubKey)
if err != nil {
2022-09-13 07:10:59 +00:00
return 400000
}
return estimate
}
if s == StickersBuy {
2022-10-25 14:25:08 +00:00
packID := &bigint.BigInt{Int: big.NewInt(2)}
2023-08-24 08:45:14 +00:00
estimate, err := service.stickers.API().BuyEstimate(context.Background(), network.ChainID, (types.Address)(from), packID)
2022-09-13 07:10:59 +00:00
if err != nil {
return 400000
}
return estimate
}
return 0
}
var zero = big.NewInt(0)
type Path struct {
2022-12-19 12:37:37 +00:00
BridgeName string
From *params.Network
To *params.Network
MaxAmountIn *hexutil.Big
AmountIn *hexutil.Big
AmountInLocked bool
AmountOut *hexutil.Big
GasAmount uint64
GasFees *SuggestedFees
BonderFees *hexutil.Big
TokenFees *big.Float
Cost *big.Float
EstimatedTime TransactionEstimation
ApprovalRequired bool
ApprovalGasFees *big.Float
ApprovalAmountRequired *hexutil.Big
ApprovalContractAddress *common.Address
2022-11-23 17:49:23 +00:00
}
func (p *Path) Equal(o *Path) bool {
return p.From.ChainID == o.From.ChainID && p.To.ChainID == o.To.ChainID
2022-09-13 07:10:59 +00:00
}
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
}
2022-09-13 07:10:59 +00:00
node := newNode(route)
2022-09-13 07:10:59 +00:00
newRoutes := make([]*Path, 0)
for _, r := range routes {
2022-11-23 17:49:23 +00:00
if route.Equal(r) {
2022-09-13 07:10:59 +00:00
continue
}
newRoutes = append(newRoutes, r)
}
2022-09-13 07:10:59 +00:00
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)
}
2022-09-13 07:10:59 +00:00
return graph
}
2022-11-23 17:49:23 +00:00
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)
2022-09-13 07:10:59 +00:00
}
}
2022-11-23 17:49:23 +00:00
return res
}
2022-09-13 07:10:59 +00:00
2022-11-23 17:49:23 +00:00
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(zero) == 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)
2022-09-13 07:10:59 +00:00
}
}
2022-11-23 17:49:23 +00:00
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)
}
2022-09-13 07:10:59 +00:00
}
2022-11-23 17:49:23 +00:00
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
2022-09-13 07:10:59 +00:00
}
type SuggestedRoutes struct {
Best []*Path
Candidates []*Path
TokenPrice float64
NativeChainTokenPrice float64
}
2022-11-23 17:49:23 +00:00
func newSuggestedRoutes(
amountIn *big.Int,
candidates []*Path,
fromLockedAmount map[uint64]*hexutil.Big,
) *SuggestedRoutes {
2022-09-13 07:10:59 +00:00
if len(candidates) == 0 {
return &SuggestedRoutes{
Candidates: candidates,
Best: candidates,
}
}
node := &Node{
Path: nil,
Children: buildGraph(amountIn, candidates, 0, []uint64{}),
}
2022-11-23 17:49:23 +00:00
routes := node.buildAllRoutes()
routes = filterRoutes(routes, amountIn, fromLockedAmount)
best := findBest(routes)
2022-09-13 07:10:59 +00:00
if len(best) > 0 {
2022-11-23 17:49:23 +00:00
sort.Slice(best, func(i, j int) bool {
return best[i].AmountInLocked
})
2022-09-13 07:10:59 +00:00
rest := new(big.Int).Set(amountIn)
for _, path := range best {
diff := new(big.Int).Sub(rest, path.MaxAmountIn.ToInt())
2022-10-25 14:25:08 +00:00
if diff.Cmp(zero) >= 0 {
2022-11-23 17:49:23 +00:00
path.AmountIn = (*hexutil.Big)(path.MaxAmountIn.ToInt())
2022-09-13 07:10:59 +00:00
} else {
path.AmountIn = (*hexutil.Big)(new(big.Int).Set(rest))
}
rest.Sub(rest, path.AmountIn.ToInt())
}
}
return &SuggestedRoutes{
Candidates: candidates,
Best: best,
}
}
func NewRouter(s *Service) *Router {
bridges := make(map[string]bridge.Bridge)
2023-11-16 10:12:01 +00:00
transfer := bridge.NewTransferBridge(s.rpcClient, s.transactor)
2023-08-24 08:45:14 +00:00
erc721Transfer := bridge.NewERC721TransferBridge(s.rpcClient, s.transactor)
2024-02-06 11:36:25 +00:00
erc1155Transfer := bridge.NewERC1155TransferBridge(s.rpcClient, s.transactor)
2022-12-19 12:37:37 +00:00
cbridge := bridge.NewCbridge(s.rpcClient, s.transactor, s.tokenManager)
hop := bridge.NewHopBridge(s.rpcClient, s.transactor, s.tokenManager)
2024-04-01 13:39:17 +00:00
paraswap := bridge.NewSwapParaswap(s.rpcClient, s.transactor, s.tokenManager)
2023-08-24 08:45:14 +00:00
bridges[transfer.Name()] = transfer
bridges[erc721Transfer.Name()] = erc721Transfer
2022-09-13 07:10:59 +00:00
bridges[hop.Name()] = hop
2022-11-22 13:49:29 +00:00
bridges[cbridge.Name()] = cbridge
2024-02-06 11:36:25 +00:00
bridges[erc1155Transfer.Name()] = erc1155Transfer
2024-04-01 13:39:17 +00:00
bridges[paraswap.Name()] = paraswap
2022-09-13 07:10:59 +00:00
2022-12-19 12:37:37 +00:00
return &Router{s, bridges, s.rpcClient}
2022-09-13 07:10:59 +00:00
}
2022-10-25 14:25:08 +00:00
func containsNetworkChainID(network *params.Network, chainIDs []uint64) bool {
2022-09-13 07:10:59 +00:00
for _, chainID := range chainIDs {
if chainID == network.ChainID {
return true
}
}
return false
}
type Router struct {
2022-12-19 12:37:37 +00:00
s *Service
bridges map[string]bridge.Bridge
rpcClient *rpc.Client
}
2024-04-01 13:39:17 +00:00
func (r *Router) requireApproval(ctx context.Context, sendType SendType, approvalContractAddress *common.Address, account common.Address, network *params.Network, token *token.Token, amountIn *big.Int) (
bool, *big.Int, uint64, uint64, error) {
2024-03-06 10:16:20 +00:00
if sendType.IsCollectiblesTransfer() {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, nil
2023-08-24 08:45:14 +00:00
}
2022-12-19 12:37:37 +00:00
if token.IsNative() {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, nil
2022-12-19 12:37:37 +00:00
}
2023-08-29 12:59:37 +00:00
contractMaker, err := contracts.NewContractMaker(r.rpcClient)
if err != nil {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, err
2022-12-19 12:37:37 +00:00
}
contract, err := contractMaker.NewERC20(network.ChainID, token.Address)
if err != nil {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, err
}
if approvalContractAddress == nil || *approvalContractAddress == bridge.ZeroAddress {
return false, nil, 0, 0, nil
2022-12-19 12:37:37 +00:00
}
allowance, err := contract.Allowance(&bind.CallOpts{
Context: ctx,
2024-04-01 13:39:17 +00:00
}, account, *approvalContractAddress)
2022-12-19 12:37:37 +00:00
if err != nil {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, err
2022-12-19 12:37:37 +00:00
}
if allowance.Cmp(amountIn) >= 0 {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, nil
2022-12-19 12:37:37 +00:00
}
ethClient, err := r.rpcClient.EthClient(network.ChainID)
if err != nil {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, err
2022-12-19 12:37:37 +00:00
}
erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
if err != nil {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, err
2022-12-19 12:37:37 +00:00
}
2024-04-01 13:39:17 +00:00
data, err := erc20ABI.Pack("approve", approvalContractAddress, amountIn)
2022-12-19 12:37:37 +00:00
if err != nil {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, err
2022-12-19 12:37:37 +00:00
}
estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
From: account,
To: &token.Address,
Value: big.NewInt(0),
Data: data,
})
if err != nil {
2024-04-01 13:39:17 +00:00
return false, nil, 0, 0, err
2022-12-19 12:37:37 +00:00
}
// fetching l1 fee
2024-04-01 13:39:17 +00:00
var l1Fee uint64
oracleContractAddress, err := gaspriceoracle.ContractAddress(network.ChainID)
2024-04-01 13:39:17 +00:00
if err == nil {
oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
if err != nil {
return false, nil, 0, 0, err
}
2024-04-01 13:39:17 +00:00
callOpt := &bind.CallOpts{}
2022-12-19 12:37:37 +00:00
2024-04-01 13:39:17 +00:00
l1FeeResult, _ := oracleContract.GetL1Fee(callOpt, data)
l1Fee = l1FeeResult.Uint64()
}
2024-04-01 13:39:17 +00:00
return true, amountIn, estimate, l1Fee, nil
2022-09-13 07:10:59 +00:00
}
func (r *Router) getBalance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
2023-02-20 09:32:45 +00:00
client, err := r.s.rpcClient.EthClient(network.ChainID)
2022-09-13 07:10:59 +00:00
if err != nil {
return nil, err
}
2023-02-20 09:32:45 +00:00
return r.s.tokenManager.GetBalance(ctx, client, account, token.Address)
2022-09-13 07:10:59 +00:00
}
2024-02-06 11:36:25 +00:00
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")
}
2024-03-05 18:57:02 +00:00
balances, err := r.s.collectiblesManager.FetchERC1155Balances(
ctx,
account,
walletCommon.ChainID(network.ChainID),
token.Address,
[]*bigint.BigInt{&bigint.BigInt{Int: tokenID}},
)
2024-02-06 11:36:25 +00:00
if err != nil {
return nil, err
}
2024-03-05 18:57:02 +00:00
if len(balances) != 1 || balances[0] == nil {
return nil, errors.New("invalid ERC1155 balance fetch response")
}
return balances[0].Int, nil
2024-02-06 11:36:25 +00:00
}
2022-11-23 17:49:23 +00:00
func (r *Router) suggestedRoutes(
ctx context.Context,
sendType SendType,
addrFrom common.Address,
addrTo common.Address,
2022-11-23 17:49:23 +00:00
amountIn *big.Int,
2023-08-24 08:45:14 +00:00
tokenID string,
2024-04-01 13:39:17 +00:00
toTokenID string,
2022-11-23 17:49:23 +00:00
disabledFromChainIDs,
disabledToChaindIDs,
preferedChainIDs []uint64,
gasFeeMode GasFeeMode,
fromLockedAmount map[uint64]*hexutil.Big,
) (*SuggestedRoutes, error) {
areTestNetworksEnabled, err := r.s.accountsDB.GetTestNetworksEnabled()
if err != nil {
return nil, err
}
networks, err := r.s.rpcClient.NetworkManager.Get(false)
if err != nil {
return nil, err
}
2023-08-24 08:45:14 +00:00
prices, err := sendType.FetchPrices(r.s, tokenID)
2022-09-13 07:10:59 +00:00
if err != nil {
return nil, err
}
var (
group = async.NewAtomicGroup(ctx)
mu sync.Mutex
2022-09-13 07:10:59 +00:00
candidates = make([]*Path, 0)
)
2024-04-01 13:39:17 +00:00
for networkIdx := range networks {
network := networks[networkIdx]
if network.IsTest != areTestNetworksEnabled {
continue
}
2022-10-25 14:25:08 +00:00
if containsNetworkChainID(network, disabledFromChainIDs) {
2022-09-13 07:10:59 +00:00
continue
}
if !sendType.isAvailableFor(network) {
continue
}
2023-08-24 08:45:14 +00:00
token := sendType.FindToken(r.s, addrFrom, network, tokenID)
2022-09-13 07:10:59 +00:00
if token == nil {
continue
}
2024-04-01 13:39:17 +00:00
var toToken *walletToken.Token
if sendType == Swap {
toToken = sendType.FindToken(r.s, common.Address{}, network, toTokenID)
}
2022-09-13 07:10:59 +00:00
nativeToken := r.s.tokenManager.FindToken(network, network.NativeCurrencySymbol)
if nativeToken == nil {
continue
}
group.Add(func(c context.Context) error {
2022-09-13 07:10:59 +00:00
gasFees, err := r.s.feesManager.suggestedFees(ctx, network.ChainID)
if err != nil {
return err
}
2023-08-24 08:45:14 +00:00
// 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)
2024-02-06 11:36:25 +00:00
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, token, addrFrom)
2023-08-24 08:45:14 +00:00
if err != nil {
return err
}
2022-09-13 07:10:59 +00:00
}
2022-11-23 17:49:23 +00:00
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, nativeToken, addrFrom)
2022-09-13 07:10:59 +00:00
if err != nil {
return err
}
2022-09-13 07:10:59 +00:00
maxFees := gasFees.feeFor(gasFeeMode)
estimatedTime := r.s.feesManager.transactionEstimatedTime(ctx, network.ChainID, maxFees)
for _, bridge := range r.bridges {
2023-08-24 08:45:14 +00:00
if !sendType.canUseBridge(bridge) {
continue
}
2022-09-13 07:10:59 +00:00
for _, dest := range networks {
if dest.IsTest != areTestNetworksEnabled {
continue
}
if !sendType.isAvailableFor(network) {
continue
}
2022-11-24 12:58:53 +00:00
if !sendType.isAvailableBetween(network, dest) {
continue
}
2022-12-01 12:41:24 +00:00
if len(preferedChainIDs) > 0 && !containsNetworkChainID(dest, preferedChainIDs) {
2022-09-13 07:10:59 +00:00
continue
}
2022-10-25 14:25:08 +00:00
if containsNetworkChainID(dest, disabledToChaindIDs) {
2022-09-13 07:10:59 +00:00
continue
}
2024-04-01 13:39:17 +00:00
can, err := bridge.Can(network, dest, token, toToken, maxAmountIn.ToInt())
2022-09-13 07:10:59 +00:00
if err != nil || !can {
continue
}
2023-08-24 08:45:14 +00:00
bonderFees, tokenFees, err := bridge.CalculateFees(network, dest, token, amountIn, prices["ETH"], prices[tokenID], gasFees.GasPrice)
2022-09-13 07:10:59 +00:00
if err != nil {
continue
}
if bonderFees.Cmp(zero) != 0 {
if maxAmountIn.ToInt().Cmp(amountIn) >= 0 {
if bonderFees.Cmp(amountIn) >= 0 {
continue
}
} else {
if bonderFees.Cmp(maxAmountIn.ToInt()) >= 0 {
continue
}
}
}
2022-09-13 07:10:59 +00:00
gasLimit := uint64(0)
if sendType.isTransfer() {
2024-04-01 13:39:17 +00:00
gasLimit, err = bridge.EstimateGas(network, dest, addrFrom, addrTo, token, toToken, amountIn)
2022-09-13 07:10:59 +00:00
if err != nil {
continue
}
} else {
gasLimit = sendType.EstimateGas(r.s, network, addrFrom, tokenID)
2022-09-13 07:10:59 +00:00
}
2023-11-16 10:12:01 +00:00
2024-04-01 13:39:17 +00:00
approvalContractAddress := bridge.GetContractAddress(network, token)
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, sendType, approvalContractAddress, addrFrom, network, token, amountIn)
if err != nil {
2022-09-13 07:10:59 +00:00
continue
}
2024-04-01 13:39:17 +00:00
var l1GasFeeWei uint64
if sendType.needL1Fee() {
tx, err := bridge.BuildTx(network, addrFrom, addrTo, token, amountIn)
if err != nil {
continue
}
l1GasFeeWei, _ = r.s.feesManager.getL1Fee(ctx, network.ChainID, tx)
l1GasFeeWei += l1ApprovalFee
}
2024-04-01 13:39:17 +00:00
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"])
2022-12-19 12:37:37 +00:00
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)
2022-12-19 12:37:37 +00:00
2022-09-13 07:10:59 +00:00
gasCost := new(big.Float)
gasCost.Mul(new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat(float64(gasLimit))), ethPrice)
2022-12-19 12:37:37 +00:00
2022-09-13 07:10:59 +00:00
tokenFeesAsFloat := new(big.Float).Quo(
new(big.Float).SetInt(tokenFees),
big.NewFloat(math.Pow(10, float64(token.Decimals))),
)
tokenCost := new(big.Float)
2023-08-24 08:45:14 +00:00
tokenCost.Mul(tokenFeesAsFloat, big.NewFloat(prices[tokenID]))
2022-12-19 12:37:37 +00:00
2022-09-13 07:10:59 +00:00
cost := new(big.Float)
cost.Add(tokenCost, gasCost)
2022-12-19 12:37:37 +00:00
cost.Add(cost, approvalGasCost)
cost.Add(cost, l1GasCost)
mu.Lock()
2022-09-13 07:10:59 +00:00
candidates = append(candidates, &Path{
2022-12-19 12:37:37 +00:00
BridgeName: bridge.Name(),
From: network,
To: dest,
MaxAmountIn: maxAmountIn,
AmountIn: (*hexutil.Big)(zero),
AmountOut: (*hexutil.Big)(zero),
GasAmount: gasLimit,
GasFees: gasFees,
BonderFees: (*hexutil.Big)(bonderFees),
TokenFees: tokenFeesAsFloat,
Cost: cost,
EstimatedTime: estimatedTime,
ApprovalRequired: approvalRequired,
ApprovalGasFees: approvalGasFees,
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
ApprovalContractAddress: approvalContractAddress,
2022-09-13 07:10:59 +00:00
})
mu.Unlock()
}
}
return nil
})
}
group.Wait()
2022-09-13 07:10:59 +00:00
2022-11-23 17:49:23 +00:00
suggestedRoutes := newSuggestedRoutes(amountIn, candidates, fromLockedAmount)
2023-08-24 08:45:14 +00:00
suggestedRoutes.TokenPrice = prices[tokenID]
2022-09-13 07:10:59 +00:00
suggestedRoutes.NativeChainTokenPrice = prices["ETH"]
for _, path := range suggestedRoutes.Best {
2023-08-24 08:45:14 +00:00
amountOut, err := r.bridges[path.BridgeName].CalculateAmountOut(path.From, path.To, (*big.Int)(path.AmountIn), tokenID)
2022-09-13 07:10:59 +00:00
if err != nil {
continue
}
path.AmountOut = (*hexutil.Big)(amountOut)
}
2023-08-24 08:45:14 +00:00
2022-09-13 07:10:59 +00:00
return suggestedRoutes, nil
}