Current section
Files
Jump to
Current section
Files
src/gleeth@contract.erl
-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", 384).
-spec make_zeros_acc(integer(), bitstring()) -> bitstring().
make_zeros_acc(N, Acc) ->
case N =< 0 of
true ->
Acc;
false ->
make_zeros_acc(N - 1, <<Acc/bitstring, 0:8>>)
end.
-file("src/gleeth/contract.gleam", 377).
-spec make_zeros(integer()) -> bitstring().
make_zeros(N) ->
case N =< 0 of
true ->
<<>>;
false ->
make_zeros_acc(N, <<>>)
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 = 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", 391).
-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", 410).
-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", 435).
-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", 420).
-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
).