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@multicall.erl
Raw

src/gleeth@multicall.erl

-module(gleeth@multicall).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/multicall.gleam").
-export([new/0, add/3, try_add/3, execute_at/3, execute/2]).
-export_type([multicall/0, call/0, call_result/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(
" Multicall3 batching for efficient contract reads.\n"
"\n"
" Batches multiple contract read calls into a single `eth_call` using the\n"
" canonical Multicall3 contract (deployed at the same address on all major\n"
" chains). This reduces RPC round trips when reading state from multiple\n"
" contracts.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" // Read 3 balances in a single RPC call\n"
" let assert Ok(results) =\n"
" multicall.new()\n"
" |> multicall.add(usdc_address, balance_of_calldata_1)\n"
" |> multicall.add(usdc_address, balance_of_calldata_2)\n"
" |> multicall.add(dai_address, balance_of_calldata_3)\n"
" |> multicall.execute(provider)\n"
" ```\n"
).
-type multicall() :: {multicall, list(call())}.
-type call() :: {call, binary(), binary(), boolean()}.
-type call_result() :: {call_success, binary()} | {call_failure, binary()}.
-file("src/gleeth/multicall.gleam", 52).
?DOC(" Create an empty multicall batch.\n").
-spec new() -> multicall().
new() ->
{multicall, []}.
-file("src/gleeth/multicall.gleam", 64).
?DOC(
" Add a call that must succeed (reverts the whole batch on failure).\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" multicall.new()\n"
" |> multicall.add(\"0xA0b8...\", \"0x70a08231...\")\n"
" ```\n"
).
-spec add(multicall(), binary(), binary()) -> multicall().
add(Batch, Target, Calldata) ->
{multicall, [{call, Target, Calldata, false} | erlang:element(2, Batch)]}.
-file("src/gleeth/multicall.gleam", 79).
?DOC(
" Add a call that is allowed to fail without reverting the batch.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" multicall.new()\n"
" |> multicall.try_add(\"0xA0b8...\", \"0x70a08231...\")\n"
" ```\n"
).
-spec try_add(multicall(), binary(), binary()) -> multicall().
try_add(Batch, Target, Calldata) ->
{multicall, [{call, Target, Calldata, true} | erlang:element(2, Batch)]}.
-file("src/gleeth/multicall.gleam", 145).
-spec encode_aggregate3(list(call())) -> {ok, binary()} |
{error, gleeth@rpc@types:gleeth_error()}.
encode_aggregate3(Calls) ->
Call_values = gleam@list:map(
Calls,
fun(C) ->
Calldata_bytes@1 = case gleeth@utils@hex:decode(
erlang:element(3, C)
) of
{ok, Calldata_bytes} -> Calldata_bytes;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gleeth/multicall"/utf8>>,
function => <<"encode_aggregate3"/utf8>>,
line => 150,
value => _assert_fail,
start => 4254,
'end' => 4308,
pattern_start => 4265,
pattern_end => 4283})
end,
{tuple_value,
[{address_value, erlang:element(2, C)},
{bool_value, erlang:element(4, C)},
{bytes_value, Calldata_bytes@1}]}
end
),
Call_type = {tuple, [address, bool, bytes]},
Array_type = {array, Call_type},
case gleeth@ethereum@abi@encode:encode(
[{Array_type, {array_value, Call_values}}]
) of
{ok, Encoded} ->
Encoded_hex = string:lowercase(gleam_stdlib:base16_encode(Encoded)),
{ok, <<"0x82ad56cb"/utf8, Encoded_hex/binary>>};
{error, Err} ->
{error, {abi_err, Err}}
end.
-file("src/gleeth/multicall.gleam", 176).
-spec decode_aggregate3_result(binary()) -> {ok, list(call_result())} |
{error, gleeth@rpc@types:gleeth_error()}.
decode_aggregate3_result(Result_hex) ->
case gleeth@utils@hex:decode(Result_hex) of
{ok, Bytes} ->
Result_type = {array, {tuple, [bool, bytes]}},
case gleeth@ethereum@abi@decode:decode([Result_type], Bytes) of
{ok, [{array_value, Items}]} ->
{ok, gleam@list:map(Items, fun(Item) -> case Item of
{tuple_value,
[{bool_value, Success},
{bytes_value, Data}]} ->
Data_hex = <<"0x"/utf8,
(string:lowercase(
gleam_stdlib:base16_encode(Data)
))/binary>>,
case Success of
true ->
{call_success, Data_hex};
false ->
{call_failure, Data_hex}
end;
_ ->
{call_failure, <<"0x"/utf8>>}
end end)};
{ok, _} ->
{error,
{parse_error,
<<"Unexpected multicall result shape"/utf8>>}};
{error, Err} ->
{error, {abi_err, Err}}
end;
{error, _} ->
{error, {parse_error, <<"Invalid hex in multicall result"/utf8>>}}
end.
-file("src/gleeth/multicall.gleam", 119).
?DOC(
" Execute using a custom Multicall3 address (for chains where it's deployed\n"
" at a non-standard address).\n"
).
-spec execute_at(multicall(), gleeth@provider:provider(), binary()) -> {ok,
list(call_result())} |
{error, gleeth@rpc@types:gleeth_error()}.
execute_at(Batch, Provider, Address) ->
Calls = lists:reverse(erlang:element(2, Batch)),
case Calls of
[] ->
{ok, []};
_ ->
gleam@result:'try'(
encode_aggregate3(Calls),
fun(Calldata) ->
gleam@result:'try'(
gleeth@rpc@methods:call_contract(
Provider,
Address,
Calldata
),
fun(Result_hex) ->
decode_aggregate3_result(Result_hex)
end
)
end
)
end.
-file("src/gleeth/multicall.gleam", 98).
?DOC(
" Execute the multicall batch using `aggregate3`.\n"
" Returns one `CallResult` per call in the order they were added.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(results) =\n"
" multicall.new()\n"
" |> multicall.add(usdc, calldata1)\n"
" |> multicall.add(usdc, calldata2)\n"
" |> multicall.execute(provider)\n"
" ```\n"
).
-spec execute(multicall(), gleeth@provider:provider()) -> {ok,
list(call_result())} |
{error, gleeth@rpc@types:gleeth_error()}.
execute(Batch, Provider) ->
Calls = lists:reverse(erlang:element(2, Batch)),
case Calls of
[] ->
{ok, []};
_ ->
gleam@result:'try'(
encode_aggregate3(Calls),
fun(Calldata) ->
gleam@result:'try'(
gleeth@rpc@methods:call_contract(
Provider,
<<"0xcA11bde05977b3631167028862bE2a173976CA11"/utf8>>,
Calldata
),
fun(Result_hex) ->
decode_aggregate3_result(Result_hex)
end
)
end
)
end.