class Eth::Client

network’s RPC-API endpoints (IPC or HTTP).
Provides the {Eth::Client} super-class to connect to Ethereum

def self.create(host)

Raises:
  • (ArgumentError) - in case it cannot determine the client type.

Returns:
  • (Eth::Client::Http) - an HTTP client.
  • (Eth::Client::Ipc) - an IPC client.

Parameters:
  • host (String) -- either an HTTP/S host or an IPC path.
def self.create(host)
  return Client::Ipc.new host if host.end_with? ".ipc"
  return Client::Http.new host if host.start_with? "http"
  raise ArgumentError, "Unable to detect client type!"
end

def call(contract, function_name, *args, **kwargs)

Returns:
  • (Object) - returns the result of the call.

Parameters:
  • legacy (Boolean) -- enables legacy transactions (pre-EIP-1559).
  • sender_key (Eth::Key) -- the sender private key.
  • value (Integer|String) -- function arguments.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.
  • value (Integer|String) -- function arguments.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.

Overloads:
  • call(contract, function_name, value, sender_key, legacy)
  • call(contract, function_name, value)
  • call(contract, function_name)
def call(contract, function_name, *args, **kwargs)
  func = contract.functions.select { |func| func.name == function_name }[0]
  raise ArgumentError, "function_name does not exist!" if func.nil?
  output = call_raw(contract, func, *args, **kwargs)
  if output.length == 1
    return output[0]
  else
    return output
  end
end

def call_payload(fun, args)

Encoding for function calls.
def call_payload(fun, args)
  types = fun.inputs.map { |i| i.type }
  encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args))
  "0x" + fun.signature + (encoded_str.empty? ? "0" * 64 : encoded_str)
end

def call_raw(contract, func, *args, **kwargs)

Returns:
  • (Object) - returns the result of the call.

Parameters:
  • legacy (Boolean) -- enables legacy transactions (pre-EIP-1559).
  • sender_key (Eth::Key) -- the sender private key.
  • value (Integer|String) -- function arguments.
  • func (Eth::Contract::Function) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.
  • value (Integer|String) -- function arguments.
  • func (Eth::Contract::Function) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.
  • func (Eth::Contract::Function) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.

Overloads:
  • call_raw(contract, func, value, sender_key, legacy)
  • call_raw(contract, func, value)
  • call_raw(contract, func)
def call_raw(contract, func, *args, **kwargs)
  gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
  params = {
    gas_limit: gas_limit,
    chain_id: chain_id,
    data: call_payload(func, args),
  }
  if kwargs[:address] || contract.address
    params.merge!({ to: kwargs[:address] || contract.address })
  end
  if kwargs[:legacy]
    params.merge!({
      gas_price: max_fee_per_gas,
    })
  else
    params.merge!({
      priority_fee: max_priority_fee_per_gas,
      max_gas_fee: max_fee_per_gas,
    })
  end
  unless kwargs[:sender_key].nil?
    # use the provided key as sender and signer
    params.merge!({
      from: kwargs[:sender_key].address,
      nonce: get_nonce(kwargs[:sender_key].address),
    })
    tx = Eth::Tx.new(params)
    tx.sign kwargs[:sender_key]
  else
    # use the default account as sender and external signer
    params.merge!({
      from: default_account,
      nonce: get_nonce(default_account),
    })
  end
  raw_result = eth_call(params)["result"]
  types = func.outputs.map { |i| i.type }
  Eth::Abi.decode(types, raw_result)
end

def chain_id

Returns:
  • (Integer) - the chain ID.
def chain_id
  @chain_id ||= eth_chain_id["result"].to_i 16
end

def default_account

Returns:
  • (Eth::Address) - the coinbase account address.
def default_account
  @default_account ||= Address.new eth_coinbase["result"]
end

def deploy(contract, sender_key: nil, legacy: false)

Returns:
  • (String) - the transaction hash.

Parameters:
  • legacy (Boolean) -- enables legacy transactions (pre-EIP-1559).
  • sender_key (Eth::Key) -- the sender private key.
  • contract (Eth::Contract) -- contracts to deploy.
  • sender_key (Eth::Key) -- the sender private key.
  • contract (Eth::Contract) -- contracts to deploy.
  • contract (Eth::Contract) -- contracts to deploy.

Overloads:
  • deploy(contract, sender_key, legacy)
  • deploy(contract, sender_key)
  • deploy(contract)
def deploy(contract, sender_key: nil, legacy: false)
  gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
  params = {
    value: 0,
    gas_limit: gas_limit,
    chain_id: chain_id,
    data: contract.bin,
  }
  if legacy
    params.merge!({
      gas_price: max_fee_per_gas,
    })
  else
    params.merge!({
      priority_fee: max_priority_fee_per_gas,
      max_gas_fee: max_fee_per_gas,
    })
  end
  unless sender_key.nil?
    # use the provided key as sender and signer
    params.merge!({
      from: sender_key.address,
      nonce: get_nonce(sender_key.address),
    })
    tx = Eth::Tx.new(params)
    tx.sign sender_key
    return eth_send_raw_transaction(tx.hex)["result"]
  else
    # use the default account as sender and external signer
    params.merge!({
      from: default_account,
      nonce: get_nonce(default_account),
    })
    return eth_send_transaction(params)["result"]
  end
end

def deploy_and_wait(contract, sender_key: nil, legacy: false)

Returns:
  • (String) - the contract address.

Parameters:
  • legacy (Boolean) -- enables legacy transactions (pre-EIP-1559).
  • sender_key (Eth::Key) -- the sender private key.
  • contract (Eth::Contract) -- contracts to deploy.
  • sender_key (Eth::Key) -- the sender private key.
  • contract (Eth::Contract) -- contracts to deploy.
  • contract (Eth::Contract) -- contracts to deploy.

Overloads:
  • deploy(contract, sender_key, legacy)
  • deploy(contract, sender_key)
  • deploy(contract)
def deploy_and_wait(contract, sender_key: nil, legacy: false)
  hash = wait_for_tx(deploy(contract, sender_key: sender_key, legacy: legacy))
  contract.address = eth_get_transaction_receipt(hash)["result"]["contractAddress"]
end

def get_balance(address)

Returns:
  • (Integer) - the balance in Wei.

Parameters:
  • address (Eth::Address) -- the address to get the balance for.
def get_balance(address)
  eth_get_balance(address)["result"].to_i 16
end

def get_nonce(address)

Returns:
  • (Integer) - the next nonce to be used.

Parameters:
  • address (Eth::Address) -- the address to get the nonce for.
def get_nonce(address)
  eth_get_transaction_count(address, "pending")["result"].to_i 16
end

def initialize(_)

use {Client.create} intead.
Constructor for the {Eth::Client} super-class. Should not be used;
def initialize(_)
  @id = 0
  @max_priority_fee_per_gas = 0
  @max_fee_per_gas = Tx::DEFAULT_GAS_PRICE
  @gas_limit = Tx::DEFAULT_GAS_LIMIT
end

def is_mined_tx?(hash)

Returns:
  • (Boolean) - true if included in a block.

Parameters:
  • hash (String) -- the transaction hash.
def is_mined_tx?(hash)
  mined_tx = eth_get_transaction_by_hash hash
  !mined_tx.nil? && !mined_tx["result"].nil? && !mined_tx["result"]["blockNumber"].nil?
end

def marshal(params)

Recursively marshals all request parameters.
def marshal(params)
  if params.is_a? Array
    return params.map! { |param| marshal(param) }
  elsif params.is_a? Hash
    return params.transform_values! { |param| marshal(param) }
  elsif params.is_a? Numeric
    return Util.prefix_hex "#{params.to_i.to_s(16)}"
  elsif params.is_a? Address
    return params.to_s
  elsif Util.is_hex? params
    return Util.prefix_hex params
  else
    return params
  end
end

def next_id

Increments the request id.
def next_id
  @id += 1
end

def reset_id

Returns:
  • (Integer) - 0
def reset_id
  @id = 0
end

def send_command(command, args)

Prepares parameters and sends the command to the client.
def send_command(command, args)
  args << "latest" if ["eth_getBalance", "eth_call"].include? command
  payload = {
    jsonrpc: "2.0",
    method: command,
    params: marshal(args),
    id: next_id,
  }
  output = JSON.parse(send(payload.to_json))
  raise IOError, output["error"]["message"] unless output["error"].nil?
  return output
end

def transact(contract, function_name, *args, **kwargs)

Returns:
  • (Object) - returns the result of the call.

Parameters:
  • address (String) -- contract address.
  • legacy (Boolean) -- enables legacy transactions (pre-EIP-1559).
  • sender_key (Eth::Key) -- the sender private key.
  • value (Integer|String) -- function arguments.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.
  • value (Integer|String) -- function arguments.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.

Overloads:
  • transact(contract, function_name, value, sender_key, legacy, address)
  • transact(contract, function_name, value)
  • transact(contract, function_name)
def transact(contract, function_name, *args, **kwargs)
  gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
  fun = contract.functions.select { |func| func.name == function_name }[0]
  params = {
    value: 0,
    gas_limit: gas_limit,
    chain_id: chain_id,
    to: kwargs[:address] || contract.address,
    data: call_payload(fun, args),
  }
  if kwargs[:legacy]
    params.merge!({
      gas_price: max_fee_per_gas,
    })
  else
    params.merge!({
      priority_fee: max_priority_fee_per_gas,
      max_gas_fee: max_fee_per_gas,
    })
  end
  unless kwargs[:sender_key].nil?
    # use the provided key as sender and signer
    params.merge!({
      from: kwargs[:sender_key].address,
      nonce: get_nonce(kwargs[:sender_key].address),
    })
    tx = Eth::Tx.new(params)
    tx.sign kwargs[:sender_key]
    return eth_send_raw_transaction(tx.hex)["result"]
  else
    # use the default account as sender and external signer
    params.merge!({
      from: default_account,
      nonce: get_nonce(default_account),
    })
    return eth_send_transaction(params)["result"]
  end
end

def transact_and_wait(contract, function_name, *args, **kwargs)

Returns:
  • (Object) - returns the result of the call.

Parameters:
  • address (String) -- contract address.
  • legacy (Boolean) -- enables legacy transactions (pre-EIP-1559).
  • sender_key (Eth::Key) -- the sender private key.
  • value (Integer|String) -- function arguments.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.
  • value (Integer|String) -- function arguments.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.
  • function_name (String) -- method name to be called.
  • contract (Eth::Contract) -- subject contract to call.

Overloads:
  • transact_and_wait(contract, function_name, value, sender_key, legacy, address)
  • transact_and_wait(contract, function_name, value)
  • transact_and_wait(contract, function_name)
def transact_and_wait(contract, function_name, *args, **kwargs)
  wait_for_tx(transact(contract, function_name, *args, **kwargs))
end

def transfer(destination, amount, sender_key = nil, legacy = false)

Returns:
  • (String) - the transaction hash.

Parameters:
  • legacy (Boolean) -- enables legacy transactions (pre-EIP-1559).
  • sender_key (Eth::Key) -- the sender private key.
  • amount (Integer) -- the transfer amount in Wei.
  • destination (Eth::Address) -- the destination address.
def transfer(destination, amount, sender_key = nil, legacy = false)
  params = {
    value: amount,
    to: destination,
    gas_limit: gas_limit,
    chain_id: chain_id,
  }
  if legacy
    params.merge!({
      gas_price: max_fee_per_gas,
    })
  else
    params.merge!({
      priority_fee: max_priority_fee_per_gas,
      max_gas_fee: max_fee_per_gas,
    })
  end
  unless sender_key.nil?
    # use the provided key as sender and signer
    params.merge!({
      from: sender_key.address,
      nonce: get_nonce(sender_key.address),
    })
    tx = Eth::Tx.new(params)
    tx.sign sender_key
    return eth_send_raw_transaction(tx.hex)["result"]
  else
    # use the default account as sender and external signer
    params.merge!({
      from: default_account,
      nonce: get_nonce(default_account),
    })
    return eth_send_transaction(params)["result"]
  end
end

def transfer_and_wait(destination, amount, sender_key = nil, legacy = false)

Returns:
  • (String) - the transaction hash.

Parameters:
  • legacy (Boolean) -- enables legacy transactions (pre-EIP-1559).
  • sender_key (Eth::Key) -- the sender private key.
  • amount (Integer) -- the transfer amount in Wei.
  • destination (Eth::Address) -- the destination address.
def transfer_and_wait(destination, amount, sender_key = nil, legacy = false)
  wait_for_tx(transfer(destination, amount, sender_key, legacy))
end

def wait_for_tx(hash)

Raises:
  • (Timeout::Error) - if it's not mined within 5 minutes.

Returns:
  • (String) - the transactin hash once the transaction is mined.

Parameters:
  • hash (String) -- the transaction hash.
def wait_for_tx(hash)
  start_time = Time.now
  timeout = 300
  retry_rate = 0.1
  loop do
    raise Timeout::Error if ((Time.now - start_time) > timeout)
    return hash if is_mined_tx? hash
    sleep retry_rate
  end
end