class Eth::Tx::Legacy
implement EIP-1559, EIP-2718, or EIP-2930.
Provides legacy support for transactions on blockchains that do not
def _set_signature(v, r, s)
def _set_signature(v, r, s) @signature_v = v @signature_r = r @signature_s = s end
def decode(hex)
-
(ParameterError)
- if transaction misses fields.
Returns:
-
(Eth::Tx::Legacy)
- transaction object.
Parameters:
-
hex
(String
) -- the raw transaction hex-string.
def decode(hex) bin = Util.hex_to_bin hex tx = Rlp.decode bin # decoded transactions always have 9 fields, even if they are empty or zero raise ParameterError, "Transaction missing fields!" if tx.size < 9 # populate the 9 fields nonce = Util.deserialize_rlp_int tx[0] gas_price = Util.deserialize_rlp_int tx[1] gas_limit = Util.deserialize_rlp_int tx[2] to = Util.bin_to_hex tx[3] value = Util.deserialize_rlp_int tx[4] data = tx[5] v = Util.bin_to_hex tx[6] r = Util.bin_to_hex tx[7] s = Util.bin_to_hex tx[8] # try to recover the chain id from v chain_id = Chain.to_chain_id Util.deserialize_rlp_int tx[6] # populate class attributes @signer_nonce = nonce.to_i @gas_price = gas_price.to_i @gas_limit = gas_limit.to_i @destination = to.to_s @amount = value.to_i @payload = data @chain_id = chain_id # allows us to force-setting a signature if the transaction is signed already _set_signature(v, r, s) unless chain_id.nil? # recover sender address public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v}", 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 # last but not least, set the type. @type = TYPE_LEGACY end
def encoded
-
(Signature::SignatureError)
- if the transaction is not yet signed.
Returns:
-
(String)
- a raw, RLP-encoded legacy transaction.
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 @signer_nonce tx_data.push Util.serialize_int_to_big_endian @gas_price 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 Util.serialize_int_to_big_endian @signature_v tx_data.push Util.serialize_int_to_big_endian @signature_r tx_data.push Util.serialize_int_to_big_endian @signature_s Rlp.encode tx_data end
def hash
-
(String)
- the transaction hash.
def hash Util.bin_to_hex Util.keccak256 encoded end
def hex
-
(String)
- the raw transaction hex.
def hex Util.bin_to_hex encoded end
def initialize(params, chain_id = Chain::ETHEREUM)
-
(ParameterError)
- if gas limit is too low.
Parameters:
-
chain_id
(Integer
) -- the EIP-155 Chain ID. -
params
(Hash
) -- all necessary transaction fields.
Options Hash:
(**params)
-
:data
(String
) -- the transaction data payload. -
:value
(Integer
) -- the transaction value. -
:to
(Eth::Address
) -- the receiver address. -
:from
(Eth::Address
) -- the sender address. -
:gas_limit
(Integer
) -- the gas limit. -
:gas_price
(Integer
) -- the gas price. -
:nonce
(Integer
) -- the signer nonce.
def initialize(params, chain_id = Chain::ETHEREUM) fields = { v: chain_id, r: 0, s: 0 }.merge params # populate optional fields with serializable empty values fields[:value] = Tx.sanitize_amount fields[:value] fields[:from] = Tx.sanitize_address fields[:from] fields[:to] = Tx.sanitize_address fields[:to] fields[:data] = Tx.sanitize_data fields[:data] # ensure sane values for all mandatory fields fields = Tx.validate_params fields fields = Tx.validate_legacy_params fields # ensure gas limit is not too low minimum_cost = Tx.estimate_intrinsic_gas fields[:data] 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 @gas_price = fields[:gas_price].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] # the signature v is set to the chain id for unsigned transactions @signature_v = fields[:v] @chain_id = 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_LEGACY end
def sign(key)
-
(Signature::SignatureError)
- if sender address does not match signing key. -
(Signature::SignatureError)
- if transaction is already signed.
Returns:
-
(String)
- a transaction hash.
Parameters:
-
key
(Eth::Key
) -- the key-pair to use for signing.
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 @signature_v = v @signature_r = r @signature_s = s return hash end
def sign_with(signature)
-
(Signature::SignatureError)
- if sender address does not match signer. -
(Signature::SignatureError)
- if transaction is already signed.
Returns:
-
(String)
- a transaction hash.
Parameters:
-
signature
(String
) -- the concatenated `r`, `s`, and `v` values.
def sign_with(signature) if Tx.signed? self raise Signature::SignatureError, "Transaction is already signed!" end # ensure the sender address matches the signature unless @sender.nil? or sender.empty? public_key = Signature.recover(unsigned_hash, signature, @chain_id) signer_address = Tx.sanitize_address Util.public_key_to_address(public_key).to_s from_address = Tx.sanitize_address @sender raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address end r, s, v = Signature.dissect signature send :_set_signature, v, r, s return hash end
def unsigned_copy(tx)
-
(TransactionTypeError)
- if transaction type does not match.
Returns:
-
(Eth::Tx::Legacy)
- an unsigned transaction object.
Parameters:
-
tx
(Eth::Tx::Legacy
) -- an legacy transaction object.
def unsigned_copy(tx) # not checking transaction validity unless it's of a different class raise TransactionTypeError, "Cannot copy transaction of different type!" unless tx.instance_of? Tx::Legacy # populate class attributes @signer_nonce = tx.signer_nonce @gas_price = tx.gas_price @gas_limit = tx.gas_limit @destination = tx.destination @amount = tx.amount @payload = tx.payload @chain_id = tx.chain_id # force-set signature to unsigned _set_signature(tx.chain_id, 0, 0) # keep the 'from' field blank @sender = Tx.sanitize_address nil # last but not least, set the type. @type = TYPE_LEGACY end
def unsigned_encoded
-
(String)
- an RLP-encoded, unsigned transaction.
def unsigned_encoded tx_data = [] tx_data.push Util.serialize_int_to_big_endian @signer_nonce tx_data.push Util.serialize_int_to_big_endian @gas_price 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 Util.serialize_int_to_big_endian @chain_id tx_data.push Util.serialize_int_to_big_endian 0 tx_data.push Util.serialize_int_to_big_endian 0 Rlp.encode tx_data end
def unsigned_hash
-
(String)
- a Keccak-256 hash of an unsigned transaction.
def unsigned_hash Util.keccak256 unsigned_encoded end