refactor: transaction info is translated in the appropriate places (#2380)

* refactor: transaction info is translated in the appropriate places

fixes https://github.com/Uniswap/interface/issues/1756

* getting closer

* more work

* finished, finally

* bit more refactoring

* move summary into its own file

* little more cleanup in the transaction summary file

* fix bad copy

* fix the migrate notification

* missing translation

* fix the language for vote and address other pr comments

* fix typo

* - remove old transactions with this update
- change to present tense
- show ens name where appropriate

* add a test that shows we don't clear old ones
This commit is contained in:
Moody Salem 2021-09-21 16:45:24 -05:00 committed by GitHub
parent 9fa3b70475
commit fb07919888
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 944 additions and 380 deletions

@ -1,14 +1,13 @@
import styled from 'styled-components/macro'
import { CheckCircle, Triangle } from 'react-feather'
import styled from 'styled-components/macro'
import { useActiveWeb3React } from '../../hooks/web3'
import { ExternalLink } from '../../theme'
import { useAllTransactions } from '../../state/transactions/hooks'
import { ExternalLink } from '../../theme'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { RowFixed } from '../Row'
import Loader from '../Loader'
const TransactionWrapper = styled.div``
import { RowFixed } from '../Row'
import { TransactionSummary } from './TransactionSummary'
const TransactionStatusText = styled.div`
margin-right: 0.5rem;
@ -40,26 +39,28 @@ export default function Transaction({ hash }: { hash: string }) {
const allTransactions = useAllTransactions()
const tx = allTransactions?.[hash]
const summary = tx?.summary
const info = tx?.info
const pending = !tx?.receipt
const success = !pending && tx && (tx.receipt?.status === 1 || typeof tx.receipt?.status === 'undefined')
if (!chainId) return null
return (
<TransactionWrapper>
<div>
<TransactionState
href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}
pending={pending}
success={success}
>
<RowFixed>
<TransactionStatusText>{summary ?? hash} </TransactionStatusText>
<TransactionStatusText>
<TransactionSummary info={info} />
</TransactionStatusText>
</RowFixed>
<IconWrapper pending={pending} success={success}>
{pending ? <Loader /> : success ? <CheckCircle size="16" /> : <Triangle size="16" />}
</IconWrapper>
</TransactionState>
</TransactionWrapper>
</div>
)
}

@ -0,0 +1,328 @@
import { Trans } from '@lingui/macro'
import { Fraction, TradeType } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { useCurrency, useToken } from '../../hooks/Tokens'
import useENSName from '../../hooks/useENSName'
import { VoteOption } from '../../state/governance/types'
import {
AddLiquidityV2PoolTransactionInfo,
AddLiquidityV3PoolTransactionInfo,
ApproveTransactionInfo,
ClaimTransactionInfo,
CollectFeesTransactionInfo,
CreateV3PoolTransactionInfo,
DelegateTransactionInfo,
DepositLiquidityStakingTransactionInfo,
ExactInputSwapTransactionInfo,
ExactOutputSwapTransactionInfo,
MigrateV2LiquidityToV3TransactionInfo,
RemoveLiquidityV3TransactionInfo,
SubmitProposalTransactionInfo,
TransactionInfo,
TransactionType,
VoteTransactionInfo,
WithdrawLiquidityStakingTransactionInfo,
WrapTransactionInfo,
} from '../../state/transactions/actions'
function formatAmount(amountRaw: string, decimals: number, sigFigs: number): string {
return new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
}
function FormattedCurrencyAmount({
rawAmount,
symbol,
decimals,
sigFigs,
}: {
rawAmount: string
symbol: string
decimals: number
sigFigs: number
}) {
return (
<>
{formatAmount(rawAmount, decimals, sigFigs)} {symbol}
</>
)
}
function FormattedCurrencyAmountManaged({
rawAmount,
currencyId,
sigFigs = 6,
}: {
rawAmount: string
currencyId: string
sigFigs: number
}) {
const currency = useCurrency(currencyId)
return currency ? (
<FormattedCurrencyAmount
rawAmount={rawAmount}
decimals={currency.decimals}
sigFigs={sigFigs}
symbol={currency.symbol ?? '???'}
/>
) : null
}
function ClaimSummary({ info: { recipient, uniAmountRaw } }: { info: ClaimTransactionInfo }) {
const { ENSName } = useENSName()
return typeof uniAmountRaw === 'string' ? (
<Trans>
Claim <FormattedCurrencyAmount rawAmount={uniAmountRaw} symbol={'UNI'} decimals={18} sigFigs={4} /> for{' '}
{ENSName ?? recipient}
</Trans>
) : (
<Trans>Claim UNI reward for {ENSName ?? recipient}</Trans>
)
}
function SubmitProposalTransactionSummary({}: { info: SubmitProposalTransactionInfo }) {
return <Trans>Submit new proposal</Trans>
}
function ApprovalSummary({ info }: { info: ApproveTransactionInfo }) {
const token = useToken(info.tokenAddress)
return <Trans>Approve {token?.symbol}</Trans>
}
function VoteSummary({ info }: { info: VoteTransactionInfo }) {
const proposalKey = `${info.governorAddress}/${info.proposalId}`
if (info.reason && info.reason.trim().length > 0) {
switch (info.decision) {
case VoteOption.For:
return <Trans>Vote for proposal {proposalKey}</Trans>
case VoteOption.Abstain:
return <Trans>Vote to abstain on proposal {proposalKey}</Trans>
case VoteOption.Against:
return <Trans>Vote against proposal {proposalKey}</Trans>
}
} else {
switch (info.decision) {
case VoteOption.For:
return (
<Trans>
Vote for proposal {proposalKey} with reason &quot;{info.reason}&quot;
</Trans>
)
case VoteOption.Abstain:
return (
<Trans>
Vote to abstain on proposal {proposalKey} with reason &quot;{info.reason}&quot;
</Trans>
)
case VoteOption.Against:
return (
<Trans>
Vote against proposal {proposalKey} with reason &quot;{info.reason}&quot;
</Trans>
)
}
}
}
function DelegateSummary({ info: { delegatee } }: { info: DelegateTransactionInfo }) {
const { ENSName } = useENSName(delegatee)
return <Trans>Delegate voting power to {ENSName ?? delegatee}</Trans>
}
function WrapSummary({ info: { currencyAmountRaw, unwrapped } }: { info: WrapTransactionInfo }) {
if (unwrapped) {
return (
<Trans>
Unwrap <FormattedCurrencyAmount rawAmount={currencyAmountRaw} symbol={'WETH'} decimals={18} sigFigs={6} /> to
ETH
</Trans>
)
} else {
return (
<Trans>
Wrap <FormattedCurrencyAmount rawAmount={currencyAmountRaw} symbol={'ETH'} decimals={18} sigFigs={6} /> to WETH
</Trans>
)
}
}
function DepositLiquidityStakingSummary({}: { info: DepositLiquidityStakingTransactionInfo }) {
// not worth rendering the tokens since you can should no longer deposit liquidity in the staking contracts
// todo: deprecate and delete the code paths that allow this, show user more information
return <Trans>Deposit liquidity</Trans>
}
function WithdrawLiquidityStakingSummary({}: { info: WithdrawLiquidityStakingTransactionInfo }) {
return <Trans>Withdraw deposited liquidity</Trans>
}
function MigrateLiquidityToV3Summary({
info: { baseCurrencyId, quoteCurrencyId },
}: {
info: MigrateV2LiquidityToV3TransactionInfo
}) {
const baseCurrency = useCurrency(baseCurrencyId)
const quoteCurrency = useCurrency(quoteCurrencyId)
return (
<Trans>
Migrate {baseCurrency?.symbol}/{quoteCurrency?.symbol} liquidity to V3
</Trans>
)
}
function CreateV3PoolSummary({ info: { quoteCurrencyId, baseCurrencyId } }: { info: CreateV3PoolTransactionInfo }) {
const baseCurrency = useCurrency(baseCurrencyId)
const quoteCurrency = useCurrency(quoteCurrencyId)
return (
<Trans>
Create {baseCurrency?.symbol}/{quoteCurrency?.symbol} V3 pool
</Trans>
)
}
function CollectFeesSummary({ info: { currencyId0, currencyId1 } }: { info: CollectFeesTransactionInfo }) {
const currency0 = useCurrency(currencyId0)
const currency1 = useCurrency(currencyId1)
return (
<Trans>
Collect {currency0?.symbol}/{currency1?.symbol} fees
</Trans>
)
}
function RemoveLiquidityV3Summary({
info: { baseCurrencyId, quoteCurrencyId, expectedAmountBaseRaw, expectedAmountQuoteRaw },
}: {
info: RemoveLiquidityV3TransactionInfo
}) {
return (
<Trans>
Remove{' '}
<FormattedCurrencyAmountManaged rawAmount={expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={3} /> and{' '}
<FormattedCurrencyAmountManaged rawAmount={expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={3} />
</Trans>
)
}
function AddLiquidityV3PoolSummary({
info: { createPool, quoteCurrencyId, baseCurrencyId },
}: {
info: AddLiquidityV3PoolTransactionInfo
}) {
const baseCurrency = useCurrency(baseCurrencyId)
const quoteCurrency = useCurrency(quoteCurrencyId)
return createPool ? (
<Trans>
Create pool and add {baseCurrency?.symbol}/{quoteCurrency?.symbol} V3 liquidity
</Trans>
) : (
<Trans>
Add {baseCurrency?.symbol}/{quoteCurrency?.symbol} V3 liquidity
</Trans>
)
}
function AddLiquidityV2PoolSummary({
info: { quoteCurrencyId, expectedAmountBaseRaw, expectedAmountQuoteRaw, baseCurrencyId },
}: {
info: AddLiquidityV2PoolTransactionInfo
}) {
return (
<Trans>
Add <FormattedCurrencyAmountManaged rawAmount={expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={3} />{' '}
and <FormattedCurrencyAmountManaged rawAmount={expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={3} />{' '}
to Uniswap V2
</Trans>
)
}
function SwapSummary({ info }: { info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo }) {
if (info.tradeType === TradeType.EXACT_INPUT) {
return (
<Trans>
Swap exactly{' '}
<FormattedCurrencyAmountManaged
rawAmount={info.inputCurrencyAmountRaw}
currencyId={info.inputCurrencyId}
sigFigs={6}
/>{' '}
for{' '}
<FormattedCurrencyAmountManaged
rawAmount={info.expectedOutputCurrencyAmountRaw}
currencyId={info.outputCurrencyId}
sigFigs={6}
/>
</Trans>
)
} else {
return (
<Trans>
Swap{' '}
<FormattedCurrencyAmountManaged
rawAmount={info.expectedInputCurrencyAmountRaw}
currencyId={info.inputCurrencyId}
sigFigs={6}
/>{' '}
for exactly{' '}
<FormattedCurrencyAmountManaged
rawAmount={info.outputCurrencyAmountRaw}
currencyId={info.outputCurrencyId}
sigFigs={6}
/>
</Trans>
)
}
}
export function TransactionSummary({ info }: { info: TransactionInfo }) {
switch (info.type) {
case TransactionType.ADD_LIQUIDITY_V3_POOL:
return <AddLiquidityV3PoolSummary info={info} />
case TransactionType.ADD_LIQUIDITY_V2_POOL:
return <AddLiquidityV2PoolSummary info={info} />
case TransactionType.CLAIM:
return <ClaimSummary info={info} />
case TransactionType.DEPOSIT_LIQUIDITY_STAKING:
return <DepositLiquidityStakingSummary info={info} />
case TransactionType.WITHDRAW_LIQUIDITY_STAKING:
return <WithdrawLiquidityStakingSummary info={info} />
case TransactionType.SWAP:
return <SwapSummary info={info} />
case TransactionType.APPROVAL:
return <ApprovalSummary info={info} />
case TransactionType.VOTE:
return <VoteSummary info={info} />
case TransactionType.DELEGATE:
return <DelegateSummary info={info} />
case TransactionType.WRAP:
return <WrapSummary info={info} />
case TransactionType.CREATE_V3_POOL:
return <CreateV3PoolSummary info={info} />
case TransactionType.MIGRATE_LIQUIDITY_V3:
return <MigrateLiquidityToV3Summary info={info} />
case TransactionType.COLLECT_FEES:
return <CollectFeesSummary info={info} />
case TransactionType.REMOVE_LIQUIDITY_V3:
return <RemoveLiquidityV3Summary info={info} />
case TransactionType.SUBMIT_PROPOSAL:
return <SubmitProposalTransactionSummary info={info} />
}
}

@ -73,9 +73,9 @@ export default function PopupItem({
let popupContent
if ('txn' in content) {
const {
txn: { hash, success, summary },
txn: { hash },
} = content
popupContent = <TransactionPopup hash={hash} success={success} summary={summary} />
popupContent = <TransactionPopup hash={hash} />
}
const faderStyle = useSpring({

@ -2,9 +2,11 @@ import { useContext } from 'react'
import { AlertCircle, CheckCircle } from 'react-feather'
import styled, { ThemeContext } from 'styled-components/macro'
import { useActiveWeb3React } from '../../hooks/web3'
import { useTransaction } from '../../state/transactions/hooks'
import { TYPE } from '../../theme'
import { ExternalLink } from '../../theme/components'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { TransactionSummary } from '../AccountDetails/TransactionSummary'
import { AutoColumn } from '../Column'
import { AutoRow } from '../Row'
@ -12,26 +14,24 @@ const RowNoFlex = styled(AutoRow)`
flex-wrap: nowrap;
`
export default function TransactionPopup({
hash,
success,
summary,
}: {
hash: string
success?: boolean
summary?: string
}) {
export default function TransactionPopup({ hash }: { hash: string }) {
const { chainId } = useActiveWeb3React()
const tx = useTransaction(hash)
const theme = useContext(ThemeContext)
if (!tx) return null
const success = Boolean(tx.receipt && tx.receipt.status === 1)
return (
<RowNoFlex>
<div style={{ paddingRight: 16 }}>
{success ? <CheckCircle color={theme.green1} size={24} /> : <AlertCircle color={theme.red1} size={24} />}
</div>
<AutoColumn gap="8px">
<TYPE.body fontWeight={500}>{summary ?? 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}</TYPE.body>
<TYPE.body fontWeight={500}>
<TransactionSummary info={tx.info} />
</TYPE.body>
{chainId && (
<ExternalLink href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}>
View on Explorer

@ -2,6 +2,7 @@ import { Currency } from '@uniswap/sdk-core'
import { ReactNode, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components/macro'
import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink'
import { TransactionSummary } from '../AccountDetails/TransactionSummary'
import Modal from '../Modal'
import { ExternalLink } from '../../theme'
import { Text } from 'rebass'
@ -273,7 +274,7 @@ function L2Content({
)}
</Text>
<Text fontWeight={400} fontSize={16} textAlign="center">
{transaction?.summary ?? pendingText ?? ''}
{transaction ? <TransactionSummary info={transaction.info} /> : pendingText}
</Text>
{chainId && hash ? (
<ExternalLink href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}>

@ -188,7 +188,7 @@ export default function ClaimModal() {
<AutoColumn gap="100px" justify={'center'}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader fontWeight={600} color="black">
{claimConfirmed ? 'Claimed!' : 'Claiming'}
{claimConfirmed ? <Trans>Claimed!</Trans> : <Trans>Claiming</Trans>}
</TYPE.largeHeader>
{!claimConfirmed && (
<Text fontSize={36} color={'#ff007a'} fontWeight={800}>

@ -1,17 +1,18 @@
import { useState } from 'react'
import Modal from '../Modal'
import { AutoColumn } from '../Column'
import styled from 'styled-components/macro'
import { RowBetween } from '../Row'
import { TYPE, CloseIcon } from '../../theme'
import { ButtonError } from '../Button'
import { StakingInfo } from '../../state/stake/hooks'
import { useStakingContract } from '../../hooks/useContract'
import { SubmittedView, LoadingView } from '../ModalViews'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useActiveWeb3React } from '../../hooks/web3'
import { t, Trans } from '@lingui/macro'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { useStakingContract } from '../../hooks/useContract'
import { useActiveWeb3React } from '../../hooks/web3'
import { StakingInfo } from '../../state/stake/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { CloseIcon, TYPE } from '../../theme'
import { ButtonError } from '../Button'
import { AutoColumn } from '../Column'
import Modal from '../Modal'
import { LoadingView, SubmittedView } from '../ModalViews'
import { RowBetween } from '../Row'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
@ -41,12 +42,15 @@ export default function ClaimRewardModal({ isOpen, onDismiss, stakingInfo }: Sta
const stakingContract = useStakingContract(stakingInfo.stakingRewardAddress)
async function onClaimReward() {
if (stakingContract && stakingInfo?.stakedAmount) {
if (stakingContract && stakingInfo?.stakedAmount && account) {
setAttempting(true)
await stakingContract
.getReward({ gasLimit: 350000 })
.then((response: TransactionResponse) => {
addTransaction(response, { summary: t`Claim accumulated UNI rewards` })
addTransaction(response, {
type: TransactionType.CLAIM,
recipient: account,
})
setHash(response.hash)
})
.catch((error: any) => {

@ -1,26 +1,27 @@
import { useState, useCallback } from 'react'
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { useCallback, useState } from 'react'
import styled from 'styled-components/macro'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { usePairContract, useStakingContract, useV2RouterContract } from '../../hooks/useContract'
import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { formatCurrencyAmount } from '../../utils/formatCurrencyAmount'
import Modal from '../Modal'
import { AutoColumn } from '../Column'
import styled from 'styled-components/macro'
import { RowBetween } from '../Row'
import { TYPE, CloseIcon } from '../../theme'
import { ButtonConfirmed, ButtonError } from '../Button'
import ProgressCircles from '../ProgressSteps'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { Pair } from '@uniswap/v2-sdk'
import { Token, CurrencyAmount } from '@uniswap/sdk-core'
import { useActiveWeb3React } from '../../hooks/web3'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { usePairContract, useStakingContract, useV2RouterContract } from '../../hooks/useContract'
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks'
import { TransactionResponse } from '@ethersproject/providers'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { CloseIcon, TYPE } from '../../theme'
import { formatCurrencyAmount } from '../../utils/formatCurrencyAmount'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { ButtonConfirmed, ButtonError } from '../Button'
import { AutoColumn } from '../Column'
import CurrencyInputPanel from '../CurrencyInputPanel'
import Modal from '../Modal'
import { LoadingView, SubmittedView } from '../ModalViews'
import { t, Trans } from '@lingui/macro'
import ProgressCircles from '../ProgressSteps'
import { RowBetween } from '../Row'
const HypotheticalRewardRate = styled.div<{ dim: boolean }>`
display: flex;
@ -105,7 +106,9 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui
)
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: t`Deposit liquidity`,
type: TransactionType.DEPOSIT_LIQUIDITY_STAKING,
token0Address: stakingInfo.tokens[0].address,
token1Address: stakingInfo.tokens[1].address,
})
setHash(response.hash)
})

@ -1,18 +1,19 @@
import { useState } from 'react'
import Modal from '../Modal'
import { AutoColumn } from '../Column'
import styled from 'styled-components/macro'
import { RowBetween } from '../Row'
import { TYPE, CloseIcon } from '../../theme'
import { ButtonError } from '../Button'
import { StakingInfo } from '../../state/stake/hooks'
import { useStakingContract } from '../../hooks/useContract'
import { SubmittedView, LoadingView } from '../ModalViews'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from '../../state/transactions/hooks'
import FormattedCurrencyAmount from '../FormattedCurrencyAmount'
import { useActiveWeb3React } from '../../hooks/web3'
import { t, Trans } from '@lingui/macro'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { useStakingContract } from '../../hooks/useContract'
import { useActiveWeb3React } from '../../hooks/web3'
import { StakingInfo } from '../../state/stake/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { CloseIcon, TYPE } from '../../theme'
import { ButtonError } from '../Button'
import { AutoColumn } from '../Column'
import FormattedCurrencyAmount from '../FormattedCurrencyAmount'
import Modal from '../Modal'
import { LoadingView, SubmittedView } from '../ModalViews'
import { RowBetween } from '../Row'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
@ -48,7 +49,9 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
.exit({ gasLimit: 300000 })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: t`Withdraw deposited liquidity`,
type: TransactionType.WITHDRAW_LIQUIDITY_STAKING,
token0Address: stakingInfo.tokens[0].address,
token1Address: stakingInfo.tokens[1].address,
})
setHash(response.hash)
})

@ -1,5 +1,6 @@
import { useState, useContext } from 'react'
import { useActiveWeb3React } from '../../hooks/web3'
import { VoteOption } from '../../state/governance/types'
import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink'
import Modal from '../Modal'
@ -10,7 +11,7 @@ import { TYPE, CustomLightSpinner } from '../../theme'
import { X, ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button'
import Circle from '../../assets/images/blue-loader.svg'
import { useVoteCallback, useUserVotes, VoteOption } from '../../state/governance/hooks'
import { useVoteCallback, useUserVotes } from '../../state/governance/hooks'
import { ExternalLink } from '../../theme/components'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { Trans } from '@lingui/macro'

@ -5,6 +5,7 @@ import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useCallback, useMemo } from 'react'
import { SWAP_ROUTER_ADDRESSES, V2_ROUTER_ADDRESS } from '../constants/addresses'
import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder, useHasPendingApproval } from '../state/transactions/hooks'
import { calculateGasMargin } from '../utils/calculateGasMargin'
import { useTokenContract } from './useContract'
@ -88,10 +89,7 @@ export function useApproveCallback(
gasLimit: calculateGasMargin(chainId, estimatedGas),
})
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: 'Approve ' + amountToApprove.currency.symbol,
approval: { tokenAddress: token.address, spender },
})
addTransaction(response, { type: TransactionType.APPROVAL, tokenAddress: token.address, spender })
})
.catch((error: Error) => {
console.debug('Failed to approve token', error)

@ -5,11 +5,11 @@ import { SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { SWAP_ROUTER_ADDRESSES } from '../constants/addresses'
import { TransactionType } from '../state/transactions/actions'
import { calculateGasMargin } from '../utils/calculateGasMargin'
import approveAmountCalldata from '../utils/approveAmountCalldata'
import { getTradeVersion } from '../utils/getTradeVersion'
import { useTransactionAdder } from '../state/transactions/hooks'
import { isAddress, shortenAddress } from '../utils'
import { currencyId } from '../utils/currencyId'
import isZero from '../utils/isZero'
import { useActiveWeb3React } from './web3'
import { useArgentWalletContract } from './useArgentWalletContract'
@ -17,7 +17,6 @@ import { useV2RouterContract } from './useContract'
import { SignatureData } from './useERC20Permit'
import useTransactionDeadline from './useTransactionDeadline'
import useENS from './useENS'
import { Version } from './useToggledVersion'
enum SwapCallbackState {
INVALID,
@ -334,28 +333,28 @@ export function useSwapCallback(
...(value && !isZero(value) ? { value } : {}),
})
.then((response) => {
const inputSymbol = trade.inputAmount.currency.symbol
const outputSymbol = trade.outputAmount.currency.symbol
const inputAmount = trade.inputAmount.toSignificant(4)
const outputAmount = trade.outputAmount.toSignificant(4)
const base = `Swap ${inputAmount} ${inputSymbol} for ${outputAmount} ${outputSymbol}`
const withRecipient =
recipient === account
? base
: `${base} to ${
recipientAddressOrName && isAddress(recipientAddressOrName)
? shortenAddress(recipientAddressOrName)
: recipientAddressOrName
}`
const tradeVersion = getTradeVersion(trade)
const withVersion = tradeVersion === Version.v3 ? withRecipient : `${withRecipient} on ${tradeVersion}`
addTransaction(response, {
summary: withVersion,
})
addTransaction(
response,
trade.tradeType === TradeType.EXACT_INPUT
? {
type: TransactionType.SWAP,
tradeType: TradeType.EXACT_INPUT,
inputCurrencyId: currencyId(trade.inputAmount.currency),
inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
outputCurrencyId: currencyId(trade.outputAmount.currency),
minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(),
}
: {
type: TransactionType.SWAP,
tradeType: TradeType.EXACT_OUTPUT,
inputCurrencyId: currencyId(trade.inputAmount.currency),
maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(),
outputCurrencyId: currencyId(trade.outputAmount.currency),
outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
}
)
return response.hash
})
@ -373,5 +372,5 @@ export function useSwapCallback(
},
error: null,
}
}, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, addTransaction])
}, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, addTransaction, allowedSlippage])
}

@ -2,10 +2,11 @@ import { Currency } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { WETH9_EXTENDED } from '../constants/tokens'
import { tryParseAmount } from '../state/swap/hooks'
import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder } from '../state/transactions/hooks'
import { useCurrencyBalance } from '../state/wallet/hooks'
import { useActiveWeb3React } from './web3'
import { useWETHContract } from './useContract'
import { useActiveWeb3React } from './web3'
export enum WrapType {
NOT_APPLICABLE,
@ -48,7 +49,11 @@ export default function useWrapCallback(
? async () => {
try {
const txReceipt = await wethContract.deposit({ value: `0x${inputAmount.quotient.toString(16)}` })
addTransaction(txReceipt, { summary: `Wrap ${inputAmount.toSignificant(6)} ETH to WETH` })
addTransaction(txReceipt, {
type: TransactionType.WRAP,
unwrapped: false,
currencyAmountRaw: inputAmount?.quotient.toString(),
})
} catch (error) {
console.error('Could not deposit', error)
}
@ -64,7 +69,11 @@ export default function useWrapCallback(
? async () => {
try {
const txReceipt = await wethContract.withdraw(`0x${inputAmount.quotient.toString(16)}`)
addTransaction(txReceipt, { summary: `Unwrap ${inputAmount.toSignificant(6)} WETH to ETH` })
addTransaction(txReceipt, {
type: TransactionType.WRAP,
unwrapped: true,
currencyAmountRaw: inputAmount?.quotient.toString(),
})
} catch (error) {
console.error('Could not withdraw', error)
}

@ -1,54 +1,3 @@
import { useCallback, useContext, useEffect, useState } from 'react'
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { AlertTriangle } from 'react-feather'
import ReactGA from 'react-ga'
import { ZERO_PERCENT } from '../../constants/misc'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from '../../constants/addresses'
import { WETH9_EXTENDED } from '../../constants/tokens'
import { useArgentWalletContract } from '../../hooks/useArgentWalletContract'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components/macro'
import { ButtonError, ButtonLight, ButtonPrimary, ButtonText, ButtonYellow } from '../../components/Button'
import { YellowCard, OutlineCard, BlueCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import Row, { RowBetween, RowFixed, AutoRow } from '../../components/Row'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useUSDCValue } from '../../hooks/useUSDCPrice'
import approveAmountCalldata from '../../utils/approveAmountCalldata'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { Review } from './Review'
import { useActiveWeb3React } from '../../hooks/web3'
import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Field, Bound } from '../../state/mint/v3/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { TYPE, ExternalLink } from '../../theme'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { Dots } from '../Pool/styleds'
import { currencyId } from '../../utils/currencyId'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import {
DynamicSection,
CurrencyDropdown,
StyledInput,
Wrapper,
ScrollablePage,
ResponsiveTwoColumns,
PageWrapper,
StackedContainer,
StackedItem,
RightContainer,
MediumOnly,
HideMedium,
} from './styled'
import { Trans, t } from '@lingui/macro'
import {
useV3MintState,
@ -57,21 +6,72 @@ import {
useV3DerivedMintInfo,
} from 'state/mint/v3/hooks'
import { FeeAmount, NonfungiblePositionManager } from '@uniswap/v3-sdk'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { useDerivedPositionInfo } from 'hooks/useDerivedPositionInfo'
import { PositionPreview } from 'components/PositionPreview'
import FeeSelector from 'components/FeeSelector'
import RangeSelector from 'components/RangeSelector'
import PresetsButtons from 'components/RangeSelector/PresetsButtons'
import RateToggle from 'components/RateToggle'
import { BigNumber } from '@ethersproject/bignumber'
import { AddRemoveTabs } from 'components/NavigationTabs'
import HoverInlineText from 'components/HoverInlineText'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import LiquidityChartRangeInput from 'components/LiquidityChartRangeInput'
import { SupportedChainId } from 'constants/chains'
import DowntimeWarning from 'components/DowntimeWarning'
import { CHAIN_INFO } from '../../constants/chains'
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { useCallback, useContext, useEffect, useState } from 'react'
import { AlertTriangle } from 'react-feather'
import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components/macro'
import { ButtonError, ButtonLight, ButtonPrimary, ButtonText, ButtonYellow } from '../../components/Button'
import { BlueCard, OutlineCard, YellowCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import FeeSelector from '../../components/FeeSelector'
import HoverInlineText from '../../components/HoverInlineText'
import LiquidityChartRangeInput from '../../components/LiquidityChartRangeInput'
import { AddRemoveTabs } from '../../components/NavigationTabs'
import { PositionPreview } from '../../components/PositionPreview'
import RangeSelector from '../../components/RangeSelector'
import PresetsButtons from '../../components/RangeSelector/PresetsButtons'
import RateToggle from '../../components/RateToggle'
import Row, { AutoRow, RowBetween, RowFixed } from '../../components/Row'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from '../../constants/addresses'
import { CHAIN_INFO, SupportedChainId } from '../../constants/chains'
import { ZERO_PERCENT } from '../../constants/misc'
import { WETH9_EXTENDED } from '../../constants/tokens'
import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { useArgentWalletContract } from '../../hooks/useArgentWalletContract'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
import { useDerivedPositionInfo } from '../../hooks/useDerivedPositionInfo'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useUSDCValue } from '../../hooks/useUSDCPrice'
import { useV3PositionFromTokenId } from '../../hooks/useV3Positions'
import { useActiveWeb3React } from '../../hooks/web3'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Bound, Field } from '../../state/mint/v3/actions'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { ExternalLink, TYPE } from '../../theme'
import approveAmountCalldata from '../../utils/approveAmountCalldata'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { currencyId } from '../../utils/currencyId'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { Dots } from '../Pool/styleds'
import { Review } from './Review'
import {
CurrencyDropdown,
DynamicSection,
HideMedium,
MediumOnly,
PageWrapper,
ResponsiveTwoColumns,
RightContainer,
ScrollablePage,
StackedContainer,
StackedItem,
StyledInput,
Wrapper,
} from './styled'
const DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
@ -208,11 +208,7 @@ export default function AddLiquidity({
async function onCreate() {
if (!chainId || !library) return
if (!positionManager || !baseCurrency || !quoteCurrency) {
return
}
if (position && account && deadline) {
if (chainId && library && position && account && deadline && baseCurrency && quoteCurrency && positionManager) {
const { calldata, value } = NonfungiblePositionManager.createCallParameters(position.pool)
const txn: { to: string; data: string; value: string } = {
@ -237,7 +233,9 @@ export default function AddLiquidity({
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: t`Create ${baseCurrency?.symbol}/${quoteCurrency?.symbol} V3 pool`,
type: TransactionType.CREATE_V3_POOL,
baseCurrencyId: currencyId(baseCurrency),
quoteCurrencyId: currencyId(quoteCurrency),
})
// dont set txn hash as we dont want submitted txn screen for create
ReactGA.event({
@ -332,9 +330,13 @@ export default function AddLiquidity({
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: noLiquidity
? t`Create pool and add ${baseCurrency?.symbol}/${quoteCurrency?.symbol} V3 liquidity`
: t`Add ${baseCurrency?.symbol}/${quoteCurrency?.symbol} V3 liquidity`,
type: TransactionType.ADD_LIQUIDITY_V3_POOL,
baseCurrencyId: currencyId(baseCurrency),
quoteCurrencyId: currencyId(quoteCurrency),
createPool: Boolean(noLiquidity),
expectedAmountBaseRaw: parsedAmounts[Field.CURRENCY_A]?.quotient?.toString() ?? '0',
expectedAmountQuoteRaw: parsedAmounts[Field.CURRENCY_B]?.quotient?.toString() ?? '0',
feeAmount: position.pool.fee,
})
setTxHash(response.hash)
ReactGA.event({

@ -1,6 +1,9 @@
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers'
import { t, Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import { useCallback, useContext, useState } from 'react'
import { Plus } from 'react-feather'
import ReactGA from 'react-ga'
@ -10,40 +13,38 @@ import { ThemeContext } from 'styled-components/macro'
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { BlueCard, LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleCurrencyLogo from '../../components/DoubleLogo'
import { AddRemoveTabs } from '../../components/NavigationTabs'
import { MinimalPositionCard } from '../../components/PositionCard'
import Row, { RowBetween, RowFlat } from '../../components/Row'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { ZERO_PERCENT } from '../../constants/misc'
import { WETH9_EXTENDED } from '../../constants/tokens'
import { useV2RouterContract } from '../../hooks/useContract'
import { PairState } from '../../hooks/useV2Pairs'
import { useActiveWeb3React } from '../../hooks/web3'
import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { useV2RouterContract } from '../../hooks/useContract'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { PairState } from '../../hooks/useV2Pairs'
import { useActiveWeb3React } from '../../hooks/web3'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { calculateSlippageAmount } from '../../utils/calculateSlippageAmount'
import { currencyId } from '../../utils/currencyId'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import AppBody from '../AppBody'
import { Dots, Wrapper } from '../Pool/styleds'
import { ConfirmAddModalBottom } from './ConfirmAddModalBottom'
import { currencyId } from '../../utils/currencyId'
import { PoolPriceBar } from './PoolPriceBar'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import { t, Trans } from '@lingui/macro'
const DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
@ -189,9 +190,11 @@ export default function AddLiquidity({
setAttemptingTxn(false)
addTransaction(response, {
summary: t`Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
type: TransactionType.ADD_LIQUIDITY_V2_POOL,
baseCurrencyId: currencyId(currencyA),
expectedAmountBaseRaw: parsedAmounts[Field.CURRENCY_A]?.quotient.toString() ?? '0',
quoteCurrencyId: currencyId(currencyB),
expectedAmountQuoteRaw: parsedAmounts[Field.CURRENCY_B]?.quotient.toString() ?? '0',
})
setTxHash(response.hash)

@ -1,55 +1,57 @@
import { Contract } from '@ethersproject/contracts'
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Fraction, Percent, Price, Token } from '@uniswap/sdk-core'
import { FeeAmount, Pool, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk'
import Badge, { BadgeVariant } from 'components/Badge'
import { ButtonConfirmed } from 'components/Button'
import { BlueCard, DarkGreyCard, LightCard, YellowCard } from 'components/Card'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import FeeSelector from 'components/FeeSelector'
import RangeSelector from 'components/RangeSelector'
import RateToggle from 'components/RateToggle'
import SettingsTab from 'components/Settings'
import { Dots } from 'components/swap/styleds'
import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { PoolState, usePool } from 'hooks/usePools'
import useTheme from 'hooks/useTheme'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import JSBI from 'jsbi'
import { useCallback, useMemo, useState, useEffect, ReactNode } from 'react'
import { Fraction, Percent, Price, Token, CurrencyAmount } from '@uniswap/sdk-core'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { AlertCircle, AlertTriangle, ArrowDown } from 'react-feather'
import ReactGA from 'react-ga'
import { Redirect, RouteComponentProps } from 'react-router'
import { Text } from 'rebass'
import { useAppDispatch } from 'state/hooks'
import { Bound, resetMintState } from 'state/mint/v3/actions'
import { useRangeHopCallbacks, useV3DerivedMintInfo, useV3MintActionHandlers } from 'state/mint/v3/hooks'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { unwrappedToken } from 'utils/unwrappedToken'
import { AutoColumn } from '../../components/Column'
import CurrencyLogo from '../../components/CurrencyLogo'
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
import { WETH9_EXTENDED } from '../../constants/tokens'
import { useToken } from '../../hooks/Tokens'
import { usePairContract, useV2MigratorContract } from '../../hooks/useContract'
import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit'
import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useActiveWeb3React } from '../../hooks/web3'
import { useToken } from '../../hooks/Tokens'
import { usePairContract, useV2MigratorContract } from '../../hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTokenBalance } from '../../state/wallet/hooks'
import { BackArrow, ExternalLink, TYPE } from '../../theme'
import { isAddress } from '../../utils'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink'
import { currencyId } from '../../utils/currencyId'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { BodyWrapper } from '../AppBody'
import { PoolState, usePool } from 'hooks/usePools'
import { FeeAmount, Pool, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk'
import { BlueCard, DarkGreyCard, LightCard, YellowCard } from 'components/Card'
import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback'
import { Dots } from 'components/swap/styleds'
import { ButtonConfirmed } from 'components/Button'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import ReactGA from 'react-ga'
import { TransactionResponse } from '@ethersproject/providers'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
import { useV3DerivedMintInfo, useRangeHopCallbacks, useV3MintActionHandlers } from 'state/mint/v3/hooks'
import { Bound, resetMintState } from 'state/mint/v3/actions'
import { Trans } from '@lingui/macro'
import { AlertCircle, AlertTriangle, ArrowDown } from 'react-feather'
import FeeSelector from 'components/FeeSelector'
import RangeSelector from 'components/RangeSelector'
import RateToggle from 'components/RateToggle'
import { Contract } from '@ethersproject/contracts'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import useTheme from 'hooks/useTheme'
import { unwrappedToken } from 'utils/unwrappedToken'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import Badge, { BadgeVariant } from 'components/Badge'
import { useAppDispatch } from 'state/hooks'
import SettingsTab from 'components/Settings'
const ZERO = JSBI.BigInt(0)
@ -341,7 +343,10 @@ function V2PairMigration({
})
addTransaction(response, {
summary: `Migrate ${currency0.symbol}/${currency1.symbol} liquidity to V3`,
type: TransactionType.MIGRATE_LIQUIDITY_V3,
baseCurrencyId: currencyId(currency0),
quoteCurrencyId: currencyId(currency1),
isFork: isNotUniswap,
})
setPendingMigrationHash(response.hash)
})

@ -1,49 +1,50 @@
import { useCallback, useMemo, useRef, useState } from 'react'
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Fraction, Percent, Price, Token } from '@uniswap/sdk-core'
import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk'
import Badge from 'components/Badge'
import { ButtonConfirmed, ButtonGray, ButtonPrimary } from 'components/Button'
import { DarkCard, LightCard } from 'components/Card'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import Loader from 'components/Loader'
import { RowBetween, RowFixed } from 'components/Row'
import { Dots } from 'components/swap/styleds'
import Toggle from 'components/Toggle'
import TransactionConfirmationModal, { ConfirmationModalContent } from 'components/TransactionConfirmationModal'
import { SupportedChainId } from 'constants/chains'
import { useToken } from 'hooks/Tokens'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import useIsTickAtLimit from 'hooks/useIsTickAtLimit'
import { PoolState, usePool } from 'hooks/usePools'
import { useToken } from 'hooks/Tokens'
import useUSDCPrice from 'hooks/useUSDCPrice'
import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { useActiveWeb3React } from 'hooks/web3'
import { useCallback, useMemo, useRef, useState } from 'react'
import ReactGA from 'react-ga'
import { Link, RouteComponentProps } from 'react-router-dom'
import { unwrappedToken } from 'utils/unwrappedToken'
import { usePositionTokenURI } from '../../hooks/usePositionTokenURI'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink'
import { Bound } from 'state/mint/v3/actions'
import { useSingleCallResult } from 'state/multicall/hooks'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
import styled from 'styled-components/macro'
import { AutoColumn } from 'components/Column'
import { RowBetween, RowFixed } from 'components/Row'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import { ExternalLink, HideExtraSmall, TYPE } from 'theme'
import Badge from 'components/Badge'
import { ButtonConfirmed, ButtonPrimary, ButtonGray } from 'components/Button'
import { DarkCard, LightCard } from 'components/Card'
import CurrencyLogo from 'components/CurrencyLogo'
import { Trans } from '@lingui/macro'
import { currencyId } from 'utils/currencyId'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { BigNumber } from '@ethersproject/bignumber'
import { Token, Currency, CurrencyAmount, Percent, Fraction, Price } from '@uniswap/sdk-core'
import { useActiveWeb3React } from 'hooks/web3'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
import ReactGA from 'react-ga'
import TransactionConfirmationModal, { ConfirmationModalContent } from 'components/TransactionConfirmationModal'
import { TransactionResponse } from '@ethersproject/providers'
import { Dots } from 'components/swap/styleds'
import { getPriceOrderingFromPositionForUI } from '../../components/PositionListItem'
import useTheme from '../../hooks/useTheme'
import RateToggle from '../../components/RateToggle'
import { useSingleCallResult } from 'state/multicall/hooks'
import RangeBadge from '../../components/Badge/RangeBadge'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import useUSDCPrice from 'hooks/useUSDCPrice'
import Loader from 'components/Loader'
import Toggle from 'components/Toggle'
import { Bound } from 'state/mint/v3/actions'
import useIsTickAtLimit from 'hooks/useIsTickAtLimit'
import { formatTickPrice } from 'utils/formatTickPrice'
import { SupportedChainId } from 'constants/chains'
import { unwrappedToken } from 'utils/unwrappedToken'
import RangeBadge from '../../components/Badge/RangeBadge'
import { getPriceOrderingFromPositionForUI } from '../../components/PositionListItem'
import RateToggle from '../../components/RateToggle'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import { usePositionTokenURI } from '../../hooks/usePositionTokenURI'
import useTheme from '../../hooks/useTheme'
import { TransactionType } from '../../state/transactions/actions'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { LoadingRows } from './styleds'
const PageWrapper = styled.div`
@ -459,7 +460,9 @@ export function PositionPage({
})
addTransaction(response, {
summary: `Collect ${feeValue0.currency.symbol}/${feeValue1.currency.symbol} fees`,
type: TransactionType.COLLECT_FEES,
currencyId0: currencyId(feeValue0.currency),
currencyId1: currencyId(feeValue1.currency),
})
})
})

@ -1,41 +1,43 @@
import { useCallback, useMemo, useState } from 'react'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { Redirect, RouteComponentProps } from 'react-router-dom'
import { WETH9_EXTENDED } from '../../constants/tokens'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import AppBody from '../AppBody'
import { BigNumber } from '@ethersproject/bignumber'
import useDebouncedChangeHandler from 'hooks/useDebouncedChangeHandler'
import { useBurnV3ActionHandlers, useBurnV3State, useDerivedV3BurnInfo } from 'state/burn/v3/hooks'
import Slider from 'components/Slider'
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { AutoColumn } from 'components/Column'
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
import { NonfungiblePositionManager } from '@uniswap/v3-sdk'
import RangeBadge from 'components/Badge/RangeBadge'
import { ButtonConfirmed, ButtonPrimary } from 'components/Button'
import { LightCard } from 'components/Card'
import { Text } from 'rebass'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import ReactGA from 'react-ga'
import { useActiveWeb3React } from 'hooks/web3'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from 'state/transactions/hooks'
import { Percent } from '@uniswap/sdk-core'
import { TYPE } from 'theme'
import { Wrapper, SmallMaxButton, ResponsiveHeaderText } from './styled'
import Loader from 'components/Loader'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import { Break } from 'components/earn/styled'
import { NonfungiblePositionManager } from '@uniswap/v3-sdk'
import useTheme from 'hooks/useTheme'
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
import Loader from 'components/Loader'
import { AddRemoveTabs } from 'components/NavigationTabs'
import RangeBadge from 'components/Badge/RangeBadge'
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
import Slider from 'components/Slider'
import Toggle from 'components/Toggle'
import { t, Trans } from '@lingui/macro'
import { SupportedChainId } from 'constants/chains'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import useDebouncedChangeHandler from 'hooks/useDebouncedChangeHandler'
import useTheme from 'hooks/useTheme'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { useActiveWeb3React } from 'hooks/web3'
import { useCallback, useMemo, useState } from 'react'
import ReactGA from 'react-ga'
import { Redirect, RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
import { useBurnV3ActionHandlers, useBurnV3State, useDerivedV3BurnInfo } from 'state/burn/v3/hooks'
import { useTransactionAdder } from 'state/transactions/hooks'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import { TYPE } from 'theme'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { WETH9_EXTENDED } from '../../constants/tokens'
import { TransactionType } from '../../state/transactions/actions'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { currencyId } from '../../utils/currencyId'
import AppBody from '../AppBody'
import { ResponsiveHeaderText, SmallMaxButton, Wrapper } from './styled'
const DEFAULT_REMOVE_V3_LIQUIDITY_SLIPPAGE_TOLERANCE = new Percent(5, 100)
@ -152,7 +154,11 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
setTxnHash(response.hash)
setAttemptingTxn(false)
addTransaction(response, {
summary: t`Remove ${liquidityValue0.currency.symbol}/${liquidityValue1.currency.symbol} V3 liquidity`,
type: TransactionType.REMOVE_LIQUIDITY_V3,
baseCurrencyId: currencyId(liquidityValue0.currency),
quoteCurrencyId: currencyId(liquidityValue1.currency),
expectedAmountBaseRaw: liquidityValue0.quotient.toString(),
expectedAmountQuoteRaw: liquidityValue1.quotient.toString(),
})
})
})
@ -187,9 +193,12 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
setTxnHash('')
}, [onPercentSelectForSlider, txnHash])
const pendingText = `Removing ${liquidityValue0?.toSignificant(6)} ${
liquidityValue0?.currency?.symbol
} and ${liquidityValue1?.toSignificant(6)} ${liquidityValue1?.currency?.symbol}`
const pendingText = (
<Trans>
Removing {liquidityValue0?.toSignificant(6)} {liquidityValue0?.currency?.symbol} and{' '}
{liquidityValue1?.toSignificant(6)} {liquidityValue1?.currency?.symbol}
</Trans>
)
function modalHeader() {
return (

@ -1,5 +1,7 @@
import { BigNumber } from '@ethersproject/bignumber'
import { Contract } from '@ethersproject/contracts'
import { TransactionResponse } from '@ethersproject/providers'
import { t, Trans } from '@lingui/macro'
import { Currency, Percent } from '@uniswap/sdk-core'
import { useCallback, useContext, useMemo, useState } from 'react'
import { ArrowDown, Plus } from 'react-feather'
@ -7,42 +9,40 @@ import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components/macro'
import { ButtonPrimary, ButtonLight, ButtonError, ButtonConfirmed } from '../../components/Button'
import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { BlueCard, LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import CurrencyLogo from '../../components/CurrencyLogo'
import DoubleCurrencyLogo from '../../components/DoubleLogo'
import { AddRemoveTabs } from '../../components/NavigationTabs'
import { MinimalPositionCard } from '../../components/PositionCard'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import Slider from '../../components/Slider'
import CurrencyLogo from '../../components/CurrencyLogo'
import { Dots } from '../../components/swap/styleds'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { WETH9_EXTENDED } from '../../constants/tokens'
import { useActiveWeb3React } from '../../hooks/web3'
import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { usePairContract, useV2RouterContract } from '../../hooks/useContract'
import useDebouncedChangeHandler from '../../hooks/useDebouncedChangeHandler'
import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useActiveWeb3React } from '../../hooks/web3'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/burn/actions'
import { useBurnActionHandlers, useBurnState, useDerivedBurnInfo } from '../../state/burn/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { StyledInternalLink, TYPE } from '../../theme'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { calculateSlippageAmount } from '../../utils/calculateSlippageAmount'
import { currencyId } from '../../utils/currencyId'
import useDebouncedChangeHandler from '../../hooks/useDebouncedChangeHandler'
import AppBody from '../AppBody'
import { ClickableText, MaxButton, Wrapper } from '../Pool/styleds'
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
import { Dots } from '../../components/swap/styleds'
import { useBurnActionHandlers } from '../../state/burn/hooks'
import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
import { Field } from '../../state/burn/actions'
import { useWalletModalToggle } from '../../state/application/hooks'
import { useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { BigNumber } from '@ethersproject/bignumber'
import { t, Trans } from '@lingui/macro'
const DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE = new Percent(5, 100)
@ -267,9 +267,11 @@ export default function RemoveLiquidity({
setAttemptingTxn(false)
addTransaction(response, {
summary: t`Remove ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencyB?.symbol}`,
type: TransactionType.REMOVE_LIQUIDITY_V3,
baseCurrencyId: currencyId(currencyA),
quoteCurrencyId: currencyId(currencyB),
expectedAmountBaseRaw: parsedAmounts[Field.CURRENCY_A]?.quotient.toString() ?? '0',
expectedAmountQuoteRaw: parsedAmounts[Field.CURRENCY_B]?.quotient.toString() ?? '0',
})
setTxHash(response.hash)
@ -277,7 +279,7 @@ export default function RemoveLiquidity({
ReactGA.event({
category: 'Liquidity',
action: 'Remove',
label: [currencyA?.symbol, currencyB?.symbol].join('/'),
label: [currencyA.symbol, currencyB.symbol].join('/'),
})
})
.catch((error: Error) => {

@ -33,8 +33,8 @@ import {
useProposalData,
useUserDelegatee,
useUserVotesAsOfBlock,
VoteOption,
} from '../../state/governance/hooks'
import { VoteOption } from '../../state/governance/types'
import { useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, TYPE } from '../../theme'
import { isAddress } from '../../utils'

@ -3,8 +3,6 @@ import { createAction } from '@reduxjs/toolkit'
export type PopupContent = {
txn: {
hash: string
success: boolean
summary?: string
}
}

@ -8,6 +8,7 @@ describe('application reducer', () => {
beforeEach(() => {
store = createStore(reducer, {
chainId: null,
chainConnectivityWarning: false,
popupList: [],
blockNumber: {
[1]: 3,
@ -18,23 +19,23 @@ describe('application reducer', () => {
describe('addPopup', () => {
it('adds the popup to list with a generated id', () => {
store.dispatch(addPopup({ content: { txn: { hash: 'abc', summary: 'test', success: true } } }))
store.dispatch(addPopup({ content: { txn: { hash: 'abc' } } }))
const list = store.getState().popupList
expect(list).toHaveLength(1)
expect(typeof list[0].key).toEqual('string')
expect(list[0].show).toEqual(true)
expect(list[0].content).toEqual({ txn: { hash: 'abc', summary: 'test', success: true } })
expect(list[0].content).toEqual({ txn: { hash: 'abc' } })
expect(list[0].removeAfterMs).toEqual(25000)
})
it('replaces any existing popups with the same key', () => {
store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc', summary: 'test', success: true } } }))
store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'def', summary: 'test2', success: false } } }))
store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc' } } }))
store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'def' } } }))
const list = store.getState().popupList
expect(list).toHaveLength(1)
expect(list[0].key).toEqual('abc')
expect(list[0].show).toEqual(true)
expect(list[0].content).toEqual({ txn: { hash: 'def', summary: 'test2', success: false } })
expect(list[0].content).toEqual({ txn: { hash: 'def' } })
expect(list[0].removeAfterMs).toEqual(25000)
})
})
@ -82,7 +83,7 @@ describe('application reducer', () => {
describe('removePopup', () => {
beforeEach(() => {
store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc', summary: 'test', success: true } } }))
store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc' } } }))
})
it('hides the popup', () => {
expect(store.getState().popupList[0].show).toBe(true)

@ -1,13 +1,14 @@
import JSBI from 'jsbi'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { TransactionResponse } from '@ethersproject/providers'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { useEffect, useState } from 'react'
import { UNI } from '../../constants/tokens'
import { useActiveWeb3React } from '../../hooks/web3'
import { useMerkleDistributorContract } from '../../hooks/useContract'
import { useActiveWeb3React } from '../../hooks/web3'
import { isAddress } from '../../utils'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { useSingleCallResult } from '../multicall/hooks'
import { isAddress } from '../../utils'
import { TransactionType } from '../transactions/actions'
import { useTransactionAdder } from '../transactions/hooks'
interface UserClaimData {
@ -165,8 +166,9 @@ export function useClaimCallback(account: string | null | undefined): {
.claim(...args, { value: null, gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Claimed ${unclaimedAmount?.toSignificant(4)} UNI`,
claim: { recipient: account },
type: TransactionType.CLAIM,
recipient: account,
uniAmountRaw: unclaimedAmount?.quotient.toString(),
})
return response.hash
})

@ -28,7 +28,9 @@ import { BRAVO_START_BLOCK, UNISWAP_GRANTS_START_BLOCK } from '../../constants/p
import { UNI } from '../../constants/tokens'
import { useLogs } from '../logs/hooks'
import { useSingleCallResult, useSingleContractMultipleData } from '../multicall/hooks'
import { TransactionType } from '../transactions/actions'
import { useTransactionAdder } from '../transactions/hooks'
import { VoteOption } from './types'
interface ProposalDetail {
target: string
@ -300,7 +302,7 @@ export function useDelegateCallback(): (delegatee: string | undefined) => undefi
return useCallback(
(delegatee: string | undefined) => {
if (!library || !chainId || !account || !isAddress(delegatee ?? '')) return undefined
if (!library || !chainId || !account || !delegatee || !isAddress(delegatee ?? '')) return undefined
const args = [delegatee]
if (!uniContract) throw new Error('No UNI Contract!')
return uniContract.estimateGas.delegate(...args, {}).then((estimatedGasLimit) => {
@ -308,7 +310,8 @@ export function useDelegateCallback(): (delegatee: string | undefined) => undefi
.delegate(...args, { value: null, gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: t`Delegated votes`,
type: TransactionType.DELEGATE,
delegatee,
})
return response.hash
})
@ -318,12 +321,6 @@ export function useDelegateCallback(): (delegatee: string | undefined) => undefi
)
}
export enum VoteOption {
Against,
For,
Abstain,
}
export function useVoteCallback(): {
voteCallback: (proposalId: string | undefined, voteOption: VoteOption) => undefined | Promise<string>
} {
@ -342,9 +339,11 @@ export function useVoteCallback(): {
.castVote(...args, { value: null, gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Voted ${
voteOption === VoteOption.Against ? 'against' : voteOption === VoteOption.For ? 'for' : 'to abstain on'
} proposal ${proposalId}`,
type: TransactionType.VOTE,
decision: voteOption,
governorAddress: latestGovernanceContract.address,
proposalId: parseInt(proposalId),
reason: '',
})
return response.hash
})
@ -380,7 +379,7 @@ export function useCreateProposalCallback(): (
.propose(...args, { gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: t`Submitted new proposal`,
type: TransactionType.SUBMIT_PROPOSAL,
})
return response.hash
})

@ -0,0 +1,5 @@
export enum VoteOption {
Against,
For,
Abstain,
}

@ -1,4 +1,6 @@
import { createAction } from '@reduxjs/toolkit'
import { TradeType } from '@uniswap/sdk-core'
import { VoteOption } from '../governance/types'
export interface SerializableTransactionReceipt {
to: string
@ -11,14 +13,168 @@ export interface SerializableTransactionReceipt {
status?: number
}
/**
* Be careful adding to this enum, always assign a unique value (typescript will not prevent duplicate values).
* These values is persisted in state and if you change the value it will cause errors
*/
export enum TransactionType {
APPROVAL = 0,
SWAP = 1,
DEPOSIT_LIQUIDITY_STAKING = 2,
WITHDRAW_LIQUIDITY_STAKING = 3,
CLAIM = 4,
VOTE = 5,
DELEGATE = 6,
WRAP = 7,
CREATE_V3_POOL = 8,
ADD_LIQUIDITY_V3_POOL = 9,
ADD_LIQUIDITY_V2_POOL = 10,
MIGRATE_LIQUIDITY_V3 = 11,
COLLECT_FEES = 12,
REMOVE_LIQUIDITY_V3 = 13,
SUBMIT_PROPOSAL = 14,
}
export interface BaseTransactionInfo {
type: TransactionType
}
export interface VoteTransactionInfo extends BaseTransactionInfo {
type: TransactionType.VOTE
governorAddress: string
proposalId: number
decision: VoteOption
reason: string
}
export interface DelegateTransactionInfo extends BaseTransactionInfo {
type: TransactionType.DELEGATE
delegatee: string
}
export interface ApproveTransactionInfo extends BaseTransactionInfo {
type: TransactionType.APPROVAL
tokenAddress: string
spender: string
}
interface BaseSwapTransactionInfo extends BaseTransactionInfo {
type: TransactionType.SWAP
tradeType: TradeType
inputCurrencyId: string
outputCurrencyId: string
}
export interface ExactInputSwapTransactionInfo extends BaseSwapTransactionInfo {
tradeType: TradeType.EXACT_INPUT
inputCurrencyAmountRaw: string
expectedOutputCurrencyAmountRaw: string
minimumOutputCurrencyAmountRaw: string
}
export interface ExactOutputSwapTransactionInfo extends BaseSwapTransactionInfo {
tradeType: TradeType.EXACT_OUTPUT
outputCurrencyAmountRaw: string
expectedInputCurrencyAmountRaw: string
maximumInputCurrencyAmountRaw: string
}
export interface DepositLiquidityStakingTransactionInfo {
type: TransactionType.DEPOSIT_LIQUIDITY_STAKING
token0Address: string
token1Address: string
}
export interface WithdrawLiquidityStakingTransactionInfo {
type: TransactionType.WITHDRAW_LIQUIDITY_STAKING
token0Address: string
token1Address: string
}
export interface WrapTransactionInfo {
type: TransactionType.WRAP
unwrapped: boolean
currencyAmountRaw: string
}
export interface ClaimTransactionInfo {
type: TransactionType.CLAIM
recipient: string
uniAmountRaw?: string
}
export interface CreateV3PoolTransactionInfo {
type: TransactionType.CREATE_V3_POOL
baseCurrencyId: string
quoteCurrencyId: string
}
export interface AddLiquidityV3PoolTransactionInfo {
type: TransactionType.ADD_LIQUIDITY_V3_POOL
createPool: boolean
baseCurrencyId: string
quoteCurrencyId: string
feeAmount: number
expectedAmountBaseRaw: string
expectedAmountQuoteRaw: string
}
export interface AddLiquidityV2PoolTransactionInfo {
type: TransactionType.ADD_LIQUIDITY_V2_POOL
baseCurrencyId: string
quoteCurrencyId: string
expectedAmountBaseRaw: string
expectedAmountQuoteRaw: string
}
export interface MigrateV2LiquidityToV3TransactionInfo {
type: TransactionType.MIGRATE_LIQUIDITY_V3
baseCurrencyId: string
quoteCurrencyId: string
isFork: boolean
}
export interface CollectFeesTransactionInfo {
type: TransactionType.COLLECT_FEES
currencyId0: string
currencyId1: string
}
export interface RemoveLiquidityV3TransactionInfo {
type: TransactionType.REMOVE_LIQUIDITY_V3
baseCurrencyId: string
quoteCurrencyId: string
expectedAmountBaseRaw: string
expectedAmountQuoteRaw: string
}
export interface SubmitProposalTransactionInfo {
type: TransactionType.SUBMIT_PROPOSAL
}
export type TransactionInfo =
| ApproveTransactionInfo
| ExactOutputSwapTransactionInfo
| ExactInputSwapTransactionInfo
| ClaimTransactionInfo
| VoteTransactionInfo
| DelegateTransactionInfo
| DepositLiquidityStakingTransactionInfo
| WithdrawLiquidityStakingTransactionInfo
| WrapTransactionInfo
| CreateV3PoolTransactionInfo
| AddLiquidityV3PoolTransactionInfo
| AddLiquidityV2PoolTransactionInfo
| MigrateV2LiquidityToV3TransactionInfo
| CollectFeesTransactionInfo
| RemoveLiquidityV3TransactionInfo
| SubmitProposalTransactionInfo
export const addTransaction =
createAction<{
chainId: number
hash: string
from: string
approval?: { tokenAddress: string; spender: string }
claim?: { recipient: string }
summary?: string
info: TransactionInfo
}>('transactions/addTransaction')
export const clearAllTransactions = createAction<{ chainId: number }>('transactions/clearAllTransactions')
export const finalizeTransaction = createAction<{

@ -3,26 +3,16 @@ import { useCallback, useMemo } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { useActiveWeb3React } from '../../hooks/web3'
import { addTransaction } from './actions'
import { addTransaction, TransactionInfo, TransactionType } from './actions'
import { TransactionDetails } from './reducer'
// helper that can take a ethers library transaction response and add it to the list of transactions
export function useTransactionAdder(): (
response: TransactionResponse,
customData?: { summary?: string; approval?: { tokenAddress: string; spender: string }; claim?: { recipient: string } }
) => void {
export function useTransactionAdder(): (response: TransactionResponse, info: TransactionInfo) => void {
const { chainId, account } = useActiveWeb3React()
const dispatch = useAppDispatch()
return useCallback(
(
response: TransactionResponse,
{
summary,
approval,
claim,
}: { summary?: string; claim?: { recipient: string }; approval?: { tokenAddress: string; spender: string } } = {}
) => {
(response: TransactionResponse, info: TransactionInfo) => {
if (!account) return
if (!chainId) return
@ -30,7 +20,7 @@ export function useTransactionAdder(): (
if (!hash) {
throw Error('No transaction hash found.')
}
dispatch(addTransaction({ hash, from: account, chainId, approval, summary, claim }))
dispatch(addTransaction({ hash, from: account, info, chainId }))
},
[dispatch, chainId, account]
)
@ -92,9 +82,8 @@ export function useHasPendingApproval(tokenAddress: string | undefined, spender:
if (tx.receipt) {
return false
} else {
const approval = tx.approval
if (!approval) return false
return approval.spender === spender && approval.tokenAddress === tokenAddress && isTransactionRecent(tx)
if (tx.info.type !== TransactionType.APPROVAL) return false
return tx.info.spender === spender && tx.info.tokenAddress === tokenAddress && isTransactionRecent(tx)
}
}),
[allTransactions, spender, tokenAddress]
@ -113,7 +102,7 @@ export function useUserHasSubmittedClaim(account?: string): {
const claimTxn = useMemo(() => {
const txnIndex = Object.keys(allTransactions).find((hash) => {
const tx = allTransactions[hash]
return tx.claim && tx.claim.recipient === account
return tx.info.type === TransactionType.CLAIM && tx.info.recipient === account
})
return txnIndex && allTransactions[txnIndex] ? allTransactions[txnIndex] : undefined
}, [account, allTransactions])

@ -1,5 +1,12 @@
import { createStore, Store } from 'redux'
import { addTransaction, checkedTransaction, clearAllTransactions, finalizeTransaction } from './actions'
import { updateVersion } from '../global/actions'
import {
addTransaction,
checkedTransaction,
clearAllTransactions,
finalizeTransaction,
TransactionType,
} from './actions'
import reducer, { initialState, TransactionState } from './reducer'
describe('transaction reducer', () => {
@ -9,16 +16,45 @@ describe('transaction reducer', () => {
store = createStore(reducer, initialState)
})
describe('updateVersion', () => {
it('clears old format transactions that do not have info', () => {
store = createStore(reducer, {
[1]: {
abc: {
hash: 'abc',
} as any,
},
})
store.dispatch(updateVersion())
expect(store.getState()[1]['abc']).toBeUndefined()
})
it('keeps old format transactions that do have info', () => {
store = createStore(reducer, {
[1]: {
abc: {
hash: 'abc',
info: {},
} as any,
},
})
store.dispatch(updateVersion())
expect(store.getState()[1]['abc']).toBeTruthy()
})
})
describe('addTransaction', () => {
it('adds the transaction', () => {
const beforeTime = new Date().getTime()
store.dispatch(
addTransaction({
chainId: 1,
summary: 'hello world',
hash: '0x0',
approval: { tokenAddress: 'abc', spender: 'def' },
from: 'abc',
info: {
type: TransactionType.APPROVAL,
tokenAddress: 'abc',
spender: 'def',
},
})
)
const txs = store.getState()
@ -27,10 +63,13 @@ describe('transaction reducer', () => {
const tx = txs[1]?.['0x0']
expect(tx).toBeTruthy()
expect(tx?.hash).toEqual('0x0')
expect(tx?.summary).toEqual('hello world')
expect(tx?.approval).toEqual({ tokenAddress: 'abc', spender: 'def' })
expect(tx?.from).toEqual('abc')
expect(tx?.addedTime).toBeGreaterThanOrEqual(beforeTime)
expect(tx?.info).toEqual({
type: TransactionType.APPROVAL,
tokenAddress: 'abc',
spender: 'def',
})
})
})
@ -59,8 +98,7 @@ describe('transaction reducer', () => {
addTransaction({
hash: '0x0',
chainId: 4,
approval: { spender: '0x0', tokenAddress: '0x0' },
summary: 'hello world',
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0' },
from: '0x0',
})
)
@ -82,7 +120,6 @@ describe('transaction reducer', () => {
})
)
const tx = store.getState()[4]?.['0x0']
expect(tx?.summary).toEqual('hello world')
expect(tx?.confirmedTime).toBeGreaterThanOrEqual(beforeTime)
expect(tx?.receipt).toEqual({
status: 1,
@ -113,8 +150,7 @@ describe('transaction reducer', () => {
addTransaction({
hash: '0x0',
chainId: 4,
approval: { spender: '0x0', tokenAddress: '0x0' },
summary: 'hello world',
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0' },
from: '0x0',
})
)
@ -133,8 +169,7 @@ describe('transaction reducer', () => {
addTransaction({
hash: '0x0',
chainId: 4,
approval: { spender: '0x0', tokenAddress: '0x0' },
summary: 'hello world',
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0' },
from: '0x0',
})
)
@ -162,18 +197,16 @@ describe('transaction reducer', () => {
store.dispatch(
addTransaction({
chainId: 1,
summary: 'hello world',
hash: '0x0',
approval: { tokenAddress: 'abc', spender: 'def' },
info: { type: TransactionType.APPROVAL, spender: 'abc', tokenAddress: 'def' },
from: 'abc',
})
)
store.dispatch(
addTransaction({
chainId: 4,
summary: 'hello world',
hash: '0x1',
approval: { tokenAddress: 'abc', spender: 'def' },
info: { type: TransactionType.APPROVAL, spender: 'abc', tokenAddress: 'def' },
from: 'abc',
})
)

@ -1,24 +1,24 @@
import { createReducer } from '@reduxjs/toolkit'
import { updateVersion } from '../global/actions'
import {
addTransaction,
checkedTransaction,
clearAllTransactions,
finalizeTransaction,
SerializableTransactionReceipt,
TransactionInfo,
} from './actions'
const now = () => new Date().getTime()
export interface TransactionDetails {
hash: string
approval?: { tokenAddress: string; spender: string }
summary?: string
claim?: { recipient: string }
receipt?: SerializableTransactionReceipt
lastCheckedBlockNumber?: number
addedTime: number
confirmedTime?: number
from: string
info: TransactionInfo
}
export interface TransactionState {
@ -31,12 +31,24 @@ export const initialState: TransactionState = {}
export default createReducer(initialState, (builder) =>
builder
.addCase(addTransaction, (transactions, { payload: { chainId, from, hash, approval, summary, claim } }) => {
.addCase(updateVersion, (transactions) => {
// in case there are any transactions in the store with the old format, remove them
Object.keys(transactions).forEach((chainId) => {
const chainTransactions = transactions[chainId as unknown as number]
Object.keys(chainTransactions).forEach((hash) => {
if (!('info' in chainTransactions[hash])) {
// clear old transactions that don't have the right format
delete chainTransactions[hash]
}
})
})
})
.addCase(addTransaction, (transactions, { payload: { chainId, from, hash, info } }) => {
if (transactions[chainId]?.[hash]) {
throw Error('Attempted to add existing transaction.')
}
const txs = transactions[chainId] ?? {}
txs[hash] = { hash, approval, summary, claim, from, addedTime: now() }
txs[hash] = { hash, info, from, addedTime: now() }
transactions[chainId] = txs
})
.addCase(clearAllTransactions, (transactions, { payload: { chainId } }) => {

@ -106,8 +106,6 @@ export default function Updater(): null {
{
txn: {
hash,
success: receipt.status === 1,
summary: transactions[hash]?.summary,
},
},
hash,