- <.link
- class="hover:scale-105 transform duration-150 active:scale-95 text-3xl"
- navigate={~p"/"}
- >
- 🟩
Aligned Explorer Home
-
-
+
+
<.link
- class={
- active_view_class(assigns.socket.view, [
- ExplorerWeb.Batches.Index,
- ExplorerWeb.Batch.Index
- ])
- }
- navigate={~p"/batches"}
+ class="hover:scale-105 transform duration-150 active:scale-95 text-3xl"
+ navigate={~p"/"}
>
- Batches
+ 🟩 Aligned Explorer Home
- <.link
- class={
- active_view_class(assigns.socket.view, [
- ExplorerWeb.Operators.Index,
- ExplorerWeb.Operator.Index
- ])
- }
- navigate={~p"/operators"}
- >
- Operators
-
- <.link
- class={
- active_view_class(assigns.socket.view, [
- ExplorerWeb.Restakes.Index,
- ExplorerWeb.Restake.Index
- ])
- }
- navigate={~p"/restaked"}
- >
- Restaked
-
-
-
-
- <.live_component module={SearchComponent} id="nav_search" />
-
-
- <.link class="hover:text-foreground" target="_blank" href="https://docs.alignedlayer.com">
- Docs
-
- <.link
- class="hover:text-foreground"
- target="_blank"
- href="https://github.com/yetanotherco/aligned_layer"
- >
- GitHub
-
-
- <.badge :if={@latest_release != nil} class="hidden md:inline">
- <%= @latest_release %>
- <.tooltip>
- Latest Aligned version
-
-
- <.hover_dropdown_selector
- current_value={Helpers.get_current_network_from_host(@host)}
- variant="accent"
- options={get_networks(Helpers.get_current_network_from_host(@host))}
- icon="hero-cube-transparent-micro"
- />
-
-
+
+ """
+ end
+
+ @doc """
+ Renders a dropdown on hover component with links.
+ """
+ attr(:title, :list, doc: "the selector title")
+ attr(:class, :list, doc: "class for selector")
+ attr(:links, :string, doc: "the links to render: (name, link, class)")
+
+ def nav_links_dropdown(assigns) do
+ ~H"""
+
+
+
+
+
+ <%= for {name, route, class} <- @links do %>
<.link
- class="hover:text-foreground"
- target="_blank"
- href="https://github.com/yetanotherco/aligned_layer"
+ class={
+ classes([
+ "group/link text-card-foreground w-full flex items-center justify-between",
+ class
+ ])
+ }
+ navigate={route}
>
- GitHub
+ <%= name %>
-
+ <% end %>
-
-
+
"""
end
diff --git a/explorer/lib/explorer_web/live/pages/agg_proof/index.ex b/explorer/lib/explorer_web/live/pages/agg_proof/index.ex
new file mode 100644
index 0000000000..73f525036d
--- /dev/null
+++ b/explorer/lib/explorer_web/live/pages/agg_proof/index.ex
@@ -0,0 +1,30 @@
+defmodule ExplorerWeb.AggProof.Index do
+ require Logger
+ use ExplorerWeb, :live_view
+
+ @impl true
+ def mount(%{"id" => id}, _, socket) do
+ agg_proof =
+ AggregatedProofs.get_aggregated_proof_by_id(id)
+
+ {
+ :ok,
+ assign(
+ socket,
+ agg_proof: agg_proof,
+ proof_hashes: :empty
+ )
+ }
+ end
+
+ @impl true
+ def handle_event("show_proofs", _value, socket) do
+ proofs = AggregationModeProof.get_all_proof_hashes(socket.assigns.agg_proof.id)
+ {:noreply, assign(socket, proof_hashes: proofs)}
+ end
+
+ @impl true
+ def handle_event("hide_proofs", _value, socket) do
+ {:noreply, assign(socket, proof_hashes: :empty)}
+ end
+end
diff --git a/explorer/lib/explorer_web/live/pages/agg_proof/index.html.heex b/explorer/lib/explorer_web/live/pages/agg_proof/index.html.heex
new file mode 100644
index 0000000000..0a7012b995
--- /dev/null
+++ b/explorer/lib/explorer_web/live/pages/agg_proof/index.html.heex
@@ -0,0 +1,127 @@
+
+ <%= if @agg_proof != :empty do %>
+ <.card_preheding class="text-4xl sm:text-5xl font-bold font-foreground">
+ Aggregated proof details
+
+ <.card
+ class="relative px-4 py-5 min-h-fit flex flex-col"
+ inner_class="font-semibold inline-flex flex-col text-base gap-y-4 text-muted-foreground [&>div>p]:text-foreground [&>div>a]:text-foreground [&>div>*]:break-all [&>div>*]:font-normal [&>div]:flex [&>div]:flex-col [&>div]:lg:flex-row [&>div>h3]:basis-1/4"
+ >
+
+
+ Merkle root:
+
+
+ <%= @agg_proof.merkle_root %>
+ <.live_component
+ module={CopyToClipboardButtonComponent}
+ text_to_copy={@agg_proof.merkle_root}
+ id={"copy_batch_hash_#{@agg_proof.merkle_root}"}
+ class="inline-flex"
+ />
+
+
+
+
+
+ Number of Proofs included:
+
+
<%= @agg_proof.number_of_proofs %>
+
+
+
+
+ Proofs included:
+
+ <%= if @proof_hashes != :empty do %>
+ <%= if @proof_hashes == :nil do %>
+
+ Proofs couldn't be shown for this aggregated proof
+
+ <% else %>
+
+
+
+ <%= proof %>
+ <.live_component
+ module={CopyToClipboardButtonComponent}
+ text_to_copy={proof}
+ id={"copy_proof_batch_hash_#{proof}_#{Utils.random_id("cp_#{index}")}"}
+ class="inline-flex"
+ />
+
+
+ <.button class="w-fit text-foreground" phx-click="hide_proofs">
+ <.icon name="hero-eye-slash" class="size-4" /> Hide Proofs
+
+
+ <% end %>
+ <% else %>
+ <.button class="w-fit text-foreground font-semibold" phx-click="show_proofs">
+ <.icon name="hero-eye" class="size-4" /> Show Proofs
+
+ <% end %>
+
+ <.divider />
+
+
+ Block Number:
+
+ <.a
+ target="_blank"
+ rel="noopener"
+ href={
+ "#{Helpers.get_etherescan_url()}/block/#{@agg_proof.block_number}"
+ }
+ class="hover:text-foreground/80"
+ >
+ <%= @agg_proof.block_number |> Helpers.format_number() %>
+
+
+
+
+ Transaction Hash:
+
+ <.a
+ target="_blank"
+ rel="noopener"
+ href={"#{Helpers.get_etherescan_url()}/tx/#{@agg_proof.tx_hash}"}
+ class="hover:text-foreground/80"
+ >
+ <%= @agg_proof.tx_hash %>
+
+
+
+
+
+ Blob versioned hash:
+
+ <.a
+ target="_blank"
+ rel="noopener"
+ href={"#{Helpers.get_blobscan_url()}/blob/#{@agg_proof.blob_versioned_hash}"}
+ class="hover:text-foreground/80"
+ >
+ <%= @agg_proof.blob_versioned_hash %>
+
+
+
+ <% else %>
+
+
Oops!
+
+ The batch you are looking for
doesn't exist.
+
+

+ <.link navigate={~p"/"}>
+ <.button>
+ Go Home
+
+
+
+ <% end %>
+
diff --git a/explorer/lib/explorer_web/live/pages/agg_proofs/index.ex b/explorer/lib/explorer_web/live/pages/agg_proofs/index.ex
new file mode 100644
index 0000000000..c4623ff2e9
--- /dev/null
+++ b/explorer/lib/explorer_web/live/pages/agg_proofs/index.ex
@@ -0,0 +1,52 @@
+defmodule ExplorerWeb.AggProofs.Index do
+ require Logger
+ import ExplorerWeb.AggProofsTable
+ use ExplorerWeb, :live_view
+
+ @page_size 15
+
+ @impl true
+ def mount(_, params, socket) do
+ current_page = get_current_page(params)
+
+ proofs =
+ AggregatedProofs.get_paginated_proofs(%{
+ page: current_page,
+ page_size: @page_size
+ })
+ |> Enum.map(fn proof ->
+ proof |> Map.merge(%{age: proof.block_timestamp |> Helpers.parse_timeago()})
+ end)
+
+ {
+ :ok,
+ assign(
+ socket,
+ proofs: proofs,
+ current_page: current_page,
+ last_page: AggregatedProofs.get_last_page(@page_size)
+ )
+ }
+ end
+
+ @impl true
+ def handle_event("change_page", %{"page" => page}, socket) do
+ {:noreply, push_navigate(socket, to: ~p"/batches?page=#{page}")}
+ end
+
+ defp get_current_page(params) do
+ case params |> Map.get("page") do
+ nil ->
+ 1
+
+ page ->
+ case Integer.parse(page) do
+ {number, _} ->
+ if number < 1, do: 1, else: number
+
+ :error ->
+ 1
+ end
+ end
+ end
+end
diff --git a/explorer/lib/explorer_web/live/pages/agg_proofs/index.html.heex b/explorer/lib/explorer_web/live/pages/agg_proofs/index.html.heex
new file mode 100644
index 0000000000..1da4493967
--- /dev/null
+++ b/explorer/lib/explorer_web/live/pages/agg_proofs/index.html.heex
@@ -0,0 +1,64 @@
+
+ <.card_preheding>Aggregation
+ <%= if @proofs != :empty and @proofs != [] do %>
+ <.card_background class="w-full overflow-x-auto sm:col-span-2">
+ <.agg_proofs_table proofs={@proofs} />
+
+ <% else %>
+ <.empty_card_background text="No aggregated proofs To Display." class="sm:col-span-2" />
+ <% end %>
+
+ <%= if @current_page >= 2 do %>
+ <.link navigate={~p"/batches?page=#{1}"}>
+ <.button class="text-muted-foreground group">
+ First
+
+
+ <% end %>
+ <%= if @current_page > 1 do %>
+ <.link navigate={~p"/aggregated_proofs?page=#{@current_page - 1}"}>
+ <.button
+ icon="arrow-right-solid"
+ icon_class="group-hover:translate-x-1 transition-all duration-150"
+ class="text-muted-foreground size-10 group rotate-180"
+ >
+ Previous Page
+
+
+ <% end %>
+
+ <%= if @current_page != @last_page do %>
+ <.link navigate={~p"/aggregated_proofs?page=#{@current_page + 1}"}>
+ <.button
+ icon="arrow-right-solid"
+ icon_class="group-hover:translate-x-1 transition-all duration-150"
+ class="text-muted-foreground size-10 group"
+ >
+ Next Page
+
+
+ <.link navigate={~p"/batches?page=#{@last_page}"}>
+ <.button class="text-muted-foreground group">
+ Last
+
+
+ <% end %>
+
+
diff --git a/explorer/lib/explorer_web/live/pages/home/index.ex b/explorer/lib/explorer_web/live/pages/home/index.ex
index 727f213d2d..b561d832c8 100644
--- a/explorer/lib/explorer_web/live/pages/home/index.ex
+++ b/explorer/lib/explorer_web/live/pages/home/index.ex
@@ -6,6 +6,7 @@ defmodule ExplorerWeb.Home.Index do
def get_stats() do
verified_batches = Batches.get_amount_of_verified_batches()
+ aggregated_proofs = AggregatedProofs.get_number_of_agg_proofs()
avg_fee_per_proof = Batches.get_avg_fee_per_proof()
avg_fee_per_proof_usd =
@@ -59,6 +60,12 @@ defmodule ExplorerWeb.Home.Index do
end,
link: nil
},
+ %{
+ title: "Aggregated proofs",
+ value: aggregated_proofs,
+ tooltip_text: nil,
+ link: "/aggregated_proofs"
+ },
%{
title: "AVG proof cost",
value: "#{avg_fee_per_proof_usd} USD",
diff --git a/explorer/lib/explorer_web/live/utils.ex b/explorer/lib/explorer_web/live/utils.ex
index c11aae9007..87545abfb2 100644
--- a/explorer/lib/explorer_web/live/utils.ex
+++ b/explorer/lib/explorer_web/live/utils.ex
@@ -155,10 +155,36 @@ defmodule ExplorerWeb.Helpers do
end
end
+ @doc """
+ Get the Etherscan URL based on the environment.
+ - `holesky` -> https://holesky.etherscan.io
+ - `mainnet` -> https://etherscan.io
+ - `default` -> http://localhost:4000
+ """
+ def get_blobscan_url() do
+ prefix = System.get_env("ENVIRONMENT")
+
+ case prefix do
+ "mainnet" -> "https://blobscan.com/"
+ "holesky" -> "https://holesky.blobscan.com/"
+ _ -> "http://localhost:4000"
+ end
+ end
+
def get_aligned_contracts_addresses() do
+ Map.merge(get_batcher_service_addresses(), get_proof_aggregation_addresses())
+ end
+
+ defp get_proof_aggregation_addresses() do
+ proof_agg_config_file = System.get_env("ALIGNED_PROOF_AGG_CONFIG_FILE")
+ {_, config_json_string} = File.read(proof_agg_config_file)
+ proof_agg_service_addresses = Jason.decode!(config_json_string) |> Map.get("addresses")
+ end
+
+ defp get_batcher_service_addresses() do
aligned_config_file = System.get_env("ALIGNED_CONFIG_FILE")
{_, config_json_string} = File.read(aligned_config_file)
- Jason.decode!(config_json_string) |> Map.get("addresses")
+ agg_service_addresses = Jason.decode!(config_json_string) |> Map.get("addresses")
end
def binary_to_hex_string(binary) do
@@ -228,22 +254,36 @@ defmodule Utils do
@batcher_submission_gas_cost Application.compile_env(:explorer, :batcher_submission_gas_cost)
@aggregator_gas_cost Application.compile_env(:explorer, :aggregator_gas_cost)
- @aggregator_fee_percentage_multiplier Application.compile_env(:explorer, :aggregator_fee_percentage_multiplier)
+ @aggregator_fee_percentage_multiplier Application.compile_env(
+ :explorer,
+ :aggregator_fee_percentage_multiplier
+ )
@percentage_divider Application.compile_env(:explorer, :percentage_divider)
- @additional_submission_gas_cost_per_proof Application.compile_env(:explorer, :additional_submission_gas_cost_per_proof)
+ @additional_submission_gas_cost_per_proof Application.compile_env(
+ :explorer,
+ :additional_submission_gas_cost_per_proof
+ )
def scheduled_batch_interval() do
default_value = 10
+
case System.get_env("SCHEDULED_BATCH_INTERVAL_MINUTES") do
nil ->
- Logger.warning("SCHEDULED_BATCH_INTERVAL_MINUTES .env var is not set, using default value: #{default_value}")
+ Logger.warning(
+ "SCHEDULED_BATCH_INTERVAL_MINUTES .env var is not set, using default value: #{default_value}"
+ )
+
default_value
+
value ->
try do
String.to_integer(value)
rescue
ArgumentError ->
- Logger.warning("Invalid SCHEDULED_BATCH_INTERVAL_MINUTES .env var: #{value}, using default value: #{default_value}")
+ Logger.warning(
+ "Invalid SCHEDULED_BATCH_INTERVAL_MINUTES .env var: #{value}, using default value: #{default_value}"
+ )
+
default_value
end
end
@@ -457,8 +497,8 @@ defmodule Utils do
def constant_batch_submission_gas_cost() do
trunc(
@aggregator_gas_cost * @aggregator_fee_percentage_multiplier /
- @percentage_divider +
- @batcher_submission_gas_cost
+ @percentage_divider +
+ @batcher_submission_gas_cost
)
end
diff --git a/explorer/lib/explorer_web/router.ex b/explorer/lib/explorer_web/router.ex
index ca855d46ca..5a7c5381e7 100644
--- a/explorer/lib/explorer_web/router.ex
+++ b/explorer/lib/explorer_web/router.ex
@@ -47,6 +47,8 @@ defmodule ExplorerWeb.Router do
live "/", Home.Index
live "/batches/:merkle_root", Batch.Index
live "/batches", Batches.Index
+ live "/aggregated_proofs", AggProofs.Index
+ live "/aggregated_proofs/:id", AggProof.Index
live "/restaked", Restakes.Index
live "/restaked/:address", Restake.Index
live "/operators", Operators.Index