Merge pull request #211 from ArtemKolodko/export_csv_erc20

Add export erc20 tokens
pull/213/head
Artem 2 years ago committed by GitHub
commit 4c5b15395d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/components/ui/ExportToCsvButton.tsx
  2. 4
      src/pages/AddressPage/tabs/transactions/Transactions.tsx
  3. 53
      src/pages/ExportData/export-utils.ts
  4. 77
      src/pages/ExportData/index.tsx

@ -2,10 +2,11 @@ import React from "react";
import { Box } from "grommet";
import { Link } from "react-router-dom";
import styled from "styled-components";
import {TRelatedTransaction} from "../../api/client.interface";
export interface IExportButtonProps {
address: string
type: 'transactions'
type: TRelatedTransaction
}
const LinkText = styled.div`

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

@ -1,6 +1,10 @@
import { RelatedTransaction } from "../../types";
import dayjs from "dayjs";
import { calculateFee, calculateFeePriceUSD } from "../../utils/fee";
import {TRelatedTransaction} from "../../api/client.interface";
import {Erc20} from "../../hooks/ERC20_Pool";
import {ERC721} from "../../hooks/ERC721_Pool";
import {ERC1155} from "../../hooks/ERC1155_Pool";
const downloadBlob = (content: any, filename: string) => {
const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });
@ -13,15 +17,14 @@ const downloadBlob = (content: any, filename: string) => {
pom.click();
}
const convertValue = (value: string | number, precision = 18) => {
const n = 4
const bigIntValue = BigInt(parseInt(value.toString())) / BigInt(10 ** (precision - n))
return parseInt(bigIntValue.toString()) / (10 ** n);
}
const mapRelatedTxToExport = (ownerAddress: string, tx: RelatedTransaction, onePrice: number) => {
const txDate = dayjs(tx.timestamp)
const convertValue = (value: string | number, n = 4) => {
const precision = 18
const bigIntValue = BigInt(parseInt(value.toString())) / BigInt(10 ** (precision - n))
return parseInt(bigIntValue.toString()) / (10 ** n);
}
const isSender = ownerAddress === tx.from
return {
Txhash: tx.hash,
@ -39,15 +42,47 @@ const mapRelatedTxToExport = (ownerAddress: string, tx: RelatedTransaction, oneP
}
}
const mapHrc20TxToExport = (ownerAddress: string, tx: any, erc20Map: Record<string, Erc20>, erc721Map: Record<string, ERC721>, erc1155Map: Record<string, ERC1155>) => {
const txDate = dayjs(tx.timestamp)
const token = erc20Map[tx.address] || erc721Map[tx.address] || erc1155Map[tx.address]
const precision = token ? token.decimals : 18
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']: convertValue(isSender ? '0': tx.value, precision),
['Value_OUT']: convertValue(isSender ? tx.value : '0', precision),
['TokenAddress']: tx.address,
['TokenName']: token ? token.name: 'N/A',
['TokenSymbol']: token ? token.symbol: 'N/A'
}
}
export interface IDownloadCsvParams {
type: TRelatedTransaction
address: string
txs: RelatedTransaction[]
onePrice: number
erc20Map: Record<string, Erc20>
erc721Map: Record<string, ERC721>
erc1155Map: Record<string, ERC1155>
}
export const downloadCSV = (params: IDownloadCsvParams, filename: string) => {
const { address, txs, onePrice} = params
const mappedTxs = txs.map((tx) => mapRelatedTxToExport(address, tx, onePrice))
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)
}
const mappedTxs = txs.map((tx) => mapTx(tx))
const header = mappedTxs.filter((_, index) => index === 0).map(item => Object.keys(item))
const body = mappedTxs
.map((item) => Object.values(item))

@ -9,6 +9,10 @@ import { downloadCSV } from "./export-utils";
import dayjs from "dayjs";
import { toaster } from "../../App";
import { useONEExchangeRate } from "../../hooks/useONEExchangeRate";
import {TRelatedTransaction} from "../../api/client.interface";
import {useERC20Pool} from "../../hooks/ERC20_Pool";
import {useERC721Pool} from "../../hooks/ERC721_Pool";
import {useERC1155Pool} from "../../hooks/ERC1155_Pool";
const IconError = styled(StatusCritical)`
margin-right: 5px;
@ -34,13 +38,17 @@ const DownloadButton = styled(Button)`
export const ExportData = () => {
const query = useQuery();
const address = query.get('address') || '';
const type = query.get('type') || '';
const type = (query.get('type') || 'transaction') as TRelatedTransaction;
const dateFormat = 'YYYY-MM-DD'
const initialDateFrom = dayjs().startOf('month').format(dateFormat)
const initialDateTo = dayjs().format(dateFormat)
const { lastPrice } = useONEExchangeRate();
const { lastPrice: onePrice } = useONEExchangeRate();
const erc20Map = useERC20Pool();
const erc721Map = useERC721Pool();
const erc1155Map = useERC1155Pool();
const [isDownloading, setIsDownloading] = useState(false)
const [dateFrom, setDateFrom] = useState(initialDateFrom)
const [dateTo, setDateTo] = useState(initialDateTo)
@ -88,10 +96,19 @@ export const ExportData = () => {
const txs = await getRelatedTransactionsByType([
0,
address,
'transaction',
type,
filter,
]);
downloadCSV({ address, txs, onePrice: lastPrice }, `export_${address}.csv`)
const downloadParams = {
type,
address,
txs,
onePrice,
erc20Map,
erc721Map,
erc1155Map
}
downloadCSV(downloadParams, `export_${type}_${address}.csv`)
} catch (e) {
console.error('Error on download:', (e as Error).message)
showErrorNotification()
@ -108,35 +125,43 @@ export const ExportData = () => {
setDateTo(dayjs(value).format(dateFormat))
}
const getTxTextType = (type: TRelatedTransaction) => {
return type === 'transaction' ? 'transactions' : type + ' transactions'
}
return <BaseContainer pad={{ horizontal: "0" }} style={{ maxWidth: '700px', alignSelf: 'center' }}>
<Heading size="xsmall" margin={{ bottom: "medium", top: "0" }}>
Export transactions
</Heading>
<BasePage pad={"medium"} style={{ overflow: "inherit" }}>
<Box pad={{ top: 'medium', bottom: 'medium' }} style={{ display: 'inline-block' }}>
Export the last {filter.limit} transactions for <Address address={address} /> starting from
Export the last {filter.limit} {getTxTextType(type)} for <Address address={address} />
{type === 'transaction' && 'starting from'}
</Box>
<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>
{/* 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>
}
<Box style={{ justifyContent: 'center', alignItems: 'center' }} pad={{ top: 'large', bottom: 'medium' }}>
<Box width={'small'}>
<DownloadButton

Loading…
Cancel
Save