Class: Eth::Tx::Eip7702
- Inherits:
-
Object
- Object
- Eth::Tx::Eip7702
- Defined in:
- lib/eth/tx/eip7702.rb
Overview
Provides support for EIP-7702 transactions utilizing EIP-2718 types and envelopes. Ref: eips.ethereum.org/EIPS/eip-7702
Defined Under Namespace
Classes: Authorization
Instance Attribute Summary collapse
-
#access_list ⇒ Object
readonly
An optional EIP-2930 access list.
-
#amount ⇒ Object
readonly
The transaction amount in Wei.
-
#authorization_list ⇒ Object
readonly
The list of of Authorization instances.
-
#chain_id ⇒ Object
readonly
The EIP-155 Chain ID.
-
#destination ⇒ Object
readonly
The recipient address.
-
#gas_limit ⇒ Object
readonly
The gas limit for the transaction.
-
#max_fee_per_gas ⇒ Object
readonly
The transaction max fee per gas in Wei.
-
#max_priority_fee_per_gas ⇒ Object
readonly
The transaction max priority fee per gas in Wei.
-
#payload ⇒ Object
readonly
The transaction data payload.
-
#sender ⇒ Object
readonly
The sender address.
-
#signature_r ⇒ Object
readonly
The signature
r
value. -
#signature_s ⇒ Object
readonly
The signature
s
value. -
#signature_y_parity ⇒ Object
readonly
The signature’s y-parity byte (not v).
-
#signer_nonce ⇒ Object
readonly
The transaction nonce provided by the signer.
-
#type ⇒ Object
readonly
The transaction type.
Instance Method Summary collapse
-
#decode(hex) ⇒ Eth::Tx::Eip7702
Decodes a raw transaction hex into an Eip7702 transaction object.
-
#encoded ⇒ String
Encodes a raw transaction object, wraps it in an EIP-2718 envelope with an EIP-7702 type prefix.
-
#hash ⇒ String
Gets the transaction hash.
-
#hex ⇒ String
Gets the encoded, enveloped, raw transaction hex.
-
#initialize(params) ⇒ Eip7702
constructor
Create a type-4 (EIP-7702) transaction payload object that can be prepared for envelope, signature and broadcast.
-
#sign(key) ⇒ String
Sign the transaction with a given key.
-
#unsigned_copy(tx) ⇒ Eth::Tx::Eip7702
Creates an unsigned copy of a transaction payload (keeping the signatures on the authorizations).
-
#unsigned_encoded ⇒ String
Encodes the unsigned transaction payload in an EIP-7702 envelope, required for signing.
-
#unsigned_hash ⇒ String
Gets the sign-hash required to sign a raw transaction.
Constructor Details
#initialize(params) ⇒ Eip7702
Create a type-4 (EIP-7702) transaction payload object that can be prepared for envelope, signature and broadcast. Ref: eips.ethereum.org/EIPS/eip-7702
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/eth/tx/eip7702.rb', line 205 def initialize(params) fields = { recovery_id: nil, r: 0, s: 0 }.merge params # populate optional fields with serializable empty values fields[:chain_id] = Tx.sanitize_chain fields[:chain_id] fields[:from] = Tx.sanitize_address fields[:from] fields[:to] = Tx.sanitize_address fields[:to] fields[:value] = Tx.sanitize_amount fields[:value] fields[:data] = Tx.sanitize_data fields[:data] # ensure sane values for all mandatory fields fields = Tx.validate_params fields fields = Tx.validate_eip1559_params fields fields = Tx.validate_eip7702_params fields fields[:access_list] = Tx.sanitize_list fields[:access_list] # ensure gas limit is not too low minimum_cost = Tx.estimate_intrinsic_gas fields[:data], fields[:access_list] raise ParameterError, "Transaction gas limit is too low, try #{minimum_cost}!" if fields[:gas_limit].to_i < minimum_cost # populate class attributes @signer_nonce = fields[:nonce].to_i @max_priority_fee_per_gas = fields[:priority_fee].to_i @max_fee_per_gas = fields[:max_gas_fee].to_i @gas_limit = fields[:gas_limit].to_i @sender = fields[:from].to_s @destination = fields[:to].to_s @amount = fields[:value].to_i @payload = fields[:data] @access_list = fields[:access_list] @authorization_list = fields[:authorization_list] # the signature v is set to the chain id for unsigned transactions @signature_y_parity = fields[:recovery_id] @chain_id = fields[:chain_id] # the signature fields are empty for unsigned transactions. @signature_r = fields[:r] @signature_s = fields[:s] # last but not least, set the type. @type = TYPE_7702 end |
Instance Attribute Details
#access_list ⇒ Object (readonly)
An optional EIP-2930 access list. Ref: eips.ethereum.org/EIPS/eip-2930
168 169 170 |
# File 'lib/eth/tx/eip7702.rb', line 168 def access_list @access_list end |
#amount ⇒ Object (readonly)
The transaction amount in Wei.
161 162 163 |
# File 'lib/eth/tx/eip7702.rb', line 161 def amount @amount end |
#authorization_list ⇒ Object (readonly)
The list of of Authorization instances
171 172 173 |
# File 'lib/eth/tx/eip7702.rb', line 171 def @authorization_list end |
#chain_id ⇒ Object (readonly)
The EIP-155 Chain ID. Ref: eips.ethereum.org/EIPS/eip-155
143 144 145 |
# File 'lib/eth/tx/eip7702.rb', line 143 def chain_id @chain_id end |
#destination ⇒ Object (readonly)
The recipient address.
158 159 160 |
# File 'lib/eth/tx/eip7702.rb', line 158 def destination @destination end |
#gas_limit ⇒ Object (readonly)
The gas limit for the transaction.
155 156 157 |
# File 'lib/eth/tx/eip7702.rb', line 155 def gas_limit @gas_limit end |
#max_fee_per_gas ⇒ Object (readonly)
The transaction max fee per gas in Wei.
152 153 154 |
# File 'lib/eth/tx/eip7702.rb', line 152 def max_fee_per_gas @max_fee_per_gas end |
#max_priority_fee_per_gas ⇒ Object (readonly)
The transaction max priority fee per gas in Wei.
149 150 151 |
# File 'lib/eth/tx/eip7702.rb', line 149 def max_priority_fee_per_gas @max_priority_fee_per_gas end |
#payload ⇒ Object (readonly)
The transaction data payload.
164 165 166 |
# File 'lib/eth/tx/eip7702.rb', line 164 def payload @payload end |
#sender ⇒ Object (readonly)
The sender address.
183 184 185 |
# File 'lib/eth/tx/eip7702.rb', line 183 def sender @sender end |
#signature_r ⇒ Object (readonly)
The signature r
value.
177 178 179 |
# File 'lib/eth/tx/eip7702.rb', line 177 def signature_r @signature_r end |
#signature_s ⇒ Object (readonly)
The signature s
value.
180 181 182 |
# File 'lib/eth/tx/eip7702.rb', line 180 def signature_s @signature_s end |
#signature_y_parity ⇒ Object (readonly)
The signature’s y-parity byte (not v).
174 175 176 |
# File 'lib/eth/tx/eip7702.rb', line 174 def signature_y_parity @signature_y_parity end |
#signer_nonce ⇒ Object (readonly)
The transaction nonce provided by the signer.
146 147 148 |
# File 'lib/eth/tx/eip7702.rb', line 146 def signer_nonce @signer_nonce end |
#type ⇒ Object (readonly)
The transaction type.
186 187 188 |
# File 'lib/eth/tx/eip7702.rb', line 186 def type @type end |
Instance Method Details
#decode(hex) ⇒ Eth::Tx::Eip7702
Decodes a raw transaction hex into an Eth::Tx::Eip7702 transaction object.
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 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 320 321 322 323 |
# File 'lib/eth/tx/eip7702.rb', line 260 def decode(hex) hex = Util.remove_hex_prefix hex type = hex[0, 2] raise TransactionTypeError, "Invalid transaction type #{type.inspect}!" if type.to_i(16) != TYPE_7702 bin = Util.hex_to_bin hex[2..] tx = Rlp.decode bin # decoded transactions always have 9 + 3 fields, even if they are empty or zero raise ParameterError, "Transaction missing fields!" if tx.size < 10 # populate the 10 payload fields chain_id = Util.deserialize_big_endian_to_int tx[0] nonce = Util.deserialize_big_endian_to_int tx[1] priority_fee = Util.deserialize_big_endian_to_int tx[2] max_gas_fee = Util.deserialize_big_endian_to_int tx[3] gas_limit = Util.deserialize_big_endian_to_int tx[4] to = Util.bin_to_hex tx[5] value = Util.deserialize_big_endian_to_int tx[6] data = tx[7] access_list = tx[8] = tx[9] = () # populate class attributes @chain_id = chain_id.to_i @signer_nonce = nonce.to_i @max_priority_fee_per_gas = priority_fee.to_i @max_fee_per_gas = max_gas_fee.to_i @gas_limit = gas_limit.to_i @destination = to.to_s @amount = value.to_i @payload = data @access_list = access_list @authorization_list = # populate the 3 signature fields if tx.size == 10 _set_signature(nil, 0, 0) elsif tx.size == 13 recovery_id = Util.bin_to_hex(tx[10]).to_i(16) r = Util.bin_to_hex tx[11] s = Util.bin_to_hex tx[12] # allows us to force-setting a signature if the transaction is signed already _set_signature(recovery_id, r, s) else raise DecoderError, "Cannot decode EIP-7702 payload!" end # last but not least, set the type. @type = TYPE_7702 unless recovery_id.nil? # recover sender address v = Chain.to_v recovery_id, chain_id public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v.to_s(16)}", chain_id) address = Util.public_key_to_address(public_key).to_s @sender = Tx.sanitize_address address else # keep the 'from' field blank @sender = Tx.sanitize_address nil end end |
#encoded ⇒ String
Encodes a raw transaction object, wraps it in an EIP-2718 envelope with an EIP-7702 type prefix.
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/eth/tx/eip7702.rb', line 397 def encoded unless Tx.signed? self raise Signature::SignatureError, "Transaction is not signed!" end tx_data = [] tx_data.push Util.serialize_int_to_big_endian @chain_id tx_data.push Util.serialize_int_to_big_endian @signer_nonce tx_data.push Util.serialize_int_to_big_endian @max_priority_fee_per_gas tx_data.push Util.serialize_int_to_big_endian @max_fee_per_gas tx_data.push Util.serialize_int_to_big_endian @gas_limit tx_data.push Util.hex_to_bin @destination tx_data.push Util.serialize_int_to_big_endian @amount tx_data.push Rlp::Sedes.binary.serialize @payload tx_data.push Rlp::Sedes.infer(@access_list).serialize @access_list = @authorization_list.map { || .raw } tx_data.push Rlp::Sedes.infer().serialize tx_data.push Util.serialize_int_to_big_endian @signature_y_parity tx_data.push Util.serialize_int_to_big_endian @signature_r tx_data.push Util.serialize_int_to_big_endian @signature_s tx_encoded = Rlp.encode tx_data # create an EIP-2718 envelope with EIP-7702 type payload tx_type = Util.serialize_int_to_big_endian @type return "#{tx_type}#{tx_encoded}" end |
#hash ⇒ String
Gets the transaction hash.
435 436 437 |
# File 'lib/eth/tx/eip7702.rb', line 435 def hash Util.bin_to_hex Util.keccak256 encoded end |
#hex ⇒ String
Gets the encoded, enveloped, raw transaction hex.
428 429 430 |
# File 'lib/eth/tx/eip7702.rb', line 428 def hex Util.bin_to_hex encoded end |
#sign(key) ⇒ String
Sign the transaction with a given key.
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/eth/tx/eip7702.rb', line 370 def sign(key) if Tx.signed? self raise Signature::SignatureError, "Transaction is already signed!" end # ensure the sender address matches the given key unless @sender.nil? or sender.empty? signer_address = Tx.sanitize_address key.address.to_s from_address = Tx.sanitize_address @sender raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address end # sign a keccak hash of the unsigned, encoded transaction signature = key.sign(unsigned_hash, @chain_id) r, s, v = Signature.dissect signature recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id @signature_y_parity = recovery_id @signature_r = r @signature_s = s return hash end |
#unsigned_copy(tx) ⇒ Eth::Tx::Eip7702
Creates an unsigned copy of a transaction payload (keeping the signatures on the authorizations).
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/eth/tx/eip7702.rb', line 330 def unsigned_copy(tx) # not checking transaction validity unless it's of a different class raise TransactionTypeError, "Cannot copy transaction of different payload type!" unless tx.instance_of? Tx::Eip7702 # populate class attributes @signer_nonce = tx.signer_nonce @max_priority_fee_per_gas = tx.max_priority_fee_per_gas @max_fee_per_gas = tx.max_fee_per_gas @gas_limit = tx.gas_limit @destination = tx.destination @amount = tx.amount @payload = tx.payload @access_list = tx.access_list @chain_id = tx.chain_id @authorization_list = tx..map do || Authorization.new(chain_id: .chain_id, address: .address, nonce: .nonce, recovery_id: .signature_y_parity, r: .signature_r, s: .signature_s) end # force-set signature to unsigned _set_signature(nil, 0, 0) # keep the 'from' field blank @sender = Tx.sanitize_address nil # last but not least, set the type. @type = TYPE_7702 end |
#unsigned_encoded ⇒ String
Encodes the unsigned transaction payload in an EIP-7702 envelope, required for signing.
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 |
# File 'lib/eth/tx/eip7702.rb', line 443 def unsigned_encoded tx_data = [] tx_data.push Util.serialize_int_to_big_endian @chain_id tx_data.push Util.serialize_int_to_big_endian @signer_nonce tx_data.push Util.serialize_int_to_big_endian @max_priority_fee_per_gas tx_data.push Util.serialize_int_to_big_endian @max_fee_per_gas tx_data.push Util.serialize_int_to_big_endian @gas_limit tx_data.push Util.hex_to_bin @destination tx_data.push Util.serialize_int_to_big_endian @amount tx_data.push Rlp::Sedes.binary.serialize @payload tx_data.push Rlp::Sedes.infer(@access_list).serialize @access_list = @authorization_list.map { || .raw } tx_data.push Rlp::Sedes.infer().serialize tx_encoded = Rlp.encode tx_data # create an EIP-2718 envelope with EIP-7702 type payload (unsigned) tx_type = Util.serialize_int_to_big_endian @type return "#{tx_type}#{tx_encoded}" end |