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

     1  package pathprocessor
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/big"
     7  	"strings"
     8  
     9  	"github.com/ethereum/go-ethereum"
    10  	"github.com/ethereum/go-ethereum/accounts/abi"
    11  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/ethereum/go-ethereum/common/hexutil"
    14  	ethTypes "github.com/ethereum/go-ethereum/core/types"
    15  	"github.com/status-im/status-go/account"
    16  	"github.com/status-im/status-go/contracts/community-tokens/collectibles"
    17  	"github.com/status-im/status-go/contracts/erc721"
    18  	"github.com/status-im/status-go/eth-node/types"
    19  	"github.com/status-im/status-go/params"
    20  	"github.com/status-im/status-go/rpc"
    21  	"github.com/status-im/status-go/services/wallet/token"
    22  	"github.com/status-im/status-go/transactions"
    23  )
    24  
    25  const (
    26  	functionNameSafeTransferFrom = "safeTransferFrom"
    27  	functionNameTransferFrom     = "transferFrom"
    28  )
    29  
    30  type ERC721TxArgs struct {
    31  	transactions.SendTxArgs
    32  	TokenID   *hexutil.Big   `json:"tokenId"`
    33  	Recipient common.Address `json:"recipient"`
    34  }
    35  
    36  type ERC721Processor struct {
    37  	rpcClient  *rpc.Client
    38  	transactor transactions.TransactorIface
    39  }
    40  
    41  func NewERC721Processor(rpcClient *rpc.Client, transactor transactions.TransactorIface) *ERC721Processor {
    42  	return &ERC721Processor{rpcClient: rpcClient, transactor: transactor}
    43  }
    44  
    45  func createERC721ErrorResponse(err error) error {
    46  	return createErrorResponse(ProcessorERC721Name, err)
    47  }
    48  
    49  func (s *ERC721Processor) Name() string {
    50  	return ProcessorERC721Name
    51  }
    52  
    53  func (s *ERC721Processor) AvailableFor(params ProcessorInputParams) (bool, error) {
    54  	return params.FromChain.ChainID == params.ToChain.ChainID && params.ToToken == nil, nil
    55  }
    56  
    57  func (s *ERC721Processor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) {
    58  	return ZeroBigIntValue, ZeroBigIntValue, nil
    59  }
    60  
    61  func (s *ERC721Processor) packTxInputDataInternally(params ProcessorInputParams, functionName string) ([]byte, error) {
    62  	abi, err := abi.JSON(strings.NewReader(erc721.Erc721MetaData.ABI))
    63  	if err != nil {
    64  		return []byte{}, createERC721ErrorResponse(err)
    65  	}
    66  
    67  	id, success := big.NewInt(0).SetString(params.FromToken.Symbol, 0)
    68  	if !success {
    69  		return []byte{}, createERC721ErrorResponse(fmt.Errorf("failed to convert %s to big.Int", params.FromToken.Symbol))
    70  	}
    71  
    72  	return abi.Pack(functionName,
    73  		params.FromAddr,
    74  		params.ToAddr,
    75  		id,
    76  	)
    77  }
    78  
    79  func (s *ERC721Processor) checkIfFunctionExists(params ProcessorInputParams, functionName string) error {
    80  	data, err := s.packTxInputDataInternally(params, functionName)
    81  	if err != nil {
    82  		return createERC721ErrorResponse(err)
    83  	}
    84  
    85  	ethClient, err := s.rpcClient.EthClient(params.FromChain.ChainID)
    86  	if err != nil {
    87  		return createERC721ErrorResponse(err)
    88  	}
    89  
    90  	value := new(big.Int)
    91  	msg := ethereum.CallMsg{
    92  		From:  params.FromAddr,
    93  		To:    &params.FromToken.Address,
    94  		Value: value,
    95  		Data:  data,
    96  	}
    97  
    98  	_, err = ethClient.CallContract(context.Background(), msg, nil)
    99  	return err
   100  }
   101  
   102  func (s *ERC721Processor) PackTxInputData(params ProcessorInputParams) ([]byte, error) {
   103  	err := s.checkIfFunctionExists(params, functionNameSafeTransferFrom)
   104  	if err == nil {
   105  		return s.packTxInputDataInternally(params, functionNameSafeTransferFrom)
   106  	}
   107  
   108  	return s.packTxInputDataInternally(params, functionNameTransferFrom)
   109  }
   110  
   111  func (s *ERC721Processor) EstimateGas(params ProcessorInputParams) (uint64, error) {
   112  	if params.TestsMode {
   113  		if params.TestEstimationMap != nil {
   114  			if val, ok := params.TestEstimationMap[s.Name()]; ok {
   115  				return val.Value, val.Err
   116  			}
   117  		}
   118  		return 0, ErrNoEstimationFound
   119  	}
   120  
   121  	ethClient, err := s.rpcClient.EthClient(params.FromChain.ChainID)
   122  	if err != nil {
   123  		return 0, createERC721ErrorResponse(err)
   124  	}
   125  
   126  	value := new(big.Int)
   127  
   128  	input, err := s.PackTxInputData(params)
   129  	if err != nil {
   130  		return 0, createERC721ErrorResponse(err)
   131  	}
   132  
   133  	msg := ethereum.CallMsg{
   134  		From:  params.FromAddr,
   135  		To:    &params.FromToken.Address,
   136  		Value: value,
   137  		Data:  input,
   138  	}
   139  
   140  	estimation, err := ethClient.EstimateGas(context.Background(), msg)
   141  	if err != nil {
   142  		return 0, createERC721ErrorResponse(err)
   143  	}
   144  
   145  	increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor
   146  	return uint64(increasedEstimation), nil
   147  }
   148  
   149  func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) {
   150  	from := common.Address(sendArgs.ERC721TransferTx.From)
   151  
   152  	useSafeTransferFrom := true
   153  	inputParams := ProcessorInputParams{
   154  		FromChain: &params.Network{
   155  			ChainID: sendArgs.ChainID,
   156  		},
   157  		FromAddr: from,
   158  		ToAddr:   sendArgs.ERC721TransferTx.Recipient,
   159  		FromToken: &token.Token{
   160  			Symbol: sendArgs.ERC721TransferTx.TokenID.String(),
   161  		},
   162  	}
   163  	err = s.checkIfFunctionExists(inputParams, functionNameSafeTransferFrom)
   164  	if err != nil {
   165  		useSafeTransferFrom = false
   166  	}
   167  
   168  	ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID)
   169  	if err != nil {
   170  		return tx, createERC721ErrorResponse(err)
   171  	}
   172  
   173  	contract, err := collectibles.NewCollectibles(common.Address(*sendArgs.ERC721TransferTx.To), ethClient)
   174  	if err != nil {
   175  		return tx, createERC721ErrorResponse(err)
   176  	}
   177  
   178  	var nonce uint64
   179  	if lastUsedNonce < 0 {
   180  		nonce, err = s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC721TransferTx.From)
   181  		if err != nil {
   182  			return tx, createERC721ErrorResponse(err)
   183  		}
   184  	} else {
   185  		nonce = uint64(lastUsedNonce) + 1
   186  	}
   187  
   188  	argNonce := hexutil.Uint64(nonce)
   189  	sendArgs.ERC721TransferTx.Nonce = &argNonce
   190  	txOpts := sendArgs.ERC721TransferTx.ToTransactOpts(signerFn)
   191  	if useSafeTransferFrom {
   192  		tx, err = contract.SafeTransferFrom(txOpts, from,
   193  			sendArgs.ERC721TransferTx.Recipient,
   194  			sendArgs.ERC721TransferTx.TokenID.ToInt())
   195  	} else {
   196  		tx, err = contract.TransferFrom(txOpts, from,
   197  			sendArgs.ERC721TransferTx.Recipient,
   198  			sendArgs.ERC721TransferTx.TokenID.ToInt())
   199  	}
   200  	if err != nil {
   201  		return tx, createERC721ErrorResponse(err)
   202  	}
   203  	err = s.transactor.StoreAndTrackPendingTx(from, sendArgs.ERC721TransferTx.Symbol, sendArgs.ChainID, sendArgs.ERC721TransferTx.MultiTransactionID, tx)
   204  	if err != nil {
   205  		return tx, createERC721ErrorResponse(err)
   206  	}
   207  	return tx, nil
   208  }
   209  
   210  func (s *ERC721Processor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) {
   211  	tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC721TransferTx.From, verifiedAccount), lastUsedNonce)
   212  	if err != nil {
   213  		return hash, 0, createERC721ErrorResponse(err)
   214  	}
   215  	return types.Hash(tx.Hash()), tx.Nonce(), nil
   216  }
   217  
   218  func (s *ERC721Processor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) {
   219  	tx, err := s.sendOrBuild(sendArgs, nil, lastUsedNonce)
   220  	return tx, tx.Nonce(), err
   221  }
   222  
   223  func (s *ERC721Processor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) {
   224  	return params.AmountIn, nil
   225  }
   226  
   227  func (s *ERC721Processor) GetContractAddress(params ProcessorInputParams) (common.Address, error) {
   228  	return params.FromToken.Address, nil
   229  }