Add internal txs export to csv

pull/234/head
artemkolodko 2 years ago
parent 1358bf9790
commit 63488720fd
  1. 2
      src/pages/AddressPage/tabs/transactions/Transactions.tsx
  2. 31
      src/pages/ExportData/export-utils.ts
  3. 155
      src/pages/ExportData/index.tsx
  4. 1
      src/types/blockchain.ts

@ -305,7 +305,7 @@ export function Transactions(props: {
rowDetails={props.rowDetails}
showPages={totalElements > 0}
/>
{['transaction', 'erc20', 'erc721', 'erc1155'].includes(props.type) &&
{['transaction', 'erc20', 'erc721', 'erc1155', 'internal_transaction'].includes(props.type) &&
<Box style={{ alignItems: 'flex-end' }}>
<ExportToCsvButton address={id} type={props.type} />
</Box>

@ -1,4 +1,4 @@
import { RelatedTransaction } from "../../types";
import {InternalTransaction, RelatedTransaction} from "../../types";
import dayjs from "dayjs";
import Big from "big.js";
import { calculateFee, calculateFeePriceUSD } from "../../utils/fee";
@ -62,6 +62,24 @@ const mapHrc20TxToExport = (ownerAddress: string, tx: any, erc20Map: Record<stri
}
}
const mapInternalTxToExport = (ownerAddress: string, tx: InternalTransaction, onePrice: number) => {
const txDate = dayjs(tx.timestamp)
const isSender = ownerAddress === tx.from
return {
Txhash: tx.transactionHash,
Blockno: tx.blockNumber,
UnixTimestamp: txDate.unix(),
DateTime: txDate.format('YYYY-MM-DD HH:MM:ss'),
From: tx.from,
To: tx.to,
['Value_IN(ONE)']: convertValue(isSender ? '0': tx.value),
['Value_OUT(ONE)']: convertValue(isSender ? tx.value : '0'),
[`CurrentValue @ $${onePrice}/ONE`]: convertValue(onePrice * +tx.value),
Method: tx.input.slice(0, 10),
Type: tx.type
}
}
export interface IDownloadCsvParams {
type: TRelatedTransaction
address: string
@ -76,12 +94,15 @@ export const downloadCSV = (params: IDownloadCsvParams, filename: string) => {
const { type, address, txs, onePrice, erc20Map, erc721Map, erc1155Map } = params
const mapTx = (tx: any) => {
return type === 'transaction'
? mapRelatedTxToExport(address, tx, onePrice)
: mapHrc20TxToExport(address, tx, erc20Map, erc721Map, erc1155Map)
if(type === 'erc20') {
return mapHrc20TxToExport(address, tx, erc20Map, erc721Map, erc1155Map)
} else if (type === 'internal_transaction') {
return mapInternalTxToExport(address, tx, onePrice)
}
return mapRelatedTxToExport(address, tx, onePrice)
}
const mappedTxs = txs.map((tx) => mapTx(tx))
const mappedTxs = txs.map(mapTx)
const header = mappedTxs.filter((_, index) => index === 0).map(item => Object.keys(item))
const body = mappedTxs
.map((item) => Object.values(item))

@ -4,7 +4,7 @@ import { Address, BaseContainer, BasePage, Button, TipContent } from "src/compon
import { Heading, DateInput, Box, Spinner, Tip, Text } from "grommet";
import styled from "styled-components";
import useQuery from "../../hooks/useQuery";
import { getRelatedTransactionsByType } from "../../api/client";
import {getBlocks, getRelatedTransactionsByType} from "../../api/client";
import { downloadCSV } from "./export-utils";
import dayjs from "dayjs";
import { toaster } from "../../App";
@ -35,9 +35,11 @@ const DownloadButton = styled(Button)`
height: 42px;
`
const DefaultLimit = 5000
export const ExportData = () => {
const query = useQuery();
const address = query.get('address') || '';
const address = (query.get('address') || '').toLowerCase()
const type = (query.get('type') || 'transaction') as TRelatedTransaction;
const dateFormat = 'YYYY-MM-DD'
@ -63,22 +65,6 @@ export const ExportData = () => {
inputProps: { width: '170px' }
}
const filter = {
offset: 0,
limit: 5000,
orderBy: 'block_number',
orderDirection: 'desc',
filters: [{
type: 'gt',
property: 'timestamp',
value: `'${dateFrom}'`
}, {
type: 'lt',
property: 'timestamp',
value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'`
}]
}
const showErrorNotification = () => {
toaster.show({
message: () => (
@ -93,11 +79,81 @@ export const ExportData = () => {
const onDownloadClicked = async () => {
try {
setIsDownloading(true)
const txsFilter: any = {
offset: 0,
limit: DefaultLimit,
orderBy: 'block_number',
orderDirection: 'desc',
filters: []
}
// if (type === 'transaction') {
// if (dateFrom) {
// txsFilter.filters.push({
// type: 'gt',
// property: 'timestamp',
// value: `'${dateFrom}'`
// })
// }
// if (dateTo) {
// txsFilter.filters.push({
// type: 'lt',
// property: 'timestamp',
// value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'`
// })
// }
// }
// Get block numbers first, then filter internal by block number (not timestamp)
if (dateFrom) {
const blockFromFilter = {
offset: 0,
limit: 1,
orderBy: 'number',
orderDirection: 'asc',
filters: [{
type: 'gte',
property: 'timestamp',
value: `'${dateFrom}'`
}]
}
const [blockFrom] = await getBlocks([0, blockFromFilter]);
if(blockFrom) {
txsFilter.filters.push({
type: 'gte',
property: 'block_number',
value: blockFrom.number
})
}
}
if (dateTo) {
const blockToFilter = {
offset: 0,
limit: 1,
orderBy: 'number',
orderDirection: 'desc',
filters: [{
type: 'lt',
property: 'timestamp',
value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'`
}]
}
const [blockTo] = await getBlocks([0, blockToFilter]);
if(blockTo) {
txsFilter.filters.push({
type: 'lte',
property: 'block_number',
value: blockTo.number
})
}
}
const txs = await getRelatedTransactionsByType([
0,
address,
type,
filter,
txsFilter,
]);
const downloadParams = {
type,
@ -126,42 +182,47 @@ export const ExportData = () => {
}
const getTxTextType = (type: TRelatedTransaction) => {
return type === 'transaction' ? 'transactions' : type + ' transactions'
if(type === 'transaction') {
return 'transactions'
} else if (type === 'internal_transaction') {
return 'internal transactions'
}
return type + ' transactions'
}
return <BaseContainer pad={{ horizontal: "0" }} style={{ maxWidth: '700px', alignSelf: 'center' }}>
return <BaseContainer pad={{ horizontal: "0" }} style={{ maxWidth: '740px', alignSelf: 'center' }}>
<Heading size="xsmall" margin={{ bottom: "medium", top: "0" }}>
Export transactions
<Box gap={'4px'} direction={'row'} align={'baseline'}>
Download Data
<Text size={'medium'} weight={'normal'} color={'minorText'}>({getTxTextType(type)})</Text>
</Box>
</Heading>
<BasePage pad={"medium"} style={{ overflow: "inherit" }}>
<Box pad={{ top: 'medium', bottom: 'medium' }} style={{ display: 'inline-block' }}>
Export the last {filter.limit} {getTxTextType(type)} for <Address address={address} />
Export the last {DefaultLimit} {getTxTextType(type)} for <Address address={address} />
{type === 'transaction' && 'starting from'}
</Box>
{/* TODO: support timestamp filter on backend side */}
{type === 'transaction' &&
<FlexWrapper>
<InputContainer>
<Tip dropProps={{ align: { bottom: "top" }}} content={<TipContent showArrow={true} message={'Select start date'} />}>
<DateInput
{...dateInputProps}
value={dayjs(dateFrom).toISOString()}
onChange={({ value }) => onChangeDateFrom(value)}
/>
</Tip>
</InputContainer>
<div>to</div>
<InputContainer>
<Tip dropProps={{ align: { bottom: "top" }}} content={<TipContent showArrow={true} message={'Select end date'} />}>
<DateInput
{...dateInputProps}
value={dayjs(dateTo).toISOString()}
onChange={({ value }) => onChangeDateTo(value)}
/>
</Tip>
</InputContainer>
</FlexWrapper>
}
<FlexWrapper>
<InputContainer>
<Tip dropProps={{ align: { bottom: "top" }}} content={<TipContent showArrow={true} message={'Select start date'} />}>
<DateInput
{...dateInputProps}
value={dayjs(dateFrom).toISOString()}
onChange={({ value }) => onChangeDateFrom(value)}
/>
</Tip>
</InputContainer>
<div>to</div>
<InputContainer>
<Tip dropProps={{ align: { bottom: "top" }}} content={<TipContent showArrow={true} message={'Select end date'} />}>
<DateInput
{...dateInputProps}
value={dayjs(dateTo).toISOString()}
onChange={({ value }) => onChangeDateTo(value)}
/>
</Tip>
</InputContainer>
</FlexWrapper>
<Box style={{ justifyContent: 'center', alignItems: 'center' }} pad={{ top: 'large', bottom: 'medium' }}>
<Box width={'small'}>
<DownloadButton

@ -231,6 +231,7 @@ export type InternalTransaction = {
blockNumber: BlockNumber;
transactionHash: TransactionHash;
signatures?: any[];
timestamp?: string
};
export type Transaction = {

Loading…
Cancel
Save