Merge pull request #2143 from poanetwork/ab-token-transfer-csv-export
export token transfers to csv filepull/2265/head
commit
e1b186aeb4
@ -0,0 +1,119 @@ |
||||
defmodule Explorer.Chain.AddressTokenTransferCsvExporter do |
||||
@moduledoc """ |
||||
Exports token transfers to a csv file. |
||||
""" |
||||
|
||||
alias Explorer.{Chain, PagingOptions} |
||||
alias Explorer.Chain.{Address, TokenTransfer, Transaction} |
||||
alias NimbleCSV.RFC4180 |
||||
|
||||
@necessity_by_association [ |
||||
necessity_by_association: %{ |
||||
[created_contract_address: :names] => :optional, |
||||
[from_address: :names] => :optional, |
||||
[to_address: :names] => :optional, |
||||
[token_transfers: :token] => :optional, |
||||
[token_transfers: :to_address] => :optional, |
||||
[token_transfers: :from_address] => :optional, |
||||
[token_transfers: :token_contract_address] => :optional, |
||||
:block => :required |
||||
} |
||||
] |
||||
|
||||
@page_size 150 |
||||
@paging_options %PagingOptions{page_size: @page_size + 1} |
||||
|
||||
def export(address) do |
||||
address |
||||
|> fetch_all_transactions(@paging_options) |
||||
|> to_token_transfers() |
||||
|> to_csv_format(address) |
||||
|> dump_to_stream() |
||||
end |
||||
|
||||
defp fetch_all_transactions(address, paging_options, acc \\ []) do |
||||
options = Keyword.merge(@necessity_by_association, paging_options: paging_options) |
||||
|
||||
transactions = |
||||
address |
||||
|> Chain.address_to_transactions_with_rewards(options) |
||||
|> Enum.filter(fn transaction -> Enum.count(transaction.token_transfers) > 0 end) |
||||
|
||||
new_acc = transactions ++ acc |
||||
|
||||
case Enum.split(transactions, @page_size) do |
||||
{_transactions, [%Transaction{block_number: block_number, index: index}]} -> |
||||
new_paging_options = %{@paging_options | key: {block_number, index}} |
||||
fetch_all_transactions(address, new_paging_options, new_acc) |
||||
|
||||
{_, []} -> |
||||
new_acc |
||||
end |
||||
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) |
||||
end |
||||
|
||||
defp dump_to_stream(transactions) do |
||||
transactions |
||||
|> RFC4180.dump_to_stream() |
||||
end |
||||
|
||||
defp to_csv_format(token_transfers, address) do |
||||
row_names = [ |
||||
"TxHash", |
||||
"BlockNumber", |
||||
"UnixTimestamp", |
||||
"FromAddress", |
||||
"ToAddress", |
||||
"TokenContractAddress", |
||||
"Type", |
||||
"TokenSymbol", |
||||
"TokensTransferred", |
||||
"TransactionFee", |
||||
"Status", |
||||
"ErrCode" |
||||
] |
||||
|
||||
token_transfer_lists = |
||||
token_transfers |
||||
|> Stream.map(fn token_transfer -> |
||||
[ |
||||
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(), |
||||
type(token_transfer, address), |
||||
token_transfer.token.symbol, |
||||
token_transfer.amount, |
||||
fee(token_transfer.transaction), |
||||
token_transfer.transaction.status, |
||||
token_transfer.transaction.error |
||||
] |
||||
end) |
||||
|
||||
Stream.concat([row_names], token_transfer_lists) |
||||
end |
||||
|
||||
defp type(%TokenTransfer{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT" |
||||
|
||||
defp type(%TokenTransfer{to_address_hash: to_address}, %Address{hash: to_address}), do: "IN" |
||||
|
||||
defp type(_, _), do: "" |
||||
|
||||
defp fee(transaction) do |
||||
transaction |
||||
|> Chain.fee(:wei) |
||||
|> case do |
||||
{:actual, value} -> value |
||||
{:maximum, value} -> "Max of #{value}" |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,72 @@ |
||||
defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do |
||||
use Explorer.DataCase |
||||
|
||||
alias Explorer.Chain.AddressTokenTransferCsvExporter |
||||
|
||||
describe "export/1" do |
||||
test "exports token transfers to csv" do |
||||
address = insert(:address) |
||||
|
||||
transaction = |
||||
:transaction |
||||
|> insert(from_address: address) |
||||
|> with_block() |
||||
|
||||
token_transfer = insert(:token_transfer, transaction: transaction, from_address: address) |
||||
|
||||
[result] = |
||||
address |
||||
|> AddressTokenTransferCsvExporter.export() |
||||
|> Enum.to_list() |
||||
|> Enum.drop(1) |
||||
|> Enum.map(fn [ |
||||
tx_hash, |
||||
_, |
||||
block_number, |
||||
_, |
||||
timestamp, |
||||
_, |
||||
from_address, |
||||
_, |
||||
to_address, |
||||
_, |
||||
token_contract_address, |
||||
_, |
||||
type, |
||||
_, |
||||
token_symbol, |
||||
_, |
||||
tokens_transferred, |
||||
_, |
||||
transaction_fee, |
||||
_, |
||||
status, |
||||
_, |
||||
err_code, |
||||
_ |
||||
] -> |
||||
%{ |
||||
tx_hash: tx_hash, |
||||
block_number: block_number, |
||||
timestamp: timestamp, |
||||
from_address: from_address, |
||||
to_address: to_address, |
||||
token_contract_address: token_contract_address, |
||||
type: type, |
||||
token_symbol: token_symbol, |
||||
tokens_transferred: tokens_transferred, |
||||
transaction_fee: transaction_fee, |
||||
status: status, |
||||
err_code: err_code |
||||
} |
||||
end) |
||||
|
||||
assert result.block_number == to_string(transaction.block_number) |
||||
assert result.tx_hash == to_string(transaction.hash) |
||||
assert result.from_address == token_transfer.from_address_hash |> to_string() |> String.downcase() |
||||
assert result.to_address == token_transfer.to_address_hash |> to_string() |> String.downcase() |
||||
assert result.timestamp == to_string(transaction.block.timestamp) |
||||
assert result.type == "OUT" |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue