Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule BlockScoutWeb.SignetChannel do
@moduledoc """
Establishes pub/sub channel for live updates of Signet related events.
"""
use BlockScoutWeb, :channel

def join("signet:new_order", _params, socket) do
{:ok, %{}, socket}
end

def join("signet:new_fill", _params, socket) do
{:ok, %{}, socket}
end

def join("signet:order_updates", _params, socket) do
{:ok, %{}, socket}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule BlockScoutWeb.V2.UserSocket do
case @chain_type do
:arbitrum -> channel("arbitrum:*", BlockScoutWeb.ArbitrumChannel)
:optimism -> channel("optimism:*", BlockScoutWeb.OptimismChannel)
:signet -> channel("signet:*", BlockScoutWeb.SignetChannel)
_ -> nil
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
defmodule BlockScoutWeb.API.V2.SignetController do
@moduledoc """
Controller for Signet order and fill API endpoints.

Provides REST API v2 endpoints for querying Signet cross-chain orders and fills.
"""
use BlockScoutWeb, :controller

import BlockScoutWeb.Chain,
only: [
next_page_params: 5,
paging_options: 1,
split_list_by_page: 1
]

alias Explorer.Chain.Hash
alias Explorer.Chain.Signet.{Fill, Order}
alias Explorer.GraphQL.Signet, as: SignetQueries
alias Explorer.{PagingOptions, Repo}

import Ecto.Query

action_fallback(BlockScoutWeb.API.V2.FallbackController)

@doc """
GET /api/v2/signet/orders

Lists Signet orders with pagination and optional block range filters.

## Query Parameters
- block_number_gte: minimum block number (optional)
- block_number_lte: maximum block number (optional)
- Standard pagination params (page, page_size)
"""
@spec orders(Plug.Conn.t(), map()) :: Plug.Conn.t()
def orders(conn, params) do
options = paging_options(params)

filter_args = %{}
|> maybe_add_filter(:block_number_gte, params["block_number_gte"])
|> maybe_add_filter(:block_number_lte, params["block_number_lte"])

{orders, next_page} =
filter_args
|> SignetQueries.orders_query()
|> paginate(options)
|> Repo.all()
|> split_list_by_page()

next_page_params =
next_page_params(
next_page,
orders,
params,
false,
fn %Order{block_number: block_number, log_index: log_index} ->
%{"block_number" => block_number, "log_index" => log_index}
end
)

conn
|> put_status(200)
|> render(:signet_orders, %{
orders: orders,
next_page_params: next_page_params
})
end

@doc """
GET /api/v2/signet/orders/count

Returns the total count of Signet orders.
"""
@spec orders_count(Plug.Conn.t(), map()) :: Plug.Conn.t()
def orders_count(conn, _params) do
count = Repo.aggregate(Order, :count, :transaction_hash)

conn
|> put_status(200)
|> render(:signet_orders_count, %{count: count})
end

@doc """
GET /api/v2/signet/orders/:transaction_hash/:log_index

Gets a single order by transaction hash and log index.
"""
@spec order(Plug.Conn.t(), map()) :: Plug.Conn.t()
def order(conn, %{"transaction_hash" => tx_hash_str, "log_index" => log_index_str}) do
with {:ok, transaction_hash} <- Hash.Full.cast(tx_hash_str),
{log_index, ""} <- Integer.parse(log_index_str),
%Order{} = order <- SignetQueries.get_order(transaction_hash, log_index) do
conn
|> put_status(200)
|> render(:signet_order, %{order: order})
else
:error ->
{:error, :not_found}

nil ->
{:error, :not_found}

_ ->
{:error, :not_found}
end
end

@doc """
GET /api/v2/signet/fills

Lists Signet fills with pagination and optional filters.

## Query Parameters
- chain_type: "rollup" or "host" (optional)
- block_number_gte: minimum block number (optional)
- block_number_lte: maximum block number (optional)
- Standard pagination params
"""
@spec fills(Plug.Conn.t(), map()) :: Plug.Conn.t()
def fills(conn, params) do
options = paging_options(params)

filter_args = %{}
|> maybe_add_chain_type_filter(params["chain_type"])
|> maybe_add_filter(:block_number_gte, params["block_number_gte"])
|> maybe_add_filter(:block_number_lte, params["block_number_lte"])

{fills, next_page} =
filter_args
|> SignetQueries.fills_query()
|> paginate(options)
|> Repo.all()
|> split_list_by_page()

next_page_params =
next_page_params(
next_page,
fills,
params,
false,
fn %Fill{block_number: block_number, log_index: log_index, chain_type: chain_type} ->
%{"block_number" => block_number, "log_index" => log_index, "chain_type" => chain_type}
end
)

conn
|> put_status(200)
|> render(:signet_fills, %{
fills: fills,
next_page_params: next_page_params
})
end

@doc """
GET /api/v2/signet/fills/count

Returns the total count of Signet fills, optionally filtered by chain type.

## Query Parameters
- chain_type: "rollup" or "host" (optional)
"""
@spec fills_count(Plug.Conn.t(), map()) :: Plug.Conn.t()
def fills_count(conn, params) do
query = from(f in Fill)

query =
case params["chain_type"] do
"rollup" -> from(f in query, where: f.chain_type == :rollup)
"host" -> from(f in query, where: f.chain_type == :host)
_ -> query
end

count = Repo.aggregate(query, :count, :transaction_hash)

conn
|> put_status(200)
|> render(:signet_fills_count, %{count: count})
end

@doc """
GET /api/v2/signet/fills/:chain_type/:transaction_hash/:log_index

Gets a single fill by chain type, transaction hash, and log index.
"""
@spec fill(Plug.Conn.t(), map()) :: Plug.Conn.t()
def fill(conn, %{
"chain_type" => chain_type_str,
"transaction_hash" => tx_hash_str,
"log_index" => log_index_str
}) do
with {:ok, chain_type} <- parse_chain_type(chain_type_str),
{:ok, transaction_hash} <- Hash.Full.cast(tx_hash_str),
{log_index, ""} <- Integer.parse(log_index_str),
%Fill{} = fill <- SignetQueries.get_fill(chain_type, transaction_hash, log_index) do
conn
|> put_status(200)
|> render(:signet_fill, %{fill: fill})
else
:error ->
{:error, :not_found}

nil ->
{:error, :not_found}

{:error, :invalid_chain_type} ->
conn
|> put_status(:bad_request)
|> render(:message, %{message: "Invalid chain_type. Must be 'rollup' or 'host'."})

_ ->
{:error, :not_found}
end
end

# Private helpers

defp parse_chain_type("rollup"), do: {:ok, :rollup}
defp parse_chain_type("host"), do: {:ok, :host}
defp parse_chain_type(_), do: {:error, :invalid_chain_type}

defp maybe_add_filter(args, key, value) when is_binary(value) do
case Integer.parse(value) do
{int_val, ""} -> Map.put(args, key, int_val)
_ -> args
end
end

defp maybe_add_filter(args, _key, _value), do: args

defp maybe_add_chain_type_filter(args, "rollup"), do: Map.put(args, :chain_type, :rollup)
defp maybe_add_chain_type_filter(args, "host"), do: Map.put(args, :chain_type, :host)
defp maybe_add_chain_type_filter(args, _), do: args

defp paginate(query, %PagingOptions{page_size: page_size}) do
from(q in query, limit: ^(page_size + 1))
end

defp paginate(query, _), do: from(q in query, limit: 51)
end
18 changes: 17 additions & 1 deletion apps/block_scout_web/lib/block_scout_web/graphql/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.GraphQL.Schema do

use Absinthe.Schema
use Absinthe.Relay.Schema, :modern
use Utils.CompileTimeEnvHelper, chain_identity: [:explorer, :chain_identity]
use Utils.CompileTimeEnvHelper, chain_identity: [:explorer, :chain_identity], chain_type: [:explorer, :chain_type]

alias Absinthe.Middleware.Dataloader, as: AbsintheDataloaderMiddleware
alias Absinthe.Plugin, as: AbsinthePlugin
Expand All @@ -23,12 +23,21 @@ defmodule BlockScoutWeb.GraphQL.Schema do
alias Explorer.Chain.TokenTransfer, as: ExplorerChainTokenTransfer
alias Explorer.Chain.Transaction, as: ExplorerChainTransaction

if @chain_type == :signet do
alias Explorer.Chain.Signet.Order, as: ExplorerChainSignetOrder
alias Explorer.Chain.Signet.Fill, as: ExplorerChainSignetFill
end

import_types(BlockScoutWeb.GraphQL.Schema.Types)

if @chain_identity == {:optimism, :celo} do
import_types(BlockScoutWeb.GraphQL.Celo.Schema.Types)
end

if @chain_type == :signet do
import_types(BlockScoutWeb.GraphQL.Signet.Schema.Types)
end

node interface do
resolve_type(fn
%ExplorerChainInternalTransaction{}, _ ->
Expand Down Expand Up @@ -114,6 +123,13 @@ defmodule BlockScoutWeb.GraphQL.Schema do

QueryFields.generate()
end

if @chain_type == :signet do
require BlockScoutWeb.GraphQL.Signet.QueryFields
alias BlockScoutWeb.GraphQL.Signet.QueryFields, as: SignetQueryFields

SignetQueryFields.generate()
end
end

subscription do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule BlockScoutWeb.GraphQL.Signet.Resolvers.Fill do
@moduledoc """
Resolvers for Signet fills, used in the Signet GraphQL schema.
"""

alias Absinthe.Relay.Connection
alias Explorer.Chain
alias Explorer.GraphQL.Signet, as: GraphQL
alias Explorer.Repo

@doc """
Gets a single fill by chain type, transaction hash, and log index.
"""
def get_by(_parent, %{chain_type: chain_type_string, transaction_hash: hash_string, log_index: log_index}, _resolution) do
with {:ok, hash} <- Chain.string_to_full_hash(hash_string),
{:ok, chain_type} <- parse_chain_type(chain_type_string) do
case GraphQL.get_fill(chain_type, hash, log_index) do
nil -> {:error, "Fill not found"}
fill -> {:ok, fill}
end
end
end

@doc """
Lists fills with optional filters and pagination.
"""
def list(_parent, args, _resolution) do
args
|> maybe_parse_chain_type_filter()
|> GraphQL.fills_query()
|> Connection.from_query(&Repo.all/1, args, options(args))
end

defp parse_chain_type("rollup"), do: {:ok, :rollup}
defp parse_chain_type("host"), do: {:ok, :host}
defp parse_chain_type(_), do: {:error, "Invalid chain_type. Must be 'rollup' or 'host'"}

defp maybe_parse_chain_type_filter(%{chain_type: chain_type_string} = args) do
case parse_chain_type(chain_type_string) do
{:ok, chain_type} -> Map.put(args, :chain_type, chain_type)
{:error, _} -> args
end
end

defp maybe_parse_chain_type_filter(args), do: args

defp options(%{before: _}), do: []
defp options(%{count: count}), do: [count: count]
defp options(_), do: []
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule BlockScoutWeb.GraphQL.Signet.Resolvers.Order do
@moduledoc """
Resolvers for Signet orders, used in the Signet GraphQL schema.
"""

alias Absinthe.Relay.Connection
alias Explorer.Chain
alias Explorer.GraphQL.Signet, as: GraphQL
alias Explorer.Repo

@doc """
Gets a single order by transaction hash and log index.
"""
def get_by(_parent, %{transaction_hash: hash_string, log_index: log_index}, _resolution) do
with {:ok, hash} <- Chain.string_to_full_hash(hash_string) do
case GraphQL.get_order(hash, log_index) do
nil -> {:error, "Order not found"}
order -> {:ok, order}
end
end
end

@doc """
Lists orders with optional filters and pagination.
"""
def list(_parent, args, _resolution) do
args
|> GraphQL.orders_query()
|> Connection.from_query(&Repo.all/1, args, options(args))
end

defp options(%{before: _}), do: []
defp options(%{count: count}), do: [count: count]
defp options(_), do: []
end
Loading