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, HTTP/S, or WS/S).

Defined Under Namespace

Classes: ContractExecutionError, Http, Ipc, RpcError, Ws

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.



82
83
84
85
86
87
# File 'lib/eth/client.rb', line 82

def initialize(_)
  @id = 0
  @closed = false
  @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, ...

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

Note, this sets the following 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, WS/S host, or an IPC path.

Returns:

Raises:

  • (ArgumentError)

    in case it cannot determine the client type.



73
74
75
76
77
78
# File 'lib/eth/client.rb', line 73

def self.create(host)
  return Client::Ipc.new host if host.end_with? ".ipc"
  return Client::Http.new host if host.start_with? "http"
  return Client::Ws.new host if host.start_with? "ws"
  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.

See Also:



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/eth/client.rb', line 276

def call(contract, function, *args, **kwargs)
  function = contract.function(function, args: args.size)
  output = function.decode_call_result(
    eth_call(
      {
        data: function.encode_call(*args),
        to: kwargs[:address] || contract.address,
        from: kwargs[:from],
        gas: kwargs[:gas],
        gasPrice: kwargs[:gas_price],
        value: kwargs[:value],
      }.compact
    )["result"]
  )
  if output&.length == 1
    output[0]
  else
    output
  end
rescue RpcError => e
  raise ContractExecutionError, contract.decode_error(e)
end

#closevoid

This method returns an undefined value.

Closes the client and releases any resources held by the underlying connection, such as persistent HTTP connections or an open WebSocket stream. Once closed, the client cannot be used anymore; any subsequent request raises an IOError. Safe to call multiple times.



388
389
390
# File 'lib/eth/client.rb', line 388

def close
  @closed = true
end

#closed?Boolean

Checks whether the client has been closed via #close.

Returns:

  • (Boolean)

    true if the client is closed.



395
396
397
# File 'lib/eth/client.rb', line 395

def closed?
  !!@closed
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.



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/eth/client.rb', line 237

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.



211
212
213
214
215
# File 'lib/eth/client.rb', line 211

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.



111
112
113
# File 'lib/eth/client.rb', line 111

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.



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

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.



365
366
367
368
369
370
371
372
# File 'lib/eth/client.rb', line 365

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



378
379
380
# File 'lib/eth/client.rb', line 378

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.



129
130
131
132
# File 'lib/eth/client.rb', line 129

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.



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/eth/client.rb', line 323

def transact(contract, function, *args, **kwargs)
  gas_limit = if kwargs[:gas_limit]
      kwargs[:gas_limit]
    else
      Tx.estimate_intrinsic_gas(contract.bin)
    end
  params = {
    value: kwargs[:tx_value] || 0,
    gas_limit: gas_limit,
    chain_id: chain_id,
    to: kwargs[:address] || contract.address,
    data: contract.function(function, args: args.size).encode_call(*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:



346
347
348
349
350
351
352
353
# File 'lib/eth/client.rb', line 346

def transact_and_wait(contract, function, *args, **kwargs)
  begin
    hash = wait_for_tx(transact(contract, function, *args, **kwargs))
    return hash, tx_succeeded?(hash)
  rescue RpcError => e
    raise ContractExecutionError, contract.decode_error(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.



162
163
164
165
166
167
168
169
170
# File 'lib/eth/client.rb', line 162

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.



141
142
143
# File 'lib/eth/client.rb', line 141

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.



200
201
202
203
# File 'lib/eth/client.rb', line 200

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.



177
178
179
# File 'lib/eth/client.rb', line 177

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.



403
404
405
406
# File 'lib/eth/client.rb', line 403

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.



412
413
414
415
# File 'lib/eth/client.rb', line 412

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.



422
423
424
425
426
427
428
429
430
431
# File 'lib/eth/client.rb', line 422

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