Packages

Ethereum library for Gleam - JSON-RPC client, transaction signing, ABI encoding, and wallet management on the BEAM

Current section

Files

Jump to
gleeth src gleeth@ens.erl
Raw

src/gleeth@ens.erl

-module(gleeth@ens).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/ens.gleam").
-export([namehash/1, decode_abi_string/1, resolve/2, reverse_resolve/2]).
-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(
" ENS (Ethereum Name Service) resolution.\n"
"\n"
" Resolves human-readable `.eth` names to Ethereum addresses (forward)\n"
" and addresses back to names (reverse) by querying the ENS registry\n"
" and resolver contracts on-chain.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" // Forward: name -> address\n"
" let assert Ok(address) = ens.resolve(provider, \"vitalik.eth\")\n"
"\n"
" // Reverse: address -> name\n"
" let assert Ok(name) = ens.reverse_resolve(provider, \"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\")\n"
" ```\n"
).
-file("src/gleeth/ens.gleam", 90).
?DOC(
" Compute the ENS namehash for a domain name.\n"
"\n"
" The namehash algorithm recursively hashes labels:\n"
" `namehash(\"\") = 0x00...00`\n"
" `namehash(\"eth\") = keccak256(namehash(\"\") + keccak256(\"eth\"))`\n"
" `namehash(\"vitalik.eth\") = keccak256(namehash(\"eth\") + keccak256(\"vitalik\"))`\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let hash = ens.namehash(\"vitalik.eth\")\n"
" ```\n"
).
-spec namehash(binary()) -> bitstring().
namehash(Name) ->
case Name of
<<""/utf8>> ->
<<0:256>>;
_ ->
Labels = gleam@string:split(Name, <<"."/utf8>>),
Reversed = lists:reverse(Labels),
gleam@list:fold(
Reversed,
<<0:256>>,
fun(Node, Label) ->
Label_hash = gleeth@crypto@keccak:keccak256_binary(
gleam_stdlib:identity(Label)
),
gleeth@crypto@keccak:keccak256_binary(
gleam_stdlib:bit_array_concat([Node, Label_hash])
)
end
)
end.
-file("src/gleeth/ens.gleam", 162).
?DOC(
" ABI-decode a string from RPC return data.\n"
" Layout: offset(32 bytes) + length(32 bytes at offset) + data.\n"
" Shared between ENS and permit modules.\n"
).
-spec decode_abi_string(binary()) -> {ok, binary()} |
{error, gleeth@rpc@types:gleeth_error()}.
decode_abi_string(Result_hex) ->
gleam@result:'try'(
begin
_pipe = gleeth@utils@hex:decode(Result_hex),
gleam@result:map_error(
_pipe,
fun(_) -> {parse_error, <<"Invalid hex response"/utf8>>} end
)
end,
fun(Bytes) -> case erlang:byte_size(Bytes) >= 64 of
false ->
{error,
{parse_error, <<"ABI string response too short"/utf8>>}};
true ->
gleam@result:'try'(
begin
_pipe@1 = gleam_stdlib:bit_array_slice(Bytes, 0, 32),
gleam@result:map_error(
_pipe@1,
fun(_) ->
{parse_error,
<<"Failed to read string offset"/utf8>>}
end
)
end,
fun(Offset_bytes) ->
Offset = gleeth@utils@hex:bytes_to_int(Offset_bytes),
gleam@result:'try'(
begin
_pipe@2 = gleam_stdlib:bit_array_slice(
Bytes,
Offset,
32
),
gleam@result:map_error(
_pipe@2,
fun(_) ->
{parse_error,
<<"Failed to read string length"/utf8>>}
end
)
end,
fun(Len_bytes) ->
Len = gleeth@utils@hex:bytes_to_int(
Len_bytes
),
case Len > 0 of
false ->
{error,
{parse_error,
<<"Empty string in ABI response"/utf8>>}};
true ->
gleam@result:'try'(
begin
_pipe@3 = gleam_stdlib:bit_array_slice(
Bytes,
Offset + 32,
Len
),
gleam@result:map_error(
_pipe@3,
fun(_) ->
{parse_error,
<<"Failed to read string data"/utf8>>}
end
)
end,
fun(Str_bytes) ->
case gleam@bit_array:to_string(
Str_bytes
) of
{ok, S} ->
{ok, S};
{error, _} ->
{error,
{parse_error,
<<"Invalid UTF-8 in ABI string"/utf8>>}}
end
end
)
end
end
)
end
)
end end
).
-file("src/gleeth/ens.gleam", 207).
?DOC(" Call a function that takes a bytes32 arg and returns a string.\n").
-spec call_string(gleeth@provider:provider(), binary(), binary(), binary()) -> {ok,
binary()} |
{error, gleeth@rpc@types:gleeth_error()}.
call_string(Provider, Contract, Selector, Arg_hex) ->
Arg_clean = gleeth@utils@hex:strip_prefix(Arg_hex),
Padded = gleam@string:pad_start(Arg_clean, 64, <<"0"/utf8>>),
Calldata = <<Selector/binary, Padded/binary>>,
gleam@result:'try'(
gleeth@rpc@methods:call_contract(Provider, Contract, Calldata),
fun(Result_hex) -> decode_abi_string(Result_hex) end
).
-file("src/gleeth/ens.gleam", 226).
?DOC(" Call a function that takes a bytes32 arg and returns an address.\n").
-spec call_address(gleeth@provider:provider(), binary(), binary(), binary()) -> {ok,
binary()} |
{error, gleeth@rpc@types:gleeth_error()}.
call_address(Provider, Contract, Selector, Arg_hex) ->
Arg_clean = gleeth@utils@hex:strip_prefix(Arg_hex),
Padded = gleam@string:pad_start(Arg_clean, 64, <<"0"/utf8>>),
Calldata = <<Selector/binary, Padded/binary>>,
gleam@result:'try'(
gleeth@rpc@methods:call_contract(Provider, Contract, Calldata),
fun(Result_hex) ->
gleam@result:'try'(
begin
_pipe = gleeth@utils@hex:decode(Result_hex),
gleam@result:map_error(
_pipe,
fun(_) ->
{parse_error,
<<"Invalid hex in address response"/utf8>>}
end
)
end,
fun(Bytes) ->
Size = erlang:byte_size(Bytes),
case Size >= 32 of
false ->
{error,
{parse_error,
<<"Address response too short"/utf8>>}};
true ->
gleam@result:'try'(
begin
_pipe@1 = gleam_stdlib:bit_array_slice(
Bytes,
12,
20
),
gleam@result:map_error(
_pipe@1,
fun(_) ->
{parse_error,
<<"Failed to extract address bytes"/utf8>>}
end
)
end,
fun(Addr_bytes) ->
Addr = <<"0x"/utf8,
(string:lowercase(
gleam_stdlib:base16_encode(
Addr_bytes
)
))/binary>>,
{ok, Addr}
end
)
end
end
)
end
).
-file("src/gleeth/ens.gleam", 41).
?DOC(
" Resolve an ENS name to an Ethereum address.\n"
"\n"
" Queries the ENS registry for the resolver, then queries the resolver\n"
" for the address. Returns an error if the name has no resolver or no\n"
" address set.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(addr) = ens.resolve(provider, \"vitalik.eth\")\n"
" ```\n"
).
-spec resolve(gleeth@provider:provider(), binary()) -> {ok, binary()} |
{error, gleeth@rpc@types:gleeth_error()}.
resolve(Provider, Name) ->
Node = namehash(Name),
Node_hex = gleeth@utils@hex:encode(Node),
gleam@result:'try'(
call_address(
Provider,
<<"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"/utf8>>,
<<"0x0178b8bf"/utf8>>,
Node_hex
),
fun(Resolver_address) -> case Resolver_address of
<<""/utf8>> ->
{error,
{parse_error,
<<"No resolver found for: "/utf8, Name/binary>>}};
<<"0x0000000000000000000000000000000000000000"/utf8>> ->
{error,
{parse_error,
<<"No resolver found for: "/utf8, Name/binary>>}};
Resolver ->
gleam@result:'try'(
call_address(
Provider,
Resolver,
<<"0x3b3b57de"/utf8>>,
Node_hex
),
fun(Address) -> case Address of
<<""/utf8>> ->
{error,
{parse_error,
<<"No address set for: "/utf8,
Name/binary>>}};
<<"0x0000000000000000000000000000000000000000"/utf8>> ->
{error,
{parse_error,
<<"No address set for: "/utf8,
Name/binary>>}};
Addr ->
{ok, Addr}
end end
)
end end
).
-file("src/gleeth/ens.gleam", 118).
?DOC(
" Reverse resolve an Ethereum address to its primary ENS name.\n"
"\n"
" Computes the reverse node (`<addr>.addr.reverse`), queries the ENS\n"
" registry for the reverse resolver, then calls `name(bytes32)` on it.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(name) = ens.reverse_resolve(\n"
" provider,\n"
" \"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\",\n"
" )\n"
" // name = \"vitalik.eth\"\n"
" ```\n"
).
-spec reverse_resolve(gleeth@provider:provider(), binary()) -> {ok, binary()} |
{error, gleeth@rpc@types:gleeth_error()}.
reverse_resolve(Provider, Address) ->
Clean_addr = string:lowercase(gleeth@utils@hex:strip_prefix(Address)),
Reverse_name = <<Clean_addr/binary, ".addr.reverse"/utf8>>,
Node = namehash(Reverse_name),
Node_hex = gleeth@utils@hex:encode(Node),
gleam@result:'try'(
call_address(
Provider,
<<"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"/utf8>>,
<<"0x0178b8bf"/utf8>>,
Node_hex
),
fun(Resolver_address) -> case Resolver_address of
<<""/utf8>> ->
{error,
{parse_error,
<<"No reverse resolver for: "/utf8, Address/binary>>}};
<<"0x0000000000000000000000000000000000000000"/utf8>> ->
{error,
{parse_error,
<<"No reverse resolver for: "/utf8, Address/binary>>}};
Resolver ->
gleam@result:'try'(
call_string(
Provider,
Resolver,
<<"0x691f3431"/utf8>>,
Node_hex
),
fun(Name) -> case Name of
<<""/utf8>> ->
{error,
{parse_error,
<<"No reverse record for: "/utf8,
Address/binary>>}};
N ->
{ok, N}
end end
)
end end
).