status-go/services/wallet/router/pathprocessor/processor_bridge_celar.go

394 lines
11 KiB
Go
Raw Normal View History

package pathprocessor
2022-11-22 13:49:29 +00:00
import (
"context"
2022-11-22 13:49:29 +00:00
"encoding/json"
"errors"
"fmt"
"math/big"
"net/url"
"strconv"
"strings"
2022-11-22 13:49:29 +00:00
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
2022-11-22 13:49:29 +00:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts/celer"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/rpc"
statusErrors "github.com/status-im/status-go/errors"
2022-11-22 13:49:29 +00:00
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/router/pathprocessor/cbridge"
"github.com/status-im/status-go/services/wallet/thirdparty"
2022-11-22 13:49:29 +00:00
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/transactions"
)
2024-06-03 10:30:29 +00:00
const (
baseURL = "https://cbridge-prod2.celer.app"
testBaseURL = "https://cbridge-v2-test.celer.network"
maxSlippage = uint32(1000)
)
2022-11-22 13:49:29 +00:00
type CelerBridgeTxArgs struct {
2022-11-22 13:49:29 +00:00
transactions.SendTxArgs
ChainID uint64 `json:"chainId"`
Symbol string `json:"symbol"`
Recipient common.Address `json:"recipient"`
Amount *hexutil.Big `json:"amount"`
}
type CelerBridgeProcessor struct {
2022-11-22 13:49:29 +00:00
rpcClient *rpc.Client
httpClient *thirdparty.HTTPClient
transactor transactions.TransactorIface
2022-11-22 13:49:29 +00:00
tokenManager *token.Manager
prodTransferConfig *cbridge.GetTransferConfigsResponse
testTransferConfig *cbridge.GetTransferConfigsResponse
}
func NewCelerBridgeProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *token.Manager) *CelerBridgeProcessor {
return &CelerBridgeProcessor{
2022-11-22 13:49:29 +00:00
rpcClient: rpcClient,
httpClient: thirdparty.NewHTTPClient(),
2022-12-19 12:37:37 +00:00
transactor: transactor,
2022-11-22 13:49:29 +00:00
tokenManager: tokenManager,
}
}
func (s *CelerBridgeProcessor) Name() string {
return ProcessorBridgeCelerName
2022-11-22 13:49:29 +00:00
}
func (s *CelerBridgeProcessor) estimateAmt(from, to *params.Network, amountIn *big.Int, symbol string) (*cbridge.EstimateAmtResponse, error) {
2022-11-22 13:49:29 +00:00
base := baseURL
if from.IsTest {
base = testBaseURL
}
params := url.Values{}
params.Add("src_chain_id", strconv.Itoa(int(from.ChainID)))
params.Add("dst_chain_id", strconv.Itoa(int(to.ChainID)))
params.Add("token_symbol", symbol)
params.Add("amt", amountIn.String())
params.Add("usr_addr", "0xaa47c83316edc05cf9ff7136296b026c5de7eccd")
params.Add("slippage_tolerance", "500")
2022-11-22 13:49:29 +00:00
url := fmt.Sprintf("%s/v2/estimateAmt", base)
response, err := s.httpClient.DoGetRequest(context.Background(), url, params)
2022-11-22 13:49:29 +00:00
if err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
var res cbridge.EstimateAmtResponse
err = json.Unmarshal(response, &res)
2022-11-22 13:49:29 +00:00
if err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
return &res, nil
}
func (s *CelerBridgeProcessor) getTransferConfig(isTest bool) (*cbridge.GetTransferConfigsResponse, error) {
2022-11-22 13:49:29 +00:00
if !isTest && s.prodTransferConfig != nil {
return s.prodTransferConfig, nil
}
if isTest && s.testTransferConfig != nil {
return s.testTransferConfig, nil
}
base := baseURL
if isTest {
base = testBaseURL
}
url := fmt.Sprintf("%s/v2/getTransferConfigs", base)
response, err := s.httpClient.DoGetRequest(context.Background(), url, nil)
2022-11-22 13:49:29 +00:00
if err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
var res cbridge.GetTransferConfigsResponse
err = json.Unmarshal(response, &res)
2022-11-22 13:49:29 +00:00
if err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
if isTest {
s.testTransferConfig = &res
} else {
s.prodTransferConfig = &res
}
return &res, nil
}
func (s *CelerBridgeProcessor) AvailableFor(params ProcessorInputParams) (bool, error) {
2024-06-05 07:56:02 +00:00
if params.FromChain.ChainID == params.ToChain.ChainID || params.ToToken != nil {
2022-11-22 13:49:29 +00:00
return false, nil
}
2024-06-05 07:56:02 +00:00
transferConfig, err := s.getTransferConfig(params.FromChain.IsTest)
2022-11-22 13:49:29 +00:00
if err != nil {
return false, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
if transferConfig.Err != nil {
return false, statusErrors.CreateErrorResponseFromError(errors.New(transferConfig.Err.Msg))
2022-11-22 13:49:29 +00:00
}
var fromAvailable *cbridge.Chain
var toAvailable *cbridge.Chain
for _, chain := range transferConfig.Chains {
2024-06-05 07:56:02 +00:00
if uint64(chain.GetId()) == params.FromChain.ChainID && chain.GasTokenSymbol == EthSymbol {
2022-11-22 13:49:29 +00:00
fromAvailable = chain
}
2024-06-05 07:56:02 +00:00
if uint64(chain.GetId()) == params.ToChain.ChainID && chain.GasTokenSymbol == EthSymbol {
2022-11-22 13:49:29 +00:00
toAvailable = chain
}
}
if fromAvailable == nil || toAvailable == nil {
return false, nil
}
found := false
2023-07-31 10:44:05 +00:00
if _, ok := transferConfig.ChainToken[fromAvailable.GetId()]; !ok {
return false, nil
}
2022-11-22 13:49:29 +00:00
for _, tokenInfo := range transferConfig.ChainToken[fromAvailable.GetId()].Token {
2024-06-05 07:56:02 +00:00
if tokenInfo.Token.Symbol == params.FromToken.Symbol {
2022-11-22 13:49:29 +00:00
found = true
break
}
}
if !found {
return false, nil
}
found = false
for _, tokenInfo := range transferConfig.ChainToken[toAvailable.GetId()].Token {
2024-06-05 07:56:02 +00:00
if tokenInfo.Token.Symbol == params.FromToken.Symbol {
2022-11-22 13:49:29 +00:00
found = true
break
}
}
2024-06-05 07:56:02 +00:00
2022-11-22 13:49:29 +00:00
if !found {
return false, nil
}
2024-06-05 07:56:02 +00:00
2022-11-22 13:49:29 +00:00
return true, nil
}
func (s *CelerBridgeProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) {
2024-06-05 07:56:02 +00:00
amt, err := s.estimateAmt(params.FromChain, params.ToChain, params.AmountIn, params.FromToken.Symbol)
2022-11-22 13:49:29 +00:00
if err != nil {
return nil, nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
baseFee, ok := new(big.Int).SetString(amt.BaseFee, 10)
if !ok {
return nil, nil, ErrFailedToParseBaseFee
}
percFee, ok := new(big.Int).SetString(amt.PercFee, 10)
if !ok {
return nil, nil, ErrFailedToParsePercentageFee
}
2022-11-22 13:49:29 +00:00
return ZeroBigIntValue, new(big.Int).Add(baseFee, percFee), nil
2022-11-22 13:49:29 +00:00
}
func (c *CelerBridgeProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) {
abi, err := abi.JSON(strings.NewReader(celer.CelerABI))
if err != nil {
return []byte{}, statusErrors.CreateErrorResponseFromError(err)
}
2024-06-05 07:56:02 +00:00
if params.FromToken.IsNative() {
return abi.Pack("sendNative",
2024-06-05 07:56:02 +00:00
params.ToAddr,
params.AmountIn,
params.ToChain.ChainID,
uint64(time.Now().UnixMilli()),
2024-06-03 10:30:29 +00:00
maxSlippage,
)
} else {
return abi.Pack("send",
2024-06-05 07:56:02 +00:00
params.ToAddr,
params.FromToken.Address,
params.AmountIn,
params.ToChain.ChainID,
uint64(time.Now().UnixMilli()),
2024-06-03 10:30:29 +00:00
maxSlippage,
)
}
}
func (s *CelerBridgeProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) {
if params.TestsMode {
if params.TestEstimationMap != nil {
if val, ok := params.TestEstimationMap[s.Name()]; ok {
return val, nil
}
}
return 0, ErrNoEstimationFound
}
value := new(big.Int)
input, err := s.PackTxInputData(params)
if err != nil {
return 0, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
2024-06-05 07:56:02 +00:00
contractAddress, err := s.GetContractAddress(params)
2024-05-23 12:38:39 +00:00
if err != nil {
return 0, statusErrors.CreateErrorResponseFromError(err)
}
2024-06-05 07:56:02 +00:00
ethClient, err := s.rpcClient.EthClient(params.FromChain.ChainID)
if err != nil {
return 0, statusErrors.CreateErrorResponseFromError(err)
}
ctx := context.Background()
msg := ethereum.CallMsg{
2024-06-05 07:56:02 +00:00
From: params.FromAddr,
2024-05-23 12:38:39 +00:00
To: &contractAddress,
Value: value,
Data: input,
}
estimation, err := ethClient.EstimateGas(ctx, msg)
if err != nil {
2024-06-05 07:56:02 +00:00
if !params.FromToken.IsNative() {
2024-06-03 10:30:29 +00:00
// TODO: this is a temporary solution until we find a better way to estimate the gas
// hardcoding the estimation for other than ETH, cause we cannot get a proper estimation without having an approval placed first
// this is an error we're facing otherwise: `execution reverted: ERC20: transfer amount exceeds allowance`
estimation = 350000
} else {
return 0, statusErrors.CreateErrorResponseFromError(err)
2024-06-03 10:30:29 +00:00
}
}
increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor
return uint64(increasedEstimation), nil
2022-11-22 13:49:29 +00:00
}
func (s *CelerBridgeProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) {
2024-06-05 07:56:02 +00:00
toAddr := types.Address(params.ToAddr)
sendArgs := &MultipathProcessorTxArgs{
CbridgeTx: &CelerBridgeTxArgs{
SendTxArgs: transactions.SendTxArgs{
2024-06-05 07:56:02 +00:00
From: types.Address(params.FromAddr),
To: &toAddr,
2024-06-05 07:56:02 +00:00
Value: (*hexutil.Big)(params.AmountIn),
Data: types.HexBytes("0x0"),
},
2024-06-05 07:56:02 +00:00
ChainID: params.ToChain.ChainID,
Symbol: params.FromToken.Symbol,
Recipient: params.ToAddr,
Amount: (*hexutil.Big)(params.AmountIn),
},
2024-06-05 07:56:02 +00:00
ChainID: params.FromChain.ChainID,
}
return s.BuildTransaction(sendArgs)
}
func (s *CelerBridgeProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) {
2024-06-05 07:56:02 +00:00
transferConfig, err := s.getTransferConfig(params.FromChain.IsTest)
2022-12-19 12:37:37 +00:00
if err != nil {
return common.Address{}, statusErrors.CreateErrorResponseFromError(err)
2022-12-19 12:37:37 +00:00
}
if transferConfig.Err != nil {
return common.Address{}, statusErrors.CreateErrorResponseFromError(errors.New(transferConfig.Err.Msg))
2022-12-19 12:37:37 +00:00
}
for _, chain := range transferConfig.Chains {
2024-06-05 07:56:02 +00:00
if uint64(chain.Id) == params.FromChain.ChainID {
2024-05-23 12:38:39 +00:00
return common.HexToAddress(chain.ContractAddr), nil
2022-12-19 12:37:37 +00:00
}
}
return common.Address{}, ErrContractNotFound
2022-12-19 12:37:37 +00:00
}
func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn) (*ethTypes.Transaction, error) {
2024-06-05 07:56:02 +00:00
fromChain := s.rpcClient.NetworkManager.Find(sendArgs.ChainID)
if fromChain == nil {
return nil, ErrNetworkNotFound
2022-11-22 13:49:29 +00:00
}
2024-06-05 07:56:02 +00:00
token := s.tokenManager.FindToken(fromChain, sendArgs.CbridgeTx.Symbol)
if token == nil {
return nil, ErrTokenNotFound
2022-11-22 13:49:29 +00:00
}
addrs, err := s.GetContractAddress(ProcessorInputParams{
2024-06-05 07:56:02 +00:00
FromChain: fromChain,
})
2024-05-23 12:38:39 +00:00
if err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
backend, err := s.rpcClient.EthClient(sendArgs.ChainID)
if err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
2024-05-23 12:38:39 +00:00
contract, err := celer.NewCeler(addrs, backend)
2022-11-22 13:49:29 +00:00
if err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
txOpts := sendArgs.CbridgeTx.ToTransactOpts(signerFn)
if token.IsNative() {
return contract.SendNative(
2022-11-22 13:49:29 +00:00
txOpts,
sendArgs.CbridgeTx.Recipient,
(*big.Int)(sendArgs.CbridgeTx.Amount),
sendArgs.CbridgeTx.ChainID,
uint64(time.Now().UnixMilli()),
2024-06-03 10:30:29 +00:00
maxSlippage,
2022-11-22 13:49:29 +00:00
)
}
return contract.Send(
txOpts,
sendArgs.CbridgeTx.Recipient,
token.Address,
(*big.Int)(sendArgs.CbridgeTx.Amount),
sendArgs.CbridgeTx.ChainID,
uint64(time.Now().UnixMilli()),
2024-06-03 10:30:29 +00:00
maxSlippage,
)
}
func (s *CelerBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (types.Hash, error) {
tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount))
2022-11-22 13:49:29 +00:00
if err != nil {
return types.HexToHash(""), statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
return types.Hash(tx.Hash()), nil
}
func (s *CelerBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) {
return s.sendOrBuild(sendArgs, nil)
}
func (s *CelerBridgeProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) {
2024-06-05 07:56:02 +00:00
amt, err := s.estimateAmt(params.FromChain, params.ToChain, params.AmountIn, params.FromToken.Symbol)
2022-11-22 13:49:29 +00:00
if err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
if amt.Err != nil {
return nil, statusErrors.CreateErrorResponseFromError(err)
2022-11-22 13:49:29 +00:00
}
amountOut, _ := new(big.Int).SetString(amt.EqValueTokenAmt, 10)
return amountOut, nil
}