feat: expected geth sdk as a module
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
package ethereum
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
type EthereumClient interface {
|
||||
GetEthClient() *ethclient.Client
|
||||
GetRPCClient() *rpc.Client
|
||||
Close()
|
||||
IsConnected(ctx context.Context) error
|
||||
|
||||
NewBoundContract(address common.Address, abi string) (*bind.BoundContract, error)
|
||||
// Creates options for sending transactions to the blockchain.
|
||||
CreateTransactOpts(ctx context.Context, privateKey string, value *big.Int) (*bind.TransactOpts, error)
|
||||
// Creates options for reading from the blockchain.
|
||||
CreateCallOpts(ctx context.Context, blockNumber *big.Int) *bind.CallOpts
|
||||
// Waits for a transaction to be mined and returns the receipt.
|
||||
WaitForTransaction(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
||||
}
|
||||
|
||||
type Geth struct {
|
||||
ethClient *ethclient.Client
|
||||
rpcClient *rpc.Client
|
||||
url string
|
||||
}
|
||||
|
||||
type GethConfig struct {
|
||||
URL string
|
||||
RequestTimeout time.Duration
|
||||
}
|
||||
|
||||
func DefaultGethConfig() *GethConfig {
|
||||
return &GethConfig{
|
||||
RequestTimeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGeth(clientURL string) (*Geth, error) {
|
||||
if clientURL == "" {
|
||||
return nil, fmt.Errorf("client URL cannot be empty")
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.Dial(clientURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to RPC client at %s: %w", clientURL, err)
|
||||
}
|
||||
|
||||
ethClient := ethclient.NewClient(rpcClient)
|
||||
|
||||
geth := &Geth{
|
||||
ethClient: ethClient,
|
||||
rpcClient: rpcClient,
|
||||
url: clientURL,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := geth.IsConnected(ctx); err != nil {
|
||||
geth.Close()
|
||||
return nil, fmt.Errorf("connection test failed: %w", err)
|
||||
}
|
||||
|
||||
return geth, nil
|
||||
}
|
||||
|
||||
func NewGethWithConfig(config *GethConfig) (*Geth, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("config cannot be nil")
|
||||
}
|
||||
|
||||
if config.URL == "" {
|
||||
return nil, fmt.Errorf("client URL cannot be empty")
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.Dial(config.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to RPC client at %s: %w", config.URL, err)
|
||||
}
|
||||
|
||||
ethClient := ethclient.NewClient(rpcClient)
|
||||
|
||||
geth := &Geth{
|
||||
ethClient: ethClient,
|
||||
rpcClient: rpcClient,
|
||||
url: config.URL,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), config.RequestTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := geth.IsConnected(ctx); err != nil {
|
||||
geth.Close()
|
||||
return nil, fmt.Errorf("connection test failed: %w", err)
|
||||
}
|
||||
|
||||
return geth, nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetEthClient() *ethclient.Client {
|
||||
return g.ethClient
|
||||
}
|
||||
|
||||
func (g *Geth) GetRPCClient() *rpc.Client {
|
||||
return g.rpcClient
|
||||
}
|
||||
|
||||
func (g *Geth) Close() {
|
||||
if g.rpcClient != nil {
|
||||
g.rpcClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Geth) IsConnected(ctx context.Context) error {
|
||||
if g.ethClient == nil {
|
||||
return fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
_, err := g.ethClient.NetworkID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network ID: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetNetworkID(ctx context.Context) (uint64, error) {
|
||||
if g.ethClient == nil {
|
||||
return 0, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
networkID, err := g.ethClient.NetworkID(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get network ID: %w", err)
|
||||
}
|
||||
|
||||
return networkID.Uint64(), nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetChainID(ctx context.Context) (uint64, error) {
|
||||
if g.ethClient == nil {
|
||||
return 0, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
chainID, err := g.ethClient.ChainID(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get chain ID: %w", err)
|
||||
}
|
||||
|
||||
return chainID.Uint64(), nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetBlockNumber(ctx context.Context) (uint64, error) {
|
||||
if g.ethClient == nil {
|
||||
return 0, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
blockNumber, err := g.ethClient.BlockNumber(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get block number: %w", err)
|
||||
}
|
||||
|
||||
return blockNumber, nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetURL() string {
|
||||
return g.url
|
||||
}
|
||||
|
||||
func (g *Geth) NewBoundContract(address common.Address, abiJSON string) (*bind.BoundContract, error) {
|
||||
if g.ethClient == nil {
|
||||
return nil, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
parsedABI, err := parseABI(abiJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse ABI: %w", err)
|
||||
}
|
||||
|
||||
return bind.NewBoundContract(address, parsedABI, g.ethClient, g.ethClient, g.ethClient), nil
|
||||
}
|
||||
|
||||
func (g *Geth) CreateTransactOpts(ctx context.Context, privateKeyHex string, value *big.Int) (*bind.TransactOpts, error) {
|
||||
if g.ethClient == nil {
|
||||
return nil, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
privateKey, err := parsePrivateKey(privateKeyHex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
|
||||
chainID, err := g.ethClient.ChainID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get chain ID: %w", err)
|
||||
}
|
||||
|
||||
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create transactor: %w", err)
|
||||
}
|
||||
|
||||
opts.Context = ctx
|
||||
if value != nil {
|
||||
opts.Value = value
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func (g *Geth) CreateCallOpts(ctx context.Context, blockNumber *big.Int) *bind.CallOpts {
|
||||
return &bind.CallOpts{
|
||||
Context: ctx,
|
||||
BlockNumber: blockNumber,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Geth) WaitForTransaction(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||
if g.ethClient == nil {
|
||||
return nil, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
receipt, err := bind.WaitMined(ctx, g.ethClient, &types.Transaction{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to wait for transaction: %w", err)
|
||||
}
|
||||
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
func parseABI(abiJSON string) (abi.ABI, error) {
|
||||
return abi.JSON(strings.NewReader(abiJSON))
|
||||
}
|
||||
|
||||
func parsePrivateKey(privateKeyHex string) (*ecdsa.PrivateKey, error) {
|
||||
privateKeyHex = strings.TrimPrefix(privateKeyHex, "0x")
|
||||
|
||||
return crypto.HexToECDSA(privateKeyHex)
|
||||
}
|
||||
Reference in New Issue
Block a user