Cartouche.Transaction (Cartouche v0.3.0)

Copy Markdown View Source

A module to help build, sign and encode Ethereum transactions.

API Functions

FunctionArityDescriptionParam Kinds
build_signed_trx_v29Build and sign an EIP-1559 transaction.address: value, nonce: exchange_data, call_data: value, max_priority_fee_per_gas: exchange_data, max_fee_per_gas: exchange_data, gas_limit: exchange_data, amount: value, access_list: value, opts: value
build_signed_trx7Build and sign a legacy transaction.address: value, nonce: exchange_data, call_data: value, gas_price: exchange_data, gas_limit: exchange_data, value: value, opts: value
build_trx_v29Build an EIP-1559 transaction for a contract call or raw calldata.address: value, nonce: exchange_data, call_data: value, max_priority_fee_per_gas: exchange_data, max_fee_per_gas: exchange_data, gas_limit: exchange_data, amount: value, access_list: value, chain_id: exchange_data
build_trx7Build a legacy transaction for a contract call or raw calldata.address: value, nonce: exchange_data, call_data: value, gas_price: exchange_data, gas_limit: exchange_data, value: value, chain_id: exchange_data
decode1Decode raw Ethereum transaction bytes into the matching transaction struct (V1/V_2930/V2/V3/V4 by envelope byte).encoded: value
encode1Encode a concrete transaction struct (V1/V2/V3/V4) into raw RLP/typed-RLP transaction bytes by dispatching on struct.transaction: value

Summary

Functions

Builds and signs a transaction, to be ready to be passed to JSON-RPC.

Decodes raw Ethereum transaction bytes into the matching transaction struct.

Encodes a concrete transaction struct into raw transaction bytes, mirroring decode/1.

Functions

build_signed_trx(address, nonce, call_data, gas_price, gas_limit, value, opts \\ [])

@spec build_signed_trx(
  <<_::160>>,
  integer(),
  binary() | {String.t(), [term()]},
  integer() | {integer(), :wei | :gwei} | nil,
  integer(),
  integer() | {integer(), :wei | :gwei},
  Keyword.t()
) :: {:ok, Cartouche.Transaction.V1.t()} | {:error, String.t()}

Builds and signs a transaction, to be ready to be passed to JSON-RPC.

Optionally takes a callback to modify the transaction before it is signed.

Examples

iex> signer_proc = Cartouche.Test.Signer.start_signer()
iex> {:ok, signed_trx} = Cartouche.Transaction.build_signed_trx(<<1::160>>, 5, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, {50, :gwei}, 100_000, 0, signer: signer_proc, chain_id: :goerli)
iex> {:ok, signer} = Cartouche.Transaction.V1.recover_signer(signed_trx, 5)
iex> Cartouche.Hex.to_address(signer)
"0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"

build_signed_trx_v2(address, nonce, call_data, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, amount, access_list, opts \\ [])

@spec build_signed_trx_v2(
  <<_::160>>,
  integer(),
  binary() | {String.t(), [term()]},
  integer() | {integer(), :wei | :gwei} | nil,
  integer() | {integer(), :wei | :gwei} | nil,
  integer(),
  integer() | {integer(), :wei | :gwei},
  list(),
  Keyword.t()
) :: {:ok, Cartouche.Transaction.V2.t()} | {:error, String.t()}

Builds and signs a V2 transaction, to be ready to be passed to JSON-RPC.

Optionally takes a callback to modify the transaction before it is signed.

Examples

iex> signer_proc = Cartouche.Test.Signer.start_signer()
iex> {:ok, signed_trx} = Cartouche.Transaction.build_signed_trx_v2(<<1::160>>, 5, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, {50, :gwei}, {10, :gwei}, 100_000, 0, [], signer: signer_proc, chain_id: :goerli)
iex> {:ok, signer} = Cartouche.Transaction.V2.recover_signer(signed_trx)
iex> Cartouche.Hex.to_address(signer)
"0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"

build_trx(address, nonce, call_data, gas_price, gas_limit, value, chain_id \\ nil)

@spec build_trx(
  <<_::160>>,
  integer(),
  binary() | {String.t(), [term()]},
  integer() | {integer(), :wei | :gwei} | nil,
  integer(),
  integer() | {integer(), :wei | :gwei},
  atom() | integer() | nil
) :: Cartouche.Transaction.V1.t()

Builds a v1-style call to a given contract

Examples

iex> use Cartouche.Hex
iex> Cartouche.Transaction.build_trx(<<1::160>>, 5, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, {50, :gwei}, 100_000, 0, 5)
%Cartouche.Transaction.V1{
  nonce: 5,
  gas_price: 50000000000,
  gas_limit: 100000,
  to: <<1::160>>,
  value: 0,
  data: ~h[0xA291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001],
  v: 5,
  r: 0,
  s: 0
}

iex> use Cartouche.Hex
iex> call_data = ~h[0xA291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001]
...> Cartouche.Transaction.build_trx(<<1::160>>, 5, call_data, {50, :gwei}, 100_000, 0, 5)
%Cartouche.Transaction.V1{
  nonce: 5,
  gas_price: 50000000000,
  gas_limit: 100000,
  to: <<1::160>>,
  value: 0,
  data: ~h[0xA291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001],
  v: 5,
  r: 0,
  s: 0
}

build_trx_v2(address, nonce, call_data, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, amount, access_list, chain_id \\ nil)

@spec build_trx_v2(
  <<_::160>>,
  integer(),
  binary() | {String.t(), [term()]},
  integer() | {integer(), :wei | :gwei} | nil,
  integer() | {integer(), :wei | :gwei} | nil,
  integer(),
  integer() | {integer(), :wei | :gwei},
  list(),
  atom() | integer() | nil
) :: Cartouche.Transaction.V2.t()

Builds a v2 (eip-1559)-style call to a given contract

Examples

iex> use Cartouche.Hex
iex> Cartouche.Transaction.build_trx_v2(<<1::160>>, 6, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, {50, :gwei}, {10, :gwei}, 100_000, 0, [<<1::160>>], :goerli)
%Cartouche.Transaction.V2{
  chain_id: 5,
  nonce: 6,
  max_priority_fee_per_gas: 50000000000,
  max_fee_per_gas: 10000000000,
  gas_limit: 100000,
  destination: <<1::160>>,
  amount: 0,
  data: ~h[0xA291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001],
  access_list: [<<1::160>>],
  signature_y_parity: nil,
  signature_r: nil,
  signature_s: nil
}

iex> use Cartouche.Hex
iex> call_data = ~h[0xA291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001]
...> Cartouche.Transaction.build_trx_v2(<<1::160>>, 5, call_data, {50, :gwei}, {10, :gwei}, 100_000, 0, [<<1::160>>], :goerli)
%Cartouche.Transaction.V2{
  chain_id: 5,
  nonce: 5,
  max_priority_fee_per_gas: 50000000000,
  max_fee_per_gas: 10000000000,
  gas_limit: 100000,
  destination: <<1::160>>,
  amount: 0,
  data: ~h[0xA291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001],
  access_list: [<<1::160>>],
  signature_y_parity: nil,
  signature_r: nil,
  signature_s: nil
}

decode(encoded)

@spec decode(binary()) ::
  {:ok,
   Cartouche.Transaction.V1.t()
   | Cartouche.Transaction.V_2930.t()
   | Cartouche.Transaction.V2.t()
   | Cartouche.Transaction.V3.t()
   | Cartouche.Transaction.V4.t()}
  | {:error, String.t() | :empty_transaction | :unknown_envelope_type}

Decodes raw Ethereum transaction bytes into the matching transaction struct.

Dispatches typed envelopes by their first byte:

Legacy transactions are untyped RLP and are decoded as Cartouche.Transaction.V1 when the first byte is an RLP prefix (>= 0x80). Empty input returns {:error, :empty_transaction}. Unknown typed envelopes (< 0x80 except supported typed envelopes) return {:error, :unknown_envelope_type}.

Examples

iex> tx = Cartouche.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
iex> {:ok, decoded} = Cartouche.Transaction.decode(Cartouche.Transaction.V1.encode(tx))
iex> decoded == tx
true

iex> Cartouche.Transaction.decode(<<>>)
{:error, :empty_transaction}

encode(transaction)

Encodes a concrete transaction struct into raw transaction bytes, mirroring decode/1.

Dispatches by struct so callers don't need to know which versioned encoder to invoke. The output is the same shape decode/1 accepts:

  • %Cartouche.Transaction.V1{} - untyped RLP-encoded legacy/EIP-155 bytes (leading byte >= 0x80).
  • %Cartouche.Transaction.V2{} - 0x02-prefixed EIP-2718 typed RLP.
  • %Cartouche.Transaction.V3{} - 0x03-prefixed EIP-2718 typed RLP (EIP-4844 blob transaction envelope).
  • %Cartouche.Transaction.V4{} - 0x04-prefixed EIP-2718 typed RLP (EIP-7702 authorization-list transaction envelope).

Each leaf encoder already emits the EIP-2718 envelope byte where applicable; this function is a pure pattern-match-and-delegate.

Examples

iex> tx = Cartouche.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
iex> {:ok, ^tx} = tx |> Cartouche.Transaction.encode() |> Cartouche.Transaction.decode()
iex> Cartouche.Transaction.encode(tx) == Cartouche.Transaction.V1.encode(tx)
true

iex> tx = Cartouche.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [], :goerli)
iex> <<0x02, _::binary>> = Cartouche.Transaction.encode(tx)
iex> {:ok, ^tx} = tx |> Cartouche.Transaction.encode() |> Cartouche.Transaction.decode()