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: 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.



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

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

Instance Attribute Details

#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 (coinbase) of the connected client.

Returns:



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

def 
  
end

#gas_limitObject

The default gas limit for the transaction.



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

def gas_limit
  @gas_limit
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.



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.



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.

Parameters:

  • host (String)

    either an HTTP/S host or an IPC path.

Returns:

Raises:

  • (ArgumentError)

    in case it cannot determine the client type.



47
48
49
50
51
# File 'lib/eth/client.rb', line 47

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_name) ⇒ Object #call(contract, function_name, value) ⇒ Object #call(contract, function_name, value, sender_key, legacy, gas_limit) ⇒ Object

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

Overloads:

  • #call(contract, function_name) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

  • #call(contract, function_name, value) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

    • value (Integer|String)

      function arguments.

  • #call(contract, function_name, value, sender_key, legacy, gas_limit) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

    • value (Integer|String)

      function arguments.

    • 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.

Returns:

  • (Object)

    returns the result of the call.

Raises:

  • (ArgumentError)


248
249
250
251
252
253
254
255
256
257
# File 'lib/eth/client.rb', line 248

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

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

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

Overloads:

  • #deploy(contract) ⇒ String

    Parameters:

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

    *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.

    Parameters:

Returns:

  • (String)

    the transaction hash.

Raises:

  • (ArgumentError)

    in case the contract does not have any source.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/eth/client.rb', line 183

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,
  }
  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?
    # Uses 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
    # Uses the default account as sender and external signer
    params.merge!({
      from: ,
      nonce: get_nonce(),
    })
    return eth_send_transaction(params)["result"]
  end
end

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

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

Overloads:

  • #deploy(contract) ⇒ String

    Parameters:

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

    *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.

    Parameters:

Returns:

  • (String)

    the contract address.



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

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.



80
81
82
# File 'lib/eth/client.rb', line 80

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.



88
89
90
# File 'lib/eth/client.rb', line 88

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

#is_mined_tx?(hash) ⇒ Boolean

Checkes wether a transaction is mined or not.

Parameters:

  • hash (String)

    the transaction hash.

Returns:

  • (Boolean)

    true if included in a block.



374
375
376
377
# File 'lib/eth/client.rb', line 374

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

#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.



353
354
355
356
357
358
359
360
# File 'lib/eth/client.rb', line 353

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.is_hex? hash
  signature = Util.hex_to_bin signature if Util.is_hex? signature
  magic = Util.hex_to_bin magic if Util.is_hex? magic
  result = call(contract, "isValidSignature", hash, signature)
  return result === magic
end

#reset_idInteger

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

Returns:

  • (Integer)

    0



366
367
368
# File 'lib/eth/client.rb', line 366

def reset_id
  @id = 0
end

#transact(contract, function_name) ⇒ Object #transact(contract, function_name, value) ⇒ Object #transact(contract, function_name, value, sender_key, legacy, address, gas_limit) ⇒ Object

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

Overloads:

  • #transact(contract, function_name) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

  • #transact(contract, function_name, value) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

    • value (Integer|String)

      function arguments.

  • #transact(contract, function_name, value, sender_key, legacy, address, gas_limit) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

    • value (Integer|String)

      function arguments.

    • sender_key (Eth::Key)

      the sender private key.

    • legacy (Boolean)

      enables legacy transactions (pre-EIP-1559).

    • address (String)

      contract address.

    • gas_limit (Integer)

      optional gas limit override for deploying the contract.

Returns:

  • (Object)

    returns the result of the call.



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/eth/client.rb', line 278

def transact(contract, function_name, *args, **kwargs)
  gas_limit = if kwargs[:gas_limit]
      kwargs[:gas_limit]
    else
      Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
    end
  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: ,
      nonce: get_nonce(),
    })
    return eth_send_transaction(params)["result"]
  end
end

#transact_and_wait(contract, function_name) ⇒ Object #transact_and_wait(contract, function_name, value) ⇒ Object #transact_and_wait(contract, function_name, value, sender_key, legacy, address) ⇒ Object

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

Overloads:

  • #transact_and_wait(contract, function_name) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

  • #transact_and_wait(contract, function_name, value) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

    • value (Integer|String)

      function arguments.

  • #transact_and_wait(contract, function_name, value, sender_key, legacy, address) ⇒ Object

    Parameters:

    • contract (Eth::Contract)

      subject contract to call.

    • function_name (String)

      method name to be called.

    • value (Integer|String)

      function arguments.

    • sender_key (Eth::Key)

      the sender private key.

    • legacy (Boolean)

      enables legacy transactions (pre-EIP-1559).

    • address (String)

      contract address.

Returns:

  • (Object)

    returns the result of the call.



339
340
341
# File 'lib/eth/client.rb', line 339

def transact_and_wait(contract, function_name, *args, **kwargs)
  wait_for_tx(transact(contract, function_name, *args, **kwargs))
end

#transfer(destination, amount, sender_key = nil, legacy = false) ⇒ String

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

Parameters:

  • destination (Eth::Address)

    the destination address.

  • amount (Integer)

    the transfer amount in Wei.

  • sender_key (Eth::Key) (defaults to: nil)

    the sender private key.

  • legacy (Boolean) (defaults to: false)

    enables legacy transactions (pre-EIP-1559).

Returns:

  • (String)

    the transaction hash.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/eth/client.rb', line 114

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: ,
      nonce: get_nonce(),
    })
    return eth_send_transaction(params)["result"]
  end
end

#transfer_and_wait(destination, amount, sender_key = nil, legacy = false) ⇒ String

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

Parameters:

  • destination (Eth::Address)

    the destination address.

  • amount (Integer)

    the transfer amount in Wei.

  • sender_key (Eth::Key) (defaults to: nil)

    the sender private key.

  • legacy (Boolean) (defaults to: false)

    enables legacy transactions (pre-EIP-1559).

Returns:

  • (String)

    the transaction hash.



101
102
103
# File 'lib/eth/client.rb', line 101

def transfer_and_wait(destination, amount, sender_key = nil, legacy = false)
  wait_for_tx(transfer(destination, amount, sender_key, legacy))
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.



384
385
386
387
388
389
390
391
392
393
# File 'lib/eth/client.rb', line 384

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