Packages
signet
1.3.11
1.6.1
1.6.0
1.5.0
1.4.2
1.4.1
1.4.0
1.3.12
1.3.11
1.3.10
1.3.9
1.3.8
1.3.7
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.0.0-echo8
1.0.0-echo7
1.0.0-echo6
1.0.0-echo5
1.0.0-echo4
1.0.0-echo3
1.0.0-echo2
1.0.0-echo1
1.0.0-delta8
1.0.0-delta7
1.0.0-delta6
1.0.0-delta5
1.0.0-delta4
1.0.0-delta3
1.0.0-delta2
1.0.0-delta1
1.0.0-charlie9
1.0.0-charlie8
1.0.0-charlie7
1.0.0-charlie6
1.0.0-charlie5
1.0.0-charlie4
1.0.0-charlie3
1.0.0-charlie2
1.0.0-charlie1
1.0.0-beta9
1.0.0-beta8
1.0.0-beta7
1.0.0-beta6
1.0.0-beta5
1.0.0-beta4
1.0.0-beta3
1.0.0-beta2
1.0.0-beta1
1.0.0-alpha9
1.0.0-alpha8
1.0.0-alpha7
1.0.0-alpha6
1.0.0-alpha5
1.0.0-alpha4
1.0.0-alpha3
1.0.0-alpha2
1.0.0-alpha10
1.0.0-alpha1
0.2.0-alpha6
0.2.0-alpha5
0.2.0-alpha4
0.2.0-alpha1
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.1.0-rc7
0.1.0-rc6
0.1.0-rc5
0.1.0-rc4
0.1.0-rc3
0.1.0-rc2
0.1.0-rc1
0.1.0-rc0
Lightweight Ethereum and Solana RPC client for Elixir
Current section
Files
Jump to
Current section
Files
lib/signet/filter.ex
defmodule Signet.Filter do
@moduledoc """
A system to create an Ethereum log filter and have
parsed events passed back to registered processes.
"""
use GenServer
use Signet.Hex
require Logger
alias Signet.RPC
@check_delay 3000
defmodule Log do
defstruct [
:address,
:block_hash,
:block_number,
:data,
:log_index,
:removed,
:topics,
:transaction_hash,
:transaction_index,
:extra_data
]
def deserialize(%{
"address" => address,
"blockHash" => block_hash,
"blockNumber" => block_number,
"data" => data,
"logIndex" => log_index,
"removed" => removed,
"topics" => topics,
"transactionHash" => transaction_hash,
"transactionIndex" => transaction_index
}) do
%__MODULE__{
address: Hex.decode_address!(address),
block_hash: Hex.decode_word!(block_hash),
block_number: Hex.decode_hex_number!(block_number),
data: from_hex!(data),
log_index: Hex.decode_hex_number!(log_index),
removed: removed,
topics: Enum.map(topics, &Hex.decode_word!/1),
transaction_hash: from_hex!(transaction_hash),
transaction_index: Hex.decode_hex_number!(transaction_index)
}
end
end
def start_link(opts \\ []) do
name = Keyword.get(opts, :name, __MODULE__)
address = Keyword.get(opts, :address, nil)
topics = Keyword.get(opts, :topics, [])
events = Keyword.get(opts, :events, [])
rpc_opts = Keyword.get(opts, :rpc_opts, [])
extra_data = Keyword.get(opts, :extra_data, nil)
check_delay = Keyword.get(opts, :check_delay, @check_delay)
decoders =
for event <- events, into: %{} do
function_selector =
case event do
%ABI.FunctionSelector{
types: [%{name: "_topic", type: {:uint, 256}, indexed: true} | rest_types]
} ->
%{event | types: rest_types}
%ABI.FunctionSelector{} ->
event
event_abi when is_binary(event_abi) ->
ABI.FunctionSelector.decode(event_abi)
end
{ABI.Event.event_signature(function_selector),
fn event_topics, event_data ->
ABI.Event.decode_event(event_data, event_topics, function_selector)
end}
end
all_topics = Enum.map(decoders, fn {topic, _} -> topic end) ++ topics
GenServer.start_link(
__MODULE__,
%{
address: address,
topics: all_topics,
name: name,
listeners: [],
decoders: decoders,
check_delay: check_delay,
rpc_opts: rpc_opts,
extra_data: extra_data
},
name: name
)
end
defp set_filter(state = %{address: nil, topics: topics, rpc_opts: rpc_opts}) do
{:ok, filter_id} =
RPC.send_rpc(
"eth_newFilter",
[
%{
"topics" => Enum.map(topics, &Signet.Hex.encode_hex/1)
}
],
rpc_opts
)
Map.put(state, :filter_id, filter_id)
end
defp set_filter(state = %{address: address, topics: topics, rpc_opts: rpc_opts}) do
{:ok, filter_id} =
RPC.send_rpc(
"eth_newFilter",
[
%{
"address" => Signet.Hex.encode_hex(address),
"topics" => Enum.map(topics, &Signet.Hex.encode_hex/1)
}
],
rpc_opts
)
Map.put(state, :filter_id, filter_id)
end
def init(state = %{check_delay: check_delay}) do
state = set_filter(state)
Process.send_after(self(), :check_filter, check_delay)
{:ok, state}
end
def listen(filter) do
GenServer.cast(filter, {:listen, self()})
end
def handle_cast({:listen, pid}, state = %{listeners: listeners}) do
{:noreply, Map.put(state, :listeners, [pid | listeners])}
end
def handle_info(
:check_filter,
state = %{
filter_id: filter_id,
listeners: listeners,
decoders: decoders,
name: name,
check_delay: check_delay,
rpc_opts: rpc_opts,
extra_data: extra_data
}
) do
Process.send_after(self(), :check_filter, check_delay)
state =
case RPC.send_rpc("eth_getFilterChanges", [filter_id], rpc_opts) do
{:ok, raw_logs} ->
{logs, events} =
raw_logs
|> Enum.map(&Log.deserialize/1)
|> Enum.map(fn log -> %{log | extra_data: extra_data} end)
|> parse_events(decoders)
for listener <- listeners, {event, log} <- events do
send(listener, {:event, event, log})
end
for listener <- listeners, log <- logs do
send(listener, {:log, log})
end
state
{:error, "error -32000: filter not found"} ->
Logger.error(
"[Filter #{name}] Filter expired, restarting... Note: some logs may have been lost."
)
set_filter(state)
{:error, error} ->
Logger.error("[Filter #{name}] Error getting filter changes: #{inspect(error)}")
state
end
{:noreply, state}
end
defp parse_events(logs, decoders) do
events = do_parse_events(logs, decoders, [])
{logs, Enum.reverse(events)}
end
defp do_parse_events([], _, events), do: events
defp do_parse_events([log | rest_logs], decoders, acc_events) do
[topic_0 | _topic_rest] = log.topics
case Map.get(decoders, topic_0) do
nil ->
do_parse_events(rest_logs, decoders, acc_events)
decoder_fn ->
case decoder_fn.(log.topics, log.data) do
{:ok, event_name, event_params} ->
do_parse_events(rest_logs, decoders, [{{event_name, event_params}, log} | acc_events])
{:error, error} ->
Logger.error("Error decoding log: #{error}")
do_parse_events(rest_logs, decoders, acc_events)
end
end
end
end