github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/blockchain/clients/eth.go (about)

     1  package clients
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"math/big"
     7  	"strings"
     8  
     9  	"github.com/ethereum/go-ethereum"
    10  	"github.com/ethereum/go-ethereum/common"
    11  	ethtypes "github.com/ethereum/go-ethereum/core/types"
    12  	"github.com/ethereum/go-ethereum/crypto"
    13  	"github.com/ethereum/go-ethereum/ethclient"
    14  	"github.com/pkg/errors"
    15  
    16  	"github.com/machinefi/w3bstream/pkg/enums"
    17  	optypes "github.com/machinefi/w3bstream/pkg/modules/operator/pool/types"
    18  )
    19  
    20  // EthClient is a client for ethereum compatible chain
    21  type EthClient struct {
    22  	endpoint string
    23  }
    24  
    25  // NewEthClient creates a new EthClient
    26  func NewEthClient(endpoint string) *EthClient {
    27  	return &EthClient{
    28  		endpoint: endpoint,
    29  	}
    30  }
    31  
    32  // TransactionByHash returns transaction by hash
    33  func (c *EthClient) TransactionByHash(ctx context.Context, hash string) (any, error) {
    34  	client, err := ethclient.Dial(c.endpoint)
    35  	if err != nil {
    36  		return nil, errors.Wrap(err, "dial chain address failed")
    37  	}
    38  	defer client.Close()
    39  
    40  	tx, _, err := client.TransactionByHash(ctx, common.HexToHash(hash))
    41  	if err != nil {
    42  		return nil, errors.Wrap(err, "query transaction failed")
    43  	}
    44  	return tx, nil
    45  }
    46  
    47  // TransactionState returns transaction state
    48  // TODO: refactor to reduce duplicate code with other chain clients (e.g. zksync.go)
    49  func (c *EthClient) TransactionState(ctx context.Context, hash string) (enums.TransactionState, error) {
    50  	client, err := ethclient.Dial(c.endpoint)
    51  	if err != nil {
    52  		return enums.TRANSACTION_STATE_UNKNOWN, errors.Wrap(err, "dial chain failed")
    53  	}
    54  	defer client.Close()
    55  	nh := common.HexToHash(hash)
    56  
    57  	_, p, err := client.TransactionByHash(ctx, nh)
    58  	if err != nil {
    59  		if err == ethereum.NotFound {
    60  			return enums.TRANSACTION_STATE__FAILED, nil
    61  		}
    62  		return enums.TRANSACTION_STATE_UNKNOWN, errors.Wrap(err, "get transaction by hash failed")
    63  	}
    64  	if p {
    65  		return enums.TRANSACTION_STATE__PENDING, nil
    66  	}
    67  
    68  	receipt, err := client.TransactionReceipt(ctx, nh)
    69  	if err != nil {
    70  		if err == ethereum.NotFound {
    71  			return enums.TRANSACTION_STATE__IN_BLOCK, nil
    72  		}
    73  		return enums.TRANSACTION_STATE_UNKNOWN, errors.Wrap(err, "get transaction receipt failed")
    74  	}
    75  	if receipt.Status == 0 {
    76  		return enums.TRANSACTION_STATE__FAILED, nil
    77  	}
    78  	return enums.TRANSACTION_STATE__CONFIRMED, nil
    79  }
    80  
    81  // SendTransaction sends transaction
    82  func (c *EthClient) SendTransaction(ctx context.Context, toStr, valueStr, dataStr string, op *optypes.SyncOperator) (*ethtypes.Transaction, error) {
    83  	cli, err := ethclient.Dial(c.endpoint)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	defer cli.Close()
    88  
    89  	pk := crypto.ToECDSAUnsafe(common.FromHex(op.Op.PrivateKey))
    90  	sender := crypto.PubkeyToAddress(pk.PublicKey)
    91  	to := common.HexToAddress(toStr)
    92  
    93  	value, ok := new(big.Int).SetString(valueStr, 10)
    94  	if !ok {
    95  		return nil, errors.New("fail to read tx value")
    96  	}
    97  	data, err := hex.DecodeString(strings.TrimPrefix(dataStr, "0x"))
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	gasPrice, err := cli.SuggestGasPrice(ctx)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	msg := ethereum.CallMsg{
   108  		From:     sender,
   109  		To:       &to,
   110  		GasPrice: gasPrice,
   111  		Value:    value,
   112  		Data:     data,
   113  	}
   114  	gasLimit, err := cli.EstimateGas(ctx, msg)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	chainid, err := cli.ChainID(ctx)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	nonce, err := cli.PendingNonceAt(ctx, sender)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	// Create a new transaction
   130  	tx := ethtypes.NewTx(
   131  		&ethtypes.LegacyTx{
   132  			Nonce:    nonce,
   133  			GasPrice: gasPrice,
   134  			Gas:      gasLimit,
   135  			To:       &to,
   136  			Value:    value,
   137  			Data:     data,
   138  		})
   139  
   140  	signedTx, err := ethtypes.SignTx(tx, ethtypes.NewLondonSigner(chainid), pk)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	if err = cli.SendTransaction(ctx, signedTx); err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return signedTx, nil
   150  }