-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) -> gleam@result:'try'( gleam@list:try_map( Calls, fun(C) -> case gleeth@utils@hex:decode(erlang:element(3, C)) of {ok, Calldata_bytes} -> {ok, {tuple_value, [{address_value, erlang:element(2, C)}, {bool_value, erlang:element(4, C)}, {bytes_value, Calldata_bytes}]}}; {error, _} -> {error, {parse_error, <<"Invalid calldata hex: "/utf8, (erlang:element(3, C))/binary>>}} end end ), fun(Call_values) -> 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 end ). -file("src/gleeth/multicall.gleam", 183). -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.