eth 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,7 +24,7 @@ module Eth
24
24
  attr_accessor :path
25
25
 
26
26
  # Constructor for the IPC Client. Should not be used; use
27
- # {Client.create} intead.
27
+ # {Client.create} instead.
28
28
  #
29
29
  # @param path [String] an URI pointing to an IPC RPC-API.
30
30
  def initialize(path)
@@ -36,7 +36,7 @@ module Eth
36
36
  #
37
37
  # @param payload [Hash] the RPC request parameters.
38
38
  # @return [String] a JSON-encoded response.
39
- def send(payload)
39
+ def send_request(payload)
40
40
  socket = UNIXSocket.new(@path)
41
41
  socket.puts(payload)
42
42
  read = socket.recvmsg(nil)[0]
data/lib/eth/client.rb CHANGED
@@ -34,28 +34,22 @@ module Eth
34
34
  # The default transaction max fee per gas in Wei, defaults to {Tx::DEFAULT_GAS_PRICE}.
35
35
  attr_accessor :max_fee_per_gas
36
36
 
37
- # The default gas limit for the transaction, defaults to {Tx::DEFAULT_GAS_LIMIT}.
38
- attr_accessor :gas_limit
39
-
40
37
  # A custom error type if a contract interaction fails.
41
38
  class ContractExecutionError < StandardError; end
42
39
 
43
40
  # Creates a new RPC-Client, either by providing an HTTP/S host or
44
41
  # an IPC path. Supports basic authentication with username and password.
45
42
  #
46
- # **Note**, this sets the folling gas defaults: {Tx::DEFAULT_PRIORITY_FEE},
47
- # {Tx::DEFAULT_GAS_PRICE}, and {Tx::DEFAULT_GAS_LIMIT}. Use
48
- # {#max_priority_fee_per_gas}, {#max_fee_per_gas}, and {#gas_limit} to set
49
- # custom values prior to submitting transactions.
43
+ # **Note**, this sets the folling gas defaults: {Tx::DEFAULT_PRIORITY_FEE}
44
+ # and {Tx::DEFAULT_GAS_PRICE. Use {#max_priority_fee_per_gas} and
45
+ # {#max_fee_per_gas} to set custom values prior to submitting transactions.
50
46
  #
51
47
  # @param host [String] either an HTTP/S host or an IPC path.
52
48
  # @return [Eth::Client::Ipc] an IPC client.
53
- # @return [Eth::Client::HttpAuth] an HTTP client with basic authentication.
54
49
  # @return [Eth::Client::Http] an HTTP client.
55
50
  # @raise [ArgumentError] in case it cannot determine the client type.
56
51
  def self.create(host)
57
52
  return Client::Ipc.new host if host.end_with? ".ipc"
58
- return Client::HttpAuth.new host if Regexp.new(":.*@.*:", Regexp::IGNORECASE).match host
59
53
  return Client::Http.new host if host.start_with? "http"
60
54
  raise ArgumentError, "Unable to detect client type!"
61
55
  end
@@ -66,7 +60,6 @@ module Eth
66
60
  @id = 0
67
61
  @max_priority_fee_per_gas = Tx::DEFAULT_PRIORITY_FEE
68
62
  @max_fee_per_gas = Tx::DEFAULT_GAS_PRICE
69
- @gas_limit = Tx::DEFAULT_GAS_LIMIT
70
63
  end
71
64
 
72
65
  # Gets the default account (coinbase) of the connected client.
@@ -76,6 +69,7 @@ module Eth
76
69
  #
77
70
  # @return [Eth::Address] the coinbase account address.
78
71
  def default_account
72
+ raise ArgumentError, "The default account is not available on remote connections!" unless local? || @default_account
79
73
  @default_account ||= Address.new eth_coinbase["result"]
80
74
  end
81
75
 
@@ -102,6 +96,17 @@ module Eth
102
96
  eth_get_transaction_count(address, "pending")["result"].to_i 16
103
97
  end
104
98
 
99
+ # Resolves an ENS name to an Ethereum address on the connected chain.
100
+ #
101
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
102
+ # @param registry [String] the address for the ENS registry.
103
+ # @param coin_type [Integer] the coin type as per EIP-2304.
104
+ # @return [Eth::Address] the Ethereum address resolved from the ENS record.
105
+ def resolve_ens(ens_name, registry = Ens::DEFAULT_ADDRESS, coin_type = Ens::CoinType::ETHEREUM)
106
+ ens = Ens::Resolver.new(self, registry)
107
+ ens.resolve(ens_name, coin_type)
108
+ end
109
+
105
110
  # Simply transfer Ether to an account and waits for it to be mined.
106
111
  # Uses `eth_coinbase` and external signer if no sender key is
107
112
  # provided.
@@ -118,7 +123,7 @@ module Eth
118
123
  # if no sender key is provided.
119
124
  #
120
125
  # **Note**, that many remote providers (e.g., Infura) do not provide
121
- # any accounts. Provide a `sender_key` if you experience issues.
126
+ # any accounts. Provide a `sender_key:` if you experience issues.
122
127
  #
123
128
  # @overload transfer(destination, amount)
124
129
  # @param destination [Eth::Address] the destination address.
@@ -134,38 +139,43 @@ module Eth
134
139
  params = {
135
140
  value: amount,
136
141
  to: destination,
137
- gas_limit: gas_limit,
142
+ gas_limit: Tx::DEFAULT_GAS_LIMIT,
138
143
  chain_id: chain_id,
139
144
  }
140
- if kwargs[:legacy]
141
- params.merge!({
142
- gas_price: max_fee_per_gas,
143
- })
144
- else
145
- params.merge!({
146
- priority_fee: max_priority_fee_per_gas,
147
- max_gas_fee: max_fee_per_gas,
148
- })
149
- end
150
- unless kwargs[:sender_key].nil?
145
+ send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
146
+ end
151
147
 
152
- # use the provided key as sender and signer
153
- params.merge!({
154
- from: kwargs[:sender_key].address,
155
- nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
156
- })
157
- tx = Eth::Tx.new(params)
158
- tx.sign kwargs[:sender_key]
159
- return eth_send_raw_transaction(tx.hex)["result"]
160
- else
148
+ # Transfers a token that implements the ERC20 `transfer()` interface.
149
+ #
150
+ # See {#transfer_erc20} for params and overloads.
151
+ #
152
+ # @return [Object] returns the result of the transaction.
153
+ def transfer_erc20_and_wait(erc20_contract, destination, amount, **kwargs)
154
+ transact_and_wait(erc20_contract, "transfer", destination, amount, **kwargs)
155
+ end
161
156
 
162
- # use the default account as sender and external signer
163
- params.merge!({
164
- from: default_account,
165
- nonce: kwargs[:nonce] || get_nonce(default_account),
166
- })
167
- return eth_send_transaction(params)["result"]
168
- end
157
+ # Transfers a token that implements the ERC20 `transfer()` interface.
158
+ #
159
+ # **Note**, that many remote providers (e.g., Infura) do not provide
160
+ # any accounts. Provide a `sender_key:` if you experience issues.
161
+ #
162
+ # @overload transfer_erc20(erc20_contract, destination, amount)
163
+ # @param erc20_contract [Eth::Contract] the ERC20 contract to write to.
164
+ # @param destination [Eth::Address] the destination address.
165
+ # @param amount [Integer] the transfer amount (mind the `decimals()`).
166
+ # @overload transfer_erc20(erc20_contract, destination, amount, **kwargs)
167
+ # @param erc20_contract [Eth::Contract] the ERC20 contract to write to.
168
+ # @param destination [Eth::Address] the destination address.
169
+ # @param amount [Integer] the transfer amount (mind the `decimals()`).
170
+ # @param **sender_key [Eth::Key] the sender private key.
171
+ # @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
172
+ # @param **gas_limit [Integer] optional gas limit override for the transfer.
173
+ # @param **nonce [Integer] optional specific nonce for transaction.
174
+ # @param **tx_value [Integer] optional transaction value field filling.
175
+ # @return [Object] returns the result of the transaction.
176
+ def transfer_erc20(erc20_contract, destination, amount, **kwargs)
177
+ destination = destination.to_s if destination.instance_of? Eth::Address
178
+ transact(erc20_contract, "transfer", destination, amount, **kwargs)
169
179
  end
170
180
 
171
181
  # Deploys a contract and waits for it to be mined. Uses
@@ -184,7 +194,7 @@ module Eth
184
194
  # if no sender key is provided.
185
195
  #
186
196
  # **Note**, that many remote providers (e.g., Infura) do not provide
187
- # any accounts. Provide a `sender_key` if you experience issues.
197
+ # any accounts. Provide a `sender_key:` if you experience issues.
188
198
  #
189
199
  # @overload deploy(contract)
190
200
  # @param contract [Eth::Contract] contracts to deploy.
@@ -218,33 +228,7 @@ module Eth
218
228
  chain_id: chain_id,
219
229
  data: data,
220
230
  }
221
- if kwargs[:legacy]
222
- params.merge!({
223
- gas_price: max_fee_per_gas,
224
- })
225
- else
226
- params.merge!({
227
- priority_fee: max_priority_fee_per_gas,
228
- max_gas_fee: max_fee_per_gas,
229
- })
230
- end
231
- unless kwargs[:sender_key].nil?
232
- # Uses the provided key as sender and signer
233
- params.merge!({
234
- from: kwargs[:sender_key].address,
235
- nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
236
- })
237
- tx = Eth::Tx.new(params)
238
- tx.sign kwargs[:sender_key]
239
- return eth_send_raw_transaction(tx.hex)["result"]
240
- else
241
- # Uses the default account as sender and external signer
242
- params.merge!({
243
- from: default_account,
244
- nonce: kwargs[:nonce] || get_nonce(default_account),
245
- })
246
- return eth_send_transaction(params)["result"]
247
- end
231
+ send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
248
232
  end
249
233
 
250
234
  # Calls a contract function without executing it
@@ -263,16 +247,21 @@ module Eth
263
247
  # @param *args optional function arguments.
264
248
  # @param **sender_key [Eth::Key] the sender private key.
265
249
  # @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
266
- # @param **gas_limit [Integer] optional gas limit override for deploying the contract.
267
250
  # @return [Object] returns the result of the call.
268
251
  def call(contract, function, *args, **kwargs)
269
- func = contract.functions.select { |func| func.name == function }[0]
270
- raise ArgumentError, "this function does not exist!" if func.nil?
271
- output = call_raw(contract, func, *args, **kwargs)
252
+ func = contract.functions.select { |func| func.name == function }
253
+ raise ArgumentError, "this function does not exist!" if func.nil? || func.size === 0
254
+ selected_func = func.first
255
+ func.each do |f|
256
+ if f.inputs.size === args.size
257
+ selected_func = f
258
+ end
259
+ end
260
+ output = call_raw(contract, selected_func, *args, **kwargs)
272
261
  if output&.length == 1
273
- return output[0]
262
+ output[0]
274
263
  else
275
- return output
264
+ output
276
265
  end
277
266
  end
278
267
 
@@ -280,7 +269,7 @@ module Eth
280
269
  # contract read/write).
281
270
  #
282
271
  # **Note**, that many remote providers (e.g., Infura) do not provide
283
- # any accounts. Provide a `sender_key` if you experience issues.
272
+ # any accounts. Provide a `sender_key:` if you experience issues.
284
273
  #
285
274
  # @overload transact(contract, function)
286
275
  # @param contract [Eth::Contract] the subject contract to write to.
@@ -296,7 +285,7 @@ module Eth
296
285
  # @param **sender_key [Eth::Key] the sender private key.
297
286
  # @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
298
287
  # @param **address [Eth::Address] contract address.
299
- # @param **gas_limit [Integer] optional gas limit override for deploying the contract.
288
+ # @param **gas_limit [Integer] optional gas limit override for transacting with the contract.
300
289
  # @param **nonce [Integer] optional specific nonce for transaction.
301
290
  # @param **tx_value [Integer] optional transaction value field filling.
302
291
  # @return [Object] returns the result of the transaction.
@@ -304,7 +293,7 @@ module Eth
304
293
  gas_limit = if kwargs[:gas_limit]
305
294
  kwargs[:gas_limit]
306
295
  else
307
- Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
296
+ Tx.estimate_intrinsic_gas(contract.bin)
308
297
  end
309
298
  fun = contract.functions.select { |func| func.name == function }[0]
310
299
  params = {
@@ -314,33 +303,7 @@ module Eth
314
303
  to: kwargs[:address] || contract.address,
315
304
  data: call_payload(fun, args),
316
305
  }
317
- if kwargs[:legacy]
318
- params.merge!({
319
- gas_price: max_fee_per_gas,
320
- })
321
- else
322
- params.merge!({
323
- priority_fee: max_priority_fee_per_gas,
324
- max_gas_fee: max_fee_per_gas,
325
- })
326
- end
327
- unless kwargs[:sender_key].nil?
328
- # use the provided key as sender and signer
329
- params.merge!({
330
- from: kwargs[:sender_key].address,
331
- nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
332
- })
333
- tx = Eth::Tx.new(params)
334
- tx.sign kwargs[:sender_key]
335
- return eth_send_raw_transaction(tx.hex)["result"]
336
- else
337
- # use the default account as sender and external signer
338
- params.merge!({
339
- from: default_account,
340
- nonce: kwargs[:nonce] || get_nonce(default_account),
341
- })
342
- return eth_send_transaction(params)["result"]
343
- end
306
+ send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
344
307
  end
345
308
 
346
309
  # Executes a contract function with a transaction and waits for it
@@ -375,7 +338,7 @@ module Eth
375
338
  signature = Util.hex_to_bin signature if Util.hex? signature
376
339
  magic = Util.hex_to_bin magic if Util.hex? magic
377
340
  result = call(contract, "isValidSignature", hash, signature)
378
- return result === magic
341
+ result === magic
379
342
  end
380
343
 
381
344
  # Gives control over resetting the RPC request ID back to zero.
@@ -431,6 +394,51 @@ module Eth
431
394
 
432
395
  private
433
396
 
397
+ # Allows to determine if we work with a local connectoin
398
+ def local?
399
+ if self.instance_of? Eth::Client::Ipc
400
+ true
401
+ elsif self.host === "127.0.0.1" || self.host === "localhost"
402
+ true
403
+ else
404
+ false
405
+ end
406
+ end
407
+
408
+ # Prepares a transaction to be send for the given params.
409
+ def send_transaction(params, legacy, key, nonce)
410
+ if legacy
411
+ params.merge!({ gas_price: max_fee_per_gas })
412
+ else
413
+ params.merge!({
414
+ priority_fee: max_priority_fee_per_gas,
415
+ max_gas_fee: max_fee_per_gas,
416
+ })
417
+ end
418
+ unless key.nil?
419
+
420
+ # use the provided key as sender and signer
421
+ params.merge!({
422
+ from: key.address,
423
+ nonce: nonce || get_nonce(key.address),
424
+ })
425
+ tx = Eth::Tx.new(params)
426
+ tx.sign key
427
+ eth_send_raw_transaction(tx.hex)["result"]
428
+ else
429
+
430
+ # do not allow accessing accounts on remote connections
431
+ raise ArgumentError, "The default account is not available on remote connections, please provide a :sender_key!" unless local?
432
+
433
+ # use the default account as sender and external signer
434
+ params.merge!({
435
+ from: default_account,
436
+ nonce: nonce || get_nonce(default_account),
437
+ })
438
+ eth_send_transaction(params)["result"]
439
+ end
440
+ end
441
+
434
442
  # Non-transactional function call called from call().
435
443
  # @see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call
436
444
  def call_raw(contract, func, *args, **kwargs)
@@ -468,9 +476,9 @@ module Eth
468
476
  params: marshal(args),
469
477
  id: next_id,
470
478
  }
471
- output = JSON.parse(send(payload.to_json))
479
+ output = JSON.parse(send_request(payload.to_json))
472
480
  raise IOError, output["error"]["message"] unless output["error"].nil?
473
- return output
481
+ output
474
482
  end
475
483
 
476
484
  # Increments the request id.
@@ -491,18 +499,18 @@ module Eth
491
499
  def marshal(params)
492
500
  params = params.dup
493
501
  if params.is_a? Array
494
- return params.map! { |param| marshal(param) }
502
+ params.map! { |param| marshal(param) }
495
503
  elsif params.is_a? Hash
496
504
  params = camelize!(params)
497
- return params.transform_values! { |param| marshal(param) }
505
+ params.transform_values! { |param| marshal(param) }
498
506
  elsif params.is_a? Numeric
499
- return Util.prefix_hex "#{params.to_i.to_s(16)}"
507
+ Util.prefix_hex "#{params.to_i.to_s(16)}"
500
508
  elsif params.is_a? Address
501
- return params.to_s
509
+ params.to_s
502
510
  elsif Util.hex? params
503
- return Util.prefix_hex params
511
+ Util.prefix_hex params
504
512
  else
505
- return params
513
+ params
506
514
  end
507
515
  end
508
516
  end
@@ -510,5 +518,4 @@ end
510
518
 
511
519
  # Load the client/* libraries
512
520
  require "eth/client/http"
513
- require "eth/client/http_auth"
514
521
  require "eth/client/ipc"
data/lib/eth/contract.rb CHANGED
@@ -14,6 +14,8 @@
14
14
 
15
15
  # -*- encoding : ascii-8bit -*-
16
16
 
17
+ require "forwardable"
18
+
17
19
  # Provides the {Eth} module.
18
20
  module Eth
19
21
 
@@ -27,11 +29,19 @@ module Eth
27
29
 
28
30
  # Constructor of the {Eth::Contract} class.
29
31
  #
32
+ # **Note**, do not use this directly. Use
33
+ # {from_abi}, {from_bin}, or {from_file}!
34
+ #
30
35
  # @param name [String] contract name.
31
36
  # @param bin [String] contract bin string.
32
37
  # @param abi [String] contract abi string.
33
38
  def initialize(name, bin, abi)
34
- @name = name
39
+
40
+ # The contract name will be the class name and needs title casing.
41
+ _name = name.dup
42
+ _name[0] = name[0].upcase
43
+
44
+ @name = _name
35
45
  @bin = bin
36
46
  @abi = abi
37
47
  @constructor_inputs, @functions, @events = parse_abi(abi)
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Provides the {Eth} module.
16
+ module Eth
17
+ # Provides ENS specific functionality
18
+ # ref: https://ens.domains
19
+ module Ens
20
+ # Provides EIP-2304 / SLIP-44 cointypes to resolve ENS addresses.
21
+ # ref: https://eips.ethereum.org/EIPS/eip-2304
22
+ module CoinType
23
+ extend self
24
+
25
+ # ENS coin type for Bitcoin.
26
+ BITCOIN = 0.freeze
27
+
28
+ # ENS coin type for Litecoin.
29
+ LITECOIN = 2.freeze
30
+
31
+ # ENS coin type for Dogecoin.
32
+ DOGECOIN = 3.freeze
33
+
34
+ # ENS coin type for Ethereum.
35
+ ETHEREUM = 60.freeze
36
+
37
+ # ENS coin type for Ethereum Classic.
38
+ ETHEREUM_CLASSIC = 61.freeze
39
+
40
+ # ENS coin type for Rootstock.
41
+ ROOTSTOCK = 137.freeze
42
+
43
+ # ENS coin type for Bitcoin Cash.
44
+ BITCOIN_CASH = 145.freeze
45
+
46
+ # ENS coin type for Binance.
47
+ BINANCE = 714.freeze
48
+ end
49
+ end
50
+ end
@@ -16,6 +16,7 @@
16
16
 
17
17
  # Provides the {Eth} module.
18
18
  module Eth
19
+
19
20
  # Provides ENS specific functionality
20
21
  # ref: https://ens.domains
21
22
  module Ens
@@ -23,43 +24,84 @@ module Eth
23
24
  # Utility class for resolving ENS names to Ethereum addresses
24
25
  class Resolver
25
26
 
26
- # The default address for ENS, which applies to most chains
27
- DEFAULT_ADDRESS = "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e".freeze
27
+ # The client instance used to resolve the ENS.
28
+ attr_accessor :client
29
+
30
+ # The address of the ENS registry on the given chain.
31
+ attr_accessor :registry
28
32
 
29
- # Create an instance of the ENS Resolver
33
+ # Create an instance of the ENS Resolver.
30
34
  #
31
- # @param client [Eth::Client] The client instance
32
- # @param address [String] The address of the ENS contract
35
+ # @param client [Eth::Client] The client instance used to resolve the ENS.
36
+ # @param address [String] The address of the ENS registry on the given chain.
33
37
  def initialize(client, address = DEFAULT_ADDRESS)
34
38
  @client = client
35
- @contract = Eth::Contract.from_abi(
36
- name: "ENS",
39
+ @registry = Eth::Contract.from_abi(
40
+ name: "ENSRegistryWithFallback",
37
41
  address: address,
38
- abi: JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../../abis/ens.json"))),
42
+ abi: JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../../abi/ens_registry.json"))),
39
43
  )
40
44
  end
41
45
 
42
- # Resolve an ENS name to an address
46
+ # Resolve an ENS name owner.
47
+ #
48
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
49
+ # @return [String] The owner address of the name as a hex string.
50
+ def owner(ens_name)
51
+ @client.call(@registry, "owner", namehash(ens_name))
52
+ end
53
+
54
+ # Retrieve the public resolver for the given ENS name.
43
55
  #
44
- # @param ens_name [String] The ENS name, eg: fancy.eth
45
- # @return [String] The owner address of the name, as a hex string
46
- def resolve(ens_name)
47
- @client.call(@contract, "owner", namehash(ens_name))
56
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
57
+ # @return [Eth::Contract] The public resolver contract that can be used
58
+ # to resolve ENS names.
59
+ def resolver(ens_name)
60
+ address = @client.call(@registry, "resolver", namehash(ens_name))
61
+ Eth::Contract.from_abi(
62
+ name: "ENSPublicResolver",
63
+ address: address,
64
+ abi: JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../../abi/ens_resolver.json"))),
65
+ )
66
+ end
67
+
68
+ # Resolve an ENS name to an address.
69
+ #
70
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
71
+ # @return [String] The owner address of the name as a hex string.
72
+ def resolve(ens_name, coin_type = Ens::CoinType::ETHEREUM)
73
+ if coin_type === Ens::CoinType::ETHEREUM
74
+ return @client.call(resolver(ens_name), "addr", namehash(ens_name))
75
+ elsif coin_type === Ens::CoinType::ETHEREUM_CLASSIC
76
+ data = @client.call(resolver(ens_name), "addr", namehash(ens_name), coin_type)
77
+ return Util.bin_to_prefixed_hex data
78
+ else
79
+ raise NotImplementedError, "Coin type #{coin_type} not implemented!"
80
+ end
81
+ end
82
+
83
+ # Resolve a text record for a given ENS name.
84
+ #
85
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
86
+ # @param key [String] The key for the text record, e.g., `url`.
87
+ # @return [String] The text record.
88
+ def text(ens_name, key = "description")
89
+ @client.call(resolver(ens_name), "text", namehash(ens_name), key)
48
90
  end
49
91
 
50
92
  # Generate node for the given domain name
51
93
  # See: https://docs.ens.domains/contract-api-reference/name-processing
52
94
  #
53
- # @param ens_name [String] The ENS name, eg: fancy.eth
54
- # @return [String] The node as a hex string
95
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
96
+ # @return [String] The node as a hex string.
55
97
  def namehash(ens_name)
56
- node = ("0" * 64)
98
+ node = Util.hex_to_bin("0" * 64)
57
99
  name = normalize(ens_name)
58
100
  name.split(".").reverse.each do |label|
59
- hash = Digest::Keccak.new(256).hexdigest(label)
60
- node = Digest::Keccak.new(256).hexdigest([node + hash].pack("H*"))
101
+ hash = Util.keccak256(label)
102
+ node = Util.keccak256(node + hash)
61
103
  end
62
- "0x#{node}"
104
+ Util.bin_to_prefixed_hex node
63
105
  end
64
106
 
65
107
  # Normalize a string as specified by http://unicode.org/reports/tr46/
@@ -67,10 +109,12 @@ module Eth
67
109
  # @param input [String] The input string
68
110
  # @return [String] The normalized output string
69
111
  def normalize(input)
70
- # TODO: This is fairly complicated, and there doesn't seem to be a ruby
71
- # library which can handle it perfectly.
72
- # https://www.unicode.org/reports/tr46/tr46-27.html
73
- input.downcase
112
+ name = input.dup
113
+ if name.gsub!(/[`~!@#$%^&*()_=+\[\]{}<>,;:'"\/\\|?]/, "").nil?
114
+ return input.downcase
115
+ else
116
+ raise ArgumentError, "Provided ENS name contains illegal characters: #{input}"
117
+ end
74
118
  end
75
119
  end
76
120
  end
data/lib/eth/ens.rb ADDED
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "eth/ens/coin_type"
16
+ require "eth/ens/resolver"
17
+
18
+ # Provides the {Eth} module.
19
+ module Eth
20
+ # Provides ENS specific functionality
21
+ # ref: https://ens.domains
22
+ module Ens
23
+ extend self
24
+
25
+ # The default address for ENS, which applies to most chains
26
+ DEFAULT_ADDRESS = Address.new("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e").freeze
27
+ end
28
+ end
data/lib/eth/solidity.rb CHANGED
@@ -28,10 +28,12 @@ module Eth
28
28
 
29
29
  # Instantiates a Solidity `solc` system compiler binding that can be
30
30
  # used to compile Solidity contracts.
31
- def initialize
31
+ #
32
+ # @param path [String] optional override of the solidity compiler path.
33
+ def initialize(path = nil)
32
34
 
33
- # Currently only supports `solc`.
34
- solc = get_compiler_path
35
+ # Currently only supports `solc`. Try to override with `path`.
36
+ solc = path || get_compiler_path
35
37
  raise SystemCallError, "Unable to find the solc compiler path!" if solc.nil?
36
38
  @compiler = solc
37
39
  end
@@ -43,9 +45,10 @@ module Eth
43
45
  def compile(contract)
44
46
  raise Errno::ENOENT, "Contract file not found: #{contract}" unless File.exist? contract
45
47
  flag_opt = "--optimize"
48
+ flag_ir = "--via-ir"
46
49
  flag_json = "--combined-json=bin,abi"
47
50
  path = File.realpath contract
48
- output, error, status = Open3.capture3 @compiler, flag_opt, flag_json, path
51
+ output, error, status = Open3.capture3 @compiler, flag_opt, flag_ir, flag_json, path
49
52
  raise SystemCallError, "Unable to run solc compiler!" if status.exitstatus === 127
50
53
  raise CompilerError, error unless status.success?
51
54
  json = JSON.parse output