2020-08-03 20:01:38 +00:00
|
|
|
package rpcclient
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-08-04 18:19:42 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"io/ioutil"
|
2020-08-03 20:01:38 +00:00
|
|
|
"math/big"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum"
|
2020-08-04 18:19:42 +00:00
|
|
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
2020-08-03 20:01:38 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2020-08-04 18:19:42 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
2020-08-03 20:01:38 +00:00
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
2020-08-04 18:19:42 +00:00
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
2020-08-03 20:01:38 +00:00
|
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
2020-08-04 18:19:42 +00:00
|
|
|
"github.com/ipfs/go-log"
|
2020-08-03 20:01:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type ethereumClient struct {
|
2020-08-04 18:19:42 +00:00
|
|
|
HttpClient *ethclient.Client
|
|
|
|
WsClient *ethclient.Client
|
|
|
|
Logger *log.ZapEventLogger
|
2020-08-03 20:01:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type EthereumClient interface {
|
|
|
|
Connect(context.Context, string) error
|
2020-08-04 18:19:42 +00:00
|
|
|
Balance(context.Context, string) (*big.Int, error)
|
2020-08-03 20:01:38 +00:00
|
|
|
SubscribeOnSmartContractEvents(context.Context, string)
|
2020-08-04 18:19:42 +00:00
|
|
|
GenerateAddressFromPrivateKey(string) string
|
|
|
|
SendTransaction(string, string, int64) string
|
|
|
|
createKeyStore(string) string
|
|
|
|
importKeyStore(string, string) string
|
2020-08-03 20:01:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ethereumClient) Connect(ctx context.Context, url string, connectionType string) error {
|
|
|
|
client, err := ethclient.Dial(url)
|
|
|
|
if err != nil {
|
2020-08-04 18:19:42 +00:00
|
|
|
c.Logger.Fatal(err)
|
2020-08-03 20:01:38 +00:00
|
|
|
}
|
|
|
|
if connectionType == "websocket" {
|
2020-08-04 18:19:42 +00:00
|
|
|
c.WsClient = client
|
2020-08-03 20:01:38 +00:00
|
|
|
} else {
|
2020-08-04 18:19:42 +00:00
|
|
|
c.HttpClient = client
|
2020-08-03 20:01:38 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Balance returns the balance of the given ethereum address.
|
2020-08-04 18:19:42 +00:00
|
|
|
func (c *ethereumClient) Balance(ctx context.Context, address string) (*big.Int, error) {
|
|
|
|
ethereumAddress := common.HexToAddress(address)
|
|
|
|
value, err := c.HttpClient.BalanceAt(ctx, ethereumAddress, nil)
|
2020-08-03 20:01:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return value, nil
|
|
|
|
}
|
|
|
|
|
2020-08-04 18:19:42 +00:00
|
|
|
func (c *ethereumClient) SendTransaction(private_key, to string, amount int64) string {
|
|
|
|
privateKey, err := crypto.HexToECDSA(private_key)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to parse private key", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
publicKey := privateKey.Public()
|
|
|
|
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
|
|
|
|
if !ok {
|
|
|
|
c.Logger.Fatal("Cannot assert type: publicKey is not of type *ecdsa.PublicKey", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
|
|
|
|
nonce, err := c.HttpClient.PendingNonceAt(context.Background(), fromAddress)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to generate wallet nonce value", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
value := big.NewInt(amount)
|
|
|
|
gasLimit := uint64(21000) // in units
|
|
|
|
gasPrice, err := c.HttpClient.SuggestGasPrice(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to suggest new gas price", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
toAddress := common.HexToAddress(to)
|
|
|
|
var data []byte
|
|
|
|
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
|
|
|
|
|
|
|
|
chainID, err := c.HttpClient.NetworkID(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to get network ID", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to sign transaction", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = c.HttpClient.SendTransaction(context.Background(), signedTx)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to send signed transaction", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
TxHash := signedTx.Hash().Hex()
|
|
|
|
|
|
|
|
c.Logger.Info("Transaction sent: %s", TxHash)
|
|
|
|
|
|
|
|
return TxHash
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ethereumClient) GenerateAddressFromPrivateKey(private_key string) string {
|
|
|
|
privateKey, err := crypto.HexToECDSA(private_key)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to generate private key", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
publicKey := privateKey.Public()
|
|
|
|
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
|
|
|
|
if !ok {
|
|
|
|
c.Logger.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
|
|
|
|
}
|
|
|
|
|
|
|
|
publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
|
|
|
|
c.Logger.Info(hexutil.Encode(publicKeyBytes)[4:])
|
|
|
|
|
|
|
|
address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
|
|
|
|
|
|
|
|
return address
|
|
|
|
}
|
|
|
|
|
2020-08-03 20:01:38 +00:00
|
|
|
func (c *ethereumClient) SubscribeOnSmartContractEvents(ctx context.Context, address string) {
|
|
|
|
contractAddress := common.HexToAddress(address)
|
|
|
|
query := ethereum.FilterQuery{
|
|
|
|
Addresses: []common.Address{contractAddress},
|
|
|
|
}
|
|
|
|
|
|
|
|
logs := make(chan types.Log)
|
2020-08-04 18:19:42 +00:00
|
|
|
sub, err := c.WsClient.SubscribeFilterLogs(context.Background(), query, logs)
|
2020-08-03 20:01:38 +00:00
|
|
|
if err != nil {
|
2020-08-04 18:19:42 +00:00
|
|
|
c.Logger.Fatal(err)
|
2020-08-03 20:01:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case err := <-sub.Err():
|
2020-08-04 18:19:42 +00:00
|
|
|
c.Logger.Fatal(err)
|
2020-08-03 20:01:38 +00:00
|
|
|
case vLog := <-logs:
|
2020-08-05 17:11:14 +00:00
|
|
|
c.Logger.Info(vLog) // pointer to event log
|
2020-08-03 20:01:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2020-08-04 18:19:42 +00:00
|
|
|
|
|
|
|
func (c *ethereumClient) createKeyStore(password string) string {
|
|
|
|
ks := keystore.NewKeyStore("./wallets", keystore.StandardScryptN, keystore.StandardScryptP)
|
|
|
|
account, err := ks.NewAccount(password)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to create new keystore", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return account.Address.Hex()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ethereumClient) importKeyStore(filePath string, password string) string {
|
|
|
|
ks := keystore.NewKeyStore("./wallets", keystore.StandardScryptN, keystore.StandardScryptP)
|
|
|
|
jsonBytes, err := ioutil.ReadFile(filePath)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to read keystore file", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
account, err := ks.Import(jsonBytes, password, password)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal("Failed to import keystore", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return account.Address.Hex()
|
|
|
|
}
|