github.com/status-im/status-go@v1.1.0/services/wallet/router/pathprocessor/processor_bridge_celar.go (about)

     1  package pathprocessor
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  	"net/url"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/ethereum/go-ethereum"
    15  	"github.com/ethereum/go-ethereum/accounts/abi"
    16  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    17  	"github.com/ethereum/go-ethereum/common"
    18  	"github.com/ethereum/go-ethereum/common/hexutil"
    19  	ethTypes "github.com/ethereum/go-ethereum/core/types"
    20  	"github.com/status-im/status-go/account"
    21  	"github.com/status-im/status-go/contracts/celer"
    22  	"github.com/status-im/status-go/eth-node/types"
    23  	"github.com/status-im/status-go/rpc"
    24  
    25  	"github.com/status-im/status-go/params"
    26  	"github.com/status-im/status-go/services/wallet/router/pathprocessor/cbridge"
    27  	"github.com/status-im/status-go/services/wallet/thirdparty"
    28  	"github.com/status-im/status-go/services/wallet/token"
    29  	"github.com/status-im/status-go/transactions"
    30  )
    31  
    32  const (
    33  	baseURL     = "https://cbridge-prod2.celer.app"
    34  	testBaseURL = "https://cbridge-v2-test.celer.network"
    35  
    36  	maxSlippage = uint32(1000)
    37  )
    38  
    39  type CelerBridgeTxArgs struct {
    40  	transactions.SendTxArgs
    41  	ChainID   uint64         `json:"chainId"`
    42  	Symbol    string         `json:"symbol"`
    43  	Recipient common.Address `json:"recipient"`
    44  	Amount    *hexutil.Big   `json:"amount"`
    45  }
    46  
    47  type CelerBridgeProcessor struct {
    48  	rpcClient          *rpc.Client
    49  	httpClient         *thirdparty.HTTPClient
    50  	transactor         transactions.TransactorIface
    51  	tokenManager       *token.Manager
    52  	prodTransferConfig *cbridge.GetTransferConfigsResponse
    53  	testTransferConfig *cbridge.GetTransferConfigsResponse
    54  }
    55  
    56  func NewCelerBridgeProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *token.Manager) *CelerBridgeProcessor {
    57  	return &CelerBridgeProcessor{
    58  		rpcClient:    rpcClient,
    59  		httpClient:   thirdparty.NewHTTPClient(),
    60  		transactor:   transactor,
    61  		tokenManager: tokenManager,
    62  	}
    63  }
    64  
    65  func createBridgeCellerErrorResponse(err error) error {
    66  	return createErrorResponse(ProcessorBridgeCelerName, err)
    67  }
    68  
    69  func (s *CelerBridgeProcessor) Name() string {
    70  	return ProcessorBridgeCelerName
    71  }
    72  
    73  func (s *CelerBridgeProcessor) estimateAmt(from, to *params.Network, amountIn *big.Int, symbol string) (*cbridge.EstimateAmtResponse, error) {
    74  	base := baseURL
    75  	if from.IsTest {
    76  		base = testBaseURL
    77  	}
    78  
    79  	params := url.Values{}
    80  	params.Add("src_chain_id", strconv.Itoa(int(from.ChainID)))
    81  	params.Add("dst_chain_id", strconv.Itoa(int(to.ChainID)))
    82  	params.Add("token_symbol", symbol)
    83  	params.Add("amt", amountIn.String())
    84  	params.Add("usr_addr", "0xaa47c83316edc05cf9ff7136296b026c5de7eccd")
    85  	params.Add("slippage_tolerance", "500")
    86  
    87  	url := fmt.Sprintf("%s/v2/estimateAmt", base)
    88  	response, err := s.httpClient.DoGetRequest(context.Background(), url, params, nil)
    89  	if err != nil {
    90  		return nil, createBridgeCellerErrorResponse(err)
    91  	}
    92  
    93  	var res cbridge.EstimateAmtResponse
    94  	err = json.Unmarshal(response, &res)
    95  	if err != nil {
    96  		return nil, createBridgeCellerErrorResponse(err)
    97  	}
    98  	return &res, nil
    99  }
   100  
   101  func (s *CelerBridgeProcessor) getTransferConfig(isTest bool) (*cbridge.GetTransferConfigsResponse, error) {
   102  	if !isTest && s.prodTransferConfig != nil {
   103  		return s.prodTransferConfig, nil
   104  	}
   105  
   106  	if isTest && s.testTransferConfig != nil {
   107  		return s.testTransferConfig, nil
   108  	}
   109  
   110  	base := baseURL
   111  	if isTest {
   112  		base = testBaseURL
   113  	}
   114  	url := fmt.Sprintf("%s/v2/getTransferConfigs", base)
   115  	response, err := s.httpClient.DoGetRequest(context.Background(), url, nil, nil)
   116  	if err != nil {
   117  		return nil, createBridgeCellerErrorResponse(err)
   118  	}
   119  
   120  	var res cbridge.GetTransferConfigsResponse
   121  	err = json.Unmarshal(response, &res)
   122  	if err != nil {
   123  		return nil, createBridgeCellerErrorResponse(err)
   124  	}
   125  	if isTest {
   126  		s.testTransferConfig = &res
   127  	} else {
   128  		s.prodTransferConfig = &res
   129  	}
   130  	return &res, nil
   131  }
   132  
   133  func (s *CelerBridgeProcessor) AvailableFor(params ProcessorInputParams) (bool, error) {
   134  	if params.FromChain.ChainID == params.ToChain.ChainID || params.ToToken != nil {
   135  		return false, nil
   136  	}
   137  
   138  	transferConfig, err := s.getTransferConfig(params.FromChain.IsTest)
   139  	if err != nil {
   140  		return false, createBridgeCellerErrorResponse(err)
   141  	}
   142  	if transferConfig.Err != nil {
   143  		return false, createBridgeCellerErrorResponse(errors.New(transferConfig.Err.Msg))
   144  	}
   145  
   146  	var fromAvailable *cbridge.Chain
   147  	var toAvailable *cbridge.Chain
   148  	for _, chain := range transferConfig.Chains {
   149  		if uint64(chain.GetId()) == params.FromChain.ChainID && chain.GasTokenSymbol == EthSymbol {
   150  			fromAvailable = chain
   151  		}
   152  
   153  		if uint64(chain.GetId()) == params.ToChain.ChainID && chain.GasTokenSymbol == EthSymbol {
   154  			toAvailable = chain
   155  		}
   156  	}
   157  
   158  	if fromAvailable == nil || toAvailable == nil {
   159  		return false, nil
   160  	}
   161  
   162  	found := false
   163  	if _, ok := transferConfig.ChainToken[fromAvailable.GetId()]; !ok {
   164  		return false, nil
   165  	}
   166  
   167  	for _, tokenInfo := range transferConfig.ChainToken[fromAvailable.GetId()].Token {
   168  		if tokenInfo.Token.Symbol == params.FromToken.Symbol {
   169  			found = true
   170  			break
   171  		}
   172  	}
   173  	if !found {
   174  		return false, nil
   175  	}
   176  
   177  	found = false
   178  	for _, tokenInfo := range transferConfig.ChainToken[toAvailable.GetId()].Token {
   179  		if tokenInfo.Token.Symbol == params.FromToken.Symbol {
   180  			found = true
   181  			break
   182  		}
   183  	}
   184  
   185  	if !found {
   186  		return false, nil
   187  	}
   188  
   189  	return true, nil
   190  }
   191  
   192  func (s *CelerBridgeProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) {
   193  	amt, err := s.estimateAmt(params.FromChain, params.ToChain, params.AmountIn, params.FromToken.Symbol)
   194  	if err != nil {
   195  		return nil, nil, createBridgeCellerErrorResponse(err)
   196  	}
   197  	baseFee, ok := new(big.Int).SetString(amt.BaseFee, 10)
   198  	if !ok {
   199  		return nil, nil, ErrFailedToParseBaseFee
   200  	}
   201  	percFee, ok := new(big.Int).SetString(amt.PercFee, 10)
   202  	if !ok {
   203  		return nil, nil, ErrFailedToParsePercentageFee
   204  	}
   205  
   206  	return ZeroBigIntValue, new(big.Int).Add(baseFee, percFee), nil
   207  }
   208  
   209  func (c *CelerBridgeProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) {
   210  	abi, err := abi.JSON(strings.NewReader(celer.CelerABI))
   211  	if err != nil {
   212  		return []byte{}, createBridgeCellerErrorResponse(err)
   213  	}
   214  
   215  	if params.FromToken.IsNative() {
   216  		return abi.Pack("sendNative",
   217  			params.ToAddr,
   218  			params.AmountIn,
   219  			params.ToChain.ChainID,
   220  			uint64(time.Now().UnixMilli()),
   221  			maxSlippage,
   222  		)
   223  	} else {
   224  		return abi.Pack("send",
   225  			params.ToAddr,
   226  			params.FromToken.Address,
   227  			params.AmountIn,
   228  			params.ToChain.ChainID,
   229  			uint64(time.Now().UnixMilli()),
   230  			maxSlippage,
   231  		)
   232  	}
   233  }
   234  
   235  func (s *CelerBridgeProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) {
   236  	if params.TestsMode {
   237  		if params.TestEstimationMap != nil {
   238  			if val, ok := params.TestEstimationMap[s.Name()]; ok {
   239  				return val.Value, val.Err
   240  			}
   241  		}
   242  		return 0, ErrNoEstimationFound
   243  	}
   244  
   245  	value := new(big.Int)
   246  
   247  	input, err := s.PackTxInputData(params)
   248  	if err != nil {
   249  		return 0, createBridgeCellerErrorResponse(err)
   250  	}
   251  
   252  	contractAddress, err := s.GetContractAddress(params)
   253  	if err != nil {
   254  		return 0, createBridgeCellerErrorResponse(err)
   255  	}
   256  
   257  	ethClient, err := s.rpcClient.EthClient(params.FromChain.ChainID)
   258  	if err != nil {
   259  		return 0, createBridgeCellerErrorResponse(err)
   260  	}
   261  
   262  	ctx := context.Background()
   263  
   264  	msg := ethereum.CallMsg{
   265  		From:  params.FromAddr,
   266  		To:    &contractAddress,
   267  		Value: value,
   268  		Data:  input,
   269  	}
   270  
   271  	estimation, err := ethClient.EstimateGas(ctx, msg)
   272  	if err != nil {
   273  		if !params.FromToken.IsNative() {
   274  			// TODO: this is a temporary solution until we find a better way to estimate the gas
   275  			// hardcoding the estimation for other than ETH, cause we cannot get a proper estimation without having an approval placed first
   276  			// this is an error we're facing otherwise: `execution reverted: ERC20: transfer amount exceeds allowance`
   277  			estimation = 350000
   278  		} else {
   279  			return 0, createBridgeCellerErrorResponse(err)
   280  		}
   281  	}
   282  	increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor
   283  	return uint64(increasedEstimation), nil
   284  }
   285  
   286  func (s *CelerBridgeProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) {
   287  	transferConfig, err := s.getTransferConfig(params.FromChain.IsTest)
   288  	if err != nil {
   289  		return common.Address{}, createBridgeCellerErrorResponse(err)
   290  	}
   291  	if transferConfig.Err != nil {
   292  		return common.Address{}, createBridgeCellerErrorResponse(errors.New(transferConfig.Err.Msg))
   293  	}
   294  
   295  	for _, chain := range transferConfig.Chains {
   296  		if uint64(chain.Id) == params.FromChain.ChainID {
   297  			return common.HexToAddress(chain.ContractAddr), nil
   298  		}
   299  	}
   300  
   301  	return common.Address{}, ErrContractNotFound
   302  }
   303  
   304  func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (*ethTypes.Transaction, error) {
   305  	fromChain := s.rpcClient.NetworkManager.Find(sendArgs.ChainID)
   306  	if fromChain == nil {
   307  		return nil, ErrNetworkNotFound
   308  	}
   309  	token := s.tokenManager.FindToken(fromChain, sendArgs.CbridgeTx.Symbol)
   310  	if token == nil {
   311  		return nil, ErrTokenNotFound
   312  	}
   313  	addrs, err := s.GetContractAddress(ProcessorInputParams{
   314  		FromChain: fromChain,
   315  	})
   316  	if err != nil {
   317  		return nil, createBridgeCellerErrorResponse(err)
   318  	}
   319  
   320  	backend, err := s.rpcClient.EthClient(sendArgs.ChainID)
   321  	if err != nil {
   322  		return nil, createBridgeCellerErrorResponse(err)
   323  	}
   324  	contract, err := celer.NewCeler(addrs, backend)
   325  	if err != nil {
   326  		return nil, createBridgeCellerErrorResponse(err)
   327  	}
   328  
   329  	if lastUsedNonce >= 0 {
   330  		lastUsedNonceHexUtil := hexutil.Uint64(uint64(lastUsedNonce) + 1)
   331  		sendArgs.CbridgeTx.Nonce = &lastUsedNonceHexUtil
   332  	}
   333  
   334  	var tx *ethTypes.Transaction
   335  	txOpts := sendArgs.CbridgeTx.ToTransactOpts(signerFn)
   336  	if token.IsNative() {
   337  		tx, err = contract.SendNative(
   338  			txOpts,
   339  			sendArgs.CbridgeTx.Recipient,
   340  			(*big.Int)(sendArgs.CbridgeTx.Amount),
   341  			sendArgs.CbridgeTx.ChainID,
   342  			uint64(time.Now().UnixMilli()),
   343  			maxSlippage,
   344  		)
   345  	} else {
   346  		tx, err = contract.Send(
   347  			txOpts,
   348  			sendArgs.CbridgeTx.Recipient,
   349  			token.Address,
   350  			(*big.Int)(sendArgs.CbridgeTx.Amount),
   351  			sendArgs.CbridgeTx.ChainID,
   352  			uint64(time.Now().UnixMilli()),
   353  			maxSlippage,
   354  		)
   355  	}
   356  	if err != nil {
   357  		return tx, createBridgeCellerErrorResponse(err)
   358  	}
   359  	err = s.transactor.StoreAndTrackPendingTx(txOpts.From, sendArgs.CbridgeTx.Symbol, sendArgs.ChainID, sendArgs.CbridgeTx.MultiTransactionID, tx)
   360  	if err != nil {
   361  		return tx, createBridgeCellerErrorResponse(err)
   362  	}
   363  	return tx, nil
   364  }
   365  
   366  func (s *CelerBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) {
   367  	tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount), lastUsedNonce)
   368  	if err != nil {
   369  		return types.HexToHash(""), 0, createBridgeCellerErrorResponse(err)
   370  	}
   371  
   372  	return types.Hash(tx.Hash()), tx.Nonce(), nil
   373  }
   374  
   375  func (s *CelerBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) {
   376  	tx, err := s.sendOrBuild(sendArgs, nil, lastUsedNonce)
   377  	return tx, tx.Nonce(), err
   378  }
   379  
   380  func (s *CelerBridgeProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) {
   381  	amt, err := s.estimateAmt(params.FromChain, params.ToChain, params.AmountIn, params.FromToken.Symbol)
   382  	if err != nil {
   383  		return nil, createBridgeCellerErrorResponse(err)
   384  	}
   385  	if amt.Err != nil {
   386  		return nil, createBridgeCellerErrorResponse(err)
   387  	}
   388  	amountOut, _ := new(big.Int).SetString(amt.EqValueTokenAmt, 10)
   389  	return amountOut, nil
   390  }