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@permit.erl
Raw

src/gleeth@permit.erl

-module(gleeth@permit).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/permit.gleam").
-export([sign_with_domain/7, sign/6]).
-export_type([permit_signature/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-2612 permit signing helper.\n"
"\n"
" Signs a token permit off-chain using EIP-712, returning the v, r, s\n"
" components ready to submit to a token's `permit()` function on-chain.\n"
" Automatically fetches the token's EIP-712 domain (name, version) and\n"
" the owner's current nonce via RPC.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(sig) = permit.sign(\n"
" provider,\n"
" \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", // USDC\n"
" \"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\", // spender\n"
" 1_000_000, // amount\n"
" 1_700_000_000, // deadline\n"
" wallet,\n"
" )\n"
" // sig.v, sig.r, sig.s ready for on-chain permit() call\n"
" ```\n"
).
-type permit_signature() :: {permit_signature, integer(), binary(), binary()}.
-file("src/gleeth/permit.gleam", 104).
?DOC(
" Sign a permit with an explicit domain. Useful when you already know\n"
" the token's EIP-712 domain parameters.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let domain = eip712.domain()\n"
" |> eip712.domain_name(\"USD Coin\")\n"
" |> eip712.domain_version(\"2\")\n"
" |> eip712.domain_chain_id(1)\n"
" |> eip712.domain_verifying_contract(usdc_address)\n"
"\n"
" let assert Ok(sig) = permit.sign_with_domain(\n"
" domain, owner, spender, amount, nonce, deadline, wallet,\n"
" )\n"
" ```\n"
).
-spec sign_with_domain(
gleeth@eip712:domain(),
binary(),
binary(),
integer(),
integer(),
integer(),
gleeth@crypto@wallet:wallet()
) -> {ok, permit_signature()} | {error, gleeth@rpc@types:gleeth_error()}.
sign_with_domain(Domain, Owner, Spender, Amount, Nonce, Deadline, W) ->
Types = maps:from_list(
[{<<"Permit"/utf8>>,
[gleeth@eip712:field(<<"owner"/utf8>>, <<"address"/utf8>>),
gleeth@eip712:field(<<"spender"/utf8>>, <<"address"/utf8>>),
gleeth@eip712:field(<<"value"/utf8>>, <<"uint256"/utf8>>),
gleeth@eip712:field(<<"nonce"/utf8>>, <<"uint256"/utf8>>),
gleeth@eip712:field(<<"deadline"/utf8>>, <<"uint256"/utf8>>)]}]
),
Message = maps:from_list(
[{<<"owner"/utf8>>, gleeth@eip712:address_val(Owner)},
{<<"spender"/utf8>>, gleeth@eip712:address_val(Spender)},
{<<"value"/utf8>>, gleeth@eip712:int_val(Amount)},
{<<"nonce"/utf8>>, gleeth@eip712:int_val(Nonce)},
{<<"deadline"/utf8>>, gleeth@eip712:int_val(Deadline)}]
),
Data = gleeth@eip712:typed_data(Types, <<"Permit"/utf8>>, Domain, Message),
case gleeth@eip712:sign_typed_data(Data, W) of
{ok, Signature} ->
{V, R, S} = gleeth@crypto@secp256k1:signature_to_vrs(Signature),
{ok, {permit_signature, V, R, S}};
{error, Msg} ->
{error,
{parse_error, <<"Permit signing failed: "/utf8, Msg/binary>>}}
end.
-file("src/gleeth/permit.gleam", 183).
?DOC(" Call a function with one address arg that returns a uint256.\n").
-spec call_uint(gleeth@provider:provider(), binary(), binary(), binary()) -> {ok,
binary()} |
{error, gleeth@rpc@types:gleeth_error()}.
call_uint(Provider, Address, Selector, Arg_address) ->
Clean_addr = gleeth@utils@hex:strip_prefix(Arg_address),
Padded = <<(gleam@string:repeat(
<<"0"/utf8>>,
64 - string:length(Clean_addr)
))/binary,
Clean_addr/binary>>,
Calldata = <<Selector/binary, (string:lowercase(Padded))/binary>>,
gleeth@rpc@methods:call_contract(Provider, Address, Calldata).
-file("src/gleeth/permit.gleam", 196).
-spec hex_decode(binary()) -> {ok, bitstring()} | {error, nil}.
hex_decode(Hex_string) ->
Clean = case gleam_stdlib:string_starts_with(Hex_string, <<"0x"/utf8>>) of
true ->
gleam@string:drop_start(Hex_string, 2);
false ->
Hex_string
end,
gleam_stdlib:base16_decode(string:uppercase(Clean)).
-file("src/gleeth/permit.gleam", 208).
-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/permit.gleam", 204).
-spec bytes_to_int(bitstring()) -> integer().
bytes_to_int(Data) ->
do_bytes_to_int(Data, 0).
-file("src/gleeth/permit.gleam", 149).
?DOC(" Call a function that returns a string (like name()).\n").
-spec call_string(gleeth@provider:provider(), binary(), binary()) -> {ok,
binary()} |
{error, gleeth@rpc@types:gleeth_error()}.
call_string(Provider, Address, Selector) ->
gleam@result:'try'(
gleeth@rpc@methods:call_contract(Provider, Address, Selector),
fun(Result_hex) -> case hex_decode(Result_hex) of
{ok, Bytes} ->
case erlang:byte_size(Bytes) >= 64 of
true ->
Offset_bytes@1 = case gleam_stdlib:bit_array_slice(
Bytes,
0,
32
) of
{ok, Offset_bytes} -> Offset_bytes;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/permit"/utf8>>,
function => <<"call_string"/utf8>>,
line => 165,
value => _assert_fail,
start => 4885,
'end' => 4944,
pattern_start => 4896,
pattern_end => 4912})
end,
Offset = bytes_to_int(Offset_bytes@1),
Len_bytes@1 = case gleam_stdlib:bit_array_slice(
Bytes,
Offset,
32
) of
{ok, Len_bytes} -> Len_bytes;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/permit"/utf8>>,
function => <<"call_string"/utf8>>,
line => 167,
value => _assert_fail@1,
start => 5005,
'end' => 5066,
pattern_start => 5016,
pattern_end => 5029})
end,
Len = bytes_to_int(Len_bytes@1),
Str_bytes@1 = case gleam_stdlib:bit_array_slice(
Bytes,
Offset + 32,
Len
) of
{ok, Str_bytes} -> Str_bytes;
_assert_fail@2 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/permit"/utf8>>,
function => <<"call_string"/utf8>>,
line => 169,
value => _assert_fail@2,
start => 5121,
'end' => 5188,
pattern_start => 5132,
pattern_end => 5145})
end,
case gleam@bit_array:to_string(Str_bytes@1) of
{ok, S} ->
{ok, S};
{error, _} ->
{ok, <<""/utf8>>}
end;
false ->
{ok, <<""/utf8>>}
end;
{error, _} ->
{ok, <<""/utf8>>}
end end
).
-file("src/gleeth/permit.gleam", 52).
?DOC(
" Sign an EIP-2612 permit for a token.\n"
"\n"
" Fetches the token's name, EIP-712 version (\"1\" or \"2\"), and the owner's\n"
" permit nonce automatically. Constructs the EIP-712 Permit typed data\n"
" and signs it.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(sig) = permit.sign(\n"
" provider, token_address, spender, amount, deadline, wallet,\n"
" )\n"
" ```\n"
).
-spec sign(
gleeth@provider:provider(),
binary(),
binary(),
integer(),
integer(),
gleeth@crypto@wallet:wallet()
) -> {ok, permit_signature()} | {error, gleeth@rpc@types:gleeth_error()}.
sign(Provider, Token_address, Spender, Amount, Deadline, W) ->
Owner = gleeth@crypto@wallet:get_address(W),
gleam@result:'try'(
call_string(Provider, Token_address, <<"0x06fdde03"/utf8>>),
fun(Name) ->
gleam@result:'try'(
call_uint(Provider, Token_address, <<"0x7ecebe00"/utf8>>, Owner),
fun(Nonce_hex) ->
gleam@result:'try'(
gleeth@rpc@methods:get_chain_id(Provider),
fun(Chain_id_hex) ->
Nonce@1 = case gleeth@utils@hex:to_int(Nonce_hex) of
{ok, Nonce} -> Nonce;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/permit"/utf8>>,
function => <<"sign"/utf8>>,
line => 74,
value => _assert_fail,
start => 2131,
'end' => 2175,
pattern_start => 2142,
pattern_end => 2151})
end,
Chain_id@1 = case gleeth@utils@hex:to_int(
Chain_id_hex
) of
{ok, Chain_id} -> Chain_id;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/permit"/utf8>>,
function => <<"sign"/utf8>>,
line => 75,
value => _assert_fail@1,
start => 2178,
'end' => 2228,
pattern_start => 2189,
pattern_end => 2201})
end,
Domain = begin
_pipe = gleeth@eip712:domain(),
_pipe@1 = gleeth@eip712:domain_name(_pipe, Name),
_pipe@2 = gleeth@eip712:domain_version(
_pipe@1,
<<"2"/utf8>>
),
_pipe@3 = gleeth@eip712:domain_chain_id(
_pipe@2,
Chain_id@1
),
gleeth@eip712:domain_verifying_contract(
_pipe@3,
Token_address
)
end,
sign_with_domain(
Domain,
Owner,
Spender,
Amount,
Nonce@1,
Deadline,
W
)
end
)
end
)
end
).