Packages
signet
1.0.0-alpha2
1.6.1
1.6.0
1.5.0
1.4.2
1.4.1
1.4.0
1.3.12
1.3.11
1.3.10
1.3.9
1.3.8
1.3.7
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.0.0-echo8
1.0.0-echo7
1.0.0-echo6
1.0.0-echo5
1.0.0-echo4
1.0.0-echo3
1.0.0-echo2
1.0.0-echo1
1.0.0-delta8
1.0.0-delta7
1.0.0-delta6
1.0.0-delta5
1.0.0-delta4
1.0.0-delta3
1.0.0-delta2
1.0.0-delta1
1.0.0-charlie9
1.0.0-charlie8
1.0.0-charlie7
1.0.0-charlie6
1.0.0-charlie5
1.0.0-charlie4
1.0.0-charlie3
1.0.0-charlie2
1.0.0-charlie1
1.0.0-beta9
1.0.0-beta8
1.0.0-beta7
1.0.0-beta6
1.0.0-beta5
1.0.0-beta4
1.0.0-beta3
1.0.0-beta2
1.0.0-beta1
1.0.0-alpha9
1.0.0-alpha8
1.0.0-alpha7
1.0.0-alpha6
1.0.0-alpha5
1.0.0-alpha4
1.0.0-alpha3
1.0.0-alpha2
1.0.0-alpha10
1.0.0-alpha1
0.2.0-alpha6
0.2.0-alpha5
0.2.0-alpha4
0.2.0-alpha1
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.1.0-rc7
0.1.0-rc6
0.1.0-rc5
0.1.0-rc4
0.1.0-rc3
0.1.0-rc2
0.1.0-rc1
0.1.0-rc0
Lightweight Ethereum and Solana RPC client for Elixir
Current section
Files
Jump to
Current section
Files
lib/signet/transaction.ex
defmodule Signet.Transaction do
@moduledoc """
A module to help build, sign and encode Ethereum transactions.
"""
defmodule V1 do
@moduledoc """
Represents a V1 or "Legacy" (that is, pre-EIP-1559) transaction.
"""
@type t :: %__MODULE__{
nonce: integer(),
gas_price: integer(),
gas_limit: integer(),
to: <<_::160>>,
value: integer(),
data: binary(),
v: integer(),
r: integer(),
s: integer()
}
defstruct [
:nonce,
:gas_price,
:gas_limit,
:to,
:value,
:data,
:v,
:r,
:s
]
@doc ~S"""
Constructs a new V1 (Legacy) Ethereum transaction.
## Examples
iex> Signet.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
%Signet.Transaction.V1{
nonce: 1,
gas_price: 100000000000,
gas_limit: 100000,
to: <<1::160>>,
value: 2,
data: <<1, 2, 3>>,
v: 42,
r: 0,
s: 0
}
"""
def new(nonce, gas_price, gas_limit, to, value, data, chain_id \\ nil) do
%__MODULE__{
nonce: nonce,
gas_price: if(!is_nil(gas_price), do: Signet.Util.to_wei(gas_price), else: nil),
gas_limit: gas_limit,
to: to,
value: Signet.Util.to_wei(value),
data: data,
v:
if(is_nil(chain_id),
do: Signet.Application.chain_id(),
else: Signet.Util.parse_chain_id(chain_id)
),
r: 0,
s: 0
}
end
@doc ~S"""
Build an RLP-encoded transaction. Note: transactions can be encoded before they are signed, which
uses `[chain_id, 0, 0]` in the signature fields, otherwise those fields are `[v, r, s]`.
## Examples
iex> Signet.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...> |> Signet.Transaction.V1.encode()
...> |> Base.encode16()
"E80185174876E800830186A094000000000000000000000000000000000000000102830102032A8080"
"""
def encode(%__MODULE__{
nonce: nonce,
gas_price: gas_price,
gas_limit: gas_limit,
to: to,
value: value,
data: data,
v: v,
r: r,
s: s
}) do
ExRLP.encode([nonce, gas_price, gas_limit, to, value, data, v, r, s])
end
@doc ~S"""
Decode an RLP-encoded transaction.
## Examples
iex> "E80185174876E800830186A094000000000000000000000000000000000000000102830102032A8080"
...> |> Base.decode16!()
...> |> Signet.Transaction.V1.decode()
{:ok, %Signet.Transaction.V1{
nonce: 1,
gas_price: 100000000000,
gas_limit: 100000,
to: <<1::160>>,
value: 2,
data: <<1, 2, 3>>,
v: 42,
r: 0,
s: 0
}}
"""
def decode(trx_enc) do
case ExRLP.decode(trx_enc) do
[nonce, gas_price, gas_limit, to, value, data, v, r, s] ->
{:ok,
%__MODULE__{
nonce: :binary.decode_unsigned(nonce),
gas_price: :binary.decode_unsigned(gas_price),
gas_limit: :binary.decode_unsigned(gas_limit),
to: to,
value: :binary.decode_unsigned(value),
data: data,
v: :binary.decode_unsigned(v),
r: :binary.decode_unsigned(r),
s: :binary.decode_unsigned(s)
}}
_ ->
{:error, "invalid legacy transaction"}
end
end
@doc ~S"""
Adds a signature to a transaction. This overwrites the `[chain_id, 0, 0]` fields, as per EIP-155.
## Examples
iex> Signet.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...> |> Signet.Transaction.V1.add_signature(<<1::256, 2::256, 3::8>>)
%Signet.Transaction.V1{
nonce: 1,
gas_price: 100000000000,
gas_limit: 100000,
to: <<1::160>>,
value: 2,
data: <<1, 2, 3>>,
v: 3,
r: <<1::256>>,
s: <<2::256>>
}
"""
def add_signature(
transaction = %__MODULE__{},
<<r::binary-size(32), s::binary-size(32), v::binary>>
) do
%{transaction | v: :binary.decode_unsigned(v), r: r, s: s}
end
@doc ~S"""
Recovers a signature from a transaction, if it's been signed. Otherwise returns an error.
## Examples
iex> Signet.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...> |> Signet.Transaction.V1.add_signature(<<1::256, 2::256, 3::8>>)
...> |> Signet.Transaction.V1.get_signature()
{:ok, <<1::256, 2::256, 3::8>>}
iex> Signet.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...> |> Signet.Transaction.V1.add_signature(<<1::256, 2::256, 0x05f5e0ff::32>>)
...> |> Signet.Transaction.V1.get_signature()
{:ok, <<1::256, 2::256, 0x05f5e0ff::32>>}
iex> Signet.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...> |> Signet.Transaction.V1.get_signature()
{:error, "transaction missing signature"}
"""
def get_signature(%__MODULE__{v: _v, r: 0, s: 0}),
do: {:error, "transaction missing signature"}
def get_signature(%__MODULE__{v: v, r: r, s: s}) do
v_enc = :binary.encode_unsigned(v)
{:ok, <<r::binary-size(32), s::binary-size(32), v_enc::binary>>}
end
@doc ~S"""
Recovers the signer from a given transaction, if it's been signed.
## Examples
iex> {:ok, address} =
...> Signet.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...> |> Signet.Transaction.V1.add_signature(<<1::256, 2::256, 3::8>>)
...> |> Signet.Transaction.V1.recover_signer(:kovan)
...> Base.encode16(address)
"47643AC1194D7E8C6D04DD631D456137028BBC1F"
iex> Signet.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...> |> Signet.Transaction.V1.recover_signer(:kovan)
{:error, "transaction missing signature"}
"""
def recover_signer(transaction, chain_id) do
trx_encoded = encode(%{transaction | v: Signet.Util.parse_chain_id(chain_id), r: 0, s: 0})
with {:ok, signature} <- get_signature(transaction) do
{:ok, Signet.Recover.recover_eth(trx_encoded, signature)}
end
end
end
defmodule V2 do
@moduledoc """
Represents a V2 or EIP-1559 transaction.
"""
@type t :: %__MODULE__{
chain_id: integer(),
nonce: integer(),
max_priority_fee_per_gas: integer(),
max_fee_per_gas: integer(),
gas_limit: integer(),
destination: <<_::160>>,
amount: integer(),
data: binary(),
access_list: [<<_::160>>],
signature_y_parity: boolean(),
signature_r: <<_::256>>,
signature_s: <<_::256>>
}
defstruct [
:chain_id,
:nonce,
:max_priority_fee_per_gas,
:max_fee_per_gas,
:gas_limit,
:destination,
:amount,
:data,
:access_list,
:signature_y_parity,
:signature_r,
:signature_s
]
@doc ~S"""
Constructs a new V2 (EIP-1559) Ethereum transaction.
## Examples
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
%Signet.Transaction.V2{
chain_id: 5,
nonce: 1,
max_priority_fee_per_gas: 1000000000,
max_fee_per_gas: 100000000000,
gas_limit: 100000,
destination: <<1::160>>,
amount: 2,
data: <<1, 2, 3>>,
access_list: [<<2::160>>, <<3::160>>],
signature_y_parity: nil,
signature_r: nil,
signature_s: nil
}
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], true, <<0x01::256>>, <<0x02::256>>, :goerli)
%Signet.Transaction.V2{
chain_id: 5,
nonce: 1,
max_priority_fee_per_gas: 1000000000,
max_fee_per_gas: 100000000000,
gas_limit: 100000,
destination: <<1::160>>,
amount: 2,
data: <<1, 2, 3>>,
access_list: [<<2::160>>, <<3::160>>],
signature_y_parity: true,
signature_r: <<0x01::256>>,
signature_s: <<0x02::256>>
}
"""
def new(
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
destination,
amount,
data,
access_list,
chain_id \\ nil
),
do:
new(
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
destination,
amount,
data,
access_list,
nil,
nil,
nil,
chain_id
)
def new(
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
destination,
amount,
data,
access_list,
signature_y_parity,
signature_r,
signature_s,
chain_id \\ nil
) do
%__MODULE__{
chain_id:
if(is_nil(chain_id),
do: Signet.Application.chain_id(),
else: Signet.Util.parse_chain_id(chain_id)
),
nonce: nonce,
max_priority_fee_per_gas:
if(!is_nil(max_priority_fee_per_gas),
do: Signet.Util.to_wei(max_priority_fee_per_gas),
else: nil
),
max_fee_per_gas:
if(!is_nil(max_fee_per_gas), do: Signet.Util.to_wei(max_fee_per_gas), else: nil),
gas_limit: gas_limit,
destination: destination,
amount: Signet.Util.to_wei(amount),
data: data,
access_list: access_list,
signature_y_parity: signature_y_parity,
signature_r: signature_r,
signature_s: signature_s
}
end
@doc ~S"""
Build an RLP-encoded transaction. Note: if the transaction does not have a signature
set (that is, `signature_y_parity`, `signature_r` or `signature_s` are `nil`), then
we will encode a partial transaction (which can be used for signing).
## Examples
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
...> |> Signet.Transaction.V2.encode()
...> |> Base.encode16()
"02F8560501843B9ACA0085174876E800830186A09400000000000000000000000000000000000000010283010203EA940000000000000000000000000000000000000002940000000000000000000000000000000000000003"
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], true, <<0x01::256>>, <<0x02::256>>, :goerli)
...> |> Signet.Transaction.V2.encode()
...> |> Base.encode16()
"02F8990501843B9ACA0085174876E800830186A09400000000000000000000000000000000000000010283010203EA94000000000000000000000000000000000000000294000000000000000000000000000000000000000301A00000000000000000000000000000000000000000000000000000000000000001A00000000000000000000000000000000000000000000000000000000000000002"
"""
def encode(%__MODULE__{
chain_id: chain_id,
nonce: nonce,
max_priority_fee_per_gas: max_priority_fee_per_gas,
max_fee_per_gas: max_fee_per_gas,
gas_limit: gas_limit,
destination: destination,
amount: amount,
data: data,
access_list: access_list,
signature_y_parity: signature_y_parity,
signature_r: signature_r,
signature_s: signature_s
})
when is_nil(signature_y_parity) or is_nil(signature_r) or is_nil(signature_s) do
<<0x02>> <>
ExRLP.encode([
chain_id,
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
destination,
amount,
data,
access_list
])
end
def encode(%__MODULE__{
chain_id: chain_id,
nonce: nonce,
max_priority_fee_per_gas: max_priority_fee_per_gas,
max_fee_per_gas: max_fee_per_gas,
gas_limit: gas_limit,
destination: destination,
amount: amount,
data: data,
access_list: access_list,
signature_y_parity: signature_y_parity,
signature_r: signature_r,
signature_s: signature_s
}) do
<<0x02>> <>
ExRLP.encode([
chain_id,
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
destination,
amount,
data,
access_list,
if(signature_y_parity, do: 1, else: 0),
signature_r,
signature_s
])
end
@doc ~S"""
Decode an RLP-encoded transaction. Note: the signature must have been
signed (i.e. properly encoded), not simply encoded for signing.
## Examples
iex> Signet.Transaction.V2.decode(Base.decode16!("02F8990501843B9ACA0085174876E800830186A09400000000000000000000000000000000000000010283010203EA94000000000000000000000000000000000000000294000000000000000000000000000000000000000301A00000000000000000000000000000000000000000000000000000000000000001A00000000000000000000000000000000000000000000000000000000000000002"))
{:ok, %Signet.Transaction.V2{
chain_id: 5,
nonce: 1,
max_priority_fee_per_gas: 1000000000,
max_fee_per_gas: 100000000000,
gas_limit: 100000,
destination: <<1::160>>,
amount: 2,
data: <<1, 2, 3>>,
access_list: [<<2::160>>, <<3::160>>],
signature_y_parity: true,
signature_r: 0x01,
signature_s: 0x02
}}
"""
def decode(<<0x02, trx_enc::binary>>) do
case ExRLP.decode(trx_enc) do
[
chain_id,
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
destination,
amount,
data,
access_list,
signature_y_parity,
signature_r,
signature_s
] ->
{:ok,
%__MODULE__{
chain_id: :binary.decode_unsigned(chain_id),
nonce: :binary.decode_unsigned(nonce),
max_priority_fee_per_gas: :binary.decode_unsigned(max_priority_fee_per_gas),
max_fee_per_gas: :binary.decode_unsigned(max_fee_per_gas),
gas_limit: :binary.decode_unsigned(gas_limit),
destination: destination,
amount: :binary.decode_unsigned(amount),
data: data,
access_list: access_list,
signature_y_parity: :binary.decode_unsigned(signature_y_parity) == 1,
signature_r: :binary.decode_unsigned(signature_r),
signature_s: :binary.decode_unsigned(signature_s)
}}
_ ->
{:error, "invalid v2 transaction"}
end
end
@doc ~S"""
Adds a signature to a transaction. This overwrites the `signature_y_parity`, `signature_r` and `signature_s` fields.
## Examples
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], 1, 0x01, 0x02, :goerli)
...> |> Signet.Transaction.V2.add_signature(true, <<1::256>>, <<2::256>>)
%Signet.Transaction.V2{
chain_id: 5,
nonce: 1,
max_priority_fee_per_gas: 1000000000,
max_fee_per_gas: 100000000000,
gas_limit: 100000,
destination: <<1::160>>,
amount: 2,
data: <<1, 2, 3>>,
access_list: [<<2::160>>, <<3::160>>],
signature_y_parity: true,
signature_r: <<0x01::256>>,
signature_s: <<0x02::256>>
}
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], 1, 0x01, 0x02, :goerli)
...> |> Signet.Transaction.V2.add_signature(<<1::256, 2::256, 1::8>>)
%Signet.Transaction.V2{
chain_id: 5,
nonce: 1,
max_priority_fee_per_gas: 1000000000,
max_fee_per_gas: 100000000000,
gas_limit: 100000,
destination: <<1::160>>,
amount: 2,
data: <<1, 2, 3>>,
access_list: [<<2::160>>, <<3::160>>],
signature_y_parity: true,
signature_r: <<0x01::256>>,
signature_s: <<0x02::256>>
}
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], 1, 0x01, 0x02, :goerli)
...> |> Signet.Transaction.V2.add_signature(<<1::256, 2::256, 27::8>>)
%Signet.Transaction.V2{
chain_id: 5,
nonce: 1,
max_priority_fee_per_gas: 1000000000,
max_fee_per_gas: 100000000000,
gas_limit: 100000,
destination: <<1::160>>,
amount: 2,
data: <<1, 2, 3>>,
access_list: [<<2::160>>, <<3::160>>],
signature_y_parity: false,
signature_r: <<0x01::256>>,
signature_s: <<0x02::256>>
}
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], 1, 0x01, 0x02, :goerli)
...> |> Signet.Transaction.V2.add_signature(<<1::256, 2::256, 38::8>>)
%Signet.Transaction.V2{
chain_id: 5,
nonce: 1,
max_priority_fee_per_gas: 1000000000,
max_fee_per_gas: 100000000000,
gas_limit: 100000,
destination: <<1::160>>,
amount: 2,
data: <<1, 2, 3>>,
access_list: [<<2::160>>, <<3::160>>],
signature_y_parity: true,
signature_r: <<0x01::256>>,
signature_s: <<0x02::256>>
}
"""
def add_signature(
transaction = %__MODULE__{},
v,
r = <<_::256>>,
s = <<_::256>>
)
when is_boolean(v) do
%{transaction | signature_y_parity: v, signature_r: r, signature_s: s}
end
def add_signature(
transaction = %__MODULE__{},
<<r::binary-size(32), s::binary-size(32), v::integer-size(8)>>
) do
y_parity =
if v < 2 do
v == 1
else
rem(v, 2) == 0
end
%{transaction | signature_y_parity: y_parity, signature_r: r, signature_s: s}
end
@doc ~S"""
Recovers a signature from a transaction, if it's been signed. Otherwise returns an error.
## Examples
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], true, <<0x01::256>>, <<0x02::256>>, :goerli)
...> |> Signet.Transaction.V2.get_signature()
{:ok, <<1::256, 2::256, 1::8>>}
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
...> |> Signet.Transaction.V2.get_signature()
{:error, "transaction missing signature"}
"""
def get_signature(%__MODULE__{signature_y_parity: v, signature_r: r, signature_s: s})
when is_nil(v) or is_nil(r) or is_nil(s),
do: {:error, "transaction missing signature"}
def get_signature(%__MODULE__{signature_y_parity: v, signature_r: r, signature_s: s}) do
v_enc = :binary.encode_unsigned(if v, do: 1, else: 0)
{:ok, <<r::binary-size(32), s::binary-size(32), v_enc::binary>>}
end
@doc ~S"""
Recovers the signer from a given transaction, if it's been signed.
## Examples
iex> {:ok, address} =
...> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], true, <<0x01::256>>, <<0x02::256>>, :goerli)
...> |> Signet.Transaction.V2.recover_signer()
...> Base.encode16(address)
"C002CA628F93E1550B5F30ED10902A9E7783364B"
iex> Signet.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
...> |> Signet.Transaction.V2.recover_signer()
{:error, "transaction missing signature"}
"""
def recover_signer(transaction) do
trx_encoded =
encode(%{transaction | signature_y_parity: nil, signature_r: nil, signature_s: nil})
with {:ok, signature} <- get_signature(transaction) do
{:ok, Signet.Recover.recover_eth(trx_encoded, signature)}
end
end
end
@doc """
Builds a v1-style call to a given contract
## Examples
iex> Signet.Transaction.build_trx(<<1::160>>, 5, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, {50, :gwei}, 100_000, 0, 5)
%Signet.Transaction.V1{
nonce: 5,
gas_price: 50000000000,
gas_limit: 100000,
to: <<1::160>>,
value: 0,
data: Base.decode16!("A291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"),
v: 5,
r: 0,
s: 0
}
iex> call_data = Base.decode16!("A291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001")
...> Signet.Transaction.build_trx(<<1::160>>, 5, call_data, {50, :gwei}, 100_000, 0, 5)
%Signet.Transaction.V1{
nonce: 5,
gas_price: 50000000000,
gas_limit: 100000,
to: <<1::160>>,
value: 0,
data: Base.decode16!("A291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"),
v: 5,
r: 0,
s: 0
}
"""
def build_trx(address, nonce, call_data, gas_price, gas_limit, value, chain_id \\ nil) do
data =
case call_data do
{abi, params} ->
ABI.encode(abi, params)
call_data when is_binary(call_data) ->
call_data
end
V1.new(nonce, gas_price, gas_limit, address, value, data, chain_id)
end
@doc """
Builds a v2 (eip-1559)-style call to a given contract
## Examples
iex> Signet.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)
%Signet.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: Base.decode16!("A291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"),
access_list: [<<1::160>>],
signature_y_parity: nil,
signature_r: nil,
signature_s: nil
}
iex> call_data = Base.decode16!("A291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001")
...> Signet.Transaction.build_trx_v2(<<1::160>>, 5, call_data, {50, :gwei}, {10, :gwei}, 100_000, 0, [<<1::160>>], :goerli)
%Signet.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: Base.decode16!("A291ADD600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"),
access_list: [<<1::160>>],
signature_y_parity: nil,
signature_r: nil,
signature_s: nil
}
"""
def build_trx_v2(
address,
nonce,
call_data,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
amount,
access_list,
chain_id \\ nil
)
when is_list(access_list) do
data =
case call_data do
{abi, params} ->
ABI.encode(abi, params)
call_data when is_binary(call_data) ->
call_data
end
V2.new(
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
address,
amount,
data,
access_list,
chain_id
)
end
@doc ~S"""
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 = Signet.Test.Signer.start_signer()
iex> {:ok, signed_trx} = Signet.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} = Signet.Transaction.V1.recover_signer(signed_trx, 5)
iex> Base.encode16(signer)
"63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"
"""
def build_signed_trx(
address,
nonce,
call_data,
gas_price,
gas_limit,
value,
opts \\ []
) do
signer = Keyword.get(opts, :signer, Signet.Signer.Default)
chain_id = Keyword.get(opts, :chain_id, nil)
callback = Keyword.get(opts, :callback, nil)
transaction = build_trx(address, nonce, call_data, gas_price, gas_limit, value, chain_id)
callback = if(is_nil(callback), do: fn trx -> {:ok, trx} end, else: callback)
with {:ok, transaction} <- callback.(transaction),
transaction_encoded <- V1.encode(transaction),
{:ok, signature} <- Signet.Signer.sign(transaction_encoded, signer, chain_id: chain_id) do
{:ok, V1.add_signature(transaction, signature)}
end
end
@doc ~S"""
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 = Signet.Test.Signer.start_signer()
iex> {:ok, signed_trx} = Signet.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} = Signet.Transaction.V2.recover_signer(signed_trx)
iex> Base.encode16(signer)
"63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"
"""
def build_signed_trx_v2(
address,
nonce,
call_data,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
amount,
access_list,
opts \\ []
)
when is_list(access_list) do
signer = Keyword.get(opts, :signer, Signet.Signer.Default)
chain_id = Keyword.get(opts, :chain_id, nil)
callback = Keyword.get(opts, :callback, nil)
transaction =
build_trx_v2(
address,
nonce,
call_data,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
amount,
access_list,
chain_id
)
callback = if(is_nil(callback), do: fn trx -> {:ok, trx} end, else: callback)
with {:ok, transaction} <- callback.(transaction),
transaction_encoded <- V2.encode(transaction),
{:ok, signature} <- Signet.Signer.sign(transaction_encoded, signer, chain_id: chain_id) do
{:ok, V2.add_signature(transaction, signature)}
end
end
end