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)

Force-sets an existing signature of a decoded transaction.
def _set_signature(v, r, s)
  @signature_v = v
  @signature_r = r
  @signature_s = s
end

def decode(hex)

Raises:
  • (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

Raises:
  • (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

Returns:
  • (String) - the transaction hash.
def hash
  Util.bin_to_hex Util.keccak256 encoded
end

def hex

Returns:
  • (String) - the raw transaction hex.
def hex
  Util.bin_to_hex encoded
end

def initialize(params, chain_id = Chain::ETHEREUM)

Raises:
  • (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)

Raises:
  • (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)

Raises:
  • (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)

Raises:
  • (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

Returns:
  • (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

Returns:
  • (String) - a Keccak-256 hash of an unsigned transaction.
def unsigned_hash
  Util.keccak256 unsigned_encoded
end