-module(gleeth@ethereum@address). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/gleeth/ethereum/address.gleam"). -export([to_lowercase/1, checksum/1, is_valid_checksum/1]). -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-55 checksummed Ethereum addresses.\n" "\n" " Ethereum addresses are 20-byte hex strings. EIP-55 defines a mixed-case\n" " encoding that serves as a checksum: each hex character is uppercased if\n" " the corresponding nibble of the keccak256 hash of the lowercase address\n" " is >= 8.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " address.checksum(\"0xd8da6bf26964af9d7eed9e03e53415d37aa96045\")\n" " // -> Ok(\"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\")\n" " ```\n" ). -file("src/gleeth/ethereum/address.gleam", 55). ?DOC(" Convert a potentially checksummed address to lowercase with 0x prefix.\n"). -spec to_lowercase(binary()) -> {ok, binary()} | {error, binary()}. to_lowercase(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 -> {ok, <<"0x"/utf8, (string:lowercase(Cleaned))/binary>>}; _ -> {error, <<"Address must be 40 hex characters"/utf8>>} end. -file("src/gleeth/ethereum/address.gleam", 85). -spec is_letter(binary()) -> boolean(). is_letter(Char) -> case Char of <<"a"/utf8>> -> true; <<"b"/utf8>> -> true; <<"c"/utf8>> -> true; <<"d"/utf8>> -> true; <<"e"/utf8>> -> true; <<"f"/utf8>> -> true; _ -> false end. -file("src/gleeth/ethereum/address.gleam", 92). -spec nibble_value(binary()) -> integer(). nibble_value(Char) -> case Char of <<"0"/utf8>> -> 0; <<"1"/utf8>> -> 1; <<"2"/utf8>> -> 2; <<"3"/utf8>> -> 3; <<"4"/utf8>> -> 4; <<"5"/utf8>> -> 5; <<"6"/utf8>> -> 6; <<"7"/utf8>> -> 7; <<"8"/utf8>> -> 8; <<"9"/utf8>> -> 9; <<"a"/utf8>> -> 10; <<"b"/utf8>> -> 11; <<"c"/utf8>> -> 12; <<"d"/utf8>> -> 13; <<"e"/utf8>> -> 14; <<"f"/utf8>> -> 15; _ -> 0 end. -file("src/gleeth/ethereum/address.gleam", 66). -spec apply_checksum(binary()) -> binary(). apply_checksum(Lower_hex) -> Hash = gleeth@crypto@keccak:keccak256_binary( gleam_stdlib:identity(Lower_hex) ), Hash_hex = string:lowercase(gleam_stdlib:base16_encode(Hash)), Addr_chars = gleam@string:to_graphemes(Lower_hex), Hash_chars = gleam@string:to_graphemes(Hash_hex), _pipe = gleam@list:map2( Addr_chars, Hash_chars, fun(Addr_char, Hash_char) -> case is_letter(Addr_char) of false -> Addr_char; true -> case nibble_value(Hash_char) >= 8 of true -> string:uppercase(Addr_char); false -> Addr_char end end end ), erlang:list_to_binary(_pipe). -file("src/gleeth/ethereum/address.gleam", 21). ?DOC(" Produce the EIP-55 checksummed form of an Ethereum address.\n"). -spec checksum(binary()) -> {ok, binary()} | {error, binary()}. checksum(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 -> {ok, <<"0x"/utf8, (apply_checksum(string:lowercase(Cleaned)))/binary>>}; _ -> {error, <<"Address must be 40 hex characters"/utf8>>} end. -file("src/gleeth/ethereum/address.gleam", 35). ?DOC( " Check whether a mixed-case address has a valid EIP-55 checksum.\n" " All-lowercase and all-uppercase addresses are considered valid\n" " (no checksum applied).\n" ). -spec is_valid_checksum(binary()) -> boolean(). is_valid_checksum(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 -> Lower = string:lowercase(Cleaned), Upper = string:uppercase(Cleaned), case (Cleaned =:= Lower) orelse (Cleaned =:= Upper) of true -> true; false -> apply_checksum(Lower) =:= Cleaned end; _ -> false end.