Merge pull request #3584 from poanetwork/vb-token-hoders-api-endpoint

Token holders API endpoint
pull/3600/head
Victor Baranov 4 years ago committed by GitHub
commit 085d342162
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 31
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex
  3. 84
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  4. 12
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex
  5. 2
      apps/explorer/lib/explorer/chain.ex

@ -1,6 +1,7 @@
## Current
### Features
- [#3584](https://github.com/poanetwork/blockscout/pull/3584) - Token holders API endpoint
- [#3564](https://github.com/poanetwork/blockscout/pull/3564) - Staking welcome message
### Fixes

@ -1,7 +1,8 @@
defmodule BlockScoutWeb.API.RPC.TokenController do
use BlockScoutWeb, :controller
alias Explorer.Chain
alias BlockScoutWeb.API.RPC.Helpers
alias Explorer.{Chain, PagingOptions}
def gettoken(conn, params) do
with {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params),
@ -20,6 +21,34 @@ defmodule BlockScoutWeb.API.RPC.TokenController do
end
end
def gettokenholders(conn, params) do
with pagination_options <- Helpers.put_pagination_options(%{}, params),
{:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params),
{:format, {:ok, address_hash}} <- to_address_hash(contractaddress_param) do
options_with_defaults =
pagination_options
|> Map.put_new(:page_number, 0)
|> Map.put_new(:page_size, 10)
options = [
paging_options: %PagingOptions{
key: nil,
page_number: options_with_defaults.page_number,
page_size: options_with_defaults.page_size
}
]
token_holders = Chain.fetch_token_holders_from_token_hash(address_hash, options)
render(conn, "gettokenholders.json", %{token_holders: token_holders})
else
{:contractaddress_param, :error} ->
render(conn, :error, error: "Query parameter contract address is required")
{:format, :error} ->
render(conn, :error, error: "Invalid contract address hash")
end
end
defp fetch_contractaddress(params) do
{:contractaddress_param, Map.fetch(params, "contractaddress")}
end

@ -276,6 +276,23 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil
}
@token_gettokenholders_example_value %{
"status" => "1",
"message" => "OK",
"result" => [
%{
"address" => "0x0000000000000000000000000000000000000000",
"value" => "965208500001258757122850"
}
]
}
@token_gettokenholders_example_value_error %{
"status" => "0",
"message" => "Invalid contract address format",
"result" => nil
}
@stats_tokensupply_example_value %{
"status" => "1",
"message" => "OK",
@ -664,6 +681,18 @@ defmodule BlockScoutWeb.Etherscan do
}
}
@token_holder_details %{
name: "Token holder Detail",
fields: %{
address: @address_hash_type,
value: %{
type: "value",
definition: "A nonnegative number used to identify the balance of the target token.",
example: ~s("1000000000000000000")
}
}
}
@address_balance %{
name: "AddressBalance",
fields: %{
@ -1825,6 +1854,56 @@ defmodule BlockScoutWeb.Etherscan do
]
}
@token_gettokenholders_action %{
name: "getTokenHolders",
description: "Get token holders by contract address.",
required_params: [
%{
key: "contractaddress",
placeholder: "contractAddressHash",
type: "string",
description: "A 160-bit code used for identifying contracts."
}
],
optional_params: [
%{
key: "page",
type: "integer",
description:
"A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction."
},
%{
key: "offset",
type: "integer",
description:
"A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@token_gettokenholders_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "array",
array_type: @token_holder_details
}
}
}
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@token_gettokenholders_example_value_error)
}
]
}
@stats_tokensupply_action %{
name: "tokensupply",
description:
@ -2446,7 +2525,10 @@ defmodule BlockScoutWeb.Etherscan do
@token_module %{
name: "token",
actions: [@token_gettoken_action]
actions: [
@token_gettoken_action,
@token_gettokenholders_action
]
}
@stats_module %{

@ -7,6 +7,11 @@ defmodule BlockScoutWeb.API.RPC.TokenView do
RPCView.render("show.json", data: prepare_token(token))
end
def render("gettokenholders.json", %{token_holders: token_holders}) do
data = Enum.map(token_holders, &prepare_token_holder/1)
RPCView.render("show.json", data: data)
end
def render("error.json", assigns) do
RPCView.render("error.json", assigns)
end
@ -22,4 +27,11 @@ defmodule BlockScoutWeb.API.RPC.TokenView do
"cataloged" => token.cataloged
}
end
defp prepare_token_holder(token_holder) do
%{
"address" => to_string(token_holder.address_hash),
"value" => token_holder.value
}
end
end

@ -4601,7 +4601,7 @@ defmodule Explorer.Chain do
end
@spec fetch_token_holders_from_token_hash(Hash.Address.t(), [paging_options]) :: [TokenBalance.t()]
def fetch_token_holders_from_token_hash(contract_address_hash, options) do
def fetch_token_holders_from_token_hash(contract_address_hash, options \\ []) do
contract_address_hash
|> CurrentTokenBalance.token_holders_ordered_by_value(options)
|> Repo.all()

Loading…
Cancel
Save