Class: Eth::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/eth/client.rb

Overview

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

Direct Known Subclasses

Http, Ipc

Defined Under Namespace

Classes: ContractExecutionError, Http, Ipc

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(_) ⇒ Client

Constructor for the Eth::Client super-class. Should not be used; use create intead.



62
63
64
65
66
# File 'lib/eth/client.rb', line 62

def initialize(_)
  @id = 0
  @max_priority_fee_per_gas = Tx::DEFAULT_PRIORITY_FEE
  @max_fee_per_gas = Tx::DEFAULT_GAS_PRICE
end

Instance Attribute Details

#block_numberObject

The block number used for archive calls.



38
39
40
# File 'lib/eth/client.rb', line 38

def block_number
  @block_number
end

#chain_idInteger (readonly)

Gets the chain ID of the connected network.

Returns:

  • (Integer)

    the chain ID.



26
27
28
# File 'lib/eth/client.rb', line 26

def chain_id
  @chain_id
end

#default_accountEth::Address

Gets the default account (first account) of the connected client.

Note, that many remote providers (e.g., Infura) do not provide any accounts.

Returns:

Raises:

  • (ArgumentError)


29
30
31
# File 'lib/eth/client.rb', line 29

def 
  @default_account
end

#idObject (readonly)

The client’s RPC-request ID starting at 0.



23
24
25
# File 'lib/eth/client.rb', line 23

def id
  @id
end

#max_fee_per_gasObject

The default transaction max fee per gas in Wei, defaults to Tx::DEFAULT_GAS_PRICE.



35
36
37
# File 'lib/eth/client.rb', line 35

def max_fee_per_gas
  @max_fee_per_gas
end

#max_priority_fee_per_gasObject

The default transaction max priority fee per gas in Wei, defaults to Tx::DEFAULT_PRIORITY_FEE.



32
33
34
# File 'lib/eth/client.rb', line 32

def max_priority_fee_per_gas
  @max_priority_fee_per_gas
end

Class Method Details

.create(host) ⇒ Eth::Client::Ipc, Eth::Client::Http

Creates a new RPC-Client, either by providing an HTTP/S host or an IPC path. Supports basic authentication with username and password.

Note, this sets the folling gas defaults: Tx::DEFAULT_PRIORITY_FEE and Use {#max_priority_fee_per_gas and #max_fee_per_gas to set custom values prior to submitting transactions.

Parameters:

  • host (String)

    either an HTTP/S host or an IPC path.

Returns:

Raises:

  • (ArgumentError)

    in case it cannot determine the client type.



54
55
56
57
58
# File 'lib/eth/client.rb', line 54

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

Instance Method Details

#call(contract, function) ⇒ Object #call(contract, function, *args) ⇒ Object #call(contract, function, *args, **kwargs) ⇒ Object

Calls a contract function without executing it (non-transactional contract read).

Overloads:

  • #call(contract, function) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      the subject contract to call.

    • function (String)

      method name to be called.

  • #call(contract, function, *args) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      the subject contract to call.

    • function (String)

      method name to be called.

    • *args

      optional function arguments.

  • #call(contract, function, *args, **kwargs) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      the subject contract to call.

    • function (String)

      method name to be called.

    • *args

      optional function arguments.

    • **sender_key (Eth::Key)

      the sender private key.

    • **legacy (Boolean)

      enables legacy transactions (pre-EIP-1559).

Returns:

  • (Object)

    returns the result of the call.

Raises:

  • (ArgumentError)


254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/eth/client.rb', line 254

def call(contract, function, *args, **kwargs)
  func = contract.functions.select { |func| func.name == function }
  raise ArgumentError, "this function does not exist!" if func.nil? || func.size === 0
  selected_func = func.first
  func.each do |f|
    if f.inputs.size === args.size
      selected_func = f
    end
  end
  output = call_raw(contract, selected_func, *args, **kwargs)
  if output&.length == 1
    output[0]
  else
    output
  end
end

#deploy(contract) ⇒ String #deploy(contract, *args) ⇒ String #deploy(contract, *args, **kwargs) ⇒ String

Deploys a contract. Uses eth_accounts or external signer if no sender key is provided.

Note, that many remote providers (e.g., Infura) do not provide any accounts. Provide a sender_key: if you experience issues.

Overloads:

  • #deploy(contract) ⇒ String

    Parameters:

  • #deploy(contract, *args) ⇒ String

    Parameters:

    • contract (Eth::Contract)

      the contracts to deploy.

    • *args (optional)

      variable constructor parameter list.

  • #deploy(contract, *args, **kwargs) ⇒ String

    Parameters:

    • contract (Eth::Contract)

      the contracts to deploy.

    • *args (optional)

      variable constructor parameter list.

    • **sender_key (Eth::Key)

      the sender private key.

    • **legacy (Boolean)

      enables legacy transactions (pre-EIP-1559).

    • **gas_limit (Integer)

      optional gas limit override for deploying the contract.

    • **nonce (Integer)

      optional specific nonce for transaction.

Returns:

  • (String)

    the transaction hash.

Raises:

  • (ArgumentError)

    in case the contract does not have any source.



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/eth/client.rb', line 216

def deploy(contract, *args, **kwargs)
  raise ArgumentError, "Cannot deploy contract without source or binary!" if contract.bin.nil?
  raise ArgumentError, "Missing contract constructor params!" if contract.constructor_inputs.length != args.length
  data = contract.bin
  unless args.empty?
    data += encode_constructor_params(contract, args)
  end
  gas_limit = if kwargs[:gas_limit]
      kwargs[:gas_limit]
    else
      Tx.estimate_intrinsic_gas(data) + Tx::CREATE_GAS
    end
  params = {
    value: 0,
    gas_limit: gas_limit,
    chain_id: chain_id,
    data: data,
  }
  send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
end

#deploy_and_wait(contract, *args, **kwargs) ⇒ String

Deploys a contract and waits for it to be mined. Uses eth_accounts or external signer if no sender key is provided.

See #deploy for params and overloads.

Returns:

  • (String)

    the contract address once it’s mined.



190
191
192
193
194
# File 'lib/eth/client.rb', line 190

def deploy_and_wait(contract, *args, **kwargs)
  hash = wait_for_tx(deploy(contract, *args, **kwargs))
  addr = eth_get_transaction_receipt(hash)["result"]["contractAddress"]
  contract.address = Address.new(addr).to_s
end

#get_balance(address) ⇒ Integer

Gets the balance for an address.

Parameters:

  • address (Eth::Address)

    the address to get the balance for.

Returns:

  • (Integer)

    the balance in Wei.



90
91
92
# File 'lib/eth/client.rb', line 90

def get_balance(address)
  eth_get_balance(address)["result"].to_i 16
end

#get_nonce(address) ⇒ Integer

Gets the next nonce for an address used to draft new transactions.

Parameters:

  • address (Eth::Address)

    the address to get the nonce for.

Returns:

  • (Integer)

    the next nonce to be used.



98
99
100
# File 'lib/eth/client.rb', line 98

def get_nonce(address)
  eth_get_transaction_count(address, "pending")["result"].to_i 16
end

#is_valid_signature(contract, hash, signature, magic = "1626ba7e") ⇒ Boolean

Provides an interface to call isValidSignature as per EIP-1271 on a given smart contract to verify the given hash and signature matching the magic value.

Parameters:

  • contract (Eth::Contract)

    a deployed contract implementing EIP-1271.

  • hash (String)

    the message hash to be checked against the signature.

  • signature (String)

    the signature to be recovered by the contract.

  • magic (String) (defaults to: "1626ba7e")

    the expected magic value (defaults to 1626ba7e).

Returns:

  • (Boolean)

    true if magic matches and signature is valid.

Raises:

  • (ArgumentError)

    in case the contract cannot be called yet.



338
339
340
341
342
343
344
345
# File 'lib/eth/client.rb', line 338

def is_valid_signature(contract, hash, signature, magic = "1626ba7e")
  raise ArgumentError, "Contract not deployed yet." if contract.address.nil?
  hash = Util.hex_to_bin hash if Util.hex? hash
  signature = Util.hex_to_bin signature if Util.hex? signature
  magic = Util.hex_to_bin magic if Util.hex? magic
  result = call(contract, "isValidSignature", hash, signature)
  result === magic
end

#reset_idInteger

Gives control over resetting the RPC request ID back to zero. Usually not needed.

Returns:

  • (Integer)

    0



351
352
353
# File 'lib/eth/client.rb', line 351

def reset_id
  @id = 0
end

#resolve_ens(ens_name, registry = Ens::DEFAULT_ADDRESS, coin_type = Ens::CoinType::ETHEREUM) ⇒ Eth::Address

Resolves an ENS name to an Ethereum address on the connected chain.

Parameters:

  • ens_name (String)

    The ENS name, e.g., fancy.eth.

  • registry (String) (defaults to: Ens::DEFAULT_ADDRESS)

    the address for the ENS registry.

  • coin_type (Integer) (defaults to: Ens::CoinType::ETHEREUM)

    the coin type as per EIP-2304.

Returns:

  • (Eth::Address)

    the Ethereum address resolved from the ENS record.



108
109
110
111
# File 'lib/eth/client.rb', line 108

def resolve_ens(ens_name, registry = Ens::DEFAULT_ADDRESS, coin_type = Ens::CoinType::ETHEREUM)
  ens = Ens::Resolver.new(self, registry)
  ens.resolve(ens_name, coin_type)
end

#transact(contract, function) ⇒ Object #transact(contract, function, *args) ⇒ Object #transact(contract, function, *args, **kwargs) ⇒ Object

Executes a contract function with a transaction (transactional contract read/write).

Note, that many remote providers (e.g., Infura) do not provide any accounts. Provide a sender_key: if you experience issues.

Overloads:

  • #transact(contract, function) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      the subject contract to write to.

    • function (String)

      method name to be executed.

  • #transact(contract, function, *args) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      the subject contract to write to.

    • function (String)

      method name to be executed.

    • *args

      optional function arguments.

  • #transact(contract, function, *args, **kwargs) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      the subject contract to write to.

    • function_name (String)

      method name to be executed.

    • *args

      optional function arguments.

    • **sender_key (Eth::Key)

      the sender private key.

    • **legacy (Boolean)

      enables legacy transactions (pre-EIP-1559).

    • **address (Eth::Address)

      contract address.

    • **gas_limit (Integer)

      optional gas limit override for transacting with the contract.

    • **nonce (Integer)

      optional specific nonce for transaction.

    • **tx_value (Integer)

      optional transaction value field filling.

Returns:

  • (Object)

    returns the result of the transaction.



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/eth/client.rb', line 295

def transact(contract, function, *args, **kwargs)
  gas_limit = if kwargs[:gas_limit]
      kwargs[:gas_limit]
    else
      Tx.estimate_intrinsic_gas(contract.bin)
    end
  fun = contract.functions.select { |func| func.name == function }[0]
  params = {
    value: kwargs[:tx_value] || 0,
    gas_limit: gas_limit,
    chain_id: chain_id,
    to: kwargs[:address] || contract.address,
    data: call_payload(fun, args),
  }
  send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
end

#transact_and_wait(contract, function, *args, **kwargs) ⇒ Object, Boolean

Executes a contract function with a transaction and waits for it to be mined (transactional contract read/write).

See #transact for params and overloads.

Returns:

  • (Object, Boolean)

    returns the result of the transaction (hash and execution status).

Raises:



319
320
321
322
323
324
325
326
# File 'lib/eth/client.rb', line 319

def transact_and_wait(contract, function, *args, **kwargs)
  begin
    hash = wait_for_tx(transact(contract, function, *args, **kwargs))
    return hash, tx_succeeded?(hash)
  rescue IOError => e
    raise ContractExecutionError, e
  end
end

#transfer(destination, amount) ⇒ String #transfer(destination, amount, **kwargs) ⇒ String

Simply transfer Ether to an account without any call data or access lists attached. Uses eth_accounts and external signer if no sender key is provided.

Note, that many remote providers (e.g., Infura) do not provide any accounts. Provide a sender_key: if you experience issues.

Overloads:

  • #transfer(destination, amount) ⇒ String

    Parameters:

    • destination (Eth::Address)

      the destination address.

    • amount (Integer)

      the transfer amount in Wei.

  • #transfer(destination, amount, **kwargs) ⇒ String

    Parameters:

    • destination (Eth::Address)

      the destination address.

    • amount (Integer)

      the transfer amount in Wei.

    • **sender_key (Eth::Key)

      the sender private key.

    • **legacy (Boolean)

      enables legacy transactions (pre-EIP-1559).

    • **nonce (Integer)

      optional specific nonce for transaction.

Returns:

  • (String)

    the local transaction hash.



141
142
143
144
145
146
147
148
149
# File 'lib/eth/client.rb', line 141

def transfer(destination, amount, **kwargs)
  params = {
    value: amount,
    to: destination,
    gas_limit: Tx::DEFAULT_GAS_LIMIT,
    chain_id: chain_id,
  }
  send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
end

#transfer_and_wait(destination, amount, **kwargs) ⇒ String

Simply transfer Ether to an account and waits for it to be mined. Uses eth_accounts and external signer if no sender key is provided.

See #transfer for params and overloads.

Returns:

  • (String)

    the transaction hash once it is mined.



120
121
122
# File 'lib/eth/client.rb', line 120

def transfer_and_wait(destination, amount, **kwargs)
  wait_for_tx(transfer(destination, amount, **kwargs))
end

#transfer_erc20(erc20_contract, destination, amount) ⇒ Object #transfer_erc20(erc20_contract, destination, amount, **kwargs) ⇒ Object

Transfers a token that implements the ERC20 transfer() interface.

Note, that many remote providers (e.g., Infura) do not provide any accounts. Provide a sender_key: if you experience issues.

Overloads:

  • #transfer_erc20(erc20_contract, destination, amount) ⇒ Object

    Parameters:

    • erc20_contract (Eth::Contract)

      the ERC20 contract to write to.

    • destination (Eth::Address)

      the destination address.

    • amount (Integer)

      the transfer amount (mind the decimals()).

  • #transfer_erc20(erc20_contract, destination, amount, **kwargs) ⇒ Object

    Parameters:

    • erc20_contract (Eth::Contract)

      the ERC20 contract to write to.

    • destination (Eth::Address)

      the destination address.

    • amount (Integer)

      the transfer amount (mind the decimals()).

    • **sender_key (Eth::Key)

      the sender private key.

    • **legacy (Boolean)

      enables legacy transactions (pre-EIP-1559).

    • **gas_limit (Integer)

      optional gas limit override for the transfer.

    • **nonce (Integer)

      optional specific nonce for transaction.

    • **tx_value (Integer)

      optional transaction value field filling.

Returns:

  • (Object)

    returns the result of the transaction.



179
180
181
182
# File 'lib/eth/client.rb', line 179

def transfer_erc20(erc20_contract, destination, amount, **kwargs)
  destination = destination.to_s if destination.instance_of? Eth::Address
  transact(erc20_contract, "transfer", destination, amount, **kwargs)
end

#transfer_erc20_and_wait(erc20_contract, destination, amount, **kwargs) ⇒ Object

Transfers a token that implements the ERC20 transfer() interface.

See #transfer_erc20 for params and overloads.

Returns:

  • (Object)

    returns the result of the transaction.



156
157
158
# File 'lib/eth/client.rb', line 156

def transfer_erc20_and_wait(erc20_contract, destination, amount, **kwargs)
  transact_and_wait(erc20_contract, "transfer", destination, amount, **kwargs)
end

#tx_mined?(hash) ⇒ Boolean

Checks whether a transaction is mined or not.

Parameters:

  • hash (String)

    the transaction hash.

Returns:

  • (Boolean)

    true if included in a block.



359
360
361
362
# File 'lib/eth/client.rb', line 359

def tx_mined?(hash)
  mined_tx = eth_get_transaction_by_hash hash
  !mined_tx.nil? && !mined_tx["result"].nil? && !mined_tx["result"]["blockNumber"].nil?
end

#tx_succeeded?(hash) ⇒ Boolean

Checks whether a contract transaction succeeded or not.

Parameters:

  • hash (String)

    the transaction hash.

Returns:

  • (Boolean)

    true if status is success.



368
369
370
371
# File 'lib/eth/client.rb', line 368

def tx_succeeded?(hash)
  tx_receipt = eth_get_transaction_receipt(hash)
  !tx_receipt.nil? && !tx_receipt["result"].nil? && tx_receipt["result"]["status"] == "0x1"
end

#wait_for_tx(hash) ⇒ String

Waits for an transaction to be mined by the connected chain.

Parameters:

  • hash (String)

    the transaction hash.

Returns:

  • (String)

    the transaction hash once the transaction is mined.

Raises:

  • (Timeout::Error)

    if it’s not mined within 5 minutes.



378
379
380
381
382
383
384
385
386
387
# File 'lib/eth/client.rb', line 378

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 tx_mined? hash
    sleep retry_rate
  end
end