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