Commit 9b07d8ce authored by aballerr's avatar aballerr Committed by GitHub

feat: Wallet v1 transactions part 1 (#4398)

* Adding in tx history for 3 transactions: Swap, Add Liquidity, Remove Liquidty
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
Co-authored-by: default avatarAlex Ball <alexball@UNISWAP-MAC-038.fios-router.home>
Co-authored-by: default avatarAlex Ball <alexball@UNISWAP-MAC-038.local>
parent b1b9da1b
import { TransactionInfo, TransactionType } from 'state/transactions/types'
import styled, { css } from 'styled-components/macro'
import { useCurrency } from '../../hooks/Tokens'
import CurrencyLogo from '../CurrencyLogo'
const CurrencyWrap = styled.div`
position: relative;
width: 36px;
height: 36px;
`
const CurrencyWrapStyles = css`
position: absolute;
height: 24px;
`
const CurrencyLogoWrap = styled.span<{ isCentered: boolean }>`
${CurrencyWrapStyles};
left: ${({ isCentered }) => (isCentered ? '50%' : '0')};
top: ${({ isCentered }) => (isCentered ? '50%' : '0')};
transform: ${({ isCentered }) => isCentered && 'translate(-50%, -50%)'};
`
const CurrencyLogoWrapTwo = styled.span`
${CurrencyWrapStyles};
bottom: 0px;
right: 0px;
`
interface CurrencyPair {
currencyId0: string | undefined
currencyId1: string | undefined
}
const getCurrency = ({ info }: { info: TransactionInfo }): CurrencyPair => {
switch (info.type) {
case TransactionType.ADD_LIQUIDITY_V3_POOL:
case TransactionType.REMOVE_LIQUIDITY_V3:
const { baseCurrencyId, quoteCurrencyId } = info
return { currencyId0: baseCurrencyId, currencyId1: quoteCurrencyId }
case TransactionType.SWAP:
const { inputCurrencyId, outputCurrencyId } = info
return { currencyId0: inputCurrencyId, currencyId1: outputCurrencyId }
default:
return { currencyId0: undefined, currencyId1: undefined }
}
}
const LogoView = ({ info }: { info: TransactionInfo }) => {
const { currencyId0, currencyId1 } = getCurrency({ info })
const currency0 = useCurrency(currencyId0)
const currency1 = useCurrency(currencyId1)
const isCentered = !(currency0 && currency1)
return (
<CurrencyWrap>
<CurrencyLogoWrap isCentered={isCentered}>
<CurrencyLogo size="24px" currency={currency0} />
</CurrencyLogoWrap>
{!isCentered && (
<CurrencyLogoWrapTwo>
<CurrencyLogo size="24px" currency={currency1} />
</CurrencyLogoWrapTwo>
)}
</CurrencyWrap>
)
}
export default LogoView
import { Trans } from '@lingui/macro'
import { Fraction, TradeType } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import {
AddLiquidityV3PoolTransactionInfo,
ExactInputSwapTransactionInfo,
ExactOutputSwapTransactionInfo,
RemoveLiquidityV3TransactionInfo,
TransactionInfo,
TransactionType,
} from 'state/transactions/types'
import styled from 'styled-components/macro'
import { useCurrency } from '../../hooks/Tokens'
import { TransactionState } from './index'
const HighlightText = styled.span`
color: ${({ theme }) => theme.textPrimary};
font-weight: 600;
`
interface ActionProps {
pending: JSX.Element
success: JSX.Element
failed: JSX.Element
transactionState: TransactionState
}
const Action = ({ pending, success, failed, transactionState }: ActionProps) => {
switch (transactionState) {
case TransactionState.Failed:
return failed
case TransactionState.Success:
return success
default:
return pending
}
}
const formatAmount = (amountRaw: string, decimals: number, sigFigs: number): string =>
new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
const FailedText = ({ transactionState }: { transactionState: TransactionState }) =>
transactionState === TransactionState.Failed ? <Trans>failed</Trans> : <span />
const FormattedCurrencyAmount = ({
rawAmount,
currencyId,
sigFigs = 2,
}: {
rawAmount: string
currencyId: string
sigFigs: number
}) => {
const currency = useCurrency(currencyId)
return currency ? (
<HighlightText>
{formatAmount(rawAmount, currency.decimals, sigFigs)} {currency.symbol}
</HighlightText>
) : null
}
const getRawAmounts = (
info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo
): { rawAmountFrom: string; rawAmountTo: string } => {
return info.tradeType === TradeType.EXACT_INPUT
? { rawAmountFrom: info.inputCurrencyAmountRaw, rawAmountTo: info.expectedOutputCurrencyAmountRaw }
: { rawAmountFrom: info.expectedInputCurrencyAmountRaw, rawAmountTo: info.outputCurrencyAmountRaw }
}
const SwapSummary = ({
info,
transactionState,
}: {
info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo
transactionState: TransactionState
}) => {
const actionProps = {
transactionState,
pending: <Trans>Swapping</Trans>,
success: <Trans>Swapped</Trans>,
failed: <Trans>Swap</Trans>,
}
const { rawAmountFrom, rawAmountTo } = getRawAmounts(info)
return (
<>
<Action {...actionProps} />{' '}
<FormattedCurrencyAmount rawAmount={rawAmountFrom} currencyId={info.inputCurrencyId} sigFigs={2} />{' '}
<Trans>for </Trans>{' '}
<FormattedCurrencyAmount rawAmount={rawAmountTo} currencyId={info.outputCurrencyId} sigFigs={2} />{' '}
<FailedText transactionState={transactionState} />
</>
)
}
const AddLiquidityV3PoolSummary = ({
info,
transactionState,
}: {
info: AddLiquidityV3PoolTransactionInfo
transactionState: TransactionState
}) => {
const { createPool, quoteCurrencyId, baseCurrencyId } = info
const actionProps = {
transactionState,
pending: <Trans>Adding</Trans>,
success: <Trans>Added</Trans>,
failed: <Trans>Add</Trans>,
}
return (
<>
{createPool ? (
<CreateV3PoolSummary info={info} transactionState={transactionState} />
) : (
<>
<Action {...actionProps} />{' '}
<FormattedCurrencyAmount rawAmount={info.expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
<Trans>and</Trans>{' '}
<FormattedCurrencyAmount rawAmount={info.expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />
</>
)}{' '}
<FailedText transactionState={transactionState} />
</>
)
}
const RemoveLiquidityV3Summary = ({
info: { baseCurrencyId, quoteCurrencyId, expectedAmountBaseRaw, expectedAmountQuoteRaw },
transactionState,
}: {
info: RemoveLiquidityV3TransactionInfo
transactionState: TransactionState
}) => {
const actionProps = {
transactionState,
pending: <Trans>Removing</Trans>,
success: <Trans>Removed</Trans>,
failed: <Trans>Remove</Trans>,
}
return (
<>
<Action {...actionProps} />{' '}
<FormattedCurrencyAmount rawAmount={expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
<Trans>and</Trans>{' '}
<FormattedCurrencyAmount rawAmount={expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />{' '}
<FailedText transactionState={transactionState} />
</>
)
}
const CreateV3PoolSummary = ({
info: { baseCurrencyId, quoteCurrencyId },
transactionState,
}: {
info: AddLiquidityV3PoolTransactionInfo
transactionState: TransactionState
}) => {
const baseCurrency = useCurrency(baseCurrencyId)
const quoteCurrency = useCurrency(quoteCurrencyId)
const actionProps = {
transactionState,
pending: <Trans>Creating</Trans>,
success: <Trans>Created</Trans>,
failed: <Trans>Create</Trans>,
}
return (
<>
<Action {...actionProps} />{' '}
<HighlightText>
{baseCurrency?.symbol}/{quoteCurrency?.symbol}{' '}
</HighlightText>
<Trans>Pool</Trans> <FailedText transactionState={transactionState} />
</>
)
}
const TransactionBody = ({ info, transactionState }: { info: TransactionInfo; transactionState: TransactionState }) => {
switch (info.type) {
case TransactionType.SWAP:
return <SwapSummary info={info} transactionState={transactionState} />
case TransactionType.ADD_LIQUIDITY_V3_POOL:
return <AddLiquidityV3PoolSummary info={info} transactionState={transactionState} />
case TransactionType.REMOVE_LIQUIDITY_V3:
return <RemoveLiquidityV3Summary info={info} transactionState={transactionState} />
default:
return <span />
}
}
export default TransactionBody
import { useWeb3React } from '@web3-react/core'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { useMemo } from 'react'
import { AlertTriangle, CheckCircle } from 'react-feather'
import styled from 'styled-components/macro'
import { colors } from 'theme/colors'
import { TransactionDetails } from '../../state/transactions/types'
import Loader from '../Loader'
import LogoView from './LogoView'
import TransactionBody from './TransactionBody'
export enum TransactionState {
Pending,
Success,
Failed,
}
const Grid = styled.a`
cursor: pointer;
display: grid;
grid-template-columns: 44px auto 24px;
width: 100%;
text-decoration: none;
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
padding: 12px;
`
const TextContainer = styled.span`
font-size: 14px;
margin-top: auto;
margin-bottom: auto;
color: ${({ theme }) => theme.textTertiary};
`
const IconStyleWrap = styled.span`
margin-top: auto;
margin-bottom: auto;
margin-left: auto;
height: 16px;
`
export const TransactionSummary = ({ transactionDetails }: { transactionDetails: TransactionDetails }) => {
const { chainId = 1 } = useWeb3React()
const tx = transactionDetails
const { explorer } = getChainInfoOrDefault(chainId ? chainId : SupportedChainId.MAINNET)
const { info, receipt, hash } = tx
const transactionState = useMemo(() => {
const pending = !receipt
const success = !pending && tx && (receipt?.status === 1 || typeof receipt?.status === 'undefined')
const transactionState = pending
? TransactionState.Pending
: success
? TransactionState.Success
: TransactionState.Failed
return transactionState
}, [receipt, tx])
const link = `${explorer}tx/${hash}`
return chainId ? (
<Grid href={link} target="_blank">
<LogoView info={info} />
<TextContainer as="span">
<TransactionBody info={info} transactionState={transactionState} />
</TextContainer>
{transactionState === TransactionState.Pending ? (
<IconStyleWrap>
<Loader />
</IconStyleWrap>
) : transactionState === TransactionState.Success ? (
<IconStyleWrap>
<CheckCircle color={colors.green200} size="16px" />
</IconStyleWrap>
) : (
<IconStyleWrap>
<AlertTriangle color={colors.gold200} size="16px" />
</IconStyleWrap>
)}
</Grid>
) : null
}
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { ButtonPrimary } from 'components/Button'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { useMemo } from 'react'
import { ChevronRight, Moon, Sun } from 'react-feather'
......@@ -11,17 +12,12 @@ import { useAllTransactions } from '../../state/transactions/hooks'
import AuthenticatedHeader from './AuthenticatedHeader'
import { MenuState } from './index'
const ConnectButton = styled.button`
border: none;
outline: none;
const ConnectButton = styled(ButtonPrimary)`
border-radius: 12px;
height: 44px;
width: 288px;
background-color: ${({ theme }) => theme.accentAction};
color: white;
font-weight: 600;
font-size: 16px;
cursor: pointer;
`
const Divider = styled.div`
......@@ -47,7 +43,8 @@ const ToggleMenuItem = styled.button`
margin-bottom: 8px;
color: ${({ theme }) => theme.textSecondary};
:hover {
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
transition: 250ms color ease;
}
`
......
......@@ -24,7 +24,6 @@ const Header = styled.span`
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
height: 200px;
`
const ClearAll = styled.div`
......@@ -38,6 +37,15 @@ const ClearAll = styled.div`
margin-bottom: auto;
`
const StyledChevron = styled(ChevronLeft)`
cursor: pointer;
&:hover {
color: ${({ theme }) => theme.textPrimary};
transition: 250ms color ease;
}
`
export const SlideOutMenu = ({
children,
onClose,
......@@ -51,8 +59,7 @@ export const SlideOutMenu = ({
}) => (
<Menu>
<BackSection>
<ChevronLeft cursor="pointer" onClick={onClose} size={24} />
<StyledChevron onClick={onClose} size={24} />
<Header>{title}</Header>
{onClear && <ClearAll onClick={onClear}>Clear All</ClearAll>}
</BackSection>
......
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { getYear, isSameDay, isSameWeek, isSameYear } from 'date-fns'
import ms from 'ms.macro'
import { useCallback, useMemo } from 'react'
import { useAppDispatch } from 'state/hooks'
import styled from 'styled-components/macro'
import { useAllTransactions } from '../../state/transactions/hooks'
import { clearAllTransactions } from '../../state/transactions/reducer'
import { TransactionDetails } from '../../state/transactions/types'
import { TransactionSummary } from '../AccountDetailsV2'
import { SlideOutMenu } from './SlideOutMenu'
export const TransactionHistoryMenu = ({ onClose }: { onClose: () => void }) => (
<SlideOutMenu onClose={onClose} onClear={undefined} title={<Trans>Transactions</Trans>}>
<div />
const THIRTY_DAYS = ms`30 days`
const Divider = styled.div`
margin-top: 16px;
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
`
const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
`
interface TransactionInformation {
title: string
transactions: TransactionDetails[]
}
const TransactionTitle = styled.span`
padding-bottom: 8px;
padding-top: 20px;
padding-left: 12px;
padding-right: 12px;
font-weight: 600;
color: ${({ theme }) => theme.textTertiary};
`
const TransactionList = ({ transactionInformation }: { transactionInformation: TransactionInformation }) => {
const { title, transactions } = transactionInformation
return (
<TransactionListWrapper key={title}>
<TransactionTitle>{title}</TransactionTitle>
{transactions.map((transactionDetails) => (
<TransactionSummary key={transactionDetails.hash} transactionDetails={transactionDetails} />
))}
</TransactionListWrapper>
)
}
const getConfirmedTransactions = (confirmedTransactions: Array<TransactionDetails>) => {
const now = new Date().getTime()
const today: Array<TransactionDetails> = []
const currentWeek: Array<TransactionDetails> = []
const last30Days: Array<TransactionDetails> = []
const currentYear: Array<TransactionDetails> = []
const yearMap: { [key: string]: Array<TransactionDetails> } = {}
confirmedTransactions.forEach((transaction) => {
const { addedTime } = transaction
if (isSameDay(now, addedTime)) {
today.push(transaction)
} else if (isSameWeek(addedTime, now)) {
currentWeek.push(transaction)
} else if (now - addedTime < THIRTY_DAYS) {
last30Days.push(transaction)
} else if (isSameYear(addedTime, now)) {
currentYear.push(transaction)
} else {
const year = getYear(addedTime)
if (!yearMap[year]) {
yearMap[year] = [transaction]
} else {
yearMap[year].push(transaction)
}
}
})
const transactionGroups: Array<TransactionInformation> = [
{
title: 'Today',
transactions: today,
},
{
title: 'This week',
transactions: currentWeek,
},
{
title: 'Past 30 Days',
transactions: last30Days,
},
{
title: 'This year',
transactions: currentYear,
},
]
const sortedYears = Object.keys(yearMap)
.sort((a, b) => parseInt(b) - parseInt(a))
.map((year) => ({ title: year, transactions: yearMap[year] }))
transactionGroups.push(...sortedYears)
return transactionGroups.filter((transactionInformation) => transactionInformation.transactions.length > 0)
}
const EmptyTransaction = styled.div`
text-align: center;
margin-top: 24px;
font-weight: 400;
font-size: 14px;
padding-left: 12px;
padding-right: 12px;
color: ${({ theme }) => theme.textSecondary};
`
export const TransactionHistoryMenu = ({ onClose }: { onClose: () => void }) => {
const allTransactions = useAllTransactions()
const { chainId } = useWeb3React()
const dispatch = useAppDispatch()
const transactionGroupsInformation = []
const clearAllTransactionsCallback = useCallback(() => {
if (chainId) dispatch(clearAllTransactions({ chainId }))
}, [dispatch, chainId])
const [confirmed, pending] = useMemo(() => {
const confirmed: Array<TransactionDetails> = []
const pending: Array<TransactionDetails> = []
const sorted = Object.values(allTransactions).sort((a, b) => b.addedTime - a.addedTime)
sorted.forEach((transaction) => (transaction.receipt ? confirmed.push(transaction) : pending.push(transaction)))
return [confirmed, pending]
}, [allTransactions])
const confirmedTransactions = useMemo(() => getConfirmedTransactions(confirmed), [confirmed])
if (pending.length) transactionGroupsInformation.push({ title: `Pending (${pending.length})`, transactions: pending })
if (confirmedTransactions.length) transactionGroupsInformation.push(...confirmedTransactions)
return (
<SlideOutMenu
onClose={onClose}
onClear={transactionGroupsInformation.length > 0 ? clearAllTransactionsCallback : undefined}
title={<Trans>Transactions</Trans>}
>
<Divider />
{transactionGroupsInformation.length > 0 ? (
<>
{transactionGroupsInformation.map((transactionInformation, index) => (
<TransactionList key={transactionInformation.title} transactionInformation={transactionInformation} />
))}
</>
) : (
<EmptyTransaction>
<Trans>Your transactions will appear here</Trans>
</EmptyTransaction>
)}
</SlideOutMenu>
)
)
}
......@@ -478,6 +478,8 @@ export function PositionPage() {
type: TransactionType.COLLECT_FEES,
currencyId0: currencyId(currency0ForFeeCollectionPurposes),
currencyId1: currencyId(currency1ForFeeCollectionPurposes),
expectedCurrencyOwed0: CurrencyAmount.fromRawAmount(currency0ForFeeCollectionPurposes, 0).toExact(),
expectedCurrencyOwed1: CurrencyAmount.fromRawAmount(currency1ForFeeCollectionPurposes, 0).toExact(),
})
})
})
......
......@@ -152,6 +152,8 @@ export interface CollectFeesTransactionInfo {
type: TransactionType.COLLECT_FEES
currencyId0: string
currencyId1: string
expectedCurrencyOwed0: string
expectedCurrencyOwed1: string
}
export interface RemoveLiquidityV3TransactionInfo {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment