commit
312b6309ff
@ -1,17 +0,0 @@ |
||||
import Path from 'path-parser' |
||||
import URI from 'urijs' |
||||
import humps from 'humps' |
||||
|
||||
export default { |
||||
when (pattern, { exactPathMatch } = { exactPathMatch: false }) { |
||||
return new Promise((resolve) => { |
||||
const path = Path.createPath(pattern || '/') |
||||
const match = exactPathMatch ? path.test(window.location.pathname) : path.partialTest(window.location.pathname) |
||||
if (match) { |
||||
const routeParams = humps.camelizeKeys(match) |
||||
const queryParams = humps.camelizeKeys(URI(window.location).query(true)) |
||||
resolve(Object.assign({}, queryParams, routeParams)) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
defmodule BlockScoutWeb.AddressTokenController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.{Chain, Market} |
||||
alias Explorer.ExchangeRates.Token |
||||
|
||||
import BlockScoutWeb.AddressController, only: [transaction_count: 1] |
||||
import BlockScoutWeb.Chain, only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1] |
||||
|
||||
def index(conn, %{"address_id" => address_hash_string} = params) do |
||||
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), |
||||
{:ok, address} <- Chain.hash_to_address(address_hash) do |
||||
tokens_plus_one = Chain.tokens_with_number_of_transfers_from_address(address_hash, paging_options(params)) |
||||
{tokens, next_page} = split_list_by_page(tokens_plus_one) |
||||
|
||||
render( |
||||
conn, |
||||
"index.html", |
||||
address: address, |
||||
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), |
||||
transaction_count: transaction_count(address), |
||||
next_page_params: next_page_params(next_page, tokens, params), |
||||
tokens: tokens |
||||
) |
||||
else |
||||
:error -> |
||||
unprocessable_entity(conn) |
||||
|
||||
{:error, :not_found} -> |
||||
not_found(conn) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,57 @@ |
||||
defmodule BlockScoutWeb.API.RPC.ContractController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.Chain |
||||
|
||||
def getabi(conn, params) do |
||||
with {:address_param, {:ok, address_param}} <- fetch_address(params), |
||||
{:format, {:ok, address_hash}} <- to_address_hash(address_param), |
||||
{:contract, {:ok, contract}} <- to_smart_contract(address_hash) do |
||||
render(conn, :getabi, %{abi: contract.abi}) |
||||
else |
||||
{:address_param, :error} -> |
||||
render(conn, :error, error: "Query parameter address is required") |
||||
|
||||
{:format, :error} -> |
||||
render(conn, :error, error: "Invalid address hash") |
||||
|
||||
{:contract, :not_found} -> |
||||
render(conn, :error, error: "Contract source code not verified") |
||||
end |
||||
end |
||||
|
||||
def getsourcecode(conn, params) do |
||||
with {:address_param, {:ok, address_param}} <- fetch_address(params), |
||||
{:format, {:ok, address_hash}} <- to_address_hash(address_param), |
||||
{:contract, {:ok, contract}} <- to_smart_contract(address_hash) do |
||||
render(conn, :getsourcecode, %{contract: contract}) |
||||
else |
||||
{:address_param, :error} -> |
||||
render(conn, :error, error: "Query parameter address is required") |
||||
|
||||
{:format, :error} -> |
||||
render(conn, :error, error: "Invalid address hash") |
||||
|
||||
{:contract, :not_found} -> |
||||
render(conn, :getsourcecode, %{contract: nil}) |
||||
end |
||||
end |
||||
|
||||
defp fetch_address(params) do |
||||
{:address_param, Map.fetch(params, "address")} |
||||
end |
||||
|
||||
defp to_address_hash(address_hash_string) do |
||||
{:format, Chain.string_to_address_hash(address_hash_string)} |
||||
end |
||||
|
||||
defp to_smart_contract(address_hash) do |
||||
result = |
||||
case Chain.address_hash_to_smart_contract(address_hash) do |
||||
nil -> :not_found |
||||
contract -> {:ok, contract} |
||||
end |
||||
|
||||
{:contract, result} |
||||
end |
||||
end |
@ -0,0 +1,34 @@ |
||||
defmodule BlockScoutWeb.API.RPC.TransactionController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.Chain |
||||
|
||||
def gettxreceiptstatus(conn, params) do |
||||
with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), |
||||
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param) do |
||||
status = to_transaction_status(transaction_hash) |
||||
render(conn, :gettxreceiptstatus, %{status: status}) |
||||
else |
||||
{:txhash_param, :error} -> |
||||
render(conn, :error, error: "Query parameter txhash is required") |
||||
|
||||
{:format, :error} -> |
||||
render(conn, :error, error: "Invalid txhash format") |
||||
end |
||||
end |
||||
|
||||
defp fetch_txhash(params) do |
||||
{:txhash_param, Map.fetch(params, "txhash")} |
||||
end |
||||
|
||||
defp to_transaction_hash(transaction_hash_string) do |
||||
{:format, Chain.string_to_transaction_hash(transaction_hash_string)} |
||||
end |
||||
|
||||
defp to_transaction_status(transaction_hash) do |
||||
case Chain.hash_to_transaction(transaction_hash) do |
||||
{:error, :not_found} -> "" |
||||
{:ok, transaction} -> transaction.status |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,36 @@ |
||||
defmodule BlockScoutWeb.Tokens.HolderController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.Chain |
||||
|
||||
import BlockScoutWeb.Chain, |
||||
only: [ |
||||
split_list_by_page: 1, |
||||
paging_options: 1, |
||||
next_page_params: 3 |
||||
] |
||||
|
||||
def index(conn, %{"token_id" => address_hash_string} = params) do |
||||
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), |
||||
{:ok, token} <- Chain.token_from_address_hash(address_hash), |
||||
token_balances <- Chain.fetch_token_holders_from_token_hash(address_hash, paging_options(params)) do |
||||
{token_balances_paginated, next_page} = split_list_by_page(token_balances) |
||||
|
||||
render( |
||||
conn, |
||||
"index.html", |
||||
token: token, |
||||
token_balances: token_balances_paginated, |
||||
total_token_holders: Chain.count_token_holders_from_token_hash(address_hash), |
||||
total_token_transfers: Chain.count_token_transfers_from_token_hash(address_hash), |
||||
next_page_params: next_page_params(next_page, token_balances_paginated, params) |
||||
) |
||||
else |
||||
:error -> |
||||
not_found(conn) |
||||
|
||||
{:error, :not_found} -> |
||||
not_found(conn) |
||||
end |
||||
end |
||||
end |
@ -1,5 +1,5 @@ |
||||
<%= if @address_hash do %> |
||||
<%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address_hash), "data-test": "address_hash_link" do %> |
||||
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: @address_hash, contract: @contract, truncate: assigns[:truncate] %> |
||||
<%= if @address do %> |
||||
<%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address), "data-test": "address_hash_link" do %> |
||||
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address: @address, contract: @contract, truncate: assigns[:truncate] %> |
||||
<% end %> |
||||
<% end %> |
||||
|
@ -1,8 +1,13 @@ |
||||
<span class="<%= if @contract do %>contract-address<% end %>" data-address-hash="<%= @address_hash %>"> |
||||
<%= if assigns[:truncate] do %> |
||||
<%= BlockScoutWeb.AddressView.trimmed_hash(@address_hash) %> |
||||
<span class="<%= if @contract do %>contract-address<% end %>" data-address-hash="<%= @address.hash %>"> |
||||
<%= if name = primary_name(@address) do %> |
||||
<span class="d-none d-md-none d-lg-inline" title="<%= @address.hash %>"><%= name %></span> |
||||
<span class="d-md-inline-block d-lg-none " title="<%= @address.hash %>"><%= name %></span> |
||||
<% else %> |
||||
<span class="d-none d-md-none d-lg-inline"><%= @address_hash %></span> |
||||
<span class="d-md-inline-block d-lg-none"><%= BlockScoutWeb.AddressView.trimmed_hash(@address_hash) %></span> |
||||
<% end%> |
||||
<%= if assigns[:truncate] do %> |
||||
<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %> |
||||
<% else %> |
||||
<span class="d-none d-md-none d-lg-inline"><%= @address.hash %></span> |
||||
<span class="d-md-inline-block d-lg-none"><%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %></span> |
||||
<% end %> |
||||
<% end %> |
||||
</span> |
||||
|
@ -1,11 +0,0 @@ |
||||
<div class="tile tile-muted tile-function-response monospace"> |
||||
[ <%= @function_name %> method Response ] |
||||
|
||||
<p class="text-dark"> |
||||
[ |
||||
<%= for item <- @outputs do %> |
||||
<span class="text-dark function-response-item"><%= if named_argument?(item) do %><%= item["name"] %><% end %>(<%= item["type"] %>) : <%= item["value"] %></span> |
||||
<% end %> |
||||
] |
||||
</p> |
||||
</div> |
@ -0,0 +1,10 @@ |
||||
<div class="tile tile-type-token"> |
||||
<div class="row justify-content"> |
||||
<div class="col-md-12 d-flex flex-column tile-label"> |
||||
<%= link(to: token_path(@conn, :show, @token.contract_address_hash), class: "tile-title-lg") do %> |
||||
<%= token_name(@token) %> |
||||
<% end %> |
||||
<span><%= @token.type %> - <%= number_of_transfers(@token) %></span> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,132 @@ |
||||
<section class="container"> |
||||
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %> |
||||
|
||||
<section> |
||||
<div class="card"> |
||||
<div class="card-header"> |
||||
<!-- DESKTOP TAB NAV --> |
||||
<ul class="nav nav-tabs card-header-tabs d-none d-md-inline-flex"> |
||||
<li class="nav-item"> |
||||
<%= link( |
||||
gettext("Transactions"), |
||||
class: "nav-link", |
||||
to: address_transaction_path(@conn, :index, @address.hash) |
||||
) %> |
||||
</li> |
||||
|
||||
<li class="nav-item"> |
||||
<%= link( |
||||
gettext("Tokens"), |
||||
class: "nav-link active", |
||||
to: address_token_path(@conn, :index, @address.hash) |
||||
) %> |
||||
</li> |
||||
|
||||
<li class="nav-item"> <%= link( |
||||
gettext("Internal Transactions"), |
||||
class: "nav-link", |
||||
"data-test": "internal_transactions_tab_link", |
||||
to: address_internal_transaction_path(@conn, :index, @address.hash) |
||||
) %> |
||||
</li> |
||||
|
||||
<%= if AddressView.contract?(@address) do %> |
||||
<li class="nav-item"> |
||||
<%= link( |
||||
to: address_contract_path(@conn, :index, @address.hash), |
||||
class: "nav-link") do %> |
||||
<%= gettext("Code") %> |
||||
|
||||
<%= if AddressView.smart_contract_verified?(@address) do %> |
||||
<i class="far fa-check-circle"></i> |
||||
<% end %> |
||||
<% end %> |
||||
</li> |
||||
<% end %> |
||||
|
||||
<%= if AddressView.smart_contract_with_read_only_functions?(@address) do %> |
||||
<li class="nav-item"> |
||||
<%= link( |
||||
gettext("Read Contract"), |
||||
to: address_read_contract_path(@conn, :index, @address.hash), |
||||
class: "nav-link")%> |
||||
</li> |
||||
<% end %> |
||||
<%= if AddressView.smart_contract_with_read_only_functions?(@address) do %> |
||||
<li class="nav-item"> |
||||
<%= link( |
||||
gettext("Read Contract"), |
||||
to: address_read_contract_path(@conn, :index, @address.hash), |
||||
class: "nav-link")%> |
||||
</li> |
||||
<% end %> |
||||
</ul> |
||||
|
||||
<!-- MOBILE DROPDOWN NAV --> |
||||
<ul class="nav nav-tabs card-header-tabs d-md-none"> |
||||
<li class="nav-item dropdown flex-fill text-center"> |
||||
<a class="nav-link active dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"><%= gettext "Tokens" %></a> |
||||
|
||||
<div class="dropdown-menu"> |
||||
<%= link( |
||||
gettext("Transactions"), |
||||
class: "dropdown-item", |
||||
to: address_transaction_path(@conn, :index, @address.hash) |
||||
) %> |
||||
<%= link( |
||||
gettext("Tokens"), |
||||
class: "dropdown-item", |
||||
to: address_token_path(@conn, :index, @address.hash) |
||||
) %> |
||||
<%= link( |
||||
gettext("Internal Transactions"), |
||||
class: "dropdown-item", |
||||
"data-test": "internal_transactions_tab_link", |
||||
to: address_internal_transaction_path(@conn, :index, @address.hash) |
||||
) %> |
||||
<%= if AddressView.contract?(@address) do %> |
||||
<%= link( |
||||
to: address_contract_path(@conn, :index, @address.hash), |
||||
class: "dropdown-item") do %> |
||||
<%= gettext("Code") %> |
||||
|
||||
<%= if AddressView.smart_contract_verified?(@address) do %> |
||||
<i class="far fa-check-circle"></i> |
||||
<% end %> |
||||
<% end %> |
||||
<% end %> |
||||
</div> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
|
||||
<div class="card-body"> |
||||
<h2 class="card-title"><%= gettext "Tokens" %></h2> |
||||
<%= if Enum.any?(@tokens) do %> |
||||
<%= for token <- @tokens do %> |
||||
<%= render "_tokens.html", conn: @conn, token: token %> |
||||
<% end %> |
||||
<% else %> |
||||
<div class="tile tile-muted text-center"> |
||||
<span><%= gettext "There are no tokens for this address." %></span> |
||||
</div> |
||||
<% end %> |
||||
|
||||
<div> |
||||
<%= if @next_page_params do %> |
||||
<%= link( |
||||
gettext("Next"), |
||||
class: "button button-secondary button-sm float-right", |
||||
to: address_token_path( |
||||
@conn, |
||||
:index, |
||||
@address, |
||||
@next_page_params |
||||
) |
||||
) %> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
</section> |
@ -1,11 +1,20 @@ |
||||
<div class="tile tile-muted tile-function-response monospace"> |
||||
[ <%= @function_name %> method Response ] |
||||
<pre class="pre-wrap"> |
||||
<code> |
||||
[ <%= @function_name %> method Response ] |
||||
|
||||
<p class="text-dark"> |
||||
[ |
||||
<%= for item <- @outputs do %> |
||||
<span class="text-dark function-response-item"><%= if named_argument?(item) do %><%= item["name"] %><% end %>(<%= item["type"] %>) : <%= item["value"] %></span> |
||||
<% end %> |
||||
] |
||||
</p> |
||||
<p class="text-dark"> |
||||
[ |
||||
<%= for item <- @outputs do %> |
||||
<span class="text-dark function-response-item"> |
||||
<%= if named_argument?(item) do %> |
||||
<%= item["name"] %> |
||||
<% end %> |
||||
(<%= item["type"] %>) : <%= values(item["value"]) %> |
||||
</span> |
||||
<% end %> |
||||
] |
||||
</p> |
||||
</code> |
||||
</pre> |
||||
</div> |
||||
|
@ -0,0 +1,19 @@ |
||||
<div class="tile tile-type-token fade-in mb-10" data-test="token_holders"> |
||||
<div class="row"> |
||||
<div class="col-md-7 col-lg-8 d-flex flex-column"> |
||||
<span> |
||||
<%= render BlockScoutWeb.AddressView, "_link.html", address: @token_balance.address, contract: BlockScoutWeb.AddressView.contract?(@token_balance.address) %> |
||||
</span> |
||||
|
||||
<span> |
||||
<span class="text-dark"> |
||||
<%= format_token_balance_value(@token_balance.value, @token) %> <%= @token.symbol %> |
||||
</span> |
||||
|
||||
<%= if @token.total_supply > 0 do %> |
||||
(<%= total_supply_percentage(@token_balance.value, @token.total_supply) %>) |
||||
<% end %> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,95 @@ |
||||
<section class="container"> |
||||
<%= render( |
||||
OverviewView, |
||||
"_details.html", |
||||
token: @token, |
||||
total_token_transfers: @total_token_transfers, |
||||
total_token_holders: @total_token_holders, |
||||
conn: @conn |
||||
) %> |
||||
|
||||
<section> |
||||
<div class="card"> |
||||
<div class="card-header"> |
||||
<!-- DESKTOP TAB NAV --> |
||||
<ul class="nav nav-tabs card-header-tabs d-none d-md-inline-flex"> |
||||
<li class="nav-item"> |
||||
<%= link( |
||||
gettext("Token Transfers"), |
||||
class: "nav-link", |
||||
to: token_path(@conn, :show, @token.contract_address_hash) |
||||
) %> |
||||
</li> |
||||
|
||||
<%= if TokenView.smart_contract_with_read_only_functions?(@token) do %> |
||||
<li class="nav-item"> |
||||
<%= link( |
||||
gettext("Read Contract"), |
||||
to: token_read_contract_path(@conn, :index, @token.contract_address_hash), |
||||
class: "nav-link")%> |
||||
</li> |
||||
<% end %> |
||||
|
||||
<li class="nav-item"> |
||||
<%= link( |
||||
gettext("Token Holders"), |
||||
class: "nav-link active", |
||||
"data-test": "token_holders_tab", |
||||
to: token_holder_path(@conn, :index, @token.contract_address_hash) |
||||
) %> |
||||
</li> |
||||
</ul> |
||||
|
||||
<!-- MOBILE DROPDOWN NAV --> |
||||
<ul class="nav nav-tabs card-header-tabs d-md-none"> |
||||
<li class="nav-item dropdown flex-fill text-center"> |
||||
<a class="nav-link active dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"><%= gettext("Token Holders") %></a> |
||||
<div class="dropdown-menu"> |
||||
<%= link( |
||||
gettext("Token Transfers"), |
||||
class: "dropdown-item", |
||||
to: token_path(@conn, :show, @token.contract_address_hash) |
||||
) %> |
||||
<%= if TokenView.smart_contract_with_read_only_functions?(@token) do %> |
||||
<%= link( |
||||
gettext("Read Contract"), |
||||
to: "#", |
||||
class: "dropdown-item")%> |
||||
<% end %> |
||||
<%= link( |
||||
gettext("Token Holders"), |
||||
class: "dropdown-item", |
||||
to: token_holder_path(@conn, :index, @token.contract_address_hash) |
||||
) %> |
||||
</div> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
|
||||
<!-- Token Holders --> |
||||
<div class="card-body"> |
||||
<h2 class="card-title"><%= gettext "Token Holders" %></h2> |
||||
|
||||
<%= if Enum.any?(@token_balances) do %> |
||||
<%= for token_balance <- @token_balances do %> |
||||
<%= render "_token_balances.html", token: @token, token_balance: token_balance %> |
||||
<% end %> |
||||
<% else %> |
||||
<div class="tile tile-muted text-center"> |
||||
<span data-selector="empty-transactions-list"> |
||||
<%= gettext "There are no holders for this Token." %> |
||||
</span> |
||||
</div> |
||||
<% end %> |
||||
|
||||
<%= if @next_page_params do %> |
||||
<%= link( |
||||
gettext("Next Page"), |
||||
class: "button button-secondary button-small float-right mt-4", |
||||
to: token_holder_path(@conn, :index, @token.contract_address_hash, @next_page_params) |
||||
) %> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
</section> |
@ -0,0 +1,9 @@ |
||||
defmodule BlockScoutWeb.AddressTokenView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
alias BlockScoutWeb.AddressView |
||||
|
||||
def number_of_transfers(token) do |
||||
ngettext("%{count} transfer", "%{count} transfers", token.number_of_transfers) |
||||
end |
||||
end |
@ -0,0 +1,41 @@ |
||||
defmodule BlockScoutWeb.API.RPC.ContractView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
alias BlockScoutWeb.API.RPC.RPCView |
||||
|
||||
def render("getabi.json", %{abi: abi}) do |
||||
RPCView.render("show.json", data: Jason.encode!(abi)) |
||||
end |
||||
|
||||
def render("getsourcecode.json", %{contract: contract}) do |
||||
RPCView.render("show.json", data: prepare_contract(contract)) |
||||
end |
||||
|
||||
def render("error.json", assigns) do |
||||
RPCView.render("error.json", assigns) |
||||
end |
||||
|
||||
defp prepare_contract(nil) do |
||||
[ |
||||
%{ |
||||
"SourceCode" => "", |
||||
"ABI" => "Contract source code not verified", |
||||
"ContractName" => "", |
||||
"CompilerVersion" => "", |
||||
"OptimizationUsed" => "" |
||||
} |
||||
] |
||||
end |
||||
|
||||
defp prepare_contract(contract) do |
||||
[ |
||||
%{ |
||||
"SourceCode" => contract.contract_source_code, |
||||
"ABI" => Jason.encode!(contract.abi), |
||||
"ContractName" => contract.name, |
||||
"CompilerVersion" => contract.compiler_version, |
||||
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0") |
||||
} |
||||
] |
||||
end |
||||
end |
@ -0,0 +1,22 @@ |
||||
defmodule BlockScoutWeb.API.RPC.TransactionView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
alias BlockScoutWeb.API.RPC.RPCView |
||||
|
||||
def render("gettxreceiptstatus.json", %{status: status}) do |
||||
prepared_status = prepare_tx_receipt_status(status) |
||||
RPCView.render("show.json", data: %{"status" => prepared_status}) |
||||
end |
||||
|
||||
def render("error.json", assigns) do |
||||
RPCView.render("error.json", assigns) |
||||
end |
||||
|
||||
defp prepare_tx_receipt_status(""), do: "" |
||||
|
||||
defp prepare_tx_receipt_status(nil), do: "" |
||||
|
||||
defp prepare_tx_receipt_status(:ok), do: "1" |
||||
|
||||
defp prepare_tx_receipt_status(_), do: "0" |
||||
end |
@ -0,0 +1,50 @@ |
||||
defmodule BlockScoutWeb.Tokens.HolderView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
alias BlockScoutWeb.Tokens.{OverviewView, TokenView} |
||||
alias Explorer.Chain.{Token} |
||||
|
||||
@doc """ |
||||
Calculates the percentage of the value from the given total supply. |
||||
|
||||
## Examples |
||||
|
||||
iex> value = Decimal.new(200) |
||||
iex> total_supply = Decimal.new(1000) |
||||
iex> BlockScoutWeb.Tokens.HolderView.total_supply_percentage(value, total_supply) |
||||
"20.0000%" |
||||
|
||||
""" |
||||
def total_supply_percentage(value, total_supply) do |
||||
result = |
||||
value |
||||
|> Decimal.div(total_supply) |
||||
|> Decimal.mult(100) |
||||
|> Decimal.round(4) |
||||
|> Decimal.to_string() |
||||
|
||||
result <> "%" |
||||
end |
||||
|
||||
@doc """ |
||||
Formats the token balance value according to the Token's type. |
||||
|
||||
## Examples |
||||
|
||||
iex> token = build(:token, type: "ERC-20", decimals: 2) |
||||
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(100000, token) |
||||
"1,000" |
||||
|
||||
iex> token = build(:token, type: "ERC-721") |
||||
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(1, token) |
||||
1 |
||||
|
||||
""" |
||||
def format_token_balance_value(value, %Token{type: "ERC-20", decimals: decimals}) do |
||||
format_according_to_decimals(value, decimals) |
||||
end |
||||
|
||||
def format_token_balance_value(value, _token) do |
||||
value |
||||
end |
||||
end |
@ -0,0 +1,110 @@ |
||||
defmodule BlockScoutWeb.AddressTokenControllerTest do |
||||
use BlockScoutWeb.ConnCase |
||||
|
||||
import BlockScoutWeb.Router.Helpers, only: [address_token_path: 3] |
||||
|
||||
alias Explorer.Chain.{Token} |
||||
|
||||
describe "GET index/2" do |
||||
test "with invalid address hash", %{conn: conn} do |
||||
conn = get(conn, address_token_path(conn, :index, "invalid_address")) |
||||
|
||||
assert html_response(conn, 422) |
||||
end |
||||
|
||||
test "with valid address hash without address", %{conn: conn} do |
||||
conn = get(conn, address_token_path(conn, :index, "0x8bf38d4764929064f2d4d3a56520a76ab3df415b")) |
||||
|
||||
assert html_response(conn, 404) |
||||
end |
||||
|
||||
test "returns tokens for the address", %{conn: conn} do |
||||
address = insert(:address) |
||||
|
||||
token1 = |
||||
:token |
||||
|> insert(name: "token1") |
||||
|
||||
token2 = |
||||
:token |
||||
|> insert(name: "token2") |
||||
|
||||
insert( |
||||
:token_transfer, |
||||
token_contract_address: token1.contract_address, |
||||
from_address: address, |
||||
to_address: build(:address) |
||||
) |
||||
|
||||
insert( |
||||
:token_transfer, |
||||
token_contract_address: token2.contract_address, |
||||
from_address: build(:address), |
||||
to_address: address |
||||
) |
||||
|
||||
conn = get(conn, address_token_path(conn, :index, address)) |
||||
|
||||
actual_token_hashes = |
||||
conn.assigns.tokens |
||||
|> Enum.map(& &1.contract_address_hash) |
||||
|
||||
assert html_response(conn, 200) |
||||
assert Enum.member?(actual_token_hashes, token1.contract_address_hash) |
||||
assert Enum.member?(actual_token_hashes, token2.contract_address_hash) |
||||
end |
||||
|
||||
test "returns next page of results based on last seen token", %{conn: conn} do |
||||
address = insert(:address) |
||||
|
||||
second_page_tokens = |
||||
1..50 |
||||
|> Enum.reduce([], fn i, acc -> |
||||
token = insert(:token, name: "A Token#{i}", type: "ERC-20") |
||||
insert(:token_transfer, token_contract_address: token.contract_address, from_address: address) |
||||
acc ++ [token.name] |
||||
end) |
||||
|> Enum.sort() |
||||
|
||||
token = insert(:token, name: "Another Token", type: "ERC-721") |
||||
insert(:token_transfer, token: token, from_address: address) |
||||
%Token{name: name, type: type} = token |
||||
|
||||
conn = |
||||
get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, address.hash), %{ |
||||
"name" => name, |
||||
"type" => type |
||||
}) |
||||
|
||||
actual_tokens = |
||||
conn.assigns.tokens |
||||
|> Enum.map(& &1.name) |
||||
|> Enum.sort() |
||||
|
||||
assert second_page_tokens == actual_tokens |
||||
end |
||||
|
||||
test "next_page_params exists if not on last page", %{conn: conn} do |
||||
address = insert(:address) |
||||
|
||||
Enum.each(1..51, fn i -> |
||||
token = insert(:token, name: "A Token#{i}", type: "ERC-20") |
||||
insert(:token_transfer, token_contract_address: token.contract_address, from_address: address) |
||||
end) |
||||
|
||||
conn = get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, address.hash)) |
||||
|
||||
assert conn.assigns.next_page_params |
||||
end |
||||
|
||||
test "next_page_params are empty if on last page", %{conn: conn} do |
||||
address = insert(:address) |
||||
token = insert(:token) |
||||
insert(:token_transfer, token_contract_address: token.contract_address, from_address: address) |
||||
|
||||
conn = get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, address.hash)) |
||||
|
||||
refute conn.assigns.next_page_params |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,172 @@ |
||||
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do |
||||
use BlockScoutWeb.ConnCase |
||||
|
||||
describe "getabi" do |
||||
test "with missing address hash", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "contract", |
||||
"action" => "getabi" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["message"] =~ "address is required" |
||||
assert response["status"] == "0" |
||||
assert Map.has_key?(response, "result") |
||||
refute response["result"] |
||||
end |
||||
|
||||
test "with an invalid address hash", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "contract", |
||||
"action" => "getabi", |
||||
"address" => "badhash" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["message"] =~ "Invalid address hash" |
||||
assert response["status"] == "0" |
||||
assert Map.has_key?(response, "result") |
||||
refute response["result"] |
||||
end |
||||
|
||||
test "with an address that doesn't exist", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "contract", |
||||
"action" => "getabi", |
||||
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["result"] == nil |
||||
assert response["status"] == "0" |
||||
assert response["message"] == "Contract source code not verified" |
||||
end |
||||
|
||||
test "with a verified contract address", %{conn: conn} do |
||||
contract = insert(:smart_contract) |
||||
|
||||
params = %{ |
||||
"module" => "contract", |
||||
"action" => "getabi", |
||||
"address" => to_string(contract.address_hash) |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["result"] == Jason.encode!(contract.abi) |
||||
assert response["status"] == "1" |
||||
assert response["message"] == "OK" |
||||
end |
||||
end |
||||
|
||||
describe "getsourcecode" do |
||||
test "with missing address hash", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "contract", |
||||
"action" => "getsourcecode" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["message"] =~ "address is required" |
||||
assert response["status"] == "0" |
||||
assert Map.has_key?(response, "result") |
||||
refute response["result"] |
||||
end |
||||
|
||||
test "with an invalid address hash", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "contract", |
||||
"action" => "getsourcecode", |
||||
"address" => "badhash" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["message"] =~ "Invalid address hash" |
||||
assert response["status"] == "0" |
||||
assert Map.has_key?(response, "result") |
||||
refute response["result"] |
||||
end |
||||
|
||||
test "with an address that doesn't exist", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "contract", |
||||
"action" => "getsourcecode", |
||||
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" |
||||
} |
||||
|
||||
expected_result = [ |
||||
%{ |
||||
"SourceCode" => "", |
||||
"ABI" => "Contract source code not verified", |
||||
"ContractName" => "", |
||||
"CompilerVersion" => "", |
||||
"OptimizationUsed" => "" |
||||
} |
||||
] |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["result"] == expected_result |
||||
assert response["status"] == "1" |
||||
assert response["message"] == "OK" |
||||
end |
||||
|
||||
test "with a verified contract address", %{conn: conn} do |
||||
contract = insert(:smart_contract, optimization: true) |
||||
|
||||
params = %{ |
||||
"module" => "contract", |
||||
"action" => "getsourcecode", |
||||
"address" => to_string(contract.address_hash) |
||||
} |
||||
|
||||
expected_result = [ |
||||
%{ |
||||
"SourceCode" => contract.contract_source_code, |
||||
"ABI" => Jason.encode!(contract.abi), |
||||
"ContractName" => contract.name, |
||||
"CompilerVersion" => contract.compiler_version, |
||||
# The contract's optimization value is true, so the expected value |
||||
# for `OptimzationUsed` is "1". If it was false, the expected value |
||||
# would be "0". |
||||
"OptimizationUsed" => "1" |
||||
} |
||||
] |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["result"] == expected_result |
||||
assert response["status"] == "1" |
||||
assert response["message"] == "OK" |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,124 @@ |
||||
defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do |
||||
use BlockScoutWeb.ConnCase |
||||
|
||||
describe "gettxreceiptstatus" do |
||||
test "with missing txhash", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "transaction", |
||||
"action" => "gettxreceiptstatus" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["message"] =~ "txhash is required" |
||||
assert response["status"] == "0" |
||||
assert Map.has_key?(response, "result") |
||||
refute response["result"] |
||||
end |
||||
|
||||
test "with an invalid txhash", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "transaction", |
||||
"action" => "gettxreceiptstatus", |
||||
"txhash" => "badhash" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["message"] =~ "Invalid txhash format" |
||||
assert response["status"] == "0" |
||||
assert Map.has_key?(response, "result") |
||||
refute response["result"] |
||||
end |
||||
|
||||
test "with a txhash that doesn't exist", %{conn: conn} do |
||||
params = %{ |
||||
"module" => "transaction", |
||||
"action" => "gettxreceiptstatus", |
||||
"txhash" => "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["result"] == %{"status" => ""} |
||||
assert response["status"] == "1" |
||||
assert response["message"] == "OK" |
||||
end |
||||
|
||||
test "with a txhash with ok status", %{conn: conn} do |
||||
block = insert(:block) |
||||
|
||||
transaction = |
||||
:transaction |
||||
|> insert() |
||||
|> with_block(block, status: :ok) |
||||
|
||||
params = %{ |
||||
"module" => "transaction", |
||||
"action" => "gettxreceiptstatus", |
||||
"txhash" => "#{transaction.hash}" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["result"] == %{"status" => "1"} |
||||
assert response["status"] == "1" |
||||
assert response["message"] == "OK" |
||||
end |
||||
|
||||
test "with a txhash with error status", %{conn: conn} do |
||||
block = insert(:block) |
||||
|
||||
transaction = |
||||
:transaction |
||||
|> insert() |
||||
|> with_block(block, status: :error) |
||||
|
||||
params = %{ |
||||
"module" => "transaction", |
||||
"action" => "gettxreceiptstatus", |
||||
"txhash" => "#{transaction.hash}" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["result"] == %{"status" => "0"} |
||||
assert response["status"] == "1" |
||||
assert response["message"] == "OK" |
||||
end |
||||
|
||||
test "with a txhash with nil status", %{conn: conn} do |
||||
transaction = insert(:transaction, status: nil) |
||||
|
||||
params = %{ |
||||
"module" => "transaction", |
||||
"action" => "gettxreceiptstatus", |
||||
"txhash" => "#{transaction.hash}" |
||||
} |
||||
|
||||
assert response = |
||||
conn |
||||
|> get("/api", params) |
||||
|> json_response(200) |
||||
|
||||
assert response["result"] == %{"status" => ""} |
||||
assert response["status"] == "1" |
||||
assert response["message"] == "OK" |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,92 @@ |
||||
defmodule BlockScoutWeb.Tokens.HolderControllerTest do |
||||
use BlockScoutWeb.ConnCase |
||||
|
||||
alias Explorer.Chain.Hash |
||||
|
||||
describe "GET index/3" do |
||||
test "with invalid address hash", %{conn: conn} do |
||||
conn = get(conn, token_holder_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) |
||||
|
||||
assert html_response(conn, 404) |
||||
end |
||||
|
||||
test "with a token that doesn't exist", %{conn: conn} do |
||||
address = build(:address) |
||||
conn = get(conn, token_holder_path(BlockScoutWeb.Endpoint, :index, address.hash)) |
||||
|
||||
assert html_response(conn, 404) |
||||
end |
||||
|
||||
test "successfully renders the page", %{conn: conn} do |
||||
token = insert(:token) |
||||
|
||||
insert_list( |
||||
2, |
||||
:token_balance, |
||||
token_contract_address_hash: token.contract_address_hash |
||||
) |
||||
|
||||
conn = |
||||
get( |
||||
conn, |
||||
token_holder_path(BlockScoutWeb.Endpoint, :index, token.contract_address_hash) |
||||
) |
||||
|
||||
assert html_response(conn, 200) |
||||
end |
||||
|
||||
test "returns next page of results based on last seen token balance", %{conn: conn} do |
||||
contract_address = build(:contract_address, hash: "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493") |
||||
token = insert(:token, contract_address: contract_address) |
||||
|
||||
second_page_token_balances = |
||||
1..50 |
||||
|> Enum.map( |
||||
&insert( |
||||
:token_balance, |
||||
token_contract_address_hash: token.contract_address_hash, |
||||
value: &1 + 1000 |
||||
) |
||||
) |
||||
|> Enum.map(& &1.value) |
||||
|
||||
token_balance = |
||||
insert( |
||||
:token_balance, |
||||
token_contract_address_hash: token.contract_address_hash, |
||||
value: 50000 |
||||
) |
||||
|
||||
conn = |
||||
get(conn, token_holder_path(conn, :index, token.contract_address_hash), %{ |
||||
"value" => Decimal.to_integer(token_balance.value), |
||||
"address_hash" => Hash.to_string(token_balance.address_hash) |
||||
}) |
||||
|
||||
actual_token_balances = |
||||
conn.assigns.token_balances |
||||
|> Enum.map(& &1.value) |
||||
|> Enum.reverse() |
||||
|
||||
assert second_page_token_balances == actual_token_balances |
||||
end |
||||
|
||||
test "next_page_params exists if not on last page", %{conn: conn} do |
||||
contract_address = build(:contract_address, hash: "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493") |
||||
token = insert(:token, contract_address: contract_address) |
||||
|
||||
Enum.each( |
||||
1..51, |
||||
&insert( |
||||
:token_balance, |
||||
token_contract_address_hash: token.contract_address_hash, |
||||
value: &1 + 1000 |
||||
) |
||||
) |
||||
|
||||
conn = get(conn, token_holder_path(conn, :index, token.contract_address_hash)) |
||||
|
||||
assert conn.assigns.next_page_params |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,23 @@ |
||||
defmodule BlockScoutWeb.TokenPage do |
||||
@moduledoc false |
||||
|
||||
use Wallaby.DSL |
||||
import Wallaby.Query, only: [css: 1, css: 2] |
||||
alias Explorer.Chain.{Address} |
||||
|
||||
def visit_page(session, %Address{hash: address_hash}) do |
||||
visit_page(session, address_hash) |
||||
end |
||||
|
||||
def visit_page(session, contract_address_hash) do |
||||
visit(session, "tokens/#{contract_address_hash}") |
||||
end |
||||
|
||||
def click_tokens_holders(session) do |
||||
click(session, css("[data-test='token_holders_tab']")) |
||||
end |
||||
|
||||
def token_holders(count: count) do |
||||
css("[data-test='token_holders']", count: count) |
||||
end |
||||
end |
@ -0,0 +1,22 @@ |
||||
defmodule BlockScoutWeb.ViewingTokensTest do |
||||
use BlockScoutWeb.FeatureCase, async: true |
||||
|
||||
alias BlockScoutWeb.TokenPage |
||||
|
||||
describe "viewing token holders" do |
||||
test "list the token holders", %{session: session} do |
||||
token = insert(:token) |
||||
|
||||
insert_list( |
||||
2, |
||||
:token_balance, |
||||
token_contract_address_hash: token.contract_address_hash |
||||
) |
||||
|
||||
session |
||||
|> TokenPage.visit_page(token.contract_address) |
||||
|> TokenPage.click_tokens_holders() |
||||
|> assert_has(TokenPage.token_holders(count: 2)) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,25 @@ |
||||
defmodule BlockScoutWeb.AddressTokenViewTest do |
||||
use BlockScoutWeb.ConnCase, async: true |
||||
|
||||
alias BlockScoutWeb.AddressTokenView |
||||
|
||||
describe "number_of_transfers/1" do |
||||
test "returns the singular form when there is only one transfer" do |
||||
token = %{number_of_transfers: 1} |
||||
|
||||
assert AddressTokenView.number_of_transfers(token) == "1 transfer" |
||||
end |
||||
|
||||
test "returns the plural form when there is more than one transfer" do |
||||
token = %{number_of_transfers: 2} |
||||
|
||||
assert AddressTokenView.number_of_transfers(token) == "2 transfers" |
||||
end |
||||
|
||||
test "returns the plural form when there are 0 transfers" do |
||||
token = %{number_of_transfers: 0} |
||||
|
||||
assert AddressTokenView.number_of_transfers(token) == "0 transfers" |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,40 @@ |
||||
defmodule BlockScoutWeb.Tokens.HolderViewTest do |
||||
use BlockScoutWeb.ConnCase, async: true |
||||
|
||||
alias BlockScoutWeb.Tokens.HolderView |
||||
alias Explorer.Chain.{Address.TokenBalance, Token} |
||||
|
||||
doctest BlockScoutWeb.Tokens.HolderView, import: true |
||||
|
||||
describe "total_supply_percentage/2" do |
||||
test "returns the percentage of the Token total supply" do |
||||
%Token{total_supply: total_supply} = build(:token, total_supply: 1000) |
||||
%TokenBalance{value: value} = build(:token_balance, value: 200) |
||||
|
||||
assert HolderView.total_supply_percentage(value, total_supply) == "20.0000%" |
||||
end |
||||
|
||||
test "considers 4 decimals" do |
||||
%Token{total_supply: total_supply} = build(:token, total_supply: 100_000_009) |
||||
%TokenBalance{value: value} = build(:token_balance, value: 500) |
||||
|
||||
assert HolderView.total_supply_percentage(value, total_supply) == "0.0005%" |
||||
end |
||||
end |
||||
|
||||
describe "format_token_balance_value/1" do |
||||
test "formats according to token decimals when it's a ERC-20" do |
||||
token = build(:token, type: "ERC-20", decimals: 2) |
||||
token_balance = build(:token_balance, value: 2_000_000) |
||||
|
||||
assert HolderView.format_token_balance_value(token_balance.value, token) == "20,000" |
||||
end |
||||
|
||||
test "returns the value when it's ERC-721" do |
||||
token = build(:token, type: "ERC-721") |
||||
token_balance = build(:token_balance, value: 1) |
||||
|
||||
assert HolderView.format_token_balance_value(token_balance.value, token) == 1 |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,73 @@ |
||||
defmodule BlockScoutWeb.SmartContractViewTest do |
||||
use BlockScoutWeb.ConnCase, async: true |
||||
|
||||
alias BlockScoutWeb.SmartContractView |
||||
|
||||
describe "queryable?" do |
||||
test "returns true when there are inputs" do |
||||
inputs = [%{"name" => "_narcoId", "type" => "uint256"}] |
||||
|
||||
assert SmartContractView.queryable?(inputs) |
||||
end |
||||
|
||||
test "returns false when there are no inputs" do |
||||
inputs = [] |
||||
|
||||
refute SmartContractView.queryable?(inputs) |
||||
end |
||||
end |
||||
|
||||
describe "address?" do |
||||
test "returns true when the type is equal to the string 'address'" do |
||||
type = "address" |
||||
|
||||
assert SmartContractView.address?(type) |
||||
end |
||||
|
||||
test "returns false when the type is not equal the string 'address'" do |
||||
type = "name" |
||||
|
||||
refute SmartContractView.address?(type) |
||||
end |
||||
end |
||||
|
||||
describe "named_argument?/1" do |
||||
test "returns false when name is blank" do |
||||
arguments = %{"name" => ""} |
||||
|
||||
refute SmartContractView.named_argument?(arguments) |
||||
end |
||||
|
||||
test "returns false when name is nil" do |
||||
arguments = %{"name" => nil} |
||||
|
||||
refute SmartContractView.named_argument?(arguments) |
||||
end |
||||
|
||||
test "returns true when there is name" do |
||||
arguments = %{"name" => "POA"} |
||||
|
||||
assert SmartContractView.named_argument?(arguments) |
||||
end |
||||
|
||||
test "returns false arguments don't match" do |
||||
arguments = nil |
||||
|
||||
refute SmartContractView.named_argument?(arguments) |
||||
end |
||||
end |
||||
|
||||
describe "values/1" do |
||||
test "joins the values when it is a list" do |
||||
values = [8, 6, 9, 2, 2, 37] |
||||
|
||||
assert SmartContractView.values(values) == "8,6,9,2,2,37" |
||||
end |
||||
|
||||
test "returns the value" do |
||||
value = "POA" |
||||
|
||||
assert SmartContractView.values(value) == "POA" |
||||
end |
||||
end |
||||
end |
@ -1 +1,13 @@ |
||||
use Mix.Config |
||||
|
||||
# Configure your database |
||||
config :explorer, Explorer.Repo, |
||||
adapter: Ecto.Adapters.Postgres, |
||||
database: "explorer_dev", |
||||
hostname: "localhost", |
||||
username: "postgres", |
||||
password: "<REPLACE WITH THE PASSWORD YOU CHOSE>", |
||||
loggers: [], |
||||
pool_size: 20, |
||||
pool_timeout: 60_000, |
||||
timeout: 80_000 |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue