Current section
Files
Jump to
Current section
Files
src/gleeth@crypto@transaction.erl
-module(gleeth@crypto@transaction).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/crypto/transaction.gleam").
-export([sign_transaction/2, sign_eip1559_transaction/2, create_legacy_transaction/7, create_eth_transfer/6, create_contract_call/6, create_eip1559_transaction/9, signed_transaction_to_string/1, hash_raw_transaction/1, get_transaction_hash/1, get_eip1559_transaction_hash/1, signed_eip1559_transaction_to_string/1, error_to_string/1, decode_legacy/1, decode_eip1559/1, decode/1]).
-export_type([unsigned_transaction/0, signed_transaction/0, eip1559_transaction/0, access_list_entry/0, signed_eip1559_transaction/0, transaction_type/0, transaction_error/0, decoded_transaction/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type unsigned_transaction() :: {unsigned_transaction,
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
integer()}.
-type signed_transaction() :: {signed_transaction,
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
integer(),
binary(),
binary(),
binary(),
binary()}.
-type eip1559_transaction() :: {eip1559_transaction,
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
integer(),
list(access_list_entry())}.
-type access_list_entry() :: {access_list_entry, binary(), list(binary())}.
-type signed_eip1559_transaction() :: {signed_eip1559_transaction,
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
integer(),
list(access_list_entry()),
binary(),
binary(),
binary(),
binary()}.
-type transaction_type() :: legacy | access_list | eip1559.
-type transaction_error() :: {invalid_address, binary()} |
{invalid_amount, binary()} |
{invalid_gas, binary()} |
{invalid_nonce, binary()} |
{invalid_chain_id, binary()} |
{invalid_data, binary()} |
{signing_failed, binary()} |
{encoding_failed, binary()}.
-type decoded_transaction() :: {decoded_legacy, signed_transaction()} |
{decoded_eip1559, signed_eip1559_transaction()}.
-file("src/gleeth/crypto/transaction.gleam", 459).
?DOC(
" Encode a hex string as raw bytes without stripping leading zeros.\n"
" Used for address and data fields which are byte strings, not integers.\n"
).
-spec encode_raw_hex(binary()) -> gleeth@encoding@rlp:rlp_item().
encode_raw_hex(Hex_string) ->
case gleeth@utils@hex:decode(Hex_string) of
{ok, Bytes} ->
{rlp_bytes, Bytes};
{error, _} ->
{rlp_bytes, <<>>}
end.
-file("src/gleeth/crypto/transaction.gleam", 365).
?DOC(" EIP-155 signing hash: keccak256(RLP([nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]))\n").
-spec create_signing_hash(unsigned_transaction()) -> bitstring().
create_signing_hash(Transaction) ->
Items = {rlp_list,
[gleeth@encoding@rlp:encode_hex_field(erlang:element(6, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(5, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(4, Transaction)),
encode_raw_hex(erlang:element(2, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(3, Transaction)),
encode_raw_hex(erlang:element(7, Transaction)),
gleeth@encoding@rlp:encode_int(erlang:element(8, Transaction)),
gleeth@encoding@rlp:encode_int(0),
gleeth@encoding@rlp:encode_int(0)]},
gleeth@crypto@keccak:keccak256_binary(gleeth@encoding@rlp:encode(Items)).
-file("src/gleeth/crypto/transaction.gleam", 452).
-spec encode_access_list_entry(access_list_entry()) -> gleeth@encoding@rlp:rlp_item().
encode_access_list_entry(Entry) ->
Storage_keys = gleam@list:map(
erlang:element(3, Entry),
fun encode_raw_hex/1
),
{rlp_list,
[encode_raw_hex(erlang:element(2, Entry)), {rlp_list, Storage_keys}]}.
-file("src/gleeth/crypto/transaction.gleam", 448).
?DOC(" Encode an access list as an RLP list of [address, [storageKeys]] entries\n").
-spec encode_access_list(list(access_list_entry())) -> gleeth@encoding@rlp:rlp_item().
encode_access_list(Entries) ->
{rlp_list, gleam@list:map(Entries, fun encode_access_list_entry/1)}.
-file("src/gleeth/crypto/transaction.gleam", 404).
?DOC(" EIP-1559 signing hash: keccak256(0x02 || RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]))\n").
-spec create_eip1559_signing_hash(eip1559_transaction()) -> bitstring().
create_eip1559_signing_hash(Transaction) ->
Items = {rlp_list,
[gleeth@encoding@rlp:encode_int(erlang:element(9, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(7, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(6, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(5, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(4, Transaction)),
encode_raw_hex(erlang:element(2, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(3, Transaction)),
encode_raw_hex(erlang:element(8, Transaction)),
encode_access_list(erlang:element(10, Transaction))]},
Payload = gleeth@encoding@rlp:encode(Items),
gleeth@crypto@keccak:keccak256_binary(<<16#02, Payload/bitstring>>).
-file("src/gleeth/crypto/transaction.gleam", 467).
?DOC(" Strip leading zero bytes for minimal big-endian integer encoding\n").
-spec strip_leading_zeros(bitstring()) -> bitstring().
strip_leading_zeros(Data) ->
case Data of
<<0:8, Rest/bitstring>> ->
strip_leading_zeros(Rest);
_ ->
Data
end.
-file("src/gleeth/crypto/transaction.gleam", 382).
?DOC(" RLP-encode the signed transaction: [nonce, gasPrice, gasLimit, to, value, data, v, r, s]\n").
-spec create_raw_transaction(
unsigned_transaction(),
integer(),
bitstring(),
bitstring()
) -> binary().
create_raw_transaction(Transaction, V, R, S) ->
Items = {rlp_list,
[gleeth@encoding@rlp:encode_hex_field(erlang:element(6, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(5, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(4, Transaction)),
encode_raw_hex(erlang:element(2, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(3, Transaction)),
encode_raw_hex(erlang:element(7, Transaction)),
gleeth@encoding@rlp:encode_int(V),
{rlp_bytes, strip_leading_zeros(R)},
{rlp_bytes, strip_leading_zeros(S)}]},
gleeth@utils@hex:encode(gleeth@encoding@rlp:encode(Items)).
-file("src/gleeth/crypto/transaction.gleam", 278).
?DOC(
" Sign a legacy (EIP-155) transaction with a wallet.\n"
" Produces an RLP-encoded raw transaction suitable for eth_sendRawTransaction.\n"
).
-spec sign_transaction(unsigned_transaction(), gleeth@crypto@wallet:wallet()) -> {ok,
signed_transaction()} |
{error, transaction_error()}.
sign_transaction(Transaction, Wallet) ->
Signing_hash = create_signing_hash(Transaction),
gleam@result:'try'(
begin
_pipe = gleeth@crypto@wallet:sign_hash(Wallet, Signing_hash),
gleam@result:map_error(
_pipe,
fun(Err) ->
{signing_failed,
<<"Failed to sign transaction: "/utf8,
(gleeth@crypto@wallet:error_to_string(Err))/binary>>}
end
)
end,
fun(Signature) ->
Eip155_v = (erlang:element(4, Signature) + (2 * erlang:element(
8,
Transaction
)))
+ 35,
V_hex = <<"0x"/utf8,
(string:lowercase(gleam@int:to_base16(Eip155_v)))/binary>>,
R_hex = gleeth@utils@hex:encode(erlang:element(2, Signature)),
S_hex = gleeth@utils@hex:encode(erlang:element(3, Signature)),
Raw_tx = create_raw_transaction(
Transaction,
Eip155_v,
erlang:element(2, Signature),
erlang:element(3, Signature)
),
{ok,
{signed_transaction,
erlang:element(2, Transaction),
erlang:element(3, Transaction),
erlang:element(4, Transaction),
erlang:element(5, Transaction),
erlang:element(6, Transaction),
erlang:element(7, Transaction),
erlang:element(8, Transaction),
V_hex,
R_hex,
S_hex,
Raw_tx}}
end
).
-file("src/gleeth/crypto/transaction.gleam", 422).
?DOC(" RLP-encode the signed EIP-1559 transaction: 0x02 || RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, v, r, s])\n").
-spec create_eip1559_raw_transaction(
eip1559_transaction(),
integer(),
bitstring(),
bitstring()
) -> binary().
create_eip1559_raw_transaction(Transaction, V, R, S) ->
Items = {rlp_list,
[gleeth@encoding@rlp:encode_int(erlang:element(9, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(7, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(6, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(5, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(4, Transaction)),
encode_raw_hex(erlang:element(2, Transaction)),
gleeth@encoding@rlp:encode_hex_field(erlang:element(3, Transaction)),
encode_raw_hex(erlang:element(8, Transaction)),
encode_access_list(erlang:element(10, Transaction)),
gleeth@encoding@rlp:encode_int(V),
{rlp_bytes, strip_leading_zeros(R)},
{rlp_bytes, strip_leading_zeros(S)}]},
Payload = gleeth@encoding@rlp:encode(Items),
gleeth@utils@hex:encode(<<16#02, Payload/bitstring>>).
-file("src/gleeth/crypto/transaction.gleam", 319).
?DOC(
" Sign an EIP-1559 (Type 2) transaction with a wallet.\n"
" Produces a type-prefixed RLP-encoded raw transaction: 0x02 || RLP([...]).\n"
).
-spec sign_eip1559_transaction(
eip1559_transaction(),
gleeth@crypto@wallet:wallet()
) -> {ok, signed_eip1559_transaction()} | {error, transaction_error()}.
sign_eip1559_transaction(Transaction, Wallet) ->
Signing_hash = create_eip1559_signing_hash(Transaction),
gleam@result:'try'(
begin
_pipe = gleeth@crypto@wallet:sign_hash(Wallet, Signing_hash),
gleam@result:map_error(
_pipe,
fun(Err) ->
{signing_failed,
<<"Failed to sign transaction: "/utf8,
(gleeth@crypto@wallet:error_to_string(Err))/binary>>}
end
)
end,
fun(Signature) ->
V = erlang:element(4, Signature),
V_hex = <<"0x"/utf8,
(string:lowercase(gleam@int:to_base16(V)))/binary>>,
R_hex = gleeth@utils@hex:encode(erlang:element(2, Signature)),
S_hex = gleeth@utils@hex:encode(erlang:element(3, Signature)),
Raw_tx = create_eip1559_raw_transaction(
Transaction,
V,
erlang:element(2, Signature),
erlang:element(3, Signature)
),
{ok,
{signed_eip1559_transaction,
erlang:element(2, Transaction),
erlang:element(3, Transaction),
erlang:element(4, Transaction),
erlang:element(5, Transaction),
erlang:element(6, Transaction),
erlang:element(7, Transaction),
erlang:element(8, Transaction),
erlang:element(9, Transaction),
erlang:element(10, Transaction),
V_hex,
R_hex,
S_hex,
Raw_tx}}
end
).
-file("src/gleeth/crypto/transaction.gleam", 531).
?DOC(" Validate chain ID\n").
-spec validate_chain_id(integer()) -> {ok, nil} | {error, transaction_error()}.
validate_chain_id(Chain_id) ->
case Chain_id > 0 of
true ->
{ok, nil};
false ->
{error, {invalid_chain_id, <<"Chain ID must be positive"/utf8>>}}
end.
-file("src/gleeth/crypto/transaction.gleam", 539).
?DOC(" Check if character is valid hex\n").
-spec is_hex_char(binary()) -> boolean().
is_hex_char(Char) ->
case Char of
<<"0"/utf8>> ->
true;
<<"1"/utf8>> ->
true;
<<"2"/utf8>> ->
true;
<<"3"/utf8>> ->
true;
<<"4"/utf8>> ->
true;
<<"5"/utf8>> ->
true;
<<"6"/utf8>> ->
true;
<<"7"/utf8>> ->
true;
<<"8"/utf8>> ->
true;
<<"9"/utf8>> ->
true;
<<"a"/utf8>> ->
true;
<<"b"/utf8>> ->
true;
<<"c"/utf8>> ->
true;
<<"d"/utf8>> ->
true;
<<"e"/utf8>> ->
true;
<<"f"/utf8>> ->
true;
<<"A"/utf8>> ->
true;
<<"B"/utf8>> ->
true;
<<"C"/utf8>> ->
true;
<<"D"/utf8>> ->
true;
<<"E"/utf8>> ->
true;
<<"F"/utf8>> ->
true;
_ ->
false
end.
-file("src/gleeth/crypto/transaction.gleam", 479).
?DOC(" Validate Ethereum address format\n").
-spec validate_address(binary()) -> {ok, nil} | {error, transaction_error()}.
validate_address(Address) ->
Cleaned = case gleam_stdlib:string_starts_with(Address, <<"0x"/utf8>>) of
true ->
gleam@string:drop_start(Address, 2);
false ->
Address
end,
case string:length(Cleaned) of
40 ->
case begin
_pipe = gleam@string:to_graphemes(Cleaned),
gleam@list:all(_pipe, fun is_hex_char/1)
end of
true ->
{ok, nil};
false ->
{error,
{invalid_address,
<<"Address contains non-hex characters"/utf8>>}}
end;
0 ->
{ok, nil};
_ ->
{error,
{invalid_address, <<"Address must be 40 hex characters"/utf8>>}}
end.
-file("src/gleeth/crypto/transaction.gleam", 499).
?DOC(" Validate hex amount (like value, gas, nonce)\n").
-spec validate_hex_amount(binary()) -> {ok, nil} | {error, transaction_error()}.
validate_hex_amount(Amount) ->
Cleaned = case gleam_stdlib:string_starts_with(Amount, <<"0x"/utf8>>) of
true ->
gleam@string:drop_start(Amount, 2);
false ->
Amount
end,
case begin
_pipe = gleam@string:to_graphemes(Cleaned),
gleam@list:all(_pipe, fun is_hex_char/1)
end of
true ->
{ok, nil};
false ->
{error,
{invalid_amount, <<"Amount contains non-hex characters"/utf8>>}}
end.
-file("src/gleeth/crypto/transaction.gleam", 512).
?DOC(" Validate hex data\n").
-spec validate_hex_data(binary()) -> {ok, nil} | {error, transaction_error()}.
validate_hex_data(Data) ->
case Data of
<<"0x"/utf8>> ->
{ok, nil};
_ ->
Cleaned = case gleam_stdlib:string_starts_with(Data, <<"0x"/utf8>>) of
true ->
gleam@string:drop_start(Data, 2);
false ->
Data
end,
case begin
_pipe = gleam@string:to_graphemes(Cleaned),
gleam@list:all(_pipe, fun is_hex_char/1)
end of
true ->
{ok, nil};
false ->
{error,
{invalid_data,
<<"Data contains non-hex characters"/utf8>>}}
end
end.
-file("src/gleeth/crypto/transaction.gleam", 553).
?DOC(" Normalize address to lowercase with 0x prefix\n").
-spec normalize_address(binary()) -> binary().
normalize_address(Address) ->
case Address of
<<""/utf8>> ->
<<""/utf8>>;
_ ->
Cleaned = case gleam_stdlib:string_starts_with(
Address,
<<"0x"/utf8>>
) of
true ->
gleam@string:drop_start(Address, 2);
false ->
Address
end,
<<"0x"/utf8, (string:lowercase(Cleaned))/binary>>
end.
-file("src/gleeth/crypto/transaction.gleam", 567).
?DOC(" Normalize hex string to lowercase with 0x prefix\n").
-spec normalize_hex(binary()) -> binary().
normalize_hex(Hex) ->
Cleaned = case gleam_stdlib:string_starts_with(Hex, <<"0x"/utf8>>) of
true ->
gleam@string:drop_start(Hex, 2);
false ->
Hex
end,
<<"0x"/utf8, (string:lowercase(Cleaned))/binary>>.
-file("src/gleeth/crypto/transaction.gleam", 576).
?DOC(" Normalize hex data (keep \"0x\" for empty data)\n").
-spec normalize_hex_data(binary()) -> binary().
normalize_hex_data(Data) ->
case Data of
<<""/utf8>> ->
<<"0x"/utf8>>;
<<"0x"/utf8>> ->
<<"0x"/utf8>>;
_ ->
normalize_hex(Data)
end.
-file("src/gleeth/crypto/transaction.gleam", 148).
?DOC(
" Create a new unsigned legacy (Type 0) transaction.\n"
"\n"
" All numeric parameters must be `0x`-prefixed hex strings. The Ethereum\n"
" JSON-RPC API returns values in this format, so results from\n"
" `methods.get_gas_price`, `methods.get_transaction_count`, etc. can be\n"
" passed directly.\n"
"\n"
" - `to` - recipient address (`\"0x...\"`, 40 hex chars after prefix)\n"
" - `value` - amount in wei as hex (e.g. `\"0xde0b6b3a7640000\"` for 1 ETH)\n"
" - `gas_limit` - gas limit as hex (e.g. `\"0x5208\"` for 21000)\n"
" - `gas_price` - gas price in wei as hex\n"
" - `nonce` - sender's transaction count as hex\n"
" - `data` - calldata as hex (`\"0x\"` for simple transfers)\n"
" - `chain_id` - network chain ID as integer (1 for mainnet, 11155111 for Sepolia)\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(tx) = transaction.create_legacy_transaction(\n"
" \"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\",\n"
" \"0xde0b6b3a7640000\", // 1 ETH in wei\n"
" \"0x5208\", // 21000 gas\n"
" \"0x3b9aca00\", // 1 gwei gas price\n"
" \"0x0\", // nonce 0\n"
" \"0x\", // no calldata\n"
" 1, // mainnet\n"
" )\n"
" ```\n"
).
-spec create_legacy_transaction(
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
integer()
) -> {ok, unsigned_transaction()} | {error, transaction_error()}.
create_legacy_transaction(
To,
Value,
Gas_limit,
Gas_price,
Nonce,
Data,
Chain_id
) ->
gleam@result:'try'(
validate_address(To),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(Value),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(Gas_limit),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(Gas_price),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(Nonce),
fun(_) ->
gleam@result:'try'(
validate_hex_data(Data),
fun(_) ->
gleam@result:'try'(
validate_chain_id(
Chain_id
),
fun(_) ->
{ok,
{unsigned_transaction,
normalize_address(
To
),
normalize_hex(
Value
),
normalize_hex(
Gas_limit
),
normalize_hex(
Gas_price
),
normalize_hex(
Nonce
),
normalize_hex_data(
Data
),
Chain_id}}
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/gleeth/crypto/transaction.gleam", 181).
?DOC(
" Create a simple ETH transfer transaction (no calldata).\n"
"\n"
" Convenience wrapper around `create_legacy_transaction` with `data` set to\n"
" `\"0x\"`. All numeric parameters are `0x`-prefixed hex strings.\n"
).
-spec create_eth_transfer(
binary(),
binary(),
binary(),
binary(),
binary(),
integer()
) -> {ok, unsigned_transaction()} | {error, transaction_error()}.
create_eth_transfer(To, Value_wei, Gas_limit, Gas_price, Nonce, Chain_id) ->
create_legacy_transaction(
To,
Value_wei,
Gas_limit,
Gas_price,
Nonce,
<<"0x"/utf8>>,
Chain_id
).
-file("src/gleeth/crypto/transaction.gleam", 204).
?DOC(
" Create a contract interaction transaction (zero value).\n"
"\n"
" Convenience wrapper around `create_legacy_transaction` with `value` set to\n"
" `\"0x0\"`. All numeric parameters are `0x`-prefixed hex strings.\n"
).
-spec create_contract_call(
binary(),
binary(),
binary(),
binary(),
binary(),
integer()
) -> {ok, unsigned_transaction()} | {error, transaction_error()}.
create_contract_call(
Contract_address,
Call_data,
Gas_limit,
Gas_price,
Nonce,
Chain_id
) ->
create_legacy_transaction(
Contract_address,
<<"0x0"/utf8>>,
Gas_limit,
Gas_price,
Nonce,
Call_data,
Chain_id
).
-file("src/gleeth/crypto/transaction.gleam", 239).
?DOC(
" Create and validate an EIP-1559 (Type 2) transaction.\n"
"\n"
" All numeric parameters must be `0x`-prefixed hex strings. Fee parameters\n"
" from `methods.get_gas_price` and `methods.get_max_priority_fee` can be\n"
" passed directly.\n"
"\n"
" - `to` - recipient address\n"
" - `value` - amount in wei as hex\n"
" - `gas_limit` - gas limit as hex\n"
" - `max_fee_per_gas` - maximum total fee per gas in wei as hex\n"
" - `max_priority_fee_per_gas` - tip per gas in wei as hex\n"
" - `nonce` - sender's transaction count as hex\n"
" - `data` - calldata as hex (`\"0x\"` for simple transfers)\n"
" - `chain_id` - network chain ID as integer\n"
" - `access_list` - EIP-2930 access list (pass `[]` if not needed)\n"
).
-spec create_eip1559_transaction(
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
integer(),
list(access_list_entry())
) -> {ok, eip1559_transaction()} | {error, transaction_error()}.
create_eip1559_transaction(
To,
Value,
Gas_limit,
Max_fee_per_gas,
Max_priority_fee_per_gas,
Nonce,
Data,
Chain_id,
Access_list
) ->
gleam@result:'try'(
validate_address(To),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(Value),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(Gas_limit),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(Max_fee_per_gas),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(
Max_priority_fee_per_gas
),
fun(_) ->
gleam@result:'try'(
validate_hex_amount(Nonce),
fun(_) ->
gleam@result:'try'(
validate_hex_data(Data),
fun(_) ->
gleam@result:'try'(
validate_chain_id(
Chain_id
),
fun(_) ->
{ok,
{eip1559_transaction,
normalize_address(
To
),
normalize_hex(
Value
),
normalize_hex(
Gas_limit
),
normalize_hex(
Max_fee_per_gas
),
normalize_hex(
Max_priority_fee_per_gas
),
normalize_hex(
Nonce
),
normalize_hex_data(
Data
),
Chain_id,
Access_list}}
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/gleeth/crypto/transaction.gleam", 589).
?DOC(" Convert signed transaction to string for display\n").
-spec signed_transaction_to_string(signed_transaction()) -> binary().
signed_transaction_to_string(Tx) ->
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"SignedTransaction {\n"/utf8,
" to: "/utf8>>/binary,
(erlang:element(
2,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" value: "/utf8>>/binary,
(erlang:element(
3,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" gas_limit: "/utf8>>/binary,
(erlang:element(
4,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" gas_price: "/utf8>>/binary,
(erlang:element(
5,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" nonce: "/utf8>>/binary,
(erlang:element(
6,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" data: "/utf8>>/binary,
(erlang:element(
7,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" chain_id: "/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(
8,
Tx
)
))/binary>>/binary,
"\n"/utf8>>/binary,
" v: "/utf8>>/binary,
(erlang:element(9, Tx))/binary>>/binary,
"\n"/utf8>>/binary,
" r: "/utf8>>/binary,
(erlang:element(10, Tx))/binary>>/binary,
"\n"/utf8>>/binary,
" s: "/utf8>>/binary,
(erlang:element(11, Tx))/binary>>/binary,
"\n"/utf8>>/binary,
" raw: "/utf8>>/binary,
(erlang:element(12, Tx))/binary>>/binary,
"\n"/utf8>>/binary,
"}"/utf8>>.
-file("src/gleeth/crypto/transaction.gleam", 638).
?DOC(" Hash a raw transaction hex string to get its transaction hash\n").
-spec hash_raw_transaction(binary()) -> binary().
hash_raw_transaction(Raw_transaction) ->
case gleeth@utils@hex:decode(Raw_transaction) of
{ok, Raw_bytes} ->
gleeth@utils@hex:encode(
gleeth@crypto@keccak:keccak256_binary(Raw_bytes)
);
{error, _} ->
<<""/utf8>>
end.
-file("src/gleeth/crypto/transaction.gleam", 628).
?DOC(" Get transaction hash from a signed legacy transaction\n").
-spec get_transaction_hash(signed_transaction()) -> binary().
get_transaction_hash(Tx) ->
hash_raw_transaction(erlang:element(12, Tx)).
-file("src/gleeth/crypto/transaction.gleam", 633).
?DOC(" Get transaction hash from a signed EIP-1559 transaction\n").
-spec get_eip1559_transaction_hash(signed_eip1559_transaction()) -> binary().
get_eip1559_transaction_hash(Tx) ->
hash_raw_transaction(erlang:element(14, Tx)).
-file("src/gleeth/crypto/transaction.gleam", 646).
?DOC(" Convert signed EIP-1559 transaction to string for display\n").
-spec signed_eip1559_transaction_to_string(signed_eip1559_transaction()) -> binary().
signed_eip1559_transaction_to_string(Tx) ->
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"SignedEip1559Transaction {\n"/utf8,
" to: "/utf8>>/binary,
(erlang:element(
2,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" value: "/utf8>>/binary,
(erlang:element(
3,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" gas_limit: "/utf8>>/binary,
(erlang:element(
4,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" max_fee_per_gas: "/utf8>>/binary,
(erlang:element(
5,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" max_priority_fee_per_gas: "/utf8>>/binary,
(erlang:element(
6,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" nonce: "/utf8>>/binary,
(erlang:element(
7,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" data: "/utf8>>/binary,
(erlang:element(
8,
Tx
))/binary>>/binary,
"\n"/utf8>>/binary,
" chain_id: "/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(
9,
Tx
)
))/binary>>/binary,
"\n"/utf8>>/binary,
" v: "/utf8>>/binary,
(erlang:element(11, Tx))/binary>>/binary,
"\n"/utf8>>/binary,
" r: "/utf8>>/binary,
(erlang:element(12, Tx))/binary>>/binary,
"\n"/utf8>>/binary,
" s: "/utf8>>/binary,
(erlang:element(13, Tx))/binary>>/binary,
"\n"/utf8>>/binary,
" raw: "/utf8>>/binary,
(erlang:element(14, Tx))/binary>>/binary,
"\n"/utf8>>/binary,
"}"/utf8>>.
-file("src/gleeth/crypto/transaction.gleam", 690).
?DOC(" Convert TransactionError to string\n").
-spec error_to_string(transaction_error()) -> binary().
error_to_string(Error) ->
case Error of
{invalid_address, Msg} ->
<<"Invalid address: "/utf8, Msg/binary>>;
{invalid_amount, Msg@1} ->
<<"Invalid amount: "/utf8, Msg@1/binary>>;
{invalid_gas, Msg@2} ->
<<"Invalid gas: "/utf8, Msg@2/binary>>;
{invalid_nonce, Msg@3} ->
<<"Invalid nonce: "/utf8, Msg@3/binary>>;
{invalid_chain_id, Msg@4} ->
<<"Invalid chain ID: "/utf8, Msg@4/binary>>;
{invalid_data, Msg@5} ->
<<"Invalid data: "/utf8, Msg@5/binary>>;
{signing_failed, Msg@6} ->
<<"Signing failed: "/utf8, Msg@6/binary>>;
{encoding_failed, Msg@7} ->
<<"Encoding failed: "/utf8, Msg@7/binary>>
end.
-file("src/gleeth/crypto/transaction.gleam", 953).
?DOC(" Convert RLP bytes to an address string (0x-prefixed, full 40 chars)\n").
-spec rlp_to_address(gleeth@encoding@rlp:rlp_item()) -> binary().
rlp_to_address(Item) ->
case Item of
{rlp_bytes, <<>>} ->
<<""/utf8>>;
{rlp_bytes, Bytes} ->
<<"0x"/utf8,
(string:lowercase(gleam_stdlib:base16_encode(Bytes)))/binary>>;
_ ->
<<""/utf8>>
end.
-file("src/gleeth/crypto/transaction.gleam", 915).
-spec decode_access_list_entry(gleeth@encoding@rlp:rlp_item()) -> {ok,
access_list_entry()} |
{error, nil}.
decode_access_list_entry(Item) ->
case Item of
{rlp_list, [Addr_rlp, Keys_rlp]} ->
Address = rlp_to_address(Addr_rlp),
Storage_keys = case Keys_rlp of
{rlp_list, Keys} ->
gleam@list:map(Keys, fun(K) -> case K of
{rlp_bytes, Bytes} ->
<<"0x"/utf8,
(string:lowercase(
gleam_stdlib:base16_encode(Bytes)
))/binary>>;
_ ->
<<"0x"/utf8>>
end end);
_ ->
[]
end,
{ok, {access_list_entry, Address, Storage_keys}};
_ ->
{error, nil}
end.
-file("src/gleeth/crypto/transaction.gleam", 908).
-spec decode_access_list(gleeth@encoding@rlp:rlp_item()) -> list(access_list_entry()).
decode_access_list(Item) ->
case Item of
{rlp_list, Entries} ->
gleam@list:filter_map(Entries, fun decode_access_list_entry/1);
_ ->
[]
end.
-file("src/gleeth/crypto/transaction.gleam", 963).
?DOC(" Convert RLP bytes to hex data string (0x-prefixed, preserves all bytes)\n").
-spec rlp_to_hex_data(gleeth@encoding@rlp:rlp_item()) -> binary().
rlp_to_hex_data(Item) ->
case Item of
{rlp_bytes, <<>>} ->
<<"0x"/utf8>>;
{rlp_bytes, Bytes} ->
<<"0x"/utf8,
(string:lowercase(gleam_stdlib:base16_encode(Bytes)))/binary>>;
_ ->
<<"0x"/utf8>>
end.
-file("src/gleeth/crypto/transaction.gleam", 973).
?DOC(" Convert RLP bytes to hex string preserving all bytes (for r, s values)\n").
-spec rlp_to_hex_bytes(gleeth@encoding@rlp:rlp_item()) -> binary().
rlp_to_hex_bytes(Item) ->
case Item of
{rlp_bytes, Bytes} ->
<<"0x"/utf8,
(string:lowercase(gleam_stdlib:base16_encode(Bytes)))/binary>>;
_ ->
<<"0x"/utf8>>
end.
-file("src/gleeth/crypto/transaction.gleam", 995).
-spec do_bytes_to_int(bitstring(), integer()) -> integer().
do_bytes_to_int(Data, Acc) ->
case Data of
<<Byte:8, Rest/bitstring>> ->
do_bytes_to_int(Rest, (Acc * 256) + Byte);
_ ->
Acc
end.
-file("src/gleeth/crypto/transaction.gleam", 991).
?DOC(" Convert big-endian bytes to an integer\n").
-spec bytes_to_int(bitstring()) -> integer().
bytes_to_int(Data) ->
do_bytes_to_int(Data, 0).
-file("src/gleeth/crypto/transaction.gleam", 982).
?DOC(" Convert RLP bytes to an integer\n").
-spec rlp_to_int(gleeth@encoding@rlp:rlp_item()) -> integer().
rlp_to_int(Item) ->
case Item of
{rlp_bytes, <<>>} ->
0;
{rlp_bytes, Bytes} ->
bytes_to_int(Bytes);
_ ->
0
end.
-file("src/gleeth/crypto/transaction.gleam", 1003).
?DOC(" Drop leading zeros from a hex string (but keep at least one digit)\n").
-spec drop_leading_hex_zeros(binary()) -> binary().
drop_leading_hex_zeros(Hex_str) ->
case Hex_str of
<<"0"/utf8, Rest/binary>> ->
case Rest of
<<""/utf8>> ->
<<"0"/utf8>>;
_ ->
drop_leading_hex_zeros(Rest)
end;
_ ->
Hex_str
end.
-file("src/gleeth/crypto/transaction.gleam", 941).
?DOC(" Convert RLP bytes to a hex amount string (0x-prefixed, minimal encoding)\n").
-spec rlp_to_hex_amount(gleeth@encoding@rlp:rlp_item()) -> binary().
rlp_to_hex_amount(Item) ->
case Item of
{rlp_bytes, <<>>} ->
<<"0x0"/utf8>>;
{rlp_bytes, Bytes} ->
Hex_str = string:lowercase(gleam_stdlib:base16_encode(Bytes)),
<<"0x"/utf8, (drop_leading_hex_zeros(Hex_str))/binary>>;
_ ->
<<"0x0"/utf8>>
end.
-file("src/gleeth/crypto/transaction.gleam", 774).
-spec decode_legacy_items(list(gleeth@encoding@rlp:rlp_item()), binary()) -> {ok,
signed_transaction()} |
{error, transaction_error()}.
decode_legacy_items(Items, Raw_hex) ->
case Items of
[Nonce_rlp,
Gas_price_rlp,
Gas_limit_rlp,
To_rlp,
Value_rlp,
Data_rlp,
V_rlp,
R_rlp,
S_rlp] ->
Nonce = rlp_to_hex_amount(Nonce_rlp),
Gas_price = rlp_to_hex_amount(Gas_price_rlp),
Gas_limit = rlp_to_hex_amount(Gas_limit_rlp),
To = rlp_to_address(To_rlp),
Value = rlp_to_hex_amount(Value_rlp),
Data = rlp_to_hex_data(Data_rlp),
V_int = rlp_to_int(V_rlp),
R = rlp_to_hex_bytes(R_rlp),
S = rlp_to_hex_bytes(S_rlp),
Chain_id = (V_int - 35) div 2,
V_hex = <<"0x"/utf8,
(string:lowercase(gleam@int:to_base16(V_int)))/binary>>,
{ok,
{signed_transaction,
To,
Value,
Gas_limit,
Gas_price,
Nonce,
Data,
Chain_id,
V_hex,
R,
S,
Raw_hex}};
_ ->
{error,
{encoding_failed,
<<"Legacy transaction must have 9 RLP items, got "/utf8,
(erlang:integer_to_binary(erlang:length(Items)))/binary>>}}
end.
-file("src/gleeth/crypto/transaction.gleam", 757).
?DOC(
" Decode a raw signed legacy transaction from its RLP-encoded hex string.\n"
" Recovers chain_id from the EIP-155 v value: chain_id = (v - 35) / 2.\n"
).
-spec decode_legacy(binary()) -> {ok, signed_transaction()} |
{error, transaction_error()}.
decode_legacy(Raw_hex) ->
gleam@result:'try'(
begin
_pipe = gleeth@utils@hex:decode(Raw_hex),
gleam@result:map_error(
_pipe,
fun(_) -> {encoding_failed, <<"Invalid hex string"/utf8>>} end
)
end,
fun(Raw_bytes) ->
gleam@result:'try'(
begin
_pipe@1 = gleeth@encoding@rlp:decode(Raw_bytes),
gleam@result:map_error(
_pipe@1,
fun(_) ->
{encoding_failed, <<"Invalid RLP encoding"/utf8>>}
end
)
end,
fun(Rlp_item) -> case Rlp_item of
{rlp_list, Items} ->
decode_legacy_items(Items, Raw_hex);
_ ->
{error,
{encoding_failed,
<<"Expected RLP list for legacy transaction"/utf8>>}}
end end
)
end
).
-file("src/gleeth/crypto/transaction.gleam", 851).
-spec decode_eip1559_items(list(gleeth@encoding@rlp:rlp_item()), binary()) -> {ok,
signed_eip1559_transaction()} |
{error, transaction_error()}.
decode_eip1559_items(Items, Raw_hex) ->
case Items of
[Chain_id_rlp,
Nonce_rlp,
Max_priority_fee_rlp,
Max_fee_rlp,
Gas_limit_rlp,
To_rlp,
Value_rlp,
Data_rlp,
Access_list_rlp,
V_rlp,
R_rlp,
S_rlp] ->
Chain_id = rlp_to_int(Chain_id_rlp),
Nonce = rlp_to_hex_amount(Nonce_rlp),
Max_priority_fee = rlp_to_hex_amount(Max_priority_fee_rlp),
Max_fee = rlp_to_hex_amount(Max_fee_rlp),
Gas_limit = rlp_to_hex_amount(Gas_limit_rlp),
To = rlp_to_address(To_rlp),
Value = rlp_to_hex_amount(Value_rlp),
Data = rlp_to_hex_data(Data_rlp),
Access_list = decode_access_list(Access_list_rlp),
V_int = rlp_to_int(V_rlp),
V_hex = <<"0x"/utf8,
(string:lowercase(gleam@int:to_base16(V_int)))/binary>>,
R = rlp_to_hex_bytes(R_rlp),
S = rlp_to_hex_bytes(S_rlp),
{ok,
{signed_eip1559_transaction,
To,
Value,
Gas_limit,
Max_fee,
Max_priority_fee,
Nonce,
Data,
Chain_id,
Access_list,
V_hex,
R,
S,
Raw_hex}};
_ ->
{error,
{encoding_failed,
<<"EIP-1559 transaction must have 12 RLP items, got "/utf8,
(erlang:integer_to_binary(erlang:length(Items)))/binary>>}}
end.
-file("src/gleeth/crypto/transaction.gleam", 829).
?DOC(
" Decode a raw signed EIP-1559 (Type 2) transaction from its hex string.\n"
" The input must start with 0x02 (the type prefix).\n"
).
-spec decode_eip1559(binary()) -> {ok, signed_eip1559_transaction()} |
{error, transaction_error()}.
decode_eip1559(Raw_hex) ->
gleam@result:'try'(
begin
_pipe = gleeth@utils@hex:decode(Raw_hex),
gleam@result:map_error(
_pipe,
fun(_) -> {encoding_failed, <<"Invalid hex string"/utf8>>} end
)
end,
fun(Raw_bytes) -> case Raw_bytes of
<<16#02, Payload/bitstring>> ->
gleam@result:'try'(
begin
_pipe@1 = gleeth@encoding@rlp:decode(Payload),
gleam@result:map_error(
_pipe@1,
fun(_) ->
{encoding_failed,
<<"Invalid RLP encoding"/utf8>>}
end
)
end,
fun(Rlp_item) -> case Rlp_item of
{rlp_list, Items} ->
decode_eip1559_items(Items, Raw_hex);
_ ->
{error,
{encoding_failed,
<<"Expected RLP list for EIP-1559 transaction"/utf8>>}}
end end
);
_ ->
{error,
{encoding_failed,
<<"EIP-1559 transaction must start with 0x02"/utf8>>}}
end end
).
-file("src/gleeth/crypto/transaction.gleam", 738).
?DOC(
" Decode a raw transaction hex string, auto-detecting the type.\n"
" Legacy transactions start with an RLP list prefix (0xc0-0xff).\n"
" EIP-1559 transactions start with 0x02.\n"
).
-spec decode(binary()) -> {ok, decoded_transaction()} |
{error, transaction_error()}.
decode(Raw_hex) ->
gleam@result:'try'(
begin
_pipe = gleeth@utils@hex:decode(Raw_hex),
gleam@result:map_error(
_pipe,
fun(_) -> {encoding_failed, <<"Invalid hex string"/utf8>>} end
)
end,
fun(Raw_bytes) -> case Raw_bytes of
<<16#02, _/bitstring>> ->
gleam@result:map(
decode_eip1559(Raw_hex),
fun(Tx) -> {decoded_eip1559, Tx} end
);
_ ->
gleam@result:map(
decode_legacy(Raw_hex),
fun(Tx@1) -> {decoded_legacy, Tx@1} end
)
end end
).