eth 0.5.13 → 0.5.15

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +1 -1
  3. data/.github/workflows/docs.yml +2 -2
  4. data/.github/workflows/spec.yml +1 -3
  5. data/CHANGELOG.md +33 -0
  6. data/CODE_OF_CONDUCT.md +3 -5
  7. data/Gemfile +3 -3
  8. data/LICENSE.txt +1 -1
  9. data/README.md +6 -6
  10. data/SECURITY.md +2 -2
  11. data/eth.gemspec +4 -1
  12. data/lib/eth/abi/decoder.rb +18 -7
  13. data/lib/eth/abi/encoder.rb +14 -26
  14. data/lib/eth/abi/event.rb +5 -1
  15. data/lib/eth/abi/function.rb +124 -0
  16. data/lib/eth/abi/packed/encoder.rb +196 -0
  17. data/lib/eth/abi/type.rb +77 -16
  18. data/lib/eth/abi.rb +29 -2
  19. data/lib/eth/address.rb +3 -1
  20. data/lib/eth/api.rb +1 -1
  21. data/lib/eth/chain.rb +9 -1
  22. data/lib/eth/client/http.rb +7 -3
  23. data/lib/eth/client/ipc.rb +1 -1
  24. data/lib/eth/client.rb +38 -37
  25. data/lib/eth/constant.rb +1 -1
  26. data/lib/eth/contract/error.rb +62 -0
  27. data/lib/eth/contract/event.rb +69 -16
  28. data/lib/eth/contract/function.rb +22 -1
  29. data/lib/eth/contract/function_input.rb +1 -1
  30. data/lib/eth/contract/function_output.rb +12 -4
  31. data/lib/eth/contract/initializer.rb +1 -1
  32. data/lib/eth/contract.rb +56 -5
  33. data/lib/eth/eip712.rb +49 -13
  34. data/lib/eth/ens/coin_type.rb +1 -1
  35. data/lib/eth/ens/resolver.rb +1 -1
  36. data/lib/eth/ens.rb +1 -1
  37. data/lib/eth/key/decrypter.rb +1 -1
  38. data/lib/eth/key/encrypter.rb +1 -1
  39. data/lib/eth/key.rb +2 -2
  40. data/lib/eth/rlp/decoder.rb +1 -1
  41. data/lib/eth/rlp/encoder.rb +1 -1
  42. data/lib/eth/rlp/sedes/big_endian_int.rb +1 -1
  43. data/lib/eth/rlp/sedes/binary.rb +1 -1
  44. data/lib/eth/rlp/sedes/list.rb +1 -1
  45. data/lib/eth/rlp/sedes.rb +1 -1
  46. data/lib/eth/rlp.rb +1 -1
  47. data/lib/eth/signature.rb +1 -1
  48. data/lib/eth/solidity.rb +1 -1
  49. data/lib/eth/tx/eip1559.rb +33 -8
  50. data/lib/eth/tx/eip2930.rb +32 -7
  51. data/lib/eth/tx/eip4844.rb +389 -0
  52. data/lib/eth/tx/eip7702.rb +520 -0
  53. data/lib/eth/tx/legacy.rb +31 -7
  54. data/lib/eth/tx.rb +88 -1
  55. data/lib/eth/unit.rb +1 -1
  56. data/lib/eth/util.rb +20 -8
  57. data/lib/eth/version.rb +2 -2
  58. data/lib/eth.rb +1 -1
  59. metadata +26 -16
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -16,40 +16,93 @@
16
16
 
17
17
  # Provides the {Eth} module.
18
18
  module Eth
19
-
20
19
  # Provide classes for contract event.
21
20
  class Contract::Event
22
- attr_accessor :name, :signature, :input_types, :inputs, :event_string, :address
23
-
24
21
  # Constructor of the {Eth::Contract::Event} class.
25
22
  #
26
23
  # @param data [Hash] contract event data.
27
24
  def initialize(data)
28
- @name = data["name"]
29
- @input_types = data["inputs"].collect do |x|
30
- type_name x
31
- end
32
- @inputs = data["inputs"].collect { |x| x["name"] }
33
- @event_string = Abi::Event.signature(data)
34
- @signature = Digest::Keccak.hexdigest(@event_string, 256)
25
+ @data = data
26
+ end
27
+
28
+ # Returns the name of the event.
29
+ #
30
+ # @return [String] The event name.
31
+ def name
32
+ @data["name"]
33
+ end
34
+
35
+ # Returns the input types for the event.
36
+ #
37
+ # @return [Array<String>] An array of input type names.
38
+ def input_types
39
+ @input_types ||= @data["inputs"].map { |x| type_name(x) }
40
+ end
41
+
42
+ # Returns the names of input parameters.
43
+ #
44
+ # @return [Array<String>] An array of input parameter names.
45
+ def inputs
46
+ @inputs ||= @data["inputs"].map { |x| x["name"] }
47
+ end
48
+
49
+ # Returns the event signature string.
50
+ #
51
+ # @return [String] The event signature string, generated from ABI.
52
+ def event_string
53
+ @event_string ||= Abi::Event.signature(@data)
54
+ end
55
+
56
+ # Returns the Keccak-256 event signature hash.
57
+ #
58
+ # @return [String] The event signature hash in hexadecimal format.
59
+ def signature
60
+ @signature ||= Digest::Keccak.hexdigest(event_string, 256)
61
+ end
62
+
63
+ # Returns the Ethereum address associated with the event.
64
+ #
65
+ # @return [String, nil] The Ethereum address, or `nil` if not set.
66
+ def address
67
+ @address ||= nil
35
68
  end
36
69
 
37
70
  # Set the address of the smart contract
38
71
  #
39
72
  # @param address [String] contract address.
40
73
  def set_address(address)
41
- @address = address.nil? ? nil : Eth::Address.new(address).address
74
+ @address = address ? Eth::Address.new(address).address : nil
75
+ end
76
+
77
+ # Decodes event parameters from logs.
78
+ #
79
+ # @param topics [Array<String>] The list of log topics, including the event selector.
80
+ # @param data [String] The log data containing non-indexed parameters.
81
+ # @return [ActiveSupport::HashWithIndifferentAccess] A hash of decoded event parameters.
82
+ def decode_params(topics, data = "0x")
83
+ inputs = @data["inputs"]
84
+
85
+ indexed_inputs, non_indexed_inputs = inputs.partition { _1["indexed"] }
86
+
87
+ {
88
+ **indexed_inputs.each_with_index.inject({}) do |result, (input, index)|
89
+ result[input["name"]] = Eth::Abi.decode([input["type"]], topics[index + 1])[0]
90
+ result
91
+ end,
92
+ **Hash[non_indexed_inputs.map { _1["name"] }.zip(
93
+ Eth::Abi.decode(non_indexed_inputs.map { |i| i["type"] }, data)
94
+ )],
95
+ }
42
96
  end
43
97
 
44
98
  private
45
99
 
46
100
  def type_name(x)
47
- type = x["type"]
48
- case type
101
+ case x["type"]
49
102
  when "tuple"
50
- "(#{x["components"].collect { |c| type_name(c) }.join(",")})"
103
+ "(#{x["components"].map { |c| type_name(c) }.join(",")})"
51
104
  else
52
- type
105
+ x["type"]
53
106
  end
54
107
  end
55
108
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -53,5 +53,26 @@ module Eth
53
53
  def self.encoded_function_signature(signature)
54
54
  Util.bin_to_hex Util.keccak256(signature)[0..3]
55
55
  end
56
+
57
+ # Encodes a function call arguments
58
+ #
59
+ # @param args [Array] function arguments
60
+ # @return [String] encoded function call data
61
+ def encode_call(*args)
62
+ types = inputs.map(&:parsed_type)
63
+ encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args))
64
+ Util.prefix_hex(signature + (encoded_str.empty? ? "0" * 64 : encoded_str))
65
+ end
66
+
67
+ # Decodes a function call result
68
+ #
69
+ # @param data [String] eth_call result in hex format
70
+ # @return [Array]
71
+ def decode_call_result(data)
72
+ return nil if data == "0x"
73
+
74
+ types = outputs.map(&:parsed_type)
75
+ Eth::Abi.decode(types, data)
76
+ end
56
77
  end
57
78
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -19,19 +19,27 @@ module Eth
19
19
 
20
20
  # Provide classes for contract function output.
21
21
  class Contract::FunctionOutput
22
- attr_accessor :type, :name
22
+ attr_accessor :type, :raw_type, :name
23
23
 
24
24
  # Constructor of the {Eth::Contract::FunctionOutput} class.
25
25
  #
26
26
  # @param data [Hash] contract abi data.
27
27
  def initialize(data)
28
- @type = Eth::Abi::Type.parse(data["type"])
28
+ @raw_type = data["type"]
29
+ @type = Eth::Abi::Type.parse(data["type"], data["components"])
29
30
  @name = data["name"]
30
31
  end
31
32
 
32
33
  # Returns complete types with subtypes, e.g., `uint256`.
33
34
  def type
34
- @type.base_type + @type.sub_type + @type.dimensions.map { |dimension| "[#{dimension > 0 ? dimension : ""}]" }.join("")
35
+ @type.base_type +
36
+ @type.sub_type +
37
+ @type.dimensions.map { |dimension| "[#{dimension > 0 ? dimension : ""}]" }.join("")
38
+ end
39
+
40
+ # Returns parsed types.
41
+ def parsed_type
42
+ @type
35
43
  end
36
44
  end
37
45
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/contract.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ module Eth
25
25
  attr_accessor :key
26
26
  attr_accessor :gas_limit, :gas_price, :max_fee_per_gas, :max_priority_fee_per_gas, :nonce
27
27
  attr_accessor :bin, :name, :abi, :class_object
28
- attr_accessor :events, :functions, :constructor_inputs
28
+ attr_accessor :events, :functions, :constructor_inputs, :errors
29
29
 
30
30
  # Constructor of the {Eth::Contract} class.
31
31
  #
@@ -44,7 +44,7 @@ module Eth
44
44
  @name = _name
45
45
  @bin = bin
46
46
  @abi = abi
47
- @constructor_inputs, @functions, @events = parse_abi(abi)
47
+ @constructor_inputs, @functions, @events, @errors = parse_abi(abi)
48
48
  end
49
49
 
50
50
  # Creates a contract wrapper from a Solidity file.
@@ -106,6 +106,52 @@ module Eth
106
106
  end
107
107
  end
108
108
 
109
+ # Finds a function by name.
110
+ #
111
+ # @param name [String] function name.
112
+ # @param args [Integer, nil] number of arguments of a function.
113
+ # @return [Eth::Contract::Function] function object.
114
+ # @raise [ArgumentError] if function not found.
115
+ def function(name, args: nil)
116
+ functions.find do |f|
117
+ f.name == name && (args.nil? || args == f.inputs.size)
118
+ end || raise(ArgumentError, "this function does not exist!")
119
+ end
120
+
121
+ # Finds an error by name.
122
+ #
123
+ # @param name [String] error name.
124
+ # @param args [Integer, nil] number of arguments of an error.
125
+ # @return [Eth::Contract::Error] error object.
126
+ # @raise [ArgumentError] if error not found.
127
+ def error(name, args: nil)
128
+ errors.find do |e|
129
+ e.name == name && (args.nil? || args == e.inputs.size)
130
+ end || raise(ArgumentError, "this error does not exist!")
131
+ end
132
+
133
+ # Decodes a custom error returned by an RPC error using the contract ABI.
134
+ #
135
+ # @param rpc_error [RpcError] the RPC error containing revert data.
136
+ # @return [String] a human readable error message.
137
+ def decode_error(rpc_error)
138
+ data = rpc_error.data
139
+ return rpc_error.message if data.nil? || errors.nil?
140
+
141
+ signature = data[0, 10]
142
+ if (err = errors.find { |e| e.signature == signature })
143
+ values = err.decode(data)
144
+ args = values&.map { |v| v.is_a?(String) ? v : v.inspect }&.join(",")
145
+ args ||= ""
146
+ "execution reverted: #{err.name}(#{args})"
147
+ elsif signature == "0x08c379a0"
148
+ reason = Abi.decode(["string"], "0x" + data[10..])&.first
149
+ "execution reverted: #{reason}"
150
+ else
151
+ rpc_error.message
152
+ end
153
+ end
154
+
109
155
  # Create meta classes for smart contracts.
110
156
  def build
111
157
  class_name = @name
@@ -116,9 +162,12 @@ module Eth
116
162
  def_delegators :parent, :name, :abi, :bin
117
163
  def_delegators :parent, :gas_limit, :gas_price, :gas_limit=, :gas_price=, :nonce, :nonce=
118
164
  def_delegators :parent, :max_fee_per_gas, :max_fee_per_gas=, :max_priority_fee_per_gas, :max_priority_fee_per_gas=
119
- def_delegators :parent, :events
165
+ def_delegators :parent, :events, :errors
120
166
  def_delegators :parent, :address, :address=
121
167
  def_delegator :parent, :functions
168
+ def_delegator :parent, :function
169
+ def_delegator :parent, :error
170
+ def_delegator :parent, :decode_error
122
171
  def_delegator :parent, :constructor_inputs
123
172
  define_method :parent do
124
173
  parent
@@ -140,7 +189,8 @@ module Eth
140
189
  end
141
190
  functions = abi.select { |x| x["type"] == "function" }.map { |fun| Eth::Contract::Function.new(fun) }
142
191
  events = abi.select { |x| x["type"] == "event" }.map { |evt| Eth::Contract::Event.new(evt) }
143
- [constructor_inputs, functions, events]
192
+ errors = abi.select { |x| x["type"] == "error" }.map { |err| Eth::Contract::Error.new(err) }
193
+ [constructor_inputs, functions, events, errors]
144
194
  end
145
195
  end
146
196
  end
@@ -151,3 +201,4 @@ require "eth/contract/function"
151
201
  require "eth/contract/function_input"
152
202
  require "eth/contract/function_output"
153
203
  require "eth/contract/initializer"
204
+ require "eth/contract/error"
data/lib/eth/eip712.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -47,7 +47,12 @@ module Eth
47
47
 
48
48
  # recursively look for further nested dependencies
49
49
  types[primary_type.to_sym].each do |t|
50
- dependency = type_dependencies t[:type], types, result
50
+ nested_type = t[:type]
51
+ # unpack arrays to their inner types to resolve dependencies
52
+ if nested_type.end_with?("]")
53
+ nested_type = nested_type.partition("[").first
54
+ end
55
+ dependency = type_dependencies nested_type, types, result
51
56
  end
52
57
  return result
53
58
  end
@@ -113,18 +118,12 @@ module Eth
113
118
  types[primary_type.to_sym].each do |field|
114
119
  value = data[field[:name].to_sym]
115
120
  type = field[:type]
116
- raise NotImplementedError, "Arrays currently unimplemented for EIP-712." if type.end_with? "]"
117
- if type == "string"
118
- encoded_types.push "bytes32"
119
- encoded_values.push Util.keccak256 value
120
- elsif type == "bytes"
121
- encoded_types.push "bytes32"
122
- value = Util.hex_to_bin value
123
- encoded_values.push Util.keccak256 value
124
- elsif !types[type.to_sym].nil?
121
+ if type.end_with?("]")
122
+ encoded_types.push type
123
+ encoded_values.push encode_array(type, value, types)
124
+ elsif type == "string" || type == "bytes" || !types[type.to_sym].nil?
125
125
  encoded_types.push "bytes32"
126
- value = encode_data type, value, types
127
- encoded_values.push Util.keccak256 value
126
+ encoded_values.push encode_value(type, value, types)
128
127
  else
129
128
  encoded_types.push type
130
129
  encoded_values.push value
@@ -135,6 +134,43 @@ module Eth
135
134
  return Abi.encode encoded_types, encoded_values
136
135
  end
137
136
 
137
+ # Encodes a single value according to its type following EIP-712 rules.
138
+ # Returns a 32-byte binary string.
139
+ def encode_value(type, value, types)
140
+ if type == "string"
141
+ return Util.keccak256 value
142
+ elsif type == "bytes"
143
+ value = Util.hex_to_bin value
144
+ return Util.keccak256 value
145
+ elsif !types[type.to_sym].nil?
146
+ nested = encode_data type, value, types
147
+ return Util.keccak256 nested
148
+ else
149
+ # encode basic types via ABI to get 32-byte representation
150
+ return Abi.encode([type], [value])
151
+ end
152
+ end
153
+
154
+ # Prepares array values by encoding each element according to its
155
+ # base type. Returns an array compatible with Abi.encode.
156
+ def encode_array(type, value, types)
157
+ inner_type = type.slice(0, type.rindex("["))
158
+ return [] if value.nil?
159
+ value.map do |v|
160
+ if inner_type.end_with?("]")
161
+ encode_array inner_type, v, types
162
+ elsif inner_type == "string"
163
+ Util.keccak256 v
164
+ elsif inner_type == "bytes"
165
+ Util.keccak256 Util.hex_to_bin(v)
166
+ elsif !types[inner_type.to_sym].nil?
167
+ Util.keccak256 encode_data(inner_type, v, types)
168
+ else
169
+ v
170
+ end
171
+ end
172
+ end
173
+
138
174
  # Recursively ABI-encodes and hashes all data and types.
139
175
  #
140
176
  # @param primary_type [String] the primary type which we want to hash.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/ens.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/key.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -75,7 +75,7 @@ module Eth
75
75
  signature = compact.bytes
76
76
  v = Chain.to_v recovery_id, chain_id
77
77
  leading_zero = true
78
- [v].pack("N").unpack("C*").each do |byte|
78
+ [v].pack("Q>").unpack("C*").each do |byte|
79
79
  leading_zero = false if byte > 0 and leading_zero
80
80
  signature.append byte unless leading_zero and byte === 0
81
81
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/rlp/sedes.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/rlp.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/signature.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/solidity.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -78,7 +78,7 @@ module Eth
78
78
  # @option params [Integer] :max_gas_fee the max transaction fee per gas.
79
79
  # @option params [Integer] :gas_limit the gas limit.
80
80
  # @option params [Eth::Address] :from the sender address.
81
- # @option params [Eth::Address] :to the reciever address.
81
+ # @option params [Eth::Address] :to the receiver address.
82
82
  # @option params [Integer] :value the transaction value.
83
83
  # @option params [String] :data the transaction data payload.
84
84
  # @option params [Array] :access_list an optional access list.
@@ -148,13 +148,13 @@ module Eth
148
148
  raise ParameterError, "Transaction missing fields!" if tx.size < 9
149
149
 
150
150
  # populate the 9 payload fields
151
- chain_id = Util.deserialize_big_endian_to_int tx[0]
152
- nonce = Util.deserialize_big_endian_to_int tx[1]
153
- priority_fee = Util.deserialize_big_endian_to_int tx[2]
154
- max_gas_fee = Util.deserialize_big_endian_to_int tx[3]
155
- gas_limit = Util.deserialize_big_endian_to_int tx[4]
151
+ chain_id = Util.deserialize_rlp_int tx[0]
152
+ nonce = Util.deserialize_rlp_int tx[1]
153
+ priority_fee = Util.deserialize_rlp_int tx[2]
154
+ max_gas_fee = Util.deserialize_rlp_int tx[3]
155
+ gas_limit = Util.deserialize_rlp_int tx[4]
156
156
  to = Util.bin_to_hex tx[5]
157
- value = Util.deserialize_big_endian_to_int tx[6]
157
+ value = Util.deserialize_rlp_int tx[6]
158
158
  data = tx[7]
159
159
  access_list = tx[8]
160
160
 
@@ -257,6 +257,31 @@ module Eth
257
257
  return hash
258
258
  end
259
259
 
260
+ # Signs the transaction with a provided signature blob.
261
+ #
262
+ # @param signature [String] the concatenated `r`, `s`, and `v` values.
263
+ # @return [String] a transaction hash.
264
+ # @raise [Signature::SignatureError] if transaction is already signed.
265
+ # @raise [Signature::SignatureError] if sender address does not match signer.
266
+ def sign_with(signature)
267
+ if Tx.signed? self
268
+ raise Signature::SignatureError, "Transaction is already signed!"
269
+ end
270
+
271
+ # ensure the sender address matches the signature
272
+ unless @sender.nil? or sender.empty?
273
+ public_key = Signature.recover(unsigned_hash, signature, @chain_id)
274
+ signer_address = Tx.sanitize_address Util.public_key_to_address(public_key).to_s
275
+ from_address = Tx.sanitize_address @sender
276
+ raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
277
+ end
278
+
279
+ r, s, v = Signature.dissect signature
280
+ recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id
281
+ send :_set_signature, recovery_id, r, s
282
+ return hash
283
+ end
284
+
260
285
  # Encodes a raw transaction object, wraps it in an EIP-2718 envelope
261
286
  # with an EIP-1559 type prefix.
262
287
  #
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2023 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2025 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -76,7 +76,7 @@ module Eth
76
76
  # @option params [Integer] :gas_price the gas price.
77
77
  # @option params [Integer] :gas_limit the gas limit.
78
78
  # @option params [Eth::Address] :from the sender address.
79
- # @option params [Eth::Address] :to the reciever address.
79
+ # @option params [Eth::Address] :to the receiver address.
80
80
  # @option params [Integer] :value the transaction value.
81
81
  # @option params [String] :data the transaction data payload.
82
82
  # @option params [Array] :access_list an optional access list.
@@ -145,12 +145,12 @@ module Eth
145
145
  raise ParameterError, "Transaction missing fields!" if tx.size < 8
146
146
 
147
147
  # populate the 8 payload fields
148
- chain_id = Util.deserialize_big_endian_to_int tx[0]
149
- nonce = Util.deserialize_big_endian_to_int tx[1]
150
- gas_price = Util.deserialize_big_endian_to_int tx[2]
151
- gas_limit = Util.deserialize_big_endian_to_int tx[3]
148
+ chain_id = Util.deserialize_rlp_int tx[0]
149
+ nonce = Util.deserialize_rlp_int tx[1]
150
+ gas_price = Util.deserialize_rlp_int tx[2]
151
+ gas_limit = Util.deserialize_rlp_int tx[3]
152
152
  to = Util.bin_to_hex tx[4]
153
- value = Util.deserialize_big_endian_to_int tx[5]
153
+ value = Util.deserialize_rlp_int tx[5]
154
154
  data = tx[6]
155
155
  access_list = tx[7]
156
156
 
@@ -251,6 +251,31 @@ module Eth
251
251
  return hash
252
252
  end
253
253
 
254
+ # Signs the transaction with a provided signature blob.
255
+ #
256
+ # @param signature [String] the concatenated `r`, `s`, and `v` values.
257
+ # @return [String] a transaction hash.
258
+ # @raise [Signature::SignatureError] if transaction is already signed.
259
+ # @raise [Signature::SignatureError] if sender address does not match signer.
260
+ def sign_with(signature)
261
+ if Tx.signed? self
262
+ raise Signature::SignatureError, "Transaction is already signed!"
263
+ end
264
+
265
+ # ensure the sender address matches the signature
266
+ unless @sender.nil? or sender.empty?
267
+ public_key = Signature.recover(unsigned_hash, signature, @chain_id)
268
+ signer_address = Tx.sanitize_address Util.public_key_to_address(public_key).to_s
269
+ from_address = Tx.sanitize_address @sender
270
+ raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
271
+ end
272
+
273
+ r, s, v = Signature.dissect signature
274
+ recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id
275
+ send :_set_signature, recovery_id, r, s
276
+ return hash
277
+ end
278
+
254
279
  # Encodes a raw transaction object, wraps it in an EIP-2718 envelope
255
280
  # with an EIP-2930 type prefix.
256
281
  #