import {h, Fragment} from 'preact'
import {Base64} from 'js-base64'
import {encodeAssetFingerprint} from '../../../wallet/shelley/helpers/addresses'
import printAda from '../../../helpers/printAda'
import toLocalDate from '../../../helpers/toLocalDate'
import {
RewardWithdrawal,
StakingHistoryItemType,
StakingHistoryObject,
StakingReward,
HexString,
Lovelace,
TxSummaryEntry,
Token,
AssetFamily,
Address,
} from '../../../types'
import {AdaIcon} from '../../common/svg'
import {
FormattedAssetItem,
FormattedAssetItemProps,
FormattedHumanReadableLabelType,
} from '../../common/asset'
import styles from './transactionHistory.module.scss'
import * as moment from 'moment'
import {useActiveAccount} from '../../../selectors'
import {useSelector} from '../../../helpers/connect'
import {createTokenRegistrySubject} from '../../../tokenRegistry/tokenRegistry'
import printTokenAmount from '../../../helpers/printTokenAmount'
import {getCexplorerUrl} from '../../../helpers//common'
import BigNumber from 'bignumber.js'
import {useState} from 'preact/hooks'
const FormattedAmount = ({amount}: {amount: Lovelace}): h.JSX.Element => {
const value = printAda(amount)
return (
{amount.gt(0) ? `+${value}` : value}
)
}
const FormattedFee = ({fee}: {fee: Lovelace}): h.JSX.Element => {
const value = printAda(fee)
return (
)
}
const FormattedTransaction = ({txid}: {txid: HexString}): h.JSX.Element => (
)
const MultiAsset = (props: FormattedAssetItemProps) => {
return (
{({
icon,
formattedHumanReadableLabelVariants,
formattedAmount,
formattedAssetLink,
formattedFingerprint,
}) => {
const {quantity} = props
return (
{formattedHumanReadableLabelVariants.label}
{formattedAssetLink}
{quantity.gt(0) ? `+${formattedAmount}` : formattedAmount}
{icon}
{formattedHumanReadableLabelVariants.type ===
FormattedHumanReadableLabelType.FINGERPRINT
? ''
: formattedFingerprint}
)
}}
)
}
interface Props {
transactionHistory: Array
stakingHistory: Array
}
const ExportCSV = ({transactionHistory, stakingHistory}: Props): h.JSX.Element => {
const tokensMetadata = useSelector((state) => state.tokensMetadata)
const withdrawalHistory: {[key: string]: Lovelace} = stakingHistory
.filter((item) => item.type === StakingHistoryItemType.REWARD_WITHDRAWAL)
.reduce((acc, withdrawal: RewardWithdrawal) => {
acc[withdrawal.txHash] = withdrawal.amount
return acc
}, {})
const stakingRewards = stakingHistory.filter(
(item) => item.type === StakingHistoryItemType.STAKING_REWARD
) as Array
const delimiter = ','
const rowsDelimiter = '\n'
const headers = [
'Date',
'Transaction ID',
'Type',
'Received from (disclaimer: may not be accurate - first sender address only)',
'Received amount',
'Received currency',
'Sent amount',
'Sent currency',
'Fee amount',
'Fee currency',
]
.map((header) => `${header}${delimiter}`)
.join('')
const enum TxSummaryType {
RECEIVED = 'Received',
SENT = 'Sent',
REWARD_AWARDED = 'Reward awarded',
}
type TxEntry = {
type: TxSummaryType
txHash?: string
dateTime: moment.Moment
fee?: Lovelace
currency: string
receivedFrom?: Address
} & (
| {
assetFamily: AssetFamily.ADA
sent?: Lovelace
received?: Lovelace
}
| {
assetFamily: AssetFamily.TOKEN
sent?: BigNumber
received?: BigNumber
decimals: number
}
)
const transactionsEntries: Array = transactionHistory.flatMap(
(transaction: TxSummaryEntry): Array => {
const common = {
txHash: transaction.ctbId,
dateTime: moment.utc(new Date(transaction.ctbTimeIssued * 1000)),
}
const createTokenEntries = (): Array =>
transaction.tokenEffects.map((tokenEffect: Token) => {
const tokenMetadata = tokensMetadata.get(
createTokenRegistrySubject(tokenEffect.policyId, tokenEffect.assetName)
)
const ticker = tokenMetadata?.ticker
const fingerprint = encodeAssetFingerprint(tokenEffect.policyId, tokenEffect.assetName)
return {
...common,
...(tokenEffect.quantity.gt(0)
? {
type: TxSummaryType.RECEIVED,
received: tokenEffect.quantity,
receivedFrom: transaction.fromAddresses[0],
}
: {
type: TxSummaryType.SENT,
sent: tokenEffect.quantity.abs(),
}),
assetFamily: AssetFamily.TOKEN,
currency: ticker ? `${ticker} (${fingerprint})` : fingerprint,
decimals: tokenMetadata?.decimals || 0,
}
})
if (withdrawalHistory.hasOwnProperty(transaction.ctbId)) {
return [
{
...common,
type: TxSummaryType.SENT,
assetFamily: AssetFamily.ADA,
sent: transaction.effect
.minus(withdrawalHistory[transaction.ctbId])
.abs()
.minus(transaction.fee) as Lovelace,
fee: transaction.fee,
currency: 'ADA',
},
]
} else if (transaction.effect.gt(0)) {
return [
{
...common,
type: TxSummaryType.RECEIVED,
assetFamily: AssetFamily.ADA,
received: transaction.effect,
receivedFrom: transaction.fromAddresses[0],
currency: 'ADA',
},
...createTokenEntries(),
]
} else {
return [
{
...common,
type: TxSummaryType.SENT,
assetFamily: AssetFamily.ADA,
sent: transaction.effect.abs().minus(transaction.fee) as Lovelace,
fee: transaction.fee,
currency: 'ADA',
},
...createTokenEntries(),
]
}
}
)
const rewardsEntries: Array = stakingRewards.map((stakingReward: StakingReward) => ({
type: TxSummaryType.REWARD_AWARDED,
assetFamily: AssetFamily.ADA,
dateTime: moment(stakingReward.dateTime),
received: stakingReward.reward,
currency: 'ADA',
}))
const entries: Array = [...transactionsEntries, ...rewardsEntries].sort(
(a, b) => b.dateTime.unix() - a.dateTime.unix()
)
const rows: Array = entries.map((entry) => {
const printAmount = (amount: BigNumber | Lovelace) =>
entry.assetFamily === AssetFamily.ADA
? printAda(amount as Lovelace)
: printTokenAmount(amount, entry.decimals as number)
return [
entry.dateTime.format('MM/DD/YYYY hh:mm A [UTC]'),
entry.txHash,
entry.type,
entry.received && entry.receivedFrom,
entry.received && printAmount(entry.received),
entry.received !== undefined ? entry.currency : undefined,
entry.sent && printAmount(entry.sent),
entry.sent !== undefined ? entry.currency : undefined,
entry.fee && printAda(entry.fee as Lovelace),
entry.fee !== undefined ? 'ADA' : undefined,
]
.map((value) => (value === undefined ? delimiter : `${value}${delimiter}`))
.join('')
})
const fileContents = `${headers}${rowsDelimiter}${rows.join(rowsDelimiter)}`
const filename = 'transactions.csv'
const filetype = 'text/plain'
const dataURI = `data:${filetype};base64,${Base64.encode(fileContents)}`
return (
Export to CSV
)
}
const DEFAULT_TRANSACTIONS_LIMIT = 50
const TransactionHistory = (): h.JSX.Element => {
const {transactionHistory, stakingHistory} = useActiveAccount()
const [showAll, setShowAll] = useState(false)
return (
{transactionHistory.length === 0 ? (
No transactions found
) : (
{(showAll
? transactionHistory
: transactionHistory.slice(0, DEFAULT_TRANSACTIONS_LIMIT)
).map((transaction: TxSummaryEntry) => (
-
{toLocalDate(new Date(transaction.ctbTimeIssued * 1000))}
{transaction.tokenEffects.map((tokenEffect: Token) => (
))}
))}
{transactionHistory.length > DEFAULT_TRANSACTIONS_LIMIT && !showAll && (
-
setShowAll(true)}>
show all
)}
)}
)
}
export default TransactionHistory