-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 = <>, 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 <> -> 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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 ).