diff --git a/apps/block_scout_web/assets/js/lib/csv_download.js b/apps/block_scout_web/assets/js/lib/csv_download.js
index 820f7b6b4e..3b2786eb20 100644
--- a/apps/block_scout_web/assets/js/lib/csv_download.js
+++ b/apps/block_scout_web/assets/js/lib/csv_download.js
@@ -10,103 +10,141 @@ const DATE_FORMAT = 'YYYY-MM-DD'
const $button = $('#export-csv-button')
// eslint-disable-next-line
-const _instance1 = new Pikaday({
- field: $('.js-datepicker-from')[0],
- onSelect: (date) => onSelect(date, 'from_period'),
- defaultDate: moment().add(-1, 'months').toDate(),
- setDefaultDate: true,
- maxDate: new Date(),
- format: DATE_FORMAT
-})
-
+const _instance1 = generateDatePicker('.js-datepicker-from', moment().add(-1, 'months').toDate())
// eslint-disable-next-line
-const _instance2 = new Pikaday({
- field: $('.js-datepicker-to')[0],
- onSelect: (date) => onSelect(date, 'to_period'),
- defaultDate: new Date(),
- setDefaultDate: true,
- maxDate: new Date(),
- format: DATE_FORMAT
-})
+const _instance2 = generateDatePicker('.js-datepicker-to', new Date())
+
+function generateDatePicker (classSelector, defaultDate) {
+ return new Pikaday({
+ field: $(classSelector)[0],
+ defaultDate,
+ setDefaultDate: true,
+ maxDate: new Date(),
+ format: DATE_FORMAT
+ })
+}
$button.on('click', () => {
// @ts-ignore
// eslint-disable-next-line
- const recaptchaResponse = grecaptcha.getResponse()
- if (recaptchaResponse) {
- $button.addClass('spinner')
- $button.prop('disabled', true)
- const downloadUrl = `${$button.data('link')}&recaptcha_response=${recaptchaResponse}`
+ const reCaptchaV2ClientKey = document.getElementById('re-captcha-client-key').value
+ // @ts-ignore
+ // eslint-disable-next-line
+ const reCaptchaV3ClientKey = document.getElementById('re-captcha-v3-client-key').value
+ const addressHash = $button.data('address-hash')
+ const from = $('.js-datepicker-from').val()
+ const to = $('.js-datepicker-to').val()
+ if (reCaptchaV3ClientKey) {
+ disableBtnWithSpinner()
+ // @ts-ignore
+ // eslint-disable-next-line
+ grecaptcha.ready(function () {
+ // @ts-ignore
+ // eslint-disable-next-line
+ grecaptcha.execute(reCaptchaV3ClientKey, { action: 'login' })
+ .then(function (token) {
+ const url = `${$button.data('link')}?address_id=${addressHash}&from_period=${from}&to_period=${to}&recaptcha_response=${token}`
- $('body').append($(''))
- $('#csv-iframe').attr('src', downloadUrl)
+ download(url)
+ })
+ })
+ } else if (reCaptchaV2ClientKey) {
+ // @ts-ignore
+ // eslint-disable-next-line
+ const recaptchaResponse = grecaptcha.getResponse()
+ if (recaptchaResponse) {
+ disableBtnWithSpinner()
+ const url = `${$button.data('link')}?address_id=${addressHash}&from_period=${from}&to_period=${to}&recaptcha_response=${recaptchaResponse}`
- const interval = setInterval(handleCSVDownloaded, 1000)
- setTimeout(resetDownload, 60000)
+ download(url, true, true)
+ }
+ } else {
+ alertWhenRecaptchaNotConfigured()
+ }
- function handleCSVDownloaded () {
- if (Cookies.get('csv-downloaded') === 'true') {
- resetDownload()
+ function download (url, resetRecaptcha, disable) {
+ fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8'
}
- }
+ })
+ .then(response => {
+ if (response.status === 200) {
+ return response.blob()
+ }
+ })
+ .then(response => {
+ if (response) {
+ const blob = new Blob([response], { type: 'application/csv' })
+ const downloadUrl = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = downloadUrl
+ a.download = `${$button.data('type')}_for_${addressHash}_from_${from}_to_${to}.csv`
+ document.body.appendChild(a)
+ a.click()
+
+ resetBtn(resetRecaptcha, disable)
+ } else {
+ alertWhenRequestFailed()
+ resetBtn(resetRecaptcha, disable)
+ }
+ })
+ }
- function resetDownload () {
- $button.removeClass('spinner')
+ function resetBtn (resetRecaptcha, disable) {
+ $button.removeClass('spinner')
+ if (!disable) {
$button.prop('disabled', false)
- clearInterval(interval)
- Cookies.remove('csv-downloaded')
+ }
+ Cookies.remove('csv-downloaded')
+ if (resetRecaptcha) {
// @ts-ignore
// eslint-disable-next-line
grecaptcha.reset()
}
}
-})
-
-function onSelect (date, paramToReplace) {
- const formattedDate = moment(date).format(DATE_FORMAT)
-
- if (date) {
- const csvExportPath = $button.data('link')
- const updatedCsvExportUrl = replaceUrlParam(csvExportPath, paramToReplace, formattedDate)
- $button.data('link', updatedCsvExportUrl)
+ function disableBtnWithSpinner () {
+ $button.addClass('spinner')
+ disableBtn()
}
-}
-function replaceUrlParam (url, paramName, paramValue) {
- if (paramValue == null) {
- paramValue = ''
- }
- const pattern = new RegExp('\\b(' + paramName + '=).*?(&|#|$)')
- if (url.search(pattern) >= 0) {
- return url.replace(pattern, '$1' + paramValue + '$2')
+ function disableBtn () {
+ $button.prop('disabled', true)
}
- url = url.replace(/[?#]$/, '')
- return url + (url.indexOf('?') > 0 ? '&' : '?') + paramName + '=' + paramValue
-}
+})
const onloadCallback = function () {
// @ts-ignore
// eslint-disable-next-line
const reCaptchaClientKey = document.getElementById('re-captcha-client-key').value
- if (reCaptchaClientKey) {
- // @ts-ignore
- // eslint-disable-next-line
- grecaptcha.render('recaptcha', {
- sitekey: reCaptchaClientKey,
- theme: getThemeMode(),
- callback: function () {
- // @ts-ignore
- document.getElementById('export-csv-button').disabled = false
- }
- })
- } else {
- Swal.fire({
- title: 'Warning',
- html: 'CSV download will not work since reCAPTCHA is not configured. Please advise server maintainer to configure RE_CAPTCHA_CLIENT_KEY and RE_CAPTCHA_SECRET_KEY environment variables.',
- icon: 'warning'
- })
- }
+ // @ts-ignore
+ // eslint-disable-next-line
+ grecaptcha.render('recaptcha', {
+ sitekey: reCaptchaClientKey,
+ theme: getThemeMode(),
+ callback: function () {
+ // @ts-ignore
+ document.getElementById('export-csv-button').disabled = false
+ }
+ })
+}
+
+function alertWhenRecaptchaNotConfigured () {
+ Swal.fire({
+ title: 'Warning',
+ html: 'CSV download is disabled since reCAPTCHA is not configured on server side. Please advise server maintainer to configure RE_CAPTCHA_CLIENT_KEY and RE_CAPTCHA_SECRET_KEY environment variables in case of reCAPTCHAv2 or RE_CAPTCHA_V3_CLIENT_KEY and RE_CAPTCHA_V3_SECRET_KEY environment variables in case of reCAPTCHAv3.',
+ icon: 'warning'
+ })
+}
+
+function alertWhenRequestFailed () {
+ Swal.fire({
+ title: 'Warning',
+ html: 'Request failed, please try again later or decrease time range for exporting data.',
+ icon: 'warning'
+ })
}
// @ts-ignore
diff --git a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex
index abeb3d16eb..a158813b99 100644
--- a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex
+++ b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex
@@ -8,7 +8,9 @@ defmodule BlockScoutWeb.CaptchaHelper do
def recaptcha_passed?(nil), do: false
def recaptcha_passed?(recaptcha_response) do
- re_captcha_secret_key = Application.get_env(:block_scout_web, :re_captcha_secret_key)
+ re_captcha_v2_secret_key = Application.get_env(:block_scout_web, :recaptcha)[:v2_secret_key]
+ re_captcha_v3_secret_key = Application.get_env(:block_scout_web, :recaptcha)[:v3_secret_key]
+ re_captcha_secret_key = re_captcha_v2_secret_key || re_captcha_v3_secret_key
body = "secret=#{re_captcha_secret_key}&response=#{recaptcha_response}"
headers = [{"Content-type", "application/x-www-form-urlencoded"}]
@@ -16,7 +18,7 @@ defmodule BlockScoutWeb.CaptchaHelper do
case HTTPoison.post("https://www.google.com/recaptcha/api/siteverify", body, headers, []) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
case Jason.decode!(body) do
- %{"success" => true} -> true
+ %{"success" => true} = resp -> is_success?(resp)
_ -> false
end
@@ -24,4 +26,18 @@ defmodule BlockScoutWeb.CaptchaHelper do
false
end
end
+
+ defp is_success?(%{"score" => score}) do
+ check_recaptcha_v3_score(score)
+ end
+
+ defp is_success?(_resp), do: true
+
+ defp check_recaptcha_v3_score(score) do
+ if score >= 0.5 do
+ true
+ else
+ false
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
index e22dc29c7b..e60dbc673b 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
@@ -14,14 +14,15 @@ defmodule BlockScoutWeb.AddressTransactionController do
alias BlockScoutWeb.{AccessHelper, Controller, TransactionView}
alias Explorer.{Chain, Market}
- alias Explorer.Chain.{
+ alias Explorer.Chain.CSVExport.{
AddressInternalTransactionCsvExporter,
AddressLogCsvExporter,
AddressTokenTransferCsvExporter,
- AddressTransactionCsvExporter,
- Wei
+ AddressTransactionCsvExporter
}
+ alias Explorer.Chain.Wei
+
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@@ -171,10 +172,10 @@ defmodule BlockScoutWeb.AddressTransactionController do
|> Application.get_env(:captcha_helper)
end
- defp put_resp_params(conn, file_name) do
+ defp put_resp_params(conn) do
conn
|> put_resp_content_type("application/csv")
- |> put_resp_header("content-disposition", "attachment; filename=#{file_name}")
+ |> put_resp_header("content-disposition", "attachment;")
|> put_resp_cookie("csv-downloaded", "true", max_age: 86_400, http_only: false)
|> send_chunked(200)
end
@@ -187,8 +188,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
"to_period" => to_period,
"recaptcha_response" => recaptcha_response
},
- csv_export_module,
- file_name
+ csv_export_module
)
when is_binary(address_hash_string) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
@@ -196,7 +196,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
{:recaptcha, true} <- {:recaptcha, captcha_helper().recaptcha_passed?(recaptcha_response)} do
address
|> csv_export_module.export(from_period, to_period)
- |> Enum.reduce_while(put_resp_params(conn, file_name), fn chunk, conn ->
+ |> Enum.reduce_while(put_resp_params(conn), fn chunk, conn ->
case Conn.chunk(conn, chunk) do
{:ok, conn} ->
{:cont, conn}
@@ -217,7 +217,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
end
end
- defp items_csv(conn, _, _, _), do: not_found(conn)
+ defp items_csv(conn, _, _), do: not_found(conn)
def token_transfers_csv(conn, params) do
items_csv(
@@ -228,8 +228,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
"to_period" => params["to_period"],
"recaptcha_response" => params["recaptcha_response"]
},
- AddressTokenTransferCsvExporter,
- "token_transfers.csv"
+ AddressTokenTransferCsvExporter
)
end
@@ -247,8 +246,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
"to_period" => to_period,
"recaptcha_response" => recaptcha_response
},
- AddressTransactionCsvExporter,
- "transactions.csv"
+ AddressTransactionCsvExporter
)
end
@@ -266,8 +264,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
"to_period" => to_period,
"recaptcha_response" => recaptcha_response
},
- AddressInternalTransactionCsvExporter,
- "internal_transactions.csv"
+ AddressInternalTransactionCsvExporter
)
end
@@ -285,8 +282,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
"to_period" => to_period,
"recaptcha_response" => recaptcha_response
},
- AddressLogCsvExporter,
- "logs.csv"
+ AddressLogCsvExporter
)
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex
index bf9b0e1d44..cd98994651 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex
@@ -21,10 +21,24 @@
-
+
-
+ <%= cond do %>
+ <% Application.get_env(:block_scout_web, :recaptcha)[:v2_client_key] -> %>
+
+ <% Application.get_env(:block_scout_web, :recaptcha)[:v3_client_key] -> %>
+
+ <% true -> %>
+ <% end %>
\ No newline at end of file
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
index 2c940a7f68..ac77e7d932 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
@@ -63,7 +63,8 @@
<%= Application.get_env(:indexer, :first_block) %>
-
+
+
<% show_maintenance_alert = Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:show_maintenance_alert] %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex b/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex
index 07b9564594..083e11f53a 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex
@@ -30,15 +30,4 @@ defmodule BlockScoutWeb.CsvExportView do
|> Address.checksum()
end
end
-
- defp default_period_start do
- DateTime.utc_now()
- |> Timex.shift(months: -1)
- |> Timex.format!("{YYYY}-{0M}-{0D}")
- end
-
- defp default_period_end do
- DateTime.utc_now()
- |> Timex.format!("{YYYY}-{0M}-{0D}")
- end
end
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index e64c8ddcf9..8df086ce3b 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -81,7 +81,7 @@ msgstr ""
msgid ") may be added for each contract. Click the Add Library button to add an additional one."
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:85
+#: lib/block_scout_web/templates/layout/app.html.eex:86
#, elixir-autogen, elixir-format
msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr ""
@@ -1072,7 +1072,7 @@ msgstr ""
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
-#: lib/block_scout_web/templates/csv_export/index.html.eex:24
+#: lib/block_scout_web/templates/csv_export/index.html.eex:32
#, elixir-autogen, elixir-format
msgid "Download"
msgstr ""
diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
index 93b21fc21e..75de13315f 100644
--- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -81,7 +81,7 @@ msgstr ""
msgid ") may be added for each contract. Click the Add Library button to add an additional one."
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:85
+#: lib/block_scout_web/templates/layout/app.html.eex:86
#, elixir-autogen, elixir-format
msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr ""
@@ -1072,7 +1072,7 @@ msgstr ""
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
-#: lib/block_scout_web/templates/csv_export/index.html.eex:24
+#: lib/block_scout_web/templates/csv_export/index.html.eex:32
#, elixir-autogen, elixir-format
msgid "Download"
msgstr ""
diff --git a/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex
similarity index 84%
rename from apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex
rename to apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex
index 96350b398b..a3872ae8ef 100644
--- a/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex
@@ -1,27 +1,24 @@
-defmodule Explorer.Chain.AddressInternalTransactionCsvExporter do
+defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporter do
@moduledoc """
Exports internal transactions to a csv file.
"""
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{Address, InternalTransaction, Wei}
- alias NimbleCSV.RFC4180
+ alias Explorer.Chain.CSVExport.Helper
- @page_size 150
-
- @paging_options %PagingOptions{page_size: @page_size + 1}
+ @paging_options %PagingOptions{page_size: Helper.page_size() + 1}
@spec export(Address.t(), String.t(), String.t()) :: Enumerable.t()
def export(address, from_period, to_period) do
- from_block = Chain.convert_date_to_min_block(from_period)
- to_block = Chain.convert_date_to_max_block(to_period)
+ {from_block, to_block} = Helper.block_from_period(from_period, to_period)
res =
address.hash
|> fetch_all_internal_transactions(from_block, to_block, @paging_options)
|> Enum.sort_by(&{&1.block_number, &1.index, &1.transaction_index}, :desc)
|> to_csv_format()
- |> dump_to_stream()
+ |> Helper.dump_to_stream()
res
end
@@ -37,7 +34,7 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporter do
new_acc = internal_transactions ++ acc
- case Enum.split(internal_transactions, @page_size) do
+ case Enum.split(internal_transactions, Helper.page_size()) do
{_internal_transactions,
[%InternalTransaction{block_number: block_number, transaction_index: transaction_index, index: index}]} ->
new_paging_options = %{@paging_options | key: {block_number, transaction_index, index}}
@@ -48,14 +45,6 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporter do
end
end
- defp dump_to_stream(internal_transactions) do
- res =
- internal_transactions
- |> RFC4180.dump_to_stream()
-
- res
- end
-
defp to_csv_format(internal_transactions) do
row_names = [
"TxHash",
diff --git a/apps/explorer/lib/explorer/chain/address_log_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex
similarity index 81%
rename from apps/explorer/lib/explorer/chain/address_log_csv_exporter.ex
rename to apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex
index ed44957ab6..55b339d6cb 100644
--- a/apps/explorer/lib/explorer/chain/address_log_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex
@@ -1,25 +1,22 @@
-defmodule Explorer.Chain.AddressLogCsvExporter do
+defmodule Explorer.Chain.CSVExport.AddressLogCsvExporter do
@moduledoc """
Exports internal transactions to a csv file.
"""
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{Address, Log, Transaction}
- alias NimbleCSV.RFC4180
+ alias Explorer.Chain.CSVExport.Helper
- @page_size 150
-
- @paging_options %PagingOptions{page_size: @page_size + 1}
+ @paging_options %PagingOptions{page_size: Helper.page_size() + 1}
@spec export(Address.t(), String.t(), String.t()) :: Enumerable.t()
def export(address, from_period, to_period) do
- from_block = Chain.convert_date_to_min_block(from_period)
- to_block = Chain.convert_date_to_max_block(to_period)
+ {from_block, to_block} = Helper.block_from_period(from_period, to_period)
address.hash
|> fetch_all_logs(from_block, to_block, @paging_options)
|> to_csv_format()
- |> dump_to_stream()
+ |> Helper.dump_to_stream()
end
defp fetch_all_logs(address_hash, from_block, to_block, paging_options, acc \\ []) do
@@ -33,7 +30,7 @@ defmodule Explorer.Chain.AddressLogCsvExporter do
new_acc = logs ++ acc
- case Enum.split(logs, @page_size) do
+ case Enum.split(logs, Helper.page_size()) do
{_logs, [%Log{block_number: block_number, transaction: %Transaction{index: transaction_index}, index: index}]} ->
new_paging_options = %{@paging_options | key: {block_number, transaction_index, index}}
fetch_all_logs(address_hash, from_block, to_block, new_paging_options, new_acc)
@@ -43,11 +40,6 @@ defmodule Explorer.Chain.AddressLogCsvExporter do
end
end
- defp dump_to_stream(logs) do
- logs
- |> RFC4180.dump_to_stream()
- end
-
defp to_csv_format(logs) do
row_names = [
"TxHash",
diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex
similarity index 84%
rename from apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
rename to apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex
index fa8139a412..d2e595d701 100644
--- a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex
@@ -1,24 +1,22 @@
-defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
+defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do
@moduledoc """
Exports token transfers to a csv file.
"""
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{Address, TokenTransfer}
- alias NimbleCSV.RFC4180
+ alias Explorer.Chain.CSVExport.Helper
- @page_size 150
- @paging_options %PagingOptions{page_size: @page_size + 1, asc_order: true}
+ @paging_options %PagingOptions{page_size: Helper.page_size() + 1, asc_order: true}
@spec export(Address.t(), String.t(), String.t()) :: Enumerable.t()
def export(address, from_period, to_period) do
- from_block = Chain.convert_date_to_min_block(from_period)
- to_block = Chain.convert_date_to_max_block(to_period)
+ {from_block, to_block} = Helper.block_from_period(from_period, to_period)
address.hash
|> fetch_all_token_transfers(from_block, to_block, @paging_options)
|> to_csv_format(address)
- |> dump_to_stream()
+ |> Helper.dump_to_stream()
end
def fetch_all_token_transfers(address_hash, from_block, to_block, paging_options, acc \\ []) do
@@ -32,7 +30,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
new_acc = acc ++ token_transfers
- case Enum.split(token_transfers, @page_size) do
+ case Enum.split(token_transfers, Helper.page_size()) do
{_token_transfers, [%TokenTransfer{block_number: block_number, log_index: log_index}]} ->
new_paging_options = %{@paging_options | key: {block_number, log_index}}
fetch_all_token_transfers(address_hash, from_block, to_block, new_paging_options, new_acc)
@@ -42,11 +40,6 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
end
end
- defp dump_to_stream(transactions) do
- transactions
- |> RFC4180.dump_to_stream()
- end
-
defp to_csv_format(token_transfers, address) do
row_names = [
"TxHash",
diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex
similarity index 88%
rename from apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
rename to apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex
index a68c4aabe0..c76815dc70 100644
--- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex
@@ -1,4 +1,4 @@
-defmodule Explorer.Chain.AddressTransactionCsvExporter do
+defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do
@moduledoc """
Exports transactions to a csv file.
"""
@@ -11,8 +11,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
alias Explorer.{Chain, Market, PagingOptions, Repo}
alias Explorer.Market.MarketHistory
alias Explorer.Chain.{Address, Transaction, Wei}
+ alias Explorer.Chain.CSVExport.Helper
alias Explorer.ExchangeRates.Token
- alias NimbleCSV.RFC4180
@necessity_by_association [
necessity_by_association: %{
@@ -27,20 +27,17 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
}
]
- @page_size 150
-
- @paging_options %PagingOptions{page_size: @page_size + 1}
+ @paging_options %PagingOptions{page_size: Helper.page_size() + 1}
@spec export(Address.t(), String.t(), String.t()) :: Enumerable.t()
def export(address, from_period, to_period) do
- from_block = Chain.convert_date_to_min_block(from_period)
- to_block = Chain.convert_date_to_max_block(to_period)
+ {from_block, to_block} = Helper.block_from_period(from_period, to_period)
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
address.hash
|> fetch_all_transactions(from_block, to_block, @paging_options)
|> to_csv_format(address, exchange_rate)
- |> dump_to_stream()
+ |> Helper.dump_to_stream()
end
def fetch_all_transactions(address_hash, from_block, to_block, paging_options, acc \\ []) do
@@ -53,7 +50,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
transactions = Chain.address_to_transactions_without_rewards(address_hash, options)
new_acc = transactions ++ acc
- case Enum.split(transactions, @page_size) do
+ case Enum.split(transactions, Helper.page_size()) do
{_transactions, [%Transaction{block_number: block_number, index: index}]} ->
new_paging_options = %{@paging_options | key: {block_number, index}}
fetch_all_transactions(address_hash, from_block, to_block, new_paging_options, new_acc)
@@ -63,11 +60,6 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
end
end
- defp dump_to_stream(transactions) do
- transactions
- |> RFC4180.dump_to_stream()
- end
-
defp to_csv_format(transactions, address, exchange_rate) do
row_names = [
"TxHash",
diff --git a/apps/explorer/lib/explorer/chain/csv_export/helper.ex b/apps/explorer/lib/explorer/chain/csv_export/helper.ex
new file mode 100644
index 0000000000..eec8c85595
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/csv_export/helper.ex
@@ -0,0 +1,29 @@
+defmodule Explorer.Chain.CSVExport.Helper do
+ @moduledoc """
+ CSV export helper functions.
+ """
+
+ alias Explorer.Chain
+ alias NimbleCSV.RFC4180
+
+ @page_size 150
+
+ def dump_to_stream(items) do
+ res =
+ items
+ |> RFC4180.dump_to_stream()
+
+ res
+ end
+
+ def page_size do
+ @page_size
+ end
+
+ def block_from_period(from_period, to_period) do
+ from_block = Chain.convert_date_to_min_block(from_period)
+ to_block = Chain.convert_date_to_max_block(to_period)
+
+ {from_block, to_block}
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs
similarity index 97%
rename from apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs
rename to apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs
index 34494dc932..be9a2c4cd6 100644
--- a/apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs
+++ b/apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs
@@ -1,7 +1,8 @@
-defmodule Explorer.Chain.AddressInternalTransactionCsvExporterTest do
+defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporterTest do
use Explorer.DataCase
- alias Explorer.Chain.{AddressInternalTransactionCsvExporter, Wei}
+ alias Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporter
+ alias Explorer.Chain.Wei
describe "export/3" do
test "exports address internal transactions to csv" do
diff --git a/apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs
similarity index 98%
rename from apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs
rename to apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs
index fe1d0601a8..e7fd8b7ec3 100644
--- a/apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs
+++ b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs
@@ -1,7 +1,7 @@
defmodule Explorer.Chain.AddressLogCsvExporterTest do
use Explorer.DataCase
- alias Explorer.Chain.AddressLogCsvExporter
+ alias Explorer.Chain.CSVExport.AddressLogCsvExporter
describe "export/3" do
test "exports address logs to csv" do
diff --git a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs
similarity index 98%
rename from apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs
rename to apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs
index c64ecafa99..ca584c19fa 100644
--- a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs
+++ b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs
@@ -1,7 +1,7 @@
defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do
use Explorer.DataCase
- alias Explorer.Chain.AddressTokenTransferCsvExporter
+ alias Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter
describe "export/3" do
test "exports token transfers to csv" do
diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs
similarity index 97%
rename from apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs
rename to apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs
index 7145e9c507..2035b3465c 100644
--- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs
+++ b/apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs
@@ -1,7 +1,8 @@
defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
use Explorer.DataCase
- alias Explorer.Chain.{AddressTransactionCsvExporter, Wei}
+ alias Explorer.Chain.CSVExport.AddressTransactionCsvExporter
+ alias Explorer.Chain.Wei
describe "export/3" do
test "exports address transactions to csv" do
diff --git a/config/runtime.exs b/config/runtime.exs
index 3fc77d3f9b..efd1602d0e 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -23,8 +23,6 @@ config :block_scout_web,
dark_forest_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST"),
dark_forest_addresses_v_0_5: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5"),
circles_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_CIRCLES"),
- re_captcha_secret_key: System.get_env("RE_CAPTCHA_SECRET_KEY"),
- re_captcha_client_key: System.get_env("RE_CAPTCHA_CLIENT_KEY"),
new_tags: System.get_env("NEW_TAGS"),
chain_id: System.get_env("CHAIN_ID"),
json_rpc: System.get_env("JSON_RPC"),
@@ -35,6 +33,12 @@ config :block_scout_web,
hide_block_miner: ConfigHelper.parse_bool_env_var("HIDE_BLOCK_MINER"),
show_tenderly_link: ConfigHelper.parse_bool_env_var("SHOW_TENDERLY_LINK")
+config :block_scout_web, :recaptcha,
+ v2_client_key: System.get_env("RE_CAPTCHA_CLIENT_KEY"),
+ v2_secret_key: System.get_env("RE_CAPTCHA_SECRET_KEY"),
+ v3_client_key: System.get_env("RE_CAPTCHA_V3_CLIENT_KEY"),
+ v3_secret_key: System.get_env("RE_CAPTCHA_V3_SECRET_KEY")
+
network_path =
"NETWORK_PATH"
|> System.get_env("/")
diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env
index b17c9fd817..039e6968da 100644
--- a/docker-compose/envs/common-blockscout.env
+++ b/docker-compose/envs/common-blockscout.env
@@ -141,6 +141,8 @@ SHOW_TENDERLY_LINK=false
TENDERLY_CHAIN_PATH=
RE_CAPTCHA_SECRET_KEY=
RE_CAPTCHA_CLIENT_KEY=
+RE_CAPTCHA_V3_SECRET_KEY=
+RE_CAPTCHA_V3_CLIENT_KEY=
JSON_RPC=
#API_RATE_LIMIT_DISABLED=true
API_RATE_LIMIT_TIME_INTERVAL=1s
diff --git a/docker/Makefile b/docker/Makefile
index dd883a704a..7854471ec2 100644
--- a/docker/Makefile
+++ b/docker/Makefile
@@ -348,6 +348,12 @@ endif
ifdef RE_CAPTCHA_CLIENT_KEY
BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_CLIENT_KEY=$(RE_CAPTCHA_CLIENT_KEY)'
endif
+ifdef RE_CAPTCHA_V3_SECRET_KEY
+ BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_V3_SECRET_KEY=$(RE_CAPTCHA_V3_SECRET_KEY)'
+endif
+ifdef RE_CAPTCHA_V3_CLIENT_KEY
+ BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_V3_CLIENT_KEY=$(RE_CAPTCHA_V3_CLIENT_KEY)'
+endif
ifdef CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD
BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=$(CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD)'
endif