diff --git a/CHANGELOG.md b/CHANGELOG.md index b6c41886e3..ed5636d402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [#4931](https://github.com/blockscout/blockscout/pull/4931) - Web3 modal with Wallet Connect for Write contract page and Staking Dapp ### Fixes +- [#4977](https://github.com/blockscout/blockscout/pull/4977) - Export token transfers on address: include transfers on contract itself - [#4965](https://github.com/blockscout/blockscout/pull/4965) - Fix search field appearance on medium size screens - [#4945](https://github.com/blockscout/blockscout/pull/4945) - Fix `Verify & Publish` button link - [#4888](https://github.com/blockscout/blockscout/pull/4888) - Fix fetch_top_tokens method: add nulls last for token holders desc order diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 3dc23fce37..5a621035c1 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -549,6 +549,29 @@ defmodule Explorer.Chain do |> Repo.all() end + @doc """ + address_hash_to_token_transfers_including_contract/2 function returns token transfers on address (to/from/contract). + It is used by CSV export of token transfers button. + """ + @spec address_hash_to_token_transfers_including_contract(Hash.Address.t(), Keyword.t()) :: [TokenTransfer.t()] + def address_hash_to_token_transfers_including_contract(address_hash, options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + query = + from( + token_transfer in TokenTransfer, + where: token_transfer.to_address_hash == ^address_hash, + or_where: token_transfer.from_address_hash == ^address_hash, + or_where: token_transfer.token_contract_address_hash == ^address_hash + ) + + query + |> handle_paging_options(paging_options) + |> preload(transaction: :block) + |> preload(:token) + |> Repo.all() + end + @spec address_to_logs(Hash.Address.t(), Keyword.t()) :: [Log.t()] def address_to_logs(address_hash, options \\ []) when is_list(options) do paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50} diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex index 8ec962046f..4b73db11c6 100644 --- a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do """ alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.{Address, AddressTransactionCsvExporter, TokenTransfer} + alias Explorer.Chain.{Address, TokenTransfer} alias NimbleCSV.RFC4180 @page_size 150 @@ -16,18 +16,30 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do to_block = Chain.convert_date_to_max_block(to_period) address.hash - |> AddressTransactionCsvExporter.fetch_all_transactions(from_block, to_block, @paging_options) - |> to_token_transfers() + |> fetch_all_token_transfers(from_block, to_block, @paging_options) |> to_csv_format(address) |> dump_to_stream() end - defp to_token_transfers(transactions) do - transactions - |> Enum.flat_map(fn transaction -> - transaction.token_transfers - |> Enum.map(fn transfer -> %{transfer | transaction: transaction} end) - end) + def fetch_all_token_transfers(address_hash, from_block, to_block, paging_options, acc \\ []) do + options = + [] + |> Keyword.put(:paging_options, paging_options) + |> Keyword.put(:from_block, from_block) + |> Keyword.put(:to_block, to_block) + + token_transfers = Chain.address_hash_to_token_transfers_including_contract(address_hash, options) + + new_acc = token_transfers ++ acc + + case Enum.split(token_transfers, @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) + + {_, []} -> + new_acc + end end defp dump_to_stream(transactions) do @@ -58,9 +70,9 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do to_string(token_transfer.transaction_hash), token_transfer.transaction.block_number, token_transfer.transaction.block.timestamp, - token_transfer.from_address |> to_string() |> String.downcase(), - token_transfer.to_address |> to_string() |> String.downcase(), - token_transfer.token_contract_address |> to_string() |> String.downcase(), + token_transfer.from_address_hash |> to_string() |> String.downcase(), + token_transfer.to_address_hash |> to_string() |> String.downcase(), + token_transfer.token_contract_address_hash |> to_string() |> String.downcase(), type(token_transfer, address.hash), token_transfer.token.symbol, token_transfer.amount,