feat: add verbosity to GraphQL token transfers query (#10770)

* feat: GraphQL query for token transfers with verbose token, transaction, and block info

* feat: extend GraphQL schema to support legacy `tokenTransferTxs` query

* fix: cspell warnings

* chore: add spec and doc for `token_transfers_by_address_hash/4`

* chore: remove duplicate function declaration

* fix: do not treat examples as doctests
pull/10844/head
Fedor Ivanov 2 months ago committed by GitHub
parent 2e6ce2c222
commit e66d345b96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 22
      apps/block_scout_web/lib/block_scout_web/graphql/celo/resolvers/token_transfer.ex
  2. 34
      apps/block_scout_web/lib/block_scout_web/graphql/celo/resolvers/token_transfer_tx.ex
  3. 28
      apps/block_scout_web/lib/block_scout_web/graphql/celo/schema/query_fields.ex
  4. 87
      apps/block_scout_web/lib/block_scout_web/graphql/celo/schema/types.ex
  5. 15
      apps/block_scout_web/lib/block_scout_web/graphql/resolvers/block.ex
  6. 19
      apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token.ex
  7. 15
      apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token_transfer.ex
  8. 18
      apps/block_scout_web/lib/block_scout_web/graphql/resolvers/transaction.ex
  9. 11
      apps/block_scout_web/lib/block_scout_web/graphql/schema.ex
  10. 135
      apps/block_scout_web/lib/block_scout_web/graphql/schema/types.ex
  11. 4
      apps/explorer/lib/explorer/chain.ex
  12. 43
      apps/explorer/lib/explorer/chain/token_transfer.ex
  13. 13
      apps/explorer/lib/explorer/graphql.ex
  14. 139
      apps/explorer/lib/explorer/graphql/celo.ex

@ -0,0 +1,22 @@
defmodule BlockScoutWeb.GraphQL.Celo.Resolvers.TokenTransfer do
@moduledoc """
Resolvers for token transfers, used in the CELO schema.
"""
alias Absinthe.Relay.Connection
alias Explorer.GraphQL.Celo, as: GraphQL
alias Explorer.Repo
def get_by(_, args, _) do
connection_args = Map.take(args, [:after, :before, :first, :last])
GraphQL.token_tx_transfers_query()
|> Connection.from_query(&Repo.all/1, connection_args, options(args))
end
defp options(%{before: _}), do: []
defp options(%{count: count}), do: [count: count]
defp options(_), do: []
end

@ -0,0 +1,34 @@
defmodule BlockScoutWeb.GraphQL.Celo.Resolvers.TokenTransferTx do
@moduledoc false
alias Absinthe.Relay.Connection
alias Explorer.GraphQL.Celo, as: GraphQL
alias Explorer.Repo
def get_by(_, %{address_hash: address_hash, first: limit} = args, _) do
connection_args = Map.take(args, [:after, :before, :first, :last])
offset =
case Connection.offset(args) do
{:ok, offset} when is_integer(offset) -> offset
_ -> 0
end
address_hash
|> GraphQL.token_tx_transfers_query_for_address(offset, limit)
|> Connection.from_query(&Repo.all/1, connection_args, options(args))
end
def get_by(_, args, _) do
connection_args = Map.take(args, [:after, :before, :first, :last])
GraphQL.token_tx_transfers_query()
|> Connection.from_query(&Repo.all/1, connection_args, options(args))
end
defp options(%{before: _}), do: []
defp options(%{count: count}), do: [count: count]
defp options(_), do: []
end

@ -0,0 +1,28 @@
defmodule BlockScoutWeb.GraphQL.Celo.QueryFields do
@moduledoc """
Query fields for the CELO schema.
"""
alias BlockScoutWeb.GraphQL.Celo.Resolvers.TokenTransferTx
use Absinthe.Schema.Notation
use Absinthe.Relay.Schema, :modern
defmacro generate do
quote do
@desc "Gets token transfer transactions."
connection field(:token_transfer_txs, node_type: :transfer_tx) do
arg(:address_hash, :address_hash)
arg(:count, :integer)
resolve(&TokenTransferTx.get_by/3)
complexity(fn
%{first: first}, child_complexity -> first * child_complexity
%{last: last}, child_complexity -> last * child_complexity
%{}, _child_complexity -> 0
end)
end
end
end
end

@ -0,0 +1,87 @@
defmodule BlockScoutWeb.GraphQL.Celo.Schema.Types do
@moduledoc false
use Absinthe.Schema.Notation
use Absinthe.Relay.Schema.Notation, :modern
alias BlockScoutWeb.GraphQL.Celo.Resolvers.TokenTransfer
@desc """
Represents a CELO or usd token transfer between addresses.
"""
node object(:celo_transfer, id_fetcher: &celo_transfer_id_fetcher/2) do
field(:value, :decimal)
field(:token, :string)
field(:token_address, :string)
field(:token_type, :string)
field(:token_id, :decimal)
field(:block_number, :integer)
field(:from_address_hash, :address_hash)
field(:to_address_hash, :address_hash)
field(:transaction_hash, :full_hash)
field(:log_index, :integer)
field(:gas_price, :wei)
field(:gas_used, :decimal)
field(:input, :string)
field(:timestamp, :datetime)
field(:comment, :string)
field(:to_account_hash, :address_hash)
field(:from_account_hash, :address_hash)
end
@desc """
Represents a CELO token transfer between addresses.
"""
node object(:transfer_tx, id_fetcher: &transfer_tx_id_fetcher/2) do
field(:gateway_fee_recipient, :address_hash)
field(:gateway_fee, :address_hash)
field(:fee_currency, :address_hash)
field(:fee_token, :string)
field(:address_hash, :address_hash)
field(:transaction_hash, :full_hash)
field(:block_number, :integer)
field(:gas_price, :wei)
field(:gas_used, :decimal)
field(:input, :string)
field(:timestamp, :datetime)
connection field(:token_transfer, node_type: :celo_transfer) do
arg(:count, :integer)
resolve(&TokenTransfer.get_by/3)
complexity(fn
%{first: first}, child_complexity ->
first * child_complexity
%{last: last}, child_complexity ->
last * child_complexity
end)
end
end
connection(node_type: :transfer_tx)
connection(node_type: :celo_transfer)
defp transfer_tx_id_fetcher(
%{transaction_hash: transaction_hash, address_hash: address_hash},
_
) do
Jason.encode!(%{
transaction_hash: to_string(transaction_hash),
address_hash: to_string(address_hash)
})
end
defp celo_transfer_id_fetcher(
%{transaction_hash: transaction_hash, log_index: log_index},
_
) do
Jason.encode!(%{
transaction_hash: to_string(transaction_hash),
log_index: log_index
})
end
end

@ -3,14 +3,27 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.Block do
alias BlockScoutWeb.GraphQL.Resolvers.Helper alias BlockScoutWeb.GraphQL.Resolvers.Helper
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Transaction
@api_true [api?: true]
def get_by(_, %{number: number}, resolution) do def get_by(_, %{number: number}, resolution) do
with {:api_enabled, true} <- {:api_enabled, resolution.context.api_enabled}, with {:api_enabled, true} <- {:api_enabled, resolution.context.api_enabled},
{:ok, _} = result <- Chain.number_to_block(number) do {:ok, _} = result <- Chain.number_to_block(number, @api_true) do
result result
else else
{:api_enabled, false} -> {:error, Helper.api_is_disabled()} {:api_enabled, false} -> {:error, Helper.api_is_disabled()}
{:error, :not_found} -> {:error, "Block number #{number} was not found."} {:error, :not_found} -> {:error, "Block number #{number} was not found."}
end end
end end
def get_by(%Transaction{block_hash: hash}, _, resolution) do
with {:api_enabled, true} <- {:api_enabled, resolution.context.api_enabled},
{:ok, _} = result <- Chain.hash_to_block(hash, @api_true) do
result
else
{:api_enabled, false} -> {:error, Helper.api_is_disabled()}
{:error, :not_found} -> {:error, "Block hash #{to_string(hash)} was not found."}
end
end
end end

@ -0,0 +1,19 @@
defmodule BlockScoutWeb.GraphQL.Resolvers.Token do
@moduledoc false
alias BlockScoutWeb.GraphQL.Resolvers.Helper
alias Explorer.Chain.TokenTransfer
alias Explorer.GraphQL
def get_by(
%TokenTransfer{token_contract_address_hash: token_contract_address_hash},
_,
resolution
) do
if resolution.context.api_enabled do
GraphQL.get_token(%{contract_address_hash: token_contract_address_hash})
else
{:error, Helper.api_is_disabled()}
end
end
end

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.TokenTransfer do
alias Absinthe.Relay.Connection alias Absinthe.Relay.Connection
alias BlockScoutWeb.GraphQL.Resolvers.Helper alias BlockScoutWeb.GraphQL.Resolvers.Helper
alias Explorer.Chain.{Address, TokenTransfer}
alias Explorer.{GraphQL, Repo} alias Explorer.{GraphQL, Repo}
def get_by(%{transaction_hash: _, log_index: _} = args, resolution) do def get_by(%{transaction_hash: _, log_index: _} = args, resolution) do
@ -19,7 +20,19 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.TokenTransfer do
token_contract_address_hash token_contract_address_hash
|> GraphQL.list_token_transfers_query() |> GraphQL.list_token_transfers_query()
|> Connection.from_query(&Repo.all/1, connection_args, options(args)) |> Connection.from_query(&Repo.replica().all/1, connection_args, options(args))
else
{:error, Helper.api_is_disabled()}
end
end
def get_by(%Address{hash: address_hash}, args, resolution) do
if resolution.context.api_enabled do
connection_args = Map.take(args, [:after, :before, :first, :last])
address_hash
|> TokenTransfer.token_transfers_by_address_hash(nil, [], nil)
|> Connection.from_query(&Repo.replica().all/1, connection_args, options(args))
else else
{:error, Helper.api_is_disabled()} {:error, Helper.api_is_disabled()}
end end

@ -4,11 +4,13 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.Transaction do
alias Absinthe.Relay.Connection alias Absinthe.Relay.Connection
alias BlockScoutWeb.GraphQL.Resolvers.Helper alias BlockScoutWeb.GraphQL.Resolvers.Helper
alias Explorer.{Chain, GraphQL, Repo} alias Explorer.{Chain, GraphQL, Repo}
alias Explorer.Chain.Address alias Explorer.Chain.{Address, TokenTransfer}
@api_true [api?: true]
def get_by(_, %{hash: hash}, resolution) do def get_by(_, %{hash: hash}, resolution) do
with {:api_enabled, true} <- {:api_enabled, resolution.context.api_enabled}, with {:api_enabled, true} <- {:api_enabled, resolution.context.api_enabled},
{:ok, transaction} <- Chain.hash_to_transaction(hash) do {:ok, transaction} <- Chain.hash_to_transaction(hash, @api_true) do
{:ok, transaction} {:ok, transaction}
else else
{:api_enabled, false} -> {:error, Helper.api_is_disabled()} {:api_enabled, false} -> {:error, Helper.api_is_disabled()}
@ -22,12 +24,22 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.Transaction do
if resolution.context.api_enabled do if resolution.context.api_enabled do
address_hash address_hash
|> GraphQL.address_to_transactions_query(args.order) |> GraphQL.address_to_transactions_query(args.order)
|> Connection.from_query(&Repo.all/1, connection_args, options(args)) |> Connection.from_query(&Repo.replica().all/1, connection_args, options(args))
else else
{:error, Helper.api_is_disabled()} {:error, Helper.api_is_disabled()}
end end
end end
def get_by(%TokenTransfer{transaction_hash: hash}, _, resolution) do
with {:api_enabled, true} <- {:api_enabled, resolution.context.api_enabled},
{:ok, transaction} <- Chain.hash_to_transaction(hash, @api_true) do
{:ok, transaction}
else
{:api_enabled, false} -> {:error, Helper.api_is_disabled()}
{:error, :not_found} -> {:error, "Transaction not found."}
end
end
defp options(%{before: _}), do: [] defp options(%{before: _}), do: []
defp options(%{count: count}), do: [count: count] defp options(%{count: count}), do: [count: count]

@ -22,6 +22,10 @@ defmodule BlockScoutWeb.GraphQL.Schema do
import_types(BlockScoutWeb.GraphQL.Schema.Types) import_types(BlockScoutWeb.GraphQL.Schema.Types)
if Application.compile_env(:explorer, :chain_type) == :celo do
import_types(BlockScoutWeb.GraphQL.Celo.Schema.Types)
end
node interface do node interface do
resolve_type(fn resolve_type(fn
%ExplorerChainInternalTransaction{}, _ -> %ExplorerChainInternalTransaction{}, _ ->
@ -100,6 +104,13 @@ defmodule BlockScoutWeb.GraphQL.Schema do
arg(:hash, non_null(:full_hash)) arg(:hash, non_null(:full_hash))
resolve(&Transaction.get_by/3) resolve(&Transaction.get_by/3)
end end
if Application.compile_env(:explorer, :chain_type) == :celo do
require BlockScoutWeb.GraphQL.Celo.QueryFields
alias BlockScoutWeb.GraphQL.Celo.QueryFields
QueryFields.generate()
end
end end
subscription do subscription do

@ -1,3 +1,66 @@
defmodule BlockScoutWeb.GraphQL.Schema.Transaction do
@moduledoc false
alias BlockScoutWeb.GraphQL.Resolvers.{Block, InternalTransaction}
case Application.compile_env(:explorer, :chain_type) do
:celo ->
@chain_type_fields quote(
do: [
field(:gas_token_contract_address_hash, :address_hash)
]
)
_ ->
@chain_type_fields quote(do: [])
end
defmacro generate do
quote do
node object(:transaction, id_fetcher: &transaction_id_fetcher/2) do
field(:cumulative_gas_used, :decimal)
field(:error, :string)
field(:gas, :decimal)
field(:gas_price, :wei)
field(:gas_used, :decimal)
field(:hash, :full_hash)
field(:index, :integer)
field(:input, :string)
field(:nonce, :nonce_hash)
field(:r, :decimal)
field(:s, :decimal)
field(:status, :status)
field(:v, :decimal)
field(:value, :wei)
field(:block_hash, :full_hash)
field(:block_number, :integer)
field(:from_address_hash, :address_hash)
field(:to_address_hash, :address_hash)
field(:created_contract_address_hash, :address_hash)
field(:earliest_processing_start, :datetime)
field(:revert_reason, :string)
field(:max_priority_fee_per_gas, :wei)
field(:max_fee_per_gas, :wei)
field(:type, :integer)
field(:has_error_in_internal_txs, :boolean)
field :block, :block do
resolve(&Block.get_by/3)
end
connection field(:internal_transactions, node_type: :internal_transaction) do
arg(:count, :integer)
resolve(&InternalTransaction.get_by/3)
complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end)
end
unquote_splicing(@chain_type_fields)
end
end
end
end
defmodule BlockScoutWeb.GraphQL.Schema.SmartContracts do defmodule BlockScoutWeb.GraphQL.Schema.SmartContracts do
@moduledoc false @moduledoc false
case Application.compile_env(:explorer, :chain_type) do case Application.compile_env(:explorer, :chain_type) do
@ -42,7 +105,7 @@ end
defmodule BlockScoutWeb.GraphQL.Schema.Types do defmodule BlockScoutWeb.GraphQL.Schema.Types do
@moduledoc false @moduledoc false
require BlockScoutWeb.GraphQL.Schema.SmartContracts require BlockScoutWeb.GraphQL.Schema.{Transaction, SmartContracts}
use Absinthe.Schema.Notation use Absinthe.Schema.Notation
use Absinthe.Relay.Schema.Notation, :modern use Absinthe.Relay.Schema.Notation, :modern
@ -50,11 +113,13 @@ defmodule BlockScoutWeb.GraphQL.Schema.Types do
import Absinthe.Resolution.Helpers import Absinthe.Resolution.Helpers
alias BlockScoutWeb.GraphQL.Resolvers.{ alias BlockScoutWeb.GraphQL.Resolvers.{
InternalTransaction, Token,
TokenTransfer,
Transaction Transaction
} }
alias BlockScoutWeb.GraphQL.Schema.SmartContracts, as: SmartContractsSchema alias BlockScoutWeb.GraphQL.Schema.SmartContracts, as: SmartContractsSchema
alias BlockScoutWeb.GraphQL.Schema.Transaction, as: TransactionSchema
import_types(Absinthe.Type.Custom) import_types(Absinthe.Type.Custom)
import_types(BlockScoutWeb.GraphQL.Schema.Scalars) import_types(BlockScoutWeb.GraphQL.Schema.Scalars)
@ -87,6 +152,13 @@ defmodule BlockScoutWeb.GraphQL.Schema.Types do
complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end) complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end)
end end
connection field(:token_transfers, node_type: :token_transfer) do
arg(:count, :integer)
resolve(&TokenTransfer.get_by/3)
complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end)
end
end end
@desc """ @desc """
@ -160,46 +232,37 @@ defmodule BlockScoutWeb.GraphQL.Schema.Types do
field(:to_address_hash, :address_hash) field(:to_address_hash, :address_hash)
field(:token_contract_address_hash, :address_hash) field(:token_contract_address_hash, :address_hash)
field(:transaction_hash, :full_hash) field(:transaction_hash, :full_hash)
field :transaction, :transaction do
resolve(&Transaction.get_by/3)
end
field :token, :token do
resolve(&Token.get_by/3)
end
end end
@desc """ @desc """
Models a Web3 transaction. Represents a token.
""" """
node object(:transaction, id_fetcher: &transaction_id_fetcher/2) do object :token do
field(:cumulative_gas_used, :decimal) field(:name, :string)
field(:error, :string) field(:symbol, :string)
field(:gas, :decimal) field(:total_supply, :decimal)
field(:gas_price, :wei) field(:decimals, :decimal)
field(:gas_used, :decimal) field(:type, :string)
field(:hash, :full_hash) field(:holder_count, :integer)
field(:index, :integer) field(:circulating_market_cap, :decimal)
field(:input, :string) field(:icon_url, :string)
field(:nonce, :nonce_hash) field(:volume_24h, :decimal)
field(:r, :decimal) field(:contract_address_hash, :address_hash)
field(:s, :decimal)
field(:status, :status)
field(:v, :decimal)
field(:value, :wei)
field(:block_hash, :full_hash)
field(:block_number, :integer)
field(:from_address_hash, :address_hash)
field(:to_address_hash, :address_hash)
field(:created_contract_address_hash, :address_hash)
field(:earliest_processing_start, :datetime)
field(:revert_reason, :string)
field(:max_priority_fee_per_gas, :wei)
field(:max_fee_per_gas, :wei)
field(:type, :integer)
field(:has_error_in_internal_txs, :boolean)
connection field(:internal_transactions, node_type: :internal_transaction) do
arg(:count, :integer)
resolve(&InternalTransaction.get_by/3)
complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end)
end
end end
@desc """
Models a Web3 transaction.
"""
TransactionSchema.generate()
def token_transfer_id_fetcher(%{transaction_hash: transaction_hash, log_index: log_index}, _) do def token_transfer_id_fetcher(%{transaction_hash: transaction_hash, log_index: log_index}, _) do
Jason.encode!(%{transaction_hash: to_string(transaction_hash), log_index: log_index}) Jason.encode!(%{transaction_hash: to_string(transaction_hash), log_index: log_index})
end end

@ -317,8 +317,8 @@ defmodule Explorer.Chain do
filters = Keyword.get(options, :token_type) filters = Keyword.get(options, :token_type)
necessity_by_association = Keyword.get(options, :necessity_by_association) necessity_by_association = Keyword.get(options, :necessity_by_association)
direction address_hash
|> TokenTransfer.token_transfers_by_address_hash(address_hash, filters, paging_options) |> TokenTransfer.token_transfers_by_address_hash(direction, filters, paging_options)
|> join_associations(necessity_by_association) |> join_associations(necessity_by_association)
|> select_repo(options).all() |> select_repo(options).all()
end end

@ -442,7 +442,46 @@ defmodule Explorer.Chain.TokenTransfer do
|> order_by([tt], desc: tt.block_number, desc: tt.log_index) |> order_by([tt], desc: tt.block_number, desc: tt.log_index)
end end
def token_transfers_by_address_hash(direction, address_hash, token_types, paging_options) do @doc """
Retrieves token transfers associated with a given address, optionally filtered
by direction and token types.
## Parameters
- `address_hash` (`Hash.Address.t()`): The address hash for which to retrieve
token transfers.
- `direction` (`nil | :to | :from`): The direction of the transfers to filter.
- `:to` - transfers where `to_address` matches `address_hash`.
- `:from` - transfers where `from_address` matches `address_hash`.
- `nil` - includes both incoming and outgoing transfers.
- `token_types` (`[binary()]`): The token types to filter, e.g `["ERC20", "ERC721"]`.
- `paging_options` (`nil | Explorer.PagingOptions.t()`): Pagination options to
limit the result set.
## Returns
An `Ecto.Query` for `TokenTransfer.t()`.
## Examples
Fetch all incoming ERC20 token transfers for a specific address:
# iex> query = token_transfers_by_address_hash(address_hash, :to, ["ERC20"], paging_options)
# iex> Repo.all(query)
Fetch both incoming and outgoing token transfers for a specific address
without pagination, token type filtering, and direction filtering:
# iex> query = token_transfers_by_address_hash(address_hash, nil, [], nil)
# iex> Repo.all(query)
"""
@spec token_transfers_by_address_hash(
Hash.Address.t(),
nil | :to | :from,
[binary()],
nil | Explorer.PagingOptions.t()
) :: Ecto.Query.t()
def token_transfers_by_address_hash(address_hash, direction, token_types, paging_options) do
if direction == :to || direction == :from do if direction == :to || direction == :from do
only_consensus_transfers_query() only_consensus_transfers_query()
|> filter_by_direction(direction, address_hash) |> filter_by_direction(direction, address_hash)
@ -474,7 +513,7 @@ defmodule Explorer.Chain.TokenTransfer do
|> union(^from_address_hash_query) |> union(^from_address_hash_query)
|> Chain.wrapped_union_subquery() |> Chain.wrapped_union_subquery()
|> order_by([tt], desc: tt.block_number, desc: tt.log_index) |> order_by([tt], desc: tt.block_number, desc: tt.log_index)
|> limit(^paging_options.page_size) |> handle_paging_options(paging_options)
end end
end end

@ -14,6 +14,7 @@ defmodule Explorer.GraphQL do
alias Explorer.Chain.{ alias Explorer.Chain.{
Hash, Hash,
InternalTransaction, InternalTransaction,
Token,
TokenTransfer, TokenTransfer,
Transaction Transaction
} }
@ -83,6 +84,18 @@ defmodule Explorer.GraphQL do
end end
end end
@doc """
Returns a token for a given contract address hash.
"""
@spec get_token(map()) :: {:ok, Token.t()} | {:error, String.t()}
def get_token(%{contract_address_hash: _} = clauses) do
if token = Repo.replica().get_by(Token, clauses) do
{:ok, token}
else
{:error, "Token not found."}
end
end
@doc """ @doc """
Returns a query to fetch token transfers for a token contract address hash. Returns a query to fetch token transfers for a token contract address hash.

@ -0,0 +1,139 @@
defmodule Explorer.GraphQL.Celo do
@moduledoc """
Defines Ecto queries to fetch Celo blockchain data for the legacy GraphQL
schema.
Includes functions to construct queries for token transfers and transactions.
"""
import Ecto.Query,
only: [from: 2, order_by: 3, where: 3, subquery: 1]
alias Explorer.Chain.{
Block,
Hash,
Token,
TokenTransfer,
Transaction
}
@doc """
Constructs a paginated query for token transfers involving a specific address.
"""
@spec token_tx_transfers_query_for_address(Hash.Address.t(), integer(), integer()) :: Ecto.Query.t()
def token_tx_transfers_query_for_address(address_hash, offset, limit) do
page = floor(offset / limit) + 1
growing_limit = limit * (page + 1)
tokens =
from(
tt in TokenTransfer,
where: not is_nil(tt.transaction_hash),
where: tt.to_address_hash == ^address_hash,
or_where: tt.from_address_hash == ^address_hash,
select: %{
transaction_hash: tt.transaction_hash,
block_number: tt.block_number,
to_address_hash: tt.to_address_hash,
from_address_hash: tt.from_address_hash
},
distinct: [desc: tt.block_number, desc: tt.transaction_hash],
order_by: [
desc: tt.block_number,
desc: tt.transaction_hash,
desc: tt.from_address_hash,
desc: tt.to_address_hash
],
limit: ^growing_limit
)
query =
from(
tt in subquery(tokens),
as: :token_transfer,
inner_join: tx in Transaction,
as: :transaction,
on: tx.hash == tt.transaction_hash,
inner_join: b in Block,
on: tx.block_hash == b.hash,
left_join: token in Token,
on: tx.gas_token_contract_address_hash == token.contract_address_hash,
select: %{
transaction_hash: tt.transaction_hash,
to_address_hash: tt.to_address_hash,
from_address_hash: tt.from_address_hash,
gas_used: tx.gas_used,
gas_price: tx.gas_price,
fee_currency: tx.gas_token_contract_address_hash,
fee_token: fragment("coalesce(?, 'CELO')", token.symbol),
# gateway_fee: tx.gateway_fee,
# gateway_fee_recipient: tx.gas_fee_recipient_hash,
timestamp: b.timestamp,
input: tx.input,
nonce: tx.nonce,
block_number: tt.block_number
}
)
query
|> order_by([transaction: t],
desc: t.block_number,
desc: t.hash,
asc: t.nonce,
desc: t.from_address_hash,
desc: t.to_address_hash
)
end
@doc """
Constructs a query for token transfers filtered by a specific address.
"""
@spec token_tx_transfers_query_by_address(Hash.Address.t()) :: Ecto.Query.t()
def token_tx_transfers_query_by_address(address_hash) do
token_tx_transfers_query()
|> where([t], t.from_address_hash == ^address_hash or t.to_address_hash == ^address_hash)
|> order_by([transaction: t], desc: t.block_number, asc: t.nonce)
end
@doc """
Constructs a query to fetch detailed token transfer information.
"""
@spec token_tx_transfers_query() :: Ecto.Query.t()
def token_tx_transfers_query do
from(
tt in TokenTransfer,
inner_join: tx in Transaction,
as: :transaction,
on: tx.hash == tt.transaction_hash,
inner_join: b in Block,
on: tt.block_number == b.number,
# left_join: wf in CeloWalletAccounts,
# on: tt.from_address_hash == wf.wallet_address_hash,
# left_join: wt in CeloWalletAccounts,
# on: tt.to_address_hash == wt.wallet_address_hash,
left_join: token in Token,
on: tt.token_contract_address_hash == token.contract_address_hash,
select: %{
gas_used: tx.gas_used,
gas_price: tx.gas_price,
timestamp: b.timestamp,
input: tx.input,
transaction_hash: tt.transaction_hash,
from_address_hash: tt.from_address_hash,
to_address_hash: tt.to_address_hash,
# from_account_hash: wf.account_address_hash,
# to_account_hash: wt.account_address_hash,
log_index: tt.log_index,
value: tt.amount,
# comment: tt.comment,
token: token.symbol,
token_address: token.contract_address_hash,
nonce: tx.nonce,
block_number: tt.block_number,
token_type: token.type,
token_id: fragment("(COALESCE(?, ARRAY[]::Decimal[]))[1]", tt.token_ids)
},
order_by: [desc: tt.block_number, desc: tt.amount, desc: tt.log_index]
)
end
end
Loading…
Cancel
Save