2022-11-22 13:49:29 +00:00
|
|
|
package bridge
|
|
|
|
|
|
|
|
import (
|
2023-11-02 11:35:28 +00:00
|
|
|
"context"
|
2022-11-22 13:49:29 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
2024-05-16 07:37:36 +00:00
|
|
|
"net/url"
|
2024-05-14 19:11:16 +00:00
|
|
|
"strconv"
|
2023-11-02 11:35:28 +00:00
|
|
|
"strings"
|
2022-11-22 13:49:29 +00:00
|
|
|
"time"
|
|
|
|
|
2023-11-02 11:35:28 +00:00
|
|
|
"github.com/ethereum/go-ethereum"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
2023-09-29 17:56:27 +00:00
|
|
|
"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"
|
|
|
|
|
|
|
|
"github.com/status-im/status-go/params"
|
|
|
|
"github.com/status-im/status-go/services/wallet/bridge/cbridge"
|
2024-05-14 19:11:16 +00:00
|
|
|
"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)
|
|
|
|
ethSymbol = "ETH"
|
|
|
|
)
|
2022-11-22 13:49:29 +00:00
|
|
|
|
|
|
|
type CBridgeTxArgs struct {
|
|
|
|
transactions.SendTxArgs
|
|
|
|
ChainID uint64 `json:"chainId"`
|
|
|
|
Symbol string `json:"symbol"`
|
|
|
|
Recipient common.Address `json:"recipient"`
|
|
|
|
Amount *hexutil.Big `json:"amount"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type CBridge struct {
|
|
|
|
rpcClient *rpc.Client
|
2024-05-14 19:11:16 +00:00
|
|
|
httpClient *thirdparty.HTTPClient
|
2024-05-26 08:31:13 +00:00
|
|
|
transactor transactions.TransactorIface
|
2022-11-22 13:49:29 +00:00
|
|
|
tokenManager *token.Manager
|
|
|
|
prodTransferConfig *cbridge.GetTransferConfigsResponse
|
|
|
|
testTransferConfig *cbridge.GetTransferConfigsResponse
|
|
|
|
}
|
|
|
|
|
2024-05-26 08:31:13 +00:00
|
|
|
func NewCbridge(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *token.Manager) *CBridge {
|
2022-11-22 13:49:29 +00:00
|
|
|
return &CBridge{
|
|
|
|
rpcClient: rpcClient,
|
2024-05-14 19:11:16 +00:00
|
|
|
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 *CBridge) Name() string {
|
|
|
|
return "CBridge"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *CBridge) estimateAmt(from, to *params.Network, amountIn *big.Int, symbol string) (*cbridge.EstimateAmtResponse, error) {
|
|
|
|
base := baseURL
|
|
|
|
if from.IsTest {
|
|
|
|
base = testBaseURL
|
|
|
|
}
|
|
|
|
|
2024-05-16 07:37:36 +00:00
|
|
|
params := url.Values{}
|
2024-05-14 19:11:16 +00:00
|
|
|
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
|
|
|
|
2024-05-14 19:11:16 +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, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var res cbridge.EstimateAmtResponse
|
2024-05-14 19:11:16 +00:00
|
|
|
err = json.Unmarshal(response, &res)
|
2022-11-22 13:49:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *CBridge) getTransferConfig(isTest bool) (*cbridge.GetTransferConfigsResponse, error) {
|
|
|
|
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)
|
2024-05-14 19:11:16 +00:00
|
|
|
response, err := s.httpClient.DoGetRequest(context.Background(), url, nil)
|
2022-11-22 13:49:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var res cbridge.GetTransferConfigsResponse
|
2024-05-14 19:11:16 +00:00
|
|
|
err = json.Unmarshal(response, &res)
|
2022-11-22 13:49:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if isTest {
|
|
|
|
s.testTransferConfig = &res
|
|
|
|
} else {
|
|
|
|
s.prodTransferConfig = &res
|
|
|
|
}
|
|
|
|
return &res, nil
|
|
|
|
}
|
|
|
|
|
2024-05-14 19:11:16 +00:00
|
|
|
func (s *CBridge) AvailableFor(from, to *params.Network, token *token.Token, toToken *token.Token) (bool, error) {
|
2024-04-18 10:22:28 +00:00
|
|
|
if from.ChainID == to.ChainID || toToken != nil {
|
2022-11-22 13:49:29 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
transferConfig, err := s.getTransferConfig(from.IsTest)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if transferConfig.Err != nil {
|
|
|
|
return false, errors.New(transferConfig.Err.Msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
var fromAvailable *cbridge.Chain
|
|
|
|
var toAvailable *cbridge.Chain
|
|
|
|
for _, chain := range transferConfig.Chains {
|
2024-06-03 10:30:29 +00:00
|
|
|
if uint64(chain.GetId()) == from.ChainID && chain.GasTokenSymbol == ethSymbol {
|
2022-11-22 13:49:29 +00:00
|
|
|
fromAvailable = chain
|
|
|
|
}
|
|
|
|
|
2024-06-03 10:30:29 +00:00
|
|
|
if uint64(chain.GetId()) == to.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 {
|
|
|
|
if tokenInfo.Token.Symbol == token.Symbol {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
found = false
|
|
|
|
for _, tokenInfo := range transferConfig.ChainToken[toAvailable.GetId()].Token {
|
|
|
|
if tokenInfo.Token.Symbol == token.Symbol {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2024-05-14 19:11:16 +00:00
|
|
|
func (s *CBridge) CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int) (*big.Int, *big.Int, error) {
|
2022-11-22 13:49:29 +00:00
|
|
|
amt, err := s.estimateAmt(from, to, amountIn, token.Symbol)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2024-05-17 13:01:48 +00:00
|
|
|
baseFee, ok := new(big.Int).SetString(amt.BaseFee, 10)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, errors.New("failed to parse base fee")
|
|
|
|
}
|
|
|
|
percFee, ok := new(big.Int).SetString(amt.PercFee, 10)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, errors.New("failed to parse percentage fee")
|
|
|
|
}
|
2022-11-22 13:49:29 +00:00
|
|
|
|
|
|
|
return big.NewInt(0), new(big.Int).Add(baseFee, percFee), nil
|
|
|
|
}
|
|
|
|
|
2024-05-23 12:38:39 +00:00
|
|
|
func (c *CBridge) PackTxInputData(contractType string, fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error) {
|
2023-11-02 11:35:28 +00:00
|
|
|
abi, err := abi.JSON(strings.NewReader(celer.CelerABI))
|
|
|
|
if err != nil {
|
2024-05-21 14:33:36 +00:00
|
|
|
return []byte{}, err
|
2023-11-02 11:35:28 +00:00
|
|
|
}
|
|
|
|
|
2022-11-22 13:49:29 +00:00
|
|
|
if token.IsNative() {
|
2024-05-21 14:33:36 +00:00
|
|
|
return abi.Pack("sendNative",
|
2023-11-02 11:35:28 +00:00
|
|
|
to,
|
|
|
|
amountIn,
|
|
|
|
toNetwork.ChainID,
|
|
|
|
uint64(time.Now().UnixMilli()),
|
2024-06-03 10:30:29 +00:00
|
|
|
maxSlippage,
|
2023-11-02 11:35:28 +00:00
|
|
|
)
|
|
|
|
} else {
|
2024-05-21 14:33:36 +00:00
|
|
|
return abi.Pack("send",
|
2023-11-02 11:35:28 +00:00
|
|
|
to,
|
|
|
|
token.Address,
|
|
|
|
amountIn,
|
|
|
|
toNetwork.ChainID,
|
|
|
|
uint64(time.Now().UnixMilli()),
|
2024-06-03 10:30:29 +00:00
|
|
|
maxSlippage,
|
2023-11-02 11:35:28 +00:00
|
|
|
)
|
2024-05-21 14:33:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *CBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
|
|
|
|
value := new(big.Int)
|
|
|
|
|
2024-05-23 12:38:39 +00:00
|
|
|
input, err := s.PackTxInputData("", fromNetwork, toNetwork, from, to, token, amountIn)
|
2024-05-21 14:33:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
2022-11-22 13:49:29 +00:00
|
|
|
}
|
|
|
|
|
2024-05-23 12:38:39 +00:00
|
|
|
contractAddress, err := s.GetContractAddress(fromNetwork, nil)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
2023-11-02 11:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
msg := ethereum.CallMsg{
|
|
|
|
From: from,
|
2024-05-23 12:38:39 +00:00
|
|
|
To: &contractAddress,
|
2023-11-02 11:35:28 +00:00
|
|
|
Value: value,
|
|
|
|
Data: input,
|
|
|
|
}
|
|
|
|
|
|
|
|
estimation, err := ethClient.EstimateGas(ctx, msg)
|
|
|
|
if err != nil {
|
2024-06-03 10:30:29 +00:00
|
|
|
if !token.IsNative() {
|
|
|
|
// 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, err
|
|
|
|
}
|
2023-11-02 11:35:28 +00:00
|
|
|
}
|
|
|
|
increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor
|
|
|
|
return uint64(increasedEstimation), nil
|
2022-11-22 13:49:29 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 14:43:58 +00:00
|
|
|
func (s *CBridge) BuildTx(fromNetwork, toNetwork *params.Network, fromAddress common.Address, toAddress common.Address, token *token.Token, amountIn *big.Int, bonderFee *big.Int) (*ethTypes.Transaction, error) {
|
2024-03-25 12:40:00 +00:00
|
|
|
toAddr := types.Address(toAddress)
|
|
|
|
sendArgs := &TransactionBridge{
|
|
|
|
CbridgeTx: &CBridgeTxArgs{
|
|
|
|
SendTxArgs: transactions.SendTxArgs{
|
|
|
|
From: types.Address(fromAddress),
|
|
|
|
To: &toAddr,
|
|
|
|
Value: (*hexutil.Big)(amountIn),
|
|
|
|
Data: types.HexBytes("0x0"),
|
|
|
|
},
|
2024-04-25 14:43:58 +00:00
|
|
|
ChainID: toNetwork.ChainID,
|
2024-03-25 12:40:00 +00:00
|
|
|
Symbol: token.Symbol,
|
|
|
|
Recipient: toAddress,
|
|
|
|
Amount: (*hexutil.Big)(amountIn),
|
|
|
|
},
|
2024-04-25 14:43:58 +00:00
|
|
|
ChainID: fromNetwork.ChainID,
|
2024-03-25 12:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return s.BuildTransaction(sendArgs)
|
|
|
|
}
|
|
|
|
|
2024-05-23 12:38:39 +00:00
|
|
|
func (s *CBridge) GetContractAddress(network *params.Network, token *token.Token) (common.Address, error) {
|
2022-12-19 12:37:37 +00:00
|
|
|
transferConfig, err := s.getTransferConfig(network.IsTest)
|
|
|
|
if err != nil {
|
2024-05-23 12:38:39 +00:00
|
|
|
return common.Address{}, err
|
2022-12-19 12:37:37 +00:00
|
|
|
}
|
|
|
|
if transferConfig.Err != nil {
|
2024-05-23 12:38:39 +00:00
|
|
|
return common.Address{}, errors.New(transferConfig.Err.Msg)
|
2022-12-19 12:37:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, chain := range transferConfig.Chains {
|
|
|
|
if uint64(chain.Id) == network.ChainID {
|
2024-05-23 12:38:39 +00:00
|
|
|
return common.HexToAddress(chain.ContractAddr), nil
|
2022-12-19 12:37:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-23 12:38:39 +00:00
|
|
|
return common.Address{}, errors.New("contract not found")
|
2022-12-19 12:37:37 +00:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:56:27 +00:00
|
|
|
func (s *CBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (*ethTypes.Transaction, error) {
|
2022-11-22 13:49:29 +00:00
|
|
|
fromNetwork := s.rpcClient.NetworkManager.Find(sendArgs.ChainID)
|
|
|
|
if fromNetwork == nil {
|
2023-09-29 17:56:27 +00:00
|
|
|
return nil, errors.New("network not found")
|
2022-11-22 13:49:29 +00:00
|
|
|
}
|
2023-11-06 09:26:02 +00:00
|
|
|
token := s.tokenManager.FindToken(fromNetwork, sendArgs.CbridgeTx.Symbol)
|
|
|
|
if token == nil {
|
2023-09-29 17:56:27 +00:00
|
|
|
return nil, errors.New("token not found")
|
2022-11-22 13:49:29 +00:00
|
|
|
}
|
2024-05-23 12:38:39 +00:00
|
|
|
addrs, err := s.GetContractAddress(fromNetwork, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-11-22 13:49:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
backend, err := s.rpcClient.EthClient(sendArgs.ChainID)
|
|
|
|
if err != nil {
|
2023-09-29 17:56:27 +00:00
|
|
|
return nil, 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 {
|
2023-09-29 17:56:27 +00:00
|
|
|
return nil, err
|
2022-11-22 13:49:29 +00:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:56:27 +00:00
|
|
|
txOpts := sendArgs.CbridgeTx.ToTransactOpts(signerFn)
|
2023-11-06 09:26:02 +00:00
|
|
|
if token.IsNative() {
|
2023-09-29 17:56:27 +00:00
|
|
|
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
|
|
|
)
|
|
|
|
}
|
2023-09-29 17:56:27 +00:00
|
|
|
|
|
|
|
return contract.Send(
|
|
|
|
txOpts,
|
|
|
|
sendArgs.CbridgeTx.Recipient,
|
2023-11-06 09:26:02 +00:00
|
|
|
token.Address,
|
2023-09-29 17:56:27 +00:00
|
|
|
(*big.Int)(sendArgs.CbridgeTx.Amount),
|
|
|
|
sendArgs.CbridgeTx.ChainID,
|
|
|
|
uint64(time.Now().UnixMilli()),
|
2024-06-03 10:30:29 +00:00
|
|
|
maxSlippage,
|
2023-09-29 17:56:27 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *CBridge) Send(sendArgs *TransactionBridge, 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(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
return types.Hash(tx.Hash()), nil
|
|
|
|
}
|
|
|
|
|
2023-12-19 13:38:01 +00:00
|
|
|
func (s *CBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) {
|
|
|
|
return s.sendOrBuild(sendArgs, nil)
|
2023-09-29 17:56:27 +00:00
|
|
|
}
|
|
|
|
|
2022-11-22 13:49:29 +00:00
|
|
|
func (s *CBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) {
|
|
|
|
amt, err := s.estimateAmt(from, to, amountIn, symbol)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if amt.Err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
amountOut, _ := new(big.Int).SetString(amt.EqValueTokenAmt, 10)
|
|
|
|
return amountOut, nil
|
|
|
|
}
|