package ethrpc
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/big"
"net/http"
"os"
)
// EthError - ethereum error
type EthError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (err EthError) Error() string {
return fmt.Sprintf("Error %d (%s)", err.Code, err.Message)
}
type ethResponse struct {
ID int `json:"id"`
JSONRPC string `json:"jsonrpc"`
Result json.RawMessage `json:"result"`
Error *EthError `json:"error"`
}
type ethRequest struct {
ID int `json:"id"`
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
}
// EthRPC - Ethereum rpc client
type EthRPC struct {
url string
client httpClient
log logger
Debug bool
}
// New create new rpc client with given url
func New(url string, options ...func(rpc *EthRPC)) *EthRPC {
rpc := &EthRPC{
url: url,
client: http.DefaultClient,
log: log.New(os.Stderr, "", log.LstdFlags),
}
for _, option := range options {
option(rpc)
}
return rpc
}
// NewEthRPC create new rpc client with given url
func NewEthRPC(url string, options ...func(rpc *EthRPC)) *EthRPC {
return New(url, options...)
}
func (rpc *EthRPC) call(method string, target interface{}, params ...interface{}) error {
result, err := rpc.Call(method, params...)
if err != nil {
return err
}
if target == nil {
return nil
}
return json.Unmarshal(result, target)
}
// URL returns client url
func (rpc *EthRPC) URL() string {
return rpc.url
}
// Call returns raw response of method call
func (rpc *EthRPC) Call(method string, params ...interface{}) (json.RawMessage, error) {
request := ethRequest{
ID: 1,
JSONRPC: "2.0",
Method: method,
Params: params,
}
body, err := json.Marshal(request)
if err != nil {
return nil, err
}
response, err := rpc.client.Post(rpc.url, "application/json", bytes.NewBuffer(body))
if response != nil {
defer response.Body.Close()
}
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
if rpc.Debug {
rpc.log.Println(fmt.Sprintf("%s\nRequest: %s\nResponse: %s\n", method, body, data))
}
resp := new(ethResponse)
if err := json.Unmarshal(data, resp); err != nil {
return nil, err
}
if resp.Error != nil {
return nil, *resp.Error
}
return resp.Result, nil
}
// RawCall returns raw response of method call (Deprecated)
func (rpc *EthRPC) RawCall(method string, params ...interface{}) (json.RawMessage, error) {
return rpc.Call(method, params...)
}
// Web3ClientVersion returns the current client version.
func (rpc *EthRPC) Web3ClientVersion() (string, error) {
var clientVersion string
err := rpc.call("web3_clientVersion", &clientVersion)
return clientVersion, err
}
// Web3Sha3 returns Keccak-256 (not the standardized SHA3-256) of the given data.
func (rpc *EthRPC) Web3Sha3(data []byte) (string, error) {
var hash string
err := rpc.call("web3_sha3", &hash, fmt.Sprintf("0x%x", data))
return hash, err
}
// NetVersion returns the current network protocol version.
func (rpc *EthRPC) NetVersion() (string, error) {
var version string
err := rpc.call("net_version", &version)
return version, err
}
// NetListening returns true if client is actively listening for network connections.
func (rpc *EthRPC) NetListening() (bool, error) {
var listening bool
err := rpc.call("net_listening", &listening)
return listening, err
}
// NetPeerCount returns number of peers currently connected to the client.
func (rpc *EthRPC) NetPeerCount() (int, error) {
var response string
if err := rpc.call("net_peerCount", &response); err != nil {
return 0, err
}
return ParseInt(response)
}
// EthProtocolVersion returns the current ethereum protocol version.
func (rpc *EthRPC) EthProtocolVersion() (string, error) {
var protocolVersion string
err := rpc.call("eth_protocolVersion", &protocolVersion)
return protocolVersion, err
}
// EthSyncing returns an object with data about the sync status or false.
func (rpc *EthRPC) EthSyncing() (*Syncing, error) {
result, err := rpc.RawCall("eth_syncing")
if err != nil {
return nil, err
}
syncing := new(Syncing)
if bytes.Equal(result, []byte("false")) {
return syncing, nil
}
err = json.Unmarshal(result, syncing)
return syncing, err
}
// EthCoinbase returns the client coinbase address
func (rpc *EthRPC) EthCoinbase() (string, error) {
var address string
err := rpc.call("eth_coinbase", &address)
return address, err
}
// EthMining returns true if client is actively mining new blocks.
func (rpc *EthRPC) EthMining() (bool, error) {
var mining bool
err := rpc.call("eth_mining", &mining)
return mining, err
}
// EthHashrate returns the number of hashes per second that the node is mining with.
func (rpc *EthRPC) EthHashrate() (int, error) {
var response string
if err := rpc.call("eth_hashrate", &response); err != nil {
return 0, err
}
return ParseInt(response)
}
// EthGasPrice returns the current price per gas in wei.
func (rpc *EthRPC) EthGasPrice() (big.Int, error) {
var response string
if err := rpc.call("eth_gasPrice", &response); err != nil {
return big.Int{}, err
}
return ParseBigInt(response)
}
// EthAccounts returns a list of addresses owned by client.
func (rpc *EthRPC) EthAccounts() ([]string, error) {
accounts := []string{}
err := rpc.call("eth_accounts", &accounts)
return accounts, err
}
// EthBlockNumber returns the number of most recent block.
func (rpc *EthRPC) EthBlockNumber() (int, error) {
var response string
if err := rpc.call("eth_blockNumber", &response); err != nil {
return 0, err
}
return ParseInt(response)
}
// EthGetBalance returns the balance of the account of given address in wei.
func (rpc *EthRPC) EthGetBalance(address, block string) (big.Int, error) {
var response string
if err := rpc.call("eth_getBalance", &response, address, block); err != nil {
return big.Int{}, err
}
return ParseBigInt(response)
}
// EthGetStorageAt returns the value from a storage position at a given address.
func (rpc *EthRPC) EthGetStorageAt(data string, position int, tag string) (string, error) {
var result string
err := rpc.call("eth_getStorageAt", &result, data, IntToHex(position), tag)
return result, err
}
// EthGetTransactionCount returns the number of transactions sent from an address.
func (rpc *EthRPC) EthGetTransactionCount(address, block string) (int, error) {
var response string
if err := rpc.call("eth_getTransactionCount", &response, address, block); err != nil {
return 0, err
}
return ParseInt(response)
}
// EthGetBlockTransactionCountByHash returns the number of transactions in a block from a block matching the given block hash.
func (rpc *EthRPC) EthGetBlockTransactionCountByHash(hash string) (int, error) {
var response string
if err := rpc.call("eth_getBlockTransactionCountByHash", &response, hash); err != nil {
return 0, err
}
return ParseInt(response)
}
// EthGetBlockTransactionCountByNumber returns the number of transactions in a block from a block matching the given block
func (rpc *EthRPC) EthGetBlockTransactionCountByNumber(number int) (int, error) {
var response string
if err := rpc.call("eth_getBlockTransactionCountByNumber", &response, IntToHex(number)); err != nil {
return 0, err
}
return ParseInt(response)
}
// EthGetUncleCountByBlockHash returns the number of uncles in a block from a block matching the given block hash.
func (rpc *EthRPC) EthGetUncleCountByBlockHash(hash string) (int, error) {
var response string
if err := rpc.call("eth_getUncleCountByBlockHash", &response, hash); err != nil {
return 0, err
}
return ParseInt(response)
}
// EthGetUncleCountByBlockNumber returns the number of uncles in a block from a block matching the given block number.
func (rpc *EthRPC) EthGetUncleCountByBlockNumber(number int) (int, error) {
var response string
if err := rpc.call("eth_getUncleCountByBlockNumber", &response, IntToHex(number)); err != nil {
return 0, err
}
return ParseInt(response)
}
// EthGetCode returns code at a given address.
func (rpc *EthRPC) EthGetCode(address, block string) (string, error) {
var code string
err := rpc.call("eth_getCode", &code, address, block)
return code, err
}
// EthSign signs data with a given address.
// Calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))
func (rpc *EthRPC) EthSign(address, data string) (string, error) {
var signature string
err := rpc.call("eth_sign", &signature, address, data)
return signature, err
}
// EthSendTransaction creates new message call transaction or a contract creation, if the data field contains code.
func (rpc *EthRPC) EthSendTransaction(transaction T) (string, error) {
var hash string
err := rpc.call("eth_sendTransaction", &hash, transaction)
return hash, err
}
// EthSendRawTransaction creates new message call transaction or a contract creation for signed transactions.
func (rpc *EthRPC) EthSendRawTransaction(data string) (string, error) {
var hash string
err := rpc.call("eth_sendRawTransaction", &hash, data)
return hash, err
}
// EthCall executes a new message call immediately without creating a transaction on the block chain.
func (rpc *EthRPC) EthCall(transaction T, tag string) (string, error) {
var data string
err := rpc.call("eth_call", &data, transaction, tag)
return data, err
}
// EthEstimateGas makes a call or transaction, which won't be added to the blockchain and returns the used gas, which can be used for estimating the used gas.
func (rpc *EthRPC) EthEstimateGas(transaction T) (int, error) {
var response string
err := rpc.call("eth_estimateGas", &response, transaction)
if err != nil {
return 0, err
}
return ParseInt(response)
}
func (rpc *EthRPC) getBlock(method string, withTransactions bool, params ...interface{}) (*Block, error) {
result, err := rpc.RawCall(method, params...)
if err != nil {
return nil, err
}
if bytes.Equal(result, []byte("null")) {
return nil, nil
}
var response proxyBlock
if withTransactions {
response = new(proxyBlockWithTransactions)
} else {
response = new(proxyBlockWithoutTransactions)
}
err = json.Unmarshal(result, response)
if err != nil {
return nil, err
}
block := response.toBlock()
return &block, nil
}
// EthGetBlockByHash returns information about a block by hash.
func (rpc *EthRPC) EthGetBlockByHash(hash string, withTransactions bool) (*Block, error) {
return rpc.getBlock("eth_getBlockByHash", withTransactions, hash, withTransactions)
}
// EthGetBlockByNumber returns information about a block by block number.
func (rpc *EthRPC) EthGetBlockByNumber(number int, withTransactions bool) (*Block, error) {
return rpc.getBlock("eth_getBlockByNumber", withTransactions, IntToHex(number), withTransactions)
}
func (rpc *EthRPC) getTransaction(method string, params ...interface{}) (*Transaction, error) {
transaction := new(Transaction)
err := rpc.call(method, transaction, params...)
return transaction, err
}
// EthGetTransactionByHash returns the information about a transaction requested by transaction hash.
func (rpc *EthRPC) EthGetTransactionByHash(hash string) (*Transaction, error) {
return rpc.getTransaction("eth_getTransactionByHash", hash)
}
// EthGetTransactionByBlockHashAndIndex returns information about a transaction by block hash and transaction index position.
func (rpc *EthRPC) EthGetTransactionByBlockHashAndIndex(blockHash string, transactionIndex int) (*Transaction, error) {
return rpc.getTransaction("eth_getTransactionByBlockHashAndIndex", blockHash, IntToHex(transactionIndex))
}
// EthGetTransactionByBlockNumberAndIndex returns information about a transaction by block number and transaction index position.
func (rpc *EthRPC) EthGetTransactionByBlockNumberAndIndex(blockNumber, transactionIndex int) (*Transaction, error) {
return rpc.getTransaction("eth_getTransactionByBlockNumberAndIndex", IntToHex(blockNumber), IntToHex(transactionIndex))
}
// EthGetTransactionReceipt returns the receipt of a transaction by transaction hash.
// Note That the receipt is not available for pending transactions.
func (rpc *EthRPC) EthGetTransactionReceipt(hash string) (*TransactionReceipt, error) {
transactionReceipt := new(TransactionReceipt)
err := rpc.call("eth_getTransactionReceipt", transactionReceipt, hash)
if err != nil {
return nil, err
}
return transactionReceipt, nil
}
// EthGetCompilers returns a list of available compilers in the client.
func (rpc *EthRPC) EthGetCompilers() ([]string, error) {
compilers := []string{}
err := rpc.call("eth_getCompilers", &compilers)
return compilers, err
}
// EthNewFilter creates a new filter object.
func (rpc *EthRPC) EthNewFilter(params FilterParams) (string, error) {
var filterID string
err := rpc.call("eth_newFilter", &filterID, params)
return filterID, err
}
// EthNewBlockFilter creates a filter in the node, to notify when a new block arrives.
// To check if the state has changed, call EthGetFilterChanges.
func (rpc *EthRPC) EthNewBlockFilter() (string, error) {
var filterID string
err := rpc.call("eth_newBlockFilter", &filterID)
return filterID, err
}
// EthNewPendingTransactionFilter creates a filter in the node, to notify when new pending transactions arrive.
// To check if the state has changed, call EthGetFilterChanges.
func (rpc *EthRPC) EthNewPendingTransactionFilter() (string, error) {
var filterID string
err := rpc.call("eth_newPendingTransactionFilter", &filterID)
return filterID, err
}
// EthUninstallFilter uninstalls a filter with given id.
func (rpc *EthRPC) EthUninstallFilter(filterID string) (bool, error) {
var res bool
err := rpc.call("eth_uninstallFilter", &res, filterID)
return res, err
}
// EthGetFilterChanges polling method for a filter, which returns an array of logs which occurred since last poll.
func (rpc *EthRPC) EthGetFilterChanges(filterID string) ([]Log, error) {
var logs = []Log{}
err := rpc.call("eth_getFilterChanges", &logs, filterID)
return logs, err
}
// EthGetFilterLogs returns an array of all logs matching filter with given id.
func (rpc *EthRPC) EthGetFilterLogs(filterID string) ([]Log, error) {
var logs = []Log{}
err := rpc.call("eth_getFilterLogs", &logs, filterID)
return logs, err
}
// EthGetLogs returns an array of all logs matching a given filter object.
func (rpc *EthRPC) EthGetLogs(params FilterParams) ([]Log, error) {
var logs = []Log{}
err := rpc.call("eth_getLogs", &logs, params)
return logs, err
}
// Eth1 returns 1 ethereum value (10^18 wei)
func (rpc *EthRPC) Eth1() *big.Int {
return Eth1()
}
// Eth1 returns 1 ethereum value (10^18 wei)
func Eth1() *big.Int {
return big.NewInt(1000000000000000000)
}