-module(glethers@hash@typed_data). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([encode_data/1, hash_struct/1, hash_domain/1, hash_message/3, encode_type/1]). -export_type([typed_data_domain/0, complex_field/0, encode_field/0]). -type typed_data_domain() :: {typed_data_domain, gleam@option:option(binary()), gleam@option:option(binary()), gleam@option:option(glethers@primitives@integer:uint256()), gleam@option:option(glethers@address:address()), gleam@option:option(glethers@primitives@bytes:bytes32())}. -type complex_field() :: {primitive, glethers@primitives:primitive()} | {struct, {binary(), list({binary(), complex_field()})}}. -type encode_field() :: name | version | chain_id | verifying_contract | salt. -spec do_domain_to_typed_data(typed_data_domain(), encode_field()) -> list({binary(), complex_field()}). do_domain_to_typed_data(Domain, Field) -> case Field of name -> case erlang:element(2, Domain) of {some, Name} -> [{<<"name"/utf8>>, {primitive, glethers@primitives:from_string(Name)}} | do_domain_to_typed_data(Domain, version)]; none -> do_domain_to_typed_data(Domain, version) end; version -> case erlang:element(3, Domain) of {some, Version} -> [{<<"version"/utf8>>, {primitive, glethers@primitives:from_string(Version)}} | do_domain_to_typed_data(Domain, chain_id)]; none -> do_domain_to_typed_data(Domain, chain_id) end; chain_id -> case erlang:element(4, Domain) of {some, Chain_id} -> [{<<"chainId"/utf8>>, {primitive, glethers@primitives:from_uint256(Chain_id)}} | do_domain_to_typed_data(Domain, verifying_contract)]; none -> do_domain_to_typed_data(Domain, verifying_contract) end; verifying_contract -> case erlang:element(5, Domain) of {some, Verifying_contract} -> [{<<"verifyingContract"/utf8>>, {primitive, glethers@primitives:from_address( Verifying_contract )}} | do_domain_to_typed_data(Domain, salt)]; none -> do_domain_to_typed_data(Domain, salt) end; salt -> case erlang:element(6, Domain) of {some, Salt} -> [{<<"salt"/utf8>>, {primitive, glethers@primitives:from_bytes32(Salt)}}]; none -> [] end end. -spec domain_to_typed_data(typed_data_domain()) -> {binary(), list({binary(), complex_field()})}. domain_to_typed_data(Domain) -> Typed_values = begin _pipe = Domain, do_domain_to_typed_data(_pipe, name) end, {<<"EIP712Domain"/utf8>>, Typed_values}. -spec encode_data({binary(), list({binary(), complex_field()})}) -> bitstring(). encode_data(Values) -> do_encode_data(erlang:element(2, Values)). -spec do_encode_data(list({binary(), complex_field()})) -> bitstring(). do_encode_data(Values) -> case Values of [] -> <<>>; [{_, Value} | Rest] -> Encoded_value = case Value of {primitive, Prim} -> _pipe = Prim, glethers@primitives:eip712_encode(_pipe); {struct, Struct} -> _pipe@1 = Struct, hash_struct(_pipe@1) end, <> end. -spec hash_struct({binary(), list({binary(), complex_field()})}) -> bitstring(). hash_struct(Data) -> Encoded_type = begin _pipe = encode_type(Data), gleam_stdlib:identity(_pipe) end, Encoded_type_hash = 'Elixir.ExKeccak':hash_256(Encoded_type), Encoded_data = encode_data(Data), 'Elixir.ExKeccak':hash_256( gleam_stdlib:bit_array_concat([Encoded_type_hash, Encoded_data]) ). -spec hash_domain(typed_data_domain()) -> bitstring(). hash_domain(Domain) -> _pipe = Domain, _pipe@1 = domain_to_typed_data(_pipe), hash_struct(_pipe@1). -spec encode(typed_data_domain(), {binary(), list({binary(), complex_field()})}) -> bitstring(). encode(Domain, Values) -> gleam_stdlib:bit_array_concat( [<<16#19, 16#01>>, hash_domain(Domain), hash_struct(Values)] ). -spec hash_message( typed_data_domain(), HPT, fun((HPT) -> {binary(), list({binary(), complex_field()})}) ) -> glethers@hash@message:hash(). hash_message(Domain, Struct, Encoder) -> Typed_data = Encoder(Struct), _assert_subject = glethers@hash@message:from_bit_array( 'Elixir.ExKeccak':hash_256(encode(Domain, Typed_data)) ), {ok, Hash} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"glethers/hash/typed_data"/utf8>>, function => <<"hash_message"/utf8>>, line => 186}) end, Hash. -spec encode_type({binary(), list({binary(), complex_field()})}) -> binary(). encode_type(Typed_data) -> {Types, Result_dict} = do_encode_type(erlang:element(2, Typed_data)), Struct_types = begin _pipe = Result_dict, _pipe@1 = maps:to_list(_pipe), _pipe@2 = gleam@list:map(_pipe@1, fun(X) -> erlang:element(2, X) end), _pipe@3 = gleam@list:sort(_pipe@2, fun gleam@string:compare/2), gleam@list:map(_pipe@3, fun gleam@string_builder:from_string/1) end, Builder = begin _pipe@4 = gleam@string_builder:from_string( <<(erlang:element(1, Typed_data))/binary, "("/utf8>> ), _pipe@5 = gleam@string_builder:append_builder( _pipe@4, gleam@string_builder:join(Types, <<","/utf8>>) ), gleam@string_builder:append(_pipe@5, <<")"/utf8>>) end, _pipe@6 = gleam@string_builder:concat([Builder | Struct_types]), gleam@string_builder:to_string(_pipe@6). -spec do_encode_type(list({binary(), complex_field()})) -> {list(gleam@string_builder:string_builder()), gleam@dict:dict(binary(), binary())}. do_encode_type(Values) -> case Values of [] -> {[], gleam@dict:new()}; [{Key, Value} | Rest] -> {Type_, New_types} = case Value of {primitive, {string, _}} -> {<<"string"/utf8>>, none}; {primitive, {address, _}} -> {<<"address"/utf8>>, none}; {primitive, {uint256, _}} -> {<<"uint256"/utf8>>, none}; {primitive, {bytes32, _}} -> {<<"bytes32"/utf8>>, none}; {struct, Struct} -> Struct_name = encode_type(Struct), {Name, _} = Struct, {Name, {some, Struct_name}} end, {Rest@1, Names} = do_encode_type(Rest), Result_dict = case New_types of {some, Name@1} -> _pipe = Names, gleam@dict:insert(_pipe, Type_, Name@1); none -> Names end, {[gleam@string_builder:from_string( <<<>/binary, Key/binary>> ) | Rest@1], Result_dict} end.