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", 110).
?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", 155).
?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) -> gleeth@ens:decode_abi_string(Result_hex) end
).
-file("src/gleeth/permit.gleam", 169).
?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", 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) ->
gleam@result:'try'(
begin
_pipe = gleeth@utils@hex:to_int(Nonce_hex),
gleam@result:map_error(
_pipe,
fun(_) ->
{parse_error,
<<"Invalid nonce hex"/utf8>>}
end
)
end,
fun(Nonce) ->
gleam@result:'try'(
begin
_pipe@1 = gleeth@utils@hex:to_int(
Chain_id_hex
),
gleam@result:map_error(
_pipe@1,
fun(_) ->
{parse_error,
<<"Invalid chain ID hex"/utf8>>}
end
)
end,
fun(Chain_id) ->
Domain = begin
_pipe@2 = gleeth@eip712:domain(),
_pipe@3 = gleeth@eip712:domain_name(
_pipe@2,
Name
),
_pipe@4 = gleeth@eip712:domain_version(
_pipe@3,
<<"2"/utf8>>
),
_pipe@5 = gleeth@eip712:domain_chain_id(
_pipe@4,
Chain_id
),
gleeth@eip712:domain_verifying_contract(
_pipe@5,
Token_address
)
end,
sign_with_domain(
Domain,
Owner,
Spender,
Amount,
Nonce,
Deadline,
W
)
end
)
end
)
end
)
end
)
end
).