From eb94a4230fda75fd9a333912d4857cbc79f2a28d Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 7 Nov 2024 13:14:26 +0200 Subject: [PATCH] feat: Xname app proxy (#11010) * Xname app proxy * fix specs * Process reviewer comments --- .../api/v2/proxy/xname_controller.ex | 24 +++++++++ .../lib/block_scout_web/routers/api_router.ex | 4 ++ .../third_party_integrations/noves_fi.ex | 20 ++++++-- .../third_party_integrations/xname.ex | 50 +++++++++++++++++++ .../third_party_integrations/zerion.ex | 12 +++-- config/runtime.exs | 4 ++ cspell.json | 2 + docker-compose/envs/common-blockscout.env | 2 + 8 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/xname_controller.ex create mode 100644 apps/explorer/lib/explorer/third_party_integrations/xname.ex diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/xname_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/xname_controller.ex new file mode 100644 index 0000000000..75426a47d0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/xname_controller.ex @@ -0,0 +1,24 @@ +defmodule BlockScoutWeb.API.V2.Proxy.XnameController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.V2.AddressController + + alias Explorer.ThirdPartyIntegrations.Xname + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @doc """ + Function to handle GET requests to `/api/v2/proxy/xname/address/:address_hash_param` endpoint. + """ + @spec address(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def address(conn, %{"address_hash_param" => address_hash_string} = params) do + with {:ok, _address_hash, _address} <- AddressController.validate_address(address_hash_string, params), + url = Xname.address_url(address_hash_string), + {response, status} <- Xname.api_request(url, conn), + {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do + conn + |> put_status(status) + |> json(response) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex b/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex index 533131077c..80038e0b10 100644 --- a/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex @@ -353,6 +353,10 @@ defmodule BlockScoutWeb.Routers.ApiRouter do get("/wallets/:address_hash_param/portfolio", V2.Proxy.ZerionController, :wallet_portfolio) end + scope "/xname" do + get("/addresses/:address_hash_param", V2.Proxy.XnameController, :address) + end + scope "/metadata" do get("/addresses", V2.Proxy.MetadataController, :addresses) end diff --git a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex index d0fda30068..c51f590982 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex @@ -3,6 +3,8 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do Module for Noves.Fi API integration https://blockscout.noves.fi/swagger/index.html """ + require Logger + alias Explorer.Helper alias Explorer.Utility.Microservice @@ -33,7 +35,11 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do {:ok, %HTTPoison.Response{status_code: status, body: body}} -> {Helper.decode_json(body), status} - _ -> + {:error, reason} -> + Logger.error(fn -> + ["Error while requesting Noves.Fi API endpoint #{url}. The reason is: ", inspect(reason)] + end) + {nil, 500} end end @@ -47,13 +53,17 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do {:ok, %HTTPoison.Response{status_code: status, body: body}} -> {Helper.decode_json(body), status} - _ -> + {:error, reason} -> + Logger.error(fn -> + ["Error while requesting Noves.Fi API endpoint #{url}. The reason is: ", inspect(reason)] + end) + {nil, 500} end end @doc """ - Noves.fi /evm/{chain}/tx/{txHash} endpoint + Noves.fi /evm/:chain/tx/:transaction_hash endpoint """ @spec transaction_url(String.t()) :: String.t() def transaction_url(transaction_hash_string) do @@ -61,7 +71,7 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do end @doc """ - Noves.fi /evm/{chain}/describeTxs endpoint + Noves.fi /evm/:chain/describeTxs endpoint """ @spec describe_transactions_url() :: String.t() def describe_transactions_url do @@ -69,7 +79,7 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do end @doc """ - Noves.fi /evm/{chain}/transactions/{accountAddress} endpoint + Noves.fi /evm/:chain/transactions/:address_hash endpoint """ @spec address_transactions_url(String.t()) :: String.t() def address_transactions_url(address_hash_string) do diff --git a/apps/explorer/lib/explorer/third_party_integrations/xname.ex b/apps/explorer/lib/explorer/third_party_integrations/xname.ex new file mode 100644 index 0000000000..aa86995cbe --- /dev/null +++ b/apps/explorer/lib/explorer/third_party_integrations/xname.ex @@ -0,0 +1,50 @@ +defmodule Explorer.ThirdPartyIntegrations.Xname do + @moduledoc """ + Module for proxying xname https://xname.app/ API endpoints + """ + + require Logger + + alias Explorer.Helper + alias Explorer.Utility.Microservice + + @recv_timeout 60_000 + + @doc """ + Proxy request to XName API endpoints + """ + @spec api_request(String.t(), Plug.Conn.t(), atom()) :: {any(), integer()} + def api_request(url, conn, method \\ :get) + + def api_request(url, _conn, :get) do + headers = [{"x-api-key", api_key()}] + + case HTTPoison.get(url, headers, recv_timeout: @recv_timeout) do + {:ok, %HTTPoison.Response{status_code: status, body: body}} -> + {Helper.decode_json(body), status} + + {:error, reason} -> + Logger.error(fn -> + ["Error while requesting XName app API endpoint #{url}. The reason is: ", inspect(reason)] + end) + + {nil, 500} + end + end + + @doc """ + https://gateway.xname.app/xhs/level/:address_hash endpoint + """ + @spec address_url(String.t()) :: String.t() + def address_url(address_hash_string) do + "#{base_url()}/xhs/level/#{address_hash_string}" + end + + defp base_url do + Microservice.base_url(__MODULE__) + end + + defp api_key do + Application.get_env(:explorer, __MODULE__)[:api_key] + end +end diff --git a/apps/explorer/lib/explorer/third_party_integrations/zerion.ex b/apps/explorer/lib/explorer/third_party_integrations/zerion.ex index 2822e1608a..7d129c319a 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/zerion.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/zerion.ex @@ -3,6 +3,8 @@ defmodule Explorer.ThirdPartyIntegrations.Zerion do Module for Zerion API integration https://developers.zerion.io/reference """ + require Logger + alias Explorer.Helper alias Explorer.Utility.Microservice @@ -11,7 +13,7 @@ defmodule Explorer.ThirdPartyIntegrations.Zerion do @doc """ Proxy request to Zerion API endpoints """ - @spec api_request(String.t(), Plug.Conn.t(), :get | :post_transactions) :: {any(), integer()} + @spec api_request(String.t(), Plug.Conn.t(), atom()) :: {any(), integer()} def api_request(url, conn, method \\ :get) def api_request(url, _conn, :get) do @@ -22,13 +24,17 @@ defmodule Explorer.ThirdPartyIntegrations.Zerion do {:ok, %HTTPoison.Response{status_code: status, body: body}} -> {Helper.decode_json(body), status} - _ -> + {:error, reason} -> + Logger.error(fn -> + ["Error while requesting Zerion API endpoint #{url}. The reason is: ", inspect(reason)] + end) + {nil, 500} end end @doc """ - Zerion /wallets/{accountAddress}/portfolio endpoint + Zerion /wallets/:address_hash/portfolio endpoint """ @spec wallet_portfolio_url(String.t()) :: String.t() def wallet_portfolio_url(address_hash_string) do diff --git a/config/runtime.exs b/config/runtime.exs index a675e1d333..5a32939124 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -499,6 +499,10 @@ config :explorer, Explorer.ThirdPartyIntegrations.Zerion, service_url: System.get_env("ZERION_BASE_API_URL", "https://api.zerion.io/v1"), api_key: System.get_env("ZERION_API_TOKEN") +config :explorer, Explorer.ThirdPartyIntegrations.Xname, + service_url: System.get_env("XNAME_BASE_API_URL", "https://gateway.xname.app"), + api_key: System.get_env("XNAME_API_TOKEN") + enabled? = ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED", "true") # or "eth_bytecode_db" type = System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier") diff --git a/cspell.json b/cspell.json index 67dec66b99..e939b1a694 100644 --- a/cspell.json +++ b/cspell.json @@ -435,6 +435,7 @@ "prederived", "progressbar", "proxiable", + "proxying", "psql", "pubkey", "pubkeys", @@ -641,6 +642,7 @@ "xakgj", "xbaddress", "xdai", + "Xname", "xffff", "xlevel", "xlink", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 35872c4e75..21d50b33e4 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -447,6 +447,8 @@ TENDERLY_CHAIN_PATH= # NOVES_FI_API_TOKEN= # ZERION_BASE_API_URL= # ZERION_API_TOKEN= +# XNAME_BASE_API_URL= +# XNAME_API_TOKEN= # BRIDGED_TOKENS_ENABLED= # BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR= # BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR=