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