-module(gleeth@contract). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/gleeth/contract.gleam"). -export([at/3, send/6, send_raw/6, call/3, call_raw/3]). -export_type([contract/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( " High-level contract interaction.\n" "\n" " Binds a provider, address, and parsed ABI together so you can call\n" " contract functions without manually encoding calldata or decoding results.\n" "\n" " There are two calling styles: typed and string-coerced. Typed calls use\n" " explicit `AbiValue` constructors; string-coerced calls (`call_raw`/`send_raw`)\n" " accept plain strings and auto-coerce them based on the ABI.\n" "\n" " ## Typed calls\n" "\n" " ```gleam\n" " let assert Ok(abi) = json.parse_abi(erc20_abi_json)\n" " let usdc = contract.at(provider, \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", abi)\n" "\n" " // Read-only call with explicit ABI value types\n" " let assert Ok(values) = contract.call(usdc, \"balanceOf\", [\n" " types.AddressVal(\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\"),\n" " ])\n" "\n" " // Write call (sends a transaction)\n" " let assert Ok(tx_hash) = contract.send(usdc, wallet, \"transfer\", [\n" " types.AddressVal(\"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\"),\n" " types.UintValue(1_000_000),\n" " ], \"0x100000\", chain_id)\n" " ```\n" "\n" " ## String-coerced calls\n" "\n" " The `call_raw`/`send_raw` variants accept plain strings and coerce them\n" " to the correct ABI types automatically:\n" "\n" " ```gleam\n" " let usdc = contract.at(provider, \"0xA0b8...eB48\", abi)\n" "\n" " // Same balanceOf call - just pass the address as a string\n" " let assert Ok(values) = contract.call_raw(usdc, \"balanceOf\", [\n" " \"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\",\n" " ])\n" "\n" " // Same transfer - amounts can be decimal strings or hex\n" " let assert Ok(tx_hash) = contract.send_raw(usdc, wallet, \"transfer\", [\n" " \"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\",\n" " \"1000000\",\n" " ], \"0x100000\", 1)\n" " ```\n" ). -type contract() :: {contract, gleeth@provider:provider(), binary(), list(gleeth@ethereum@abi@json:abi_entry())}. -file("src/gleeth/contract.gleam", 77). ?DOC( " Create a contract instance bound to a provider, address, and parsed ABI.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let assert Ok(abi) = json.parse_abi(erc20_abi_json)\n" " let usdc = contract.at(provider, \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", abi)\n" " ```\n" ). -spec at( gleeth@provider:provider(), binary(), list(gleeth@ethereum@abi@json:abi_entry()) ) -> contract(). at(Provider, Address, Abi) -> {contract, Provider, Address, Abi}. -file("src/gleeth/contract.gleam", 363). -spec parse_int_value(binary()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@rpc@types:gleeth_error()}. parse_int_value(Value) -> case gleam_stdlib:parse_int(Value) of {ok, N} -> {ok, {uint_value, N}}; {error, _} -> case gleeth@utils@hex:to_int(Value) of {ok, N@1} -> {ok, {uint_value, N@1}}; {error, _} -> {error, {parse_error, <<"Cannot parse integer: "/utf8, Value/binary>>}} end end. -file("src/gleeth/contract.gleam", 318). -spec coerce_value(gleeth@ethereum@abi@types:abi_type(), binary()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@rpc@types:gleeth_error()}. coerce_value(Type_, Value) -> case Type_ of address -> {ok, {address_value, Value}}; bool -> case string:lowercase(Value) of <<"true"/utf8>> -> {ok, {bool_value, true}}; <<"1"/utf8>> -> {ok, {bool_value, true}}; <<"false"/utf8>> -> {ok, {bool_value, false}}; <<"0"/utf8>> -> {ok, {bool_value, false}}; _ -> {error, {parse_error, <<"Cannot parse bool: "/utf8, Value/binary>>}} end; string -> {ok, {string_value, Value}}; {uint, _} -> parse_int_value(Value); {int, _} -> parse_int_value(Value); {fixed_bytes, Size} -> case gleeth@utils@hex:decode(Value) of {ok, Bytes} -> Pad_size = Size - erlang:byte_size(Bytes), case Pad_size >= 0 of true -> Padding = gleeth@utils@hex:make_zeros(Pad_size), {ok, {fixed_bytes_value, gleam_stdlib:bit_array_concat( [Bytes, Padding] )}}; false -> {error, {parse_error, <<"bytes value too long"/utf8>>}} end; {error, _} -> {error, {parse_error, <<"Invalid hex for bytes: "/utf8, Value/binary>>}} end; bytes -> case gleeth@utils@hex:decode(Value) of {ok, Bytes@1} -> {ok, {bytes_value, Bytes@1}}; {error, _} -> {error, {parse_error, <<"Invalid hex for bytes: "/utf8, Value/binary>>}} end; _ -> {error, {parse_error, <<"Unsupported type for string coercion: "/utf8, (gleeth@ethereum@abi@types:to_string(Type_))/binary>>}} end. -file("src/gleeth/contract.gleam", 297). ?DOC(" Coerce string arguments to ABI values based on the expected types.\n"). -spec coerce_args(list(gleeth@ethereum@abi@types:abi_type()), list(binary())) -> {ok, list(gleeth@ethereum@abi@types:abi_value())} | {error, gleeth@rpc@types:gleeth_error()}. coerce_args(Types, Args) -> case erlang:length(Types) =:= erlang:length(Args) of false -> {error, {parse_error, <<<<<<"Expected "/utf8, (erlang:integer_to_binary(erlang:length(Types)))/binary>>/binary, " arguments, got "/utf8>>/binary, (erlang:integer_to_binary(erlang:length(Args)))/binary>>}}; true -> _pipe = gleam@list:zip(Types, Args), gleam@list:try_map( _pipe, fun(Pair) -> {Type_, Value} = Pair, coerce_value(Type_, Value) end ) end. -file("src/gleeth/contract.gleam", 377). -spec find_function_types(contract(), binary()) -> {ok, {list(gleeth@ethereum@abi@types:abi_type()), list(gleeth@ethereum@abi@types:abi_type())}} | {error, gleeth@rpc@types:gleeth_error()}. find_function_types(Contract, Name) -> case gleeth@ethereum@abi@json:find_function( erlang:element(4, Contract), Name ) of {ok, {function_entry, _, Inputs, Outputs, _}} -> Input_types = gleam@list:map( Inputs, fun(P) -> erlang:element(3, P) end ), Output_types = gleam@list:map( Outputs, fun(P@1) -> erlang:element(3, P@1) end ), {ok, {Input_types, Output_types}}; {ok, _} -> {error, {parse_error, <<"Expected function entry for: "/utf8, Name/binary>>}}; {error, Err} -> {error, {abi_err, Err}} end. -file("src/gleeth/contract.gleam", 396). -spec encode_calldata( binary(), list({gleeth@ethereum@abi@types:abi_type(), gleeth@ethereum@abi@types:abi_value()}) ) -> {ok, binary()} | {error, gleeth@rpc@types:gleeth_error()}. encode_calldata(Function_name, Params) -> case gleeth@ethereum@abi@encode:encode_call(Function_name, Params) of {ok, Bytes} -> {ok, <<"0x"/utf8, (string:lowercase(gleam_stdlib:base16_encode(Bytes)))/binary>>}; {error, Err} -> {error, {abi_err, Err}} end. -file("src/gleeth/contract.gleam", 140). ?DOC( " Send a write transaction to the contract.\n" " Encodes the arguments, signs, broadcasts, and returns the transaction hash.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let assert Ok(abi) = json.parse_abi(erc20_abi_json)\n" " let usdc = contract.at(provider, \"0xA0b8...eB48\", abi)\n" "\n" " // Transfer 1,000,000 units (1 USDC) to another address\n" " let assert Ok(tx_hash) =\n" " contract.send(usdc, wallet, \"transfer\", [\n" " types.AddressValue(\"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\"),\n" " types.UintValue(1_000_000),\n" " ], \"0x100000\", 1)\n" " ```\n" ). -spec send( contract(), gleeth@crypto@wallet:wallet(), binary(), list(gleeth@ethereum@abi@types:abi_value()), binary(), integer() ) -> {ok, binary()} | {error, gleeth@rpc@types:gleeth_error()}. send(Contract, W, Function_name, Args, Gas_limit, Chain_id) -> gleam@result:'try'( find_function_types(Contract, Function_name), fun(_use0) -> {Input_types, _} = _use0, Params = gleam@list:zip(Input_types, Args), gleam@result:'try'( encode_calldata(Function_name, Params), fun(Calldata) -> Sender = gleeth@crypto@wallet:get_address(W), gleam@result:'try'( gleeth@rpc@methods:get_transaction_count( erlang:element(2, Contract), Sender, <<"pending"/utf8>> ), fun(Nonce) -> gleam@result:'try'( gleeth@rpc@methods:get_gas_price( erlang:element(2, Contract) ), fun(Gas_price) -> gleam@result:'try'( begin _pipe = gleeth@crypto@transaction:create_legacy_transaction( erlang:element(3, Contract), <<"0x0"/utf8>>, Gas_limit, Gas_price, Nonce, Calldata, Chain_id ), gleam@result:map_error( _pipe, fun(Field@0) -> {transaction_err, Field@0} end ) end, fun(Tx) -> gleam@result:'try'( begin _pipe@1 = gleeth@crypto@transaction:sign_transaction( Tx, W ), gleam@result:map_error( _pipe@1, fun(Field@0) -> {transaction_err, Field@0} end ) end, fun(Signed) -> gleeth@rpc@methods:send_raw_transaction( erlang:element( 2, Contract ), erlang:element( 12, Signed ) ) end ) end ) end ) end ) end ) end ). -file("src/gleeth/contract.gleam", 248). ?DOC( " Send a write transaction using string arguments that are auto-coerced.\n" "\n" " Like `send`, but accepts plain strings instead of typed `AbiValue`\n" " constructors. Addresses are passed as hex strings, integers as decimal\n" " strings or hex, booleans as \"true\"/\"false\".\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let usdc = contract.at(provider, \"0xA0b8...eB48\", abi)\n" "\n" " // Transfer 1 USDC - just pass strings\n" " let assert Ok(tx_hash) =\n" " contract.send_raw(usdc, wallet, \"transfer\", [\n" " \"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\",\n" " \"1000000\",\n" " ], \"0x100000\", 1)\n" "\n" " // Approve a spender for a given amount\n" " let assert Ok(tx_hash) =\n" " contract.send_raw(usdc, wallet, \"approve\", [\n" " \"0xSpenderAddress...\",\n" " \"5000000\",\n" " ], \"0x100000\", 1)\n" " ```\n" ). -spec send_raw( contract(), gleeth@crypto@wallet:wallet(), binary(), list(binary()), binary(), integer() ) -> {ok, binary()} | {error, gleeth@rpc@types:gleeth_error()}. send_raw(Contract, W, Function_name, Args, Gas_limit, Chain_id) -> gleam@result:'try'( find_function_types(Contract, Function_name), fun(_use0) -> {Input_types, _} = _use0, gleam@result:'try'( coerce_args(Input_types, Args), fun(Abi_args) -> Params = gleam@list:zip(Input_types, Abi_args), gleam@result:'try'( encode_calldata(Function_name, Params), fun(Calldata) -> Sender = gleeth@crypto@wallet:get_address(W), gleam@result:'try'( gleeth@rpc@methods:get_transaction_count( erlang:element(2, Contract), Sender, <<"pending"/utf8>> ), fun(Nonce) -> gleam@result:'try'( gleeth@rpc@methods:get_gas_price( erlang:element(2, Contract) ), fun(Gas_price) -> gleam@result:'try'( begin _pipe = gleeth@crypto@transaction:create_legacy_transaction( erlang:element( 3, Contract ), <<"0x0"/utf8>>, Gas_limit, Gas_price, Nonce, Calldata, Chain_id ), gleam@result:map_error( _pipe, fun(Field@0) -> {transaction_err, Field@0} end ) end, fun(Tx) -> gleam@result:'try'( begin _pipe@1 = gleeth@crypto@transaction:sign_transaction( Tx, W ), gleam@result:map_error( _pipe@1, fun(Field@0) -> {transaction_err, Field@0} end ) end, fun(Signed) -> gleeth@rpc@methods:send_raw_transaction( erlang:element( 2, Contract ), erlang:element( 12, Signed ) ) end ) end ) end ) end ) end ) end ) end ). -file("src/gleeth/contract.gleam", 421). -spec gleeth_hex_decode(binary()) -> {ok, bitstring()} | {error, nil}. gleeth_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/contract.gleam", 406). -spec decode_output(list(gleeth@ethereum@abi@types:abi_type()), binary()) -> {ok, list(gleeth@ethereum@abi@types:abi_value())} | {error, gleeth@ethereum@abi@types:abi_error()}. decode_output(Output_types, Hex_data) -> case Output_types of [] -> {ok, []}; Types -> case gleeth_hex_decode(Hex_data) of {ok, Bytes} -> gleeth@ethereum@abi@decode:decode(Types, Bytes); {error, _} -> {error, {decode_error, <<"Invalid hex in response"/utf8>>}} end end. -file("src/gleeth/contract.gleam", 100). ?DOC( " Call a read-only function on the contract.\n" " Encodes the arguments, calls `eth_call`, and decodes the return values.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let assert Ok(abi) = json.parse_abi(erc20_abi_json)\n" " let usdc = contract.at(provider, \"0xA0b8...eB48\", abi)\n" "\n" " // Read the balance for an address\n" " let assert Ok([types.UintValue(balance)]) =\n" " contract.call(usdc, \"balanceOf\", [\n" " types.AddressValue(\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\"),\n" " ])\n" " ```\n" ). -spec call(contract(), binary(), list(gleeth@ethereum@abi@types:abi_value())) -> {ok, list(gleeth@ethereum@abi@types:abi_value())} | {error, gleeth@rpc@types:gleeth_error()}. call(Contract, Function_name, Args) -> gleam@result:'try'( find_function_types(Contract, Function_name), fun(_use0) -> {Input_types, Output_types} = _use0, Params = gleam@list:zip(Input_types, Args), gleam@result:'try'( encode_calldata(Function_name, Params), fun(Calldata) -> gleam@result:'try'( gleeth@rpc@methods:call_contract( erlang:element(2, Contract), erlang:element(3, Contract), Calldata ), fun(Result_hex) -> gleam@result:'try'( begin _pipe = decode_output( Output_types, Result_hex ), gleam@result:map_error( _pipe, fun(Field@0) -> {abi_err, Field@0} end ) end, fun(Decoded) -> {ok, Decoded} end ) end ) end ) end ). -file("src/gleeth/contract.gleam", 198). ?DOC( " Call a read-only function using string arguments that are auto-coerced\n" " to the correct ABI types based on the function's ABI definition.\n" "\n" " Addresses are passed as hex strings, integers as decimal strings or hex,\n" " booleans as \"true\"/\"false\".\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " // Instead of: contract.call(c, \"balanceOf\", [AddressVal(\"0xf39f...\")])\n" " contract.call_raw(c, \"balanceOf\", [\"0xf39f...\"])\n" "\n" " // Multiple args\n" " contract.call_raw(c, \"allowance\", [\"0xf39f...\", \"0x7099...\"])\n" " ```\n" ). -spec call_raw(contract(), binary(), list(binary())) -> {ok, list(gleeth@ethereum@abi@types:abi_value())} | {error, gleeth@rpc@types:gleeth_error()}. call_raw(Contract, Function_name, Args) -> gleam@result:'try'( find_function_types(Contract, Function_name), fun(_use0) -> {Input_types, Output_types} = _use0, gleam@result:'try'( coerce_args(Input_types, Args), fun(Abi_args) -> Params = gleam@list:zip(Input_types, Abi_args), gleam@result:'try'( encode_calldata(Function_name, Params), fun(Calldata) -> gleam@result:'try'( gleeth@rpc@methods:call_contract( erlang:element(2, Contract), erlang:element(3, Contract), Calldata ), fun(Result_hex) -> gleam@result:'try'( begin _pipe = decode_output( Output_types, Result_hex ), gleam@result:map_error( _pipe, fun(Field@0) -> {abi_err, Field@0} end ) end, fun(Decoded) -> {ok, Decoded} end ) end ) end ) end ) end ).