Current section
Files
Jump to
Current section
Files
src/gleeth@eip712.erl
-module(gleeth@eip712).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/eip712.gleam").
-export([domain/0, domain_name/2, domain_version/2, domain_chain_id/2, domain_verifying_contract/2, domain_salt/2, field/2, string_val/1, int_val/1, bool_val/1, address_val/1, bytes32_val/1, bytes_val/1, array_val/1, struct_val/1, typed_data/4, encode_type/2, hash_type/2, hash_struct/3, hash_domain/2, hash_typed_data/1, sign_typed_data/2, recover_typed_data/2]).
-export_type([domain/0, typed_field/0, typed_value/0, typed_data/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.
?MODULEDOC(
" EIP-712 typed structured data hashing and signing.\n"
"\n"
" Implements the encoding rules from the EIP-712 specification for signing\n"
" structured data with domain separation. Used for ERC-2612 permits,\n"
" DEX order signing, meta-transactions, and other off-chain authorization.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let domain = eip712.domain()\n"
" |> eip712.domain_name(\"MyDapp\")\n"
" |> eip712.domain_version(\"1\")\n"
" |> eip712.domain_chain_id(1)\n"
"\n"
" let types = dict.from_list([\n"
" #(\"Mail\", [\n"
" eip712.field(\"from\", \"address\"),\n"
" eip712.field(\"to\", \"address\"),\n"
" eip712.field(\"contents\", \"string\"),\n"
" ]),\n"
" ])\n"
"\n"
" let message = dict.from_list([\n"
" #(\"from\", eip712.address_val(\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\")),\n"
" #(\"to\", eip712.address_val(\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\")),\n"
" #(\"contents\", eip712.string_val(\"Hello, Bob!\")),\n"
" ])\n"
"\n"
" let data = eip712.typed_data(types, \"Mail\", domain, message)\n"
" let assert Ok(sig) = eip712.sign_typed_data(data, wallet)\n"
" ```\n"
).
-type domain() :: {domain,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(integer()),
gleam@option:option(binary()),
gleam@option:option(binary())}.
-type typed_field() :: {typed_field, binary(), binary()}.
-type typed_value() :: {string_val, binary()} |
{int_val, integer()} |
{bool_val, boolean()} |
{address_val, binary()} |
{bytes32_val, bitstring()} |
{bytes_val, bitstring()} |
{array_val, list(typed_value())} |
{struct_val, gleam@dict:dict(binary(), typed_value())}.
-type typed_data() :: {typed_data,
gleam@dict:dict(binary(), list(typed_field())),
binary(),
domain(),
gleam@dict:dict(binary(), typed_value())}.
-file("src/gleeth/eip712.gleam", 93).
?DOC(" Create an empty domain. Use `domain_name`, `domain_version`, etc. to set fields.\n").
-spec domain() -> domain().
domain() ->
{domain, none, none, none, none, none}.
-file("src/gleeth/eip712.gleam", 104).
?DOC(" Set the domain name field.\n").
-spec domain_name(domain(), binary()) -> domain().
domain_name(D, Name) ->
{domain,
{some, Name},
erlang:element(3, D),
erlang:element(4, D),
erlang:element(5, D),
erlang:element(6, D)}.
-file("src/gleeth/eip712.gleam", 109).
?DOC(" Set the domain version field.\n").
-spec domain_version(domain(), binary()) -> domain().
domain_version(D, Version) ->
{domain,
erlang:element(2, D),
{some, Version},
erlang:element(4, D),
erlang:element(5, D),
erlang:element(6, D)}.
-file("src/gleeth/eip712.gleam", 114).
?DOC(" Set the domain chain ID field.\n").
-spec domain_chain_id(domain(), integer()) -> domain().
domain_chain_id(D, Id) ->
{domain,
erlang:element(2, D),
erlang:element(3, D),
{some, Id},
erlang:element(5, D),
erlang:element(6, D)}.
-file("src/gleeth/eip712.gleam", 119).
?DOC(" Set the domain verifying contract address.\n").
-spec domain_verifying_contract(domain(), binary()) -> domain().
domain_verifying_contract(D, Address) ->
{domain,
erlang:element(2, D),
erlang:element(3, D),
erlang:element(4, D),
{some, Address},
erlang:element(6, D)}.
-file("src/gleeth/eip712.gleam", 124).
?DOC(" Set the domain salt field (hex-encoded bytes32).\n").
-spec domain_salt(domain(), binary()) -> domain().
domain_salt(D, Salt) ->
{domain,
erlang:element(2, D),
erlang:element(3, D),
erlang:element(4, D),
erlang:element(5, D),
{some, Salt}}.
-file("src/gleeth/eip712.gleam", 129).
?DOC(" Create a typed field definition.\n").
-spec field(binary(), binary()) -> typed_field().
field(Name, Type_name) ->
{typed_field, Name, Type_name}.
-file("src/gleeth/eip712.gleam", 134).
?DOC(" Convenience constructors for typed values.\n").
-spec string_val(binary()) -> typed_value().
string_val(S) ->
{string_val, S}.
-file("src/gleeth/eip712.gleam", 139).
?DOC(" Wrap an integer as a typed value (for uint256, int256, etc.).\n").
-spec int_val(integer()) -> typed_value().
int_val(N) ->
{int_val, N}.
-file("src/gleeth/eip712.gleam", 144).
?DOC(" Wrap a boolean as a typed value.\n").
-spec bool_val(boolean()) -> typed_value().
bool_val(B) ->
{bool_val, B}.
-file("src/gleeth/eip712.gleam", 149).
?DOC(" Wrap an Ethereum address as a typed value.\n").
-spec address_val(binary()) -> typed_value().
address_val(Addr) ->
{address_val, Addr}.
-file("src/gleeth/eip712.gleam", 154).
?DOC(" Wrap a fixed 32-byte value as a typed value.\n").
-spec bytes32_val(bitstring()) -> typed_value().
bytes32_val(B) ->
{bytes32_val, B}.
-file("src/gleeth/eip712.gleam", 159).
?DOC(" Wrap dynamic bytes as a typed value.\n").
-spec bytes_val(bitstring()) -> typed_value().
bytes_val(B) ->
{bytes_val, B}.
-file("src/gleeth/eip712.gleam", 164).
?DOC(" Wrap a list of typed values as an array value.\n").
-spec array_val(list(typed_value())) -> typed_value().
array_val(Items) ->
{array_val, Items}.
-file("src/gleeth/eip712.gleam", 169).
?DOC(" Wrap a dict of named fields as a struct value.\n").
-spec struct_val(gleam@dict:dict(binary(), typed_value())) -> typed_value().
struct_val(Fields) ->
{struct_val, Fields}.
-file("src/gleeth/eip712.gleam", 174).
?DOC(" Create a complete typed data structure.\n").
-spec typed_data(
gleam@dict:dict(binary(), list(typed_field())),
binary(),
domain(),
gleam@dict:dict(binary(), typed_value())
) -> typed_data().
typed_data(Types, Primary_type, D, Message) ->
{typed_data, Types, Primary_type, D, Message}.
-file("src/gleeth/eip712.gleam", 281).
-spec encode_single_type(
binary(),
gleam@dict:dict(binary(), list(typed_field()))
) -> binary().
encode_single_type(Type_name, Types) ->
case gleam_stdlib:map_get(Types, Type_name) of
{ok, Fields} ->
Field_strs = gleam@list:map(
Fields,
fun(F) ->
<<<<(erlang:element(3, F))/binary, " "/utf8>>/binary,
(erlang:element(2, F))/binary>>
end
),
<<<<<<Type_name/binary, "("/utf8>>/binary,
(gleam@string:join(Field_strs, <<","/utf8>>))/binary>>/binary,
")"/utf8>>;
{error, _} ->
<<Type_name/binary, "()"/utf8>>
end.
-file("src/gleeth/eip712.gleam", 327).
-spec strip_array_suffix(binary()) -> binary().
strip_array_suffix(Type_name) ->
case gleam_stdlib:string_ends_with(Type_name, <<"[]"/utf8>>) of
true ->
gleam@string:drop_end(Type_name, 2);
false ->
Type_name
end.
-file("src/gleeth/eip712.gleam", 296).
?DOC(
" Recursively collect all struct types referenced by a type's fields,\n"
" excluding the root type itself. Deduplicates across siblings.\n"
).
-spec collect_referenced_types(
binary(),
gleam@dict:dict(binary(), list(typed_field())),
list(binary())
) -> list(binary()).
collect_referenced_types(Type_name, Types, Seen) ->
case gleam_stdlib:map_get(Types, Type_name) of
{ok, Fields} ->
{Result, _} = gleam@list:fold(
Fields,
{[], Seen},
fun(Acc, F) ->
{Collected, Current_seen} = Acc,
Base_type = strip_array_suffix(erlang:element(3, F)),
case gleam@dict:has_key(Types, Base_type) andalso not gleam@list:contains(
Current_seen,
Base_type
) of
true ->
New_seen = [Base_type | Current_seen],
Nested = collect_referenced_types(
Base_type,
Types,
New_seen
),
All_new = [Base_type | Nested],
Updated_seen = lists:append(All_new, New_seen),
{lists:append(Collected, All_new), Updated_seen};
false ->
Acc
end
end
),
Result;
{error, _} ->
[]
end.
-file("src/gleeth/eip712.gleam", 268).
?DOC(
" Build the canonical type encoding string.\n"
" Example: \"Mail(address from,address to,string contents)\"\n"
" Referenced structs are collected, sorted, and appended.\n"
).
-spec encode_type(binary(), gleam@dict:dict(binary(), list(typed_field()))) -> binary().
encode_type(Type_name, Types) ->
Primary = encode_single_type(Type_name, Types),
Referenced = collect_referenced_types(Type_name, Types, [Type_name]),
Sorted = gleam@list:sort(Referenced, fun gleam@string:compare/2),
Suffix = begin
_pipe = gleam@list:map(
Sorted,
fun(Name) -> encode_single_type(Name, Types) end
),
erlang:list_to_binary(_pipe)
end,
<<Primary/binary, Suffix/binary>>.
-file("src/gleeth/eip712.gleam", 228).
?DOC(" Compute typeHash: keccak256(encodeType(typeName)).\n").
-spec hash_type(binary(), gleam@dict:dict(binary(), list(typed_field()))) -> bitstring().
hash_type(Type_name, Types) ->
Type_string = encode_type(Type_name, Types),
gleeth@crypto@keccak:keccak256_binary(gleam_stdlib:identity(Type_string)).
-file("src/gleeth/eip712.gleam", 495).
-spec append_if(
list(typed_field()),
gleam@option:option(any()),
binary(),
binary()
) -> list(typed_field()).
append_if(Acc, Opt, Type_name, Name) ->
case Opt of
{some, _} ->
[{typed_field, Name, Type_name} | Acc];
none ->
Acc
end.
-file("src/gleeth/eip712.gleam", 467).
-spec build_domain_fields(domain()) -> list(typed_field()).
build_domain_fields(D) ->
_pipe = [],
_pipe@1 = append_if(
_pipe,
erlang:element(2, D),
<<"string"/utf8>>,
<<"name"/utf8>>
),
_pipe@2 = append_if(
_pipe@1,
erlang:element(3, D),
<<"string"/utf8>>,
<<"version"/utf8>>
),
_pipe@3 = append_if(
_pipe@2,
erlang:element(4, D),
<<"uint256"/utf8>>,
<<"chainId"/utf8>>
),
_pipe@4 = append_if(
_pipe@3,
erlang:element(5, D),
<<"address"/utf8>>,
<<"verifyingContract"/utf8>>
),
_pipe@5 = append_if(
_pipe@4,
erlang:element(6, D),
<<"bytes32"/utf8>>,
<<"salt"/utf8>>
),
lists:reverse(_pipe@5).
-file("src/gleeth/eip712.gleam", 507).
-spec append_val_if(
list({binary(), typed_value()}),
gleam@option:option(PHH),
binary(),
fun((PHH) -> typed_value())
) -> list({binary(), typed_value()}).
append_val_if(Acc, Opt, Name, To_val) ->
case Opt of
{some, V} ->
[{Name, To_val(V)} | Acc];
none ->
Acc
end.
-file("src/gleeth/eip712.gleam", 477).
-spec build_domain_values(domain()) -> gleam@dict:dict(binary(), typed_value()).
build_domain_values(D) ->
Entries = begin
_pipe = [],
_pipe@1 = append_val_if(
_pipe,
erlang:element(2, D),
<<"name"/utf8>>,
fun(V) -> {string_val, V} end
),
_pipe@2 = append_val_if(
_pipe@1,
erlang:element(3, D),
<<"version"/utf8>>,
fun(V@1) -> {string_val, V@1} end
),
_pipe@3 = append_val_if(
_pipe@2,
erlang:element(4, D),
<<"chainId"/utf8>>,
fun(V@2) -> {int_val, V@2} end
),
_pipe@4 = append_val_if(
_pipe@3,
erlang:element(5, D),
<<"verifyingContract"/utf8>>,
fun(V@3) -> {address_val, V@3} end
),
append_val_if(
_pipe@4,
erlang:element(6, D),
<<"salt"/utf8>>,
fun(V@4) -> case gleeth@utils@hex:decode(V@4) of
{ok, Bytes} ->
{bytes32_val, Bytes};
{error, _} ->
{bytes32_val, <<>>}
end end
)
end,
maps:from_list(Entries).
-file("src/gleeth/eip712.gleam", 559).
-spec make_zeros_acc(integer(), bitstring()) -> bitstring().
make_zeros_acc(N, Acc) ->
case N =< 0 of
true ->
Acc;
false ->
make_zeros_acc(N - 1, <<Acc/bitstring, 0:8>>)
end.
-file("src/gleeth/eip712.gleam", 552).
-spec make_zeros(integer()) -> bitstring().
make_zeros(N) ->
case N =< 0 of
true ->
<<>>;
false ->
make_zeros_acc(N, <<>>)
end.
-file("src/gleeth/eip712.gleam", 523).
-spec pad_left(bitstring(), integer()) -> bitstring().
pad_left(Data, Target) ->
Size = erlang:byte_size(Data),
case Size >= Target of
true ->
Result@1 = case gleam_stdlib:bit_array_slice(
Data,
Size - Target,
Target
) of
{ok, Result} -> Result;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/eip712"/utf8>>,
function => <<"pad_left"/utf8>>,
line => 528,
value => _assert_fail,
start => 15716,
'end' => 15784,
pattern_start => 15727,
pattern_end => 15737})
end,
Result@1;
false ->
Padding = make_zeros(Target - Size),
gleam_stdlib:bit_array_concat([Padding, Data])
end.
-file("src/gleeth/eip712.gleam", 440).
-spec encode_address(binary()) -> {ok, bitstring()} | {error, binary()}.
encode_address(Addr) ->
case gleeth@utils@hex:decode(Addr) of
{ok, Bytes} ->
{ok, pad_left(Bytes, 32)};
{error, _} ->
{error, <<"Invalid address: "/utf8, Addr/binary>>}
end.
-file("src/gleeth/eip712.gleam", 538).
-spec pad_right(bitstring(), integer()) -> bitstring().
pad_right(Data, Target) ->
Size = erlang:byte_size(Data),
case Size >= Target of
true ->
Result@1 = case gleam_stdlib:bit_array_slice(Data, 0, Target) of
{ok, Result} -> Result;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/eip712"/utf8>>,
function => <<"pad_right"/utf8>>,
line => 542,
value => _assert_fail,
start => 16057,
'end' => 16113,
pattern_start => 16068,
pattern_end => 16078})
end,
Result@1;
false ->
Padding = make_zeros(Target - Size),
gleam_stdlib:bit_array_concat([Data, Padding])
end.
-file("src/gleeth/eip712.gleam", 573).
-spec int_to_bytes_acc(integer(), bitstring()) -> bitstring().
int_to_bytes_acc(N, Acc) ->
case N of
0 ->
Acc;
_ ->
Byte = erlang:'band'(N, 16#ff),
Rest = erlang:'bsr'(N, 8),
int_to_bytes_acc(Rest, <<Byte:8, Acc/bitstring>>)
end.
-file("src/gleeth/eip712.gleam", 566).
-spec int_to_bytes(integer()) -> bitstring().
int_to_bytes(N) ->
case N of
0 ->
<<0>>;
_ ->
int_to_bytes_acc(N, <<>>)
end.
-file("src/gleeth/eip712.gleam", 590).
-spec invert_bytes(bitstring(), bitstring()) -> bitstring().
invert_bytes(Data, Acc) ->
case Data of
<<Byte:8, Rest/bitstring>> ->
invert_bytes(
Rest,
<<Acc/bitstring, ((erlang:'bxor'(Byte, 16#ff))):8>>
);
_ ->
Acc
end.
-file("src/gleeth/eip712.gleam", 606).
-spec add_one_at(bitstring(), integer(), integer()) -> bitstring().
add_one_at(Data, Pos, Carry) ->
case Pos < 0 of
true ->
Data;
false ->
Byte@1 = case gleam_stdlib:bit_array_slice(Data, Pos, 1) of
{ok, <<Byte:8>>} -> Byte;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/eip712"/utf8>>,
function => <<"add_one_at"/utf8>>,
line => 610,
value => _assert_fail,
start => 17476,
'end' => 17533,
pattern_start => 17487,
pattern_end => 17501})
end,
Sum = Byte@1 + Carry,
New_byte = erlang:'band'(Sum, 16#ff),
New_carry = erlang:'bsr'(Sum, 8),
Before@1 = case gleam_stdlib:bit_array_slice(Data, 0, Pos) of
{ok, Before} -> Before;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/eip712"/utf8>>,
function => <<"add_one_at"/utf8>>,
line => 614,
value => _assert_fail@1,
start => 17671,
'end' => 17724,
pattern_start => 17682,
pattern_end => 17692})
end,
After_start = Pos + 1,
After_len = erlang:byte_size(Data) - After_start,
After@1 = case gleam_stdlib:bit_array_slice(
Data,
After_start,
After_len
) of
{ok, After} -> After;
_assert_fail@2 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/eip712"/utf8>>,
function => <<"add_one_at"/utf8>>,
line => 617,
value => _assert_fail@2,
start => 17825,
'end' => 17893,
pattern_start => 17836,
pattern_end => 17845})
end,
New_data = gleam_stdlib:bit_array_concat(
[Before@1, <<New_byte:8>>, After@1]
),
case New_carry of
0 ->
New_data;
_ ->
add_one_at(New_data, Pos - 1, New_carry)
end
end.
-file("src/gleeth/eip712.gleam", 601).
-spec add_one(bitstring()) -> bitstring().
add_one(Data) ->
Size = erlang:byte_size(Data),
add_one_at(Data, Size - 1, 1).
-file("src/gleeth/eip712.gleam", 584).
-spec twos_complement(bitstring()) -> bitstring().
twos_complement(Bytes) ->
Inverted = invert_bytes(Bytes, <<>>),
add_one(Inverted).
-file("src/gleeth/eip712.gleam", 447).
-spec encode_int(integer()) -> {ok, bitstring()} | {error, binary()}.
encode_int(N) ->
case N >= 0 of
true ->
Bytes = int_to_bytes(N),
{ok, pad_left(Bytes, 32)};
false ->
Pos = int_to_bytes(- N),
Padded = pad_left(Pos, 32),
{ok, twos_complement(Padded)}
end.
-file("src/gleeth/eip712.gleam", 372).
-spec encode_non_array_value(
binary(),
typed_value(),
gleam@dict:dict(binary(), list(typed_field()))
) -> {ok, bitstring()} | {error, binary()}.
encode_non_array_value(Type_name, Value, Types) ->
case {Type_name, Value} of
{<<"string"/utf8>>, {string_val, S}} ->
{ok,
gleeth@crypto@keccak:keccak256_binary(gleam_stdlib:identity(S))};
{<<"bytes"/utf8>>, {bytes_val, B}} ->
{ok, gleeth@crypto@keccak:keccak256_binary(B)};
{<<"address"/utf8>>, {address_val, Addr}} ->
encode_address(Addr);
{<<"bool"/utf8>>, {bool_val, B@1}} ->
{ok, case B@1 of
true ->
pad_left(<<1>>, 32);
false ->
pad_left(<<>>, 32)
end};
{<<"bytes32"/utf8>>, {bytes32_val, B@2}} ->
{ok, pad_right(B@2, 32)};
{_, {int_val, N}} ->
case gleam_stdlib:string_starts_with(Type_name, <<"uint"/utf8>>)
orelse gleam_stdlib:string_starts_with(Type_name, <<"int"/utf8>>) of
true ->
encode_int(N);
false ->
{error,
<<<<"Type mismatch: expected "/utf8, Type_name/binary>>/binary,
", got int"/utf8>>}
end;
{_, {bytes32_val, B@3}} ->
case gleam_stdlib:string_starts_with(Type_name, <<"bytes"/utf8>>) of
true ->
{ok, pad_right(B@3, 32)};
false ->
{error,
<<<<"Type mismatch: expected "/utf8, Type_name/binary>>/binary,
", got bytes"/utf8>>}
end;
{_, {struct_val, Fields}} ->
case gleam@dict:has_key(Types, Type_name) of
true ->
gleam@result:'try'(
hash_struct(Type_name, Fields, Types),
fun(Hashed) -> {ok, Hashed} end
);
false ->
{error, <<"Unknown struct type: "/utf8, Type_name/binary>>}
end;
{_, _} ->
{error,
<<<<"Cannot encode "/utf8, Type_name/binary>>/binary,
" with given value"/utf8>>}
end.
-file("src/gleeth/eip712.gleam", 217).
?DOC(" Compute hashStruct: keccak256(typeHash || encodeData(s)).\n").
-spec hash_struct(
binary(),
gleam@dict:dict(binary(), typed_value()),
gleam@dict:dict(binary(), list(typed_field()))
) -> {ok, bitstring()} | {error, binary()}.
hash_struct(Type_name, Data, Types) ->
Type_hash = hash_type(Type_name, Types),
gleam@result:'try'(
encode_data(Type_name, Data, Types),
fun(Encoded) ->
{ok,
gleeth@crypto@keccak:keccak256_binary(
gleam_stdlib:bit_array_concat([Type_hash, Encoded])
)}
end
).
-file("src/gleeth/eip712.gleam", 338).
-spec encode_data(
binary(),
gleam@dict:dict(binary(), typed_value()),
gleam@dict:dict(binary(), list(typed_field()))
) -> {ok, bitstring()} | {error, binary()}.
encode_data(Type_name, Data, Types) ->
case gleam_stdlib:map_get(Types, Type_name) of
{ok, Fields} ->
gleam@result:'try'(
gleam@list:try_map(
Fields,
fun(F) ->
case gleam_stdlib:map_get(Data, erlang:element(2, F)) of
{ok, Value} ->
encode_value(erlang:element(3, F), Value, Types);
{error, _} ->
{error,
<<"Missing field: "/utf8,
(erlang:element(2, F))/binary>>}
end
end
),
fun(Encoded_parts) ->
{ok, gleam_stdlib:bit_array_concat(Encoded_parts)}
end
);
{error, _} ->
{error, <<"Unknown type: "/utf8, Type_name/binary>>}
end.
-file("src/gleeth/eip712.gleam", 360).
?DOC(" Encode a single value to exactly 32 bytes per EIP-712 rules.\n").
-spec encode_value(
binary(),
typed_value(),
gleam@dict:dict(binary(), list(typed_field()))
) -> {ok, bitstring()} | {error, binary()}.
encode_value(Type_name, Value, Types) ->
case gleam_stdlib:string_ends_with(Type_name, <<"[]"/utf8>>) of
true ->
encode_array_value(Type_name, Value, Types);
false ->
encode_non_array_value(Type_name, Value, Types)
end.
-file("src/gleeth/eip712.gleam", 205).
?DOC(" Compute the domain separator hash.\n").
-spec hash_domain(domain(), gleam@dict:dict(binary(), list(typed_field()))) -> {ok,
bitstring()} |
{error, binary()}.
hash_domain(D, Custom_types) ->
Domain_fields = build_domain_fields(D),
Domain_values = build_domain_values(D),
Types = gleam@dict:insert(
Custom_types,
<<"EIP712Domain"/utf8>>,
Domain_fields
),
hash_struct(<<"EIP712Domain"/utf8>>, Domain_values, Types).
-file("src/gleeth/eip712.gleam", 194).
?DOC(
" Compute the EIP-712 digest: keccak256(\"\\x19\\x01\" || domainSeparator || hashStruct(message)).\n"
" This is the hash that gets signed.\n"
).
-spec hash_typed_data(typed_data()) -> {ok, bitstring()} | {error, binary()}.
hash_typed_data(Data) ->
gleam@result:'try'(
hash_domain(erlang:element(4, Data), erlang:element(2, Data)),
fun(Domain_sep) ->
gleam@result:'try'(
hash_struct(
erlang:element(3, Data),
erlang:element(5, Data),
erlang:element(2, Data)
),
fun(Struct_hash) ->
{ok,
gleeth@crypto@keccak:keccak256_binary(
<<16#19,
16#01,
Domain_sep/bitstring,
Struct_hash/bitstring>>
)}
end
)
end
).
-file("src/gleeth/eip712.gleam", 241).
?DOC(" Sign typed data with a wallet. Returns the signature.\n").
-spec sign_typed_data(typed_data(), gleeth@crypto@wallet:wallet()) -> {ok,
gleeth@crypto@secp256k1:signature()} |
{error, binary()}.
sign_typed_data(Data, W) ->
gleam@result:'try'(
hash_typed_data(Data),
fun(Digest) -> _pipe = gleeth@crypto@wallet:sign_hash(W, Digest),
gleam@result:map_error(
_pipe,
fun gleeth@crypto@wallet:error_to_string/1
) end
).
-file("src/gleeth/eip712.gleam", 251).
?DOC(" Recover the signer address from a typed data signature.\n").
-spec recover_typed_data(typed_data(), binary()) -> {ok, binary()} |
{error, binary()}.
recover_typed_data(Data, Signature_hex) ->
gleam@result:'try'(
hash_typed_data(Data),
fun(Digest) ->
gleam@result:'try'(
gleeth@crypto@secp256k1:signature_from_hex(Signature_hex),
fun(Signature) ->
gleam@result:'try'(
gleeth@crypto@secp256k1:recover_address(
Digest,
Signature
),
fun(Address) ->
{ok,
gleeth@crypto@secp256k1:address_to_string(
Address
)}
end
)
end
)
end
).
-file("src/gleeth/eip712.gleam", 423).
-spec encode_array_value(
binary(),
typed_value(),
gleam@dict:dict(binary(), list(typed_field()))
) -> {ok, bitstring()} | {error, binary()}.
encode_array_value(Type_name, Value, Types) ->
Element_type = gleam@string:drop_end(Type_name, 2),
case Value of
{array_val, Items} ->
gleam@result:'try'(
gleam@list:try_map(
Items,
fun(Item) -> encode_value(Element_type, Item, Types) end
),
fun(Encoded_items) ->
{ok,
gleeth@crypto@keccak:keccak256_binary(
gleam_stdlib:bit_array_concat(Encoded_items)
)}
end
);
_ ->
{error, <<"Expected array value for "/utf8, Type_name/binary>>}
end.