Rewrite tx store (#754)
* Rewrite the transaction store * Working state * Fix lint errors * Just always call getSigner
This commit is contained in:
parent
19a53cd999
commit
ef65943659
@ -9,7 +9,7 @@ import Copy from './Copy'
|
|||||||
import Circle from '../../assets/images/circle.svg'
|
import Circle from '../../assets/images/circle.svg'
|
||||||
|
|
||||||
import { transparentize } from 'polished'
|
import { transparentize } from 'polished'
|
||||||
import { useAllTransactions } from '../../contexts/Transactions'
|
import { useAllTransactions } from '../../state/transactions/hooks'
|
||||||
|
|
||||||
const TransactionStatusWrapper = styled.div`
|
const TransactionStatusWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -74,7 +74,7 @@ export default function Transaction({ hash, pending }: { hash: string; pending:
|
|||||||
const { chainId } = useWeb3React()
|
const { chainId } = useWeb3React()
|
||||||
const allTransactions = useAllTransactions()
|
const allTransactions = useAllTransactions()
|
||||||
|
|
||||||
const summary = allTransactions?.[hash]?.response?.summary
|
const summary = allTransactions?.[hash]?.summary
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TransactionWrapper key={hash}>
|
<TransactionWrapper key={hash}>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||||
import React, { useState, useCallback, useEffect, useContext } from 'react'
|
import React, { useState, useCallback, useEffect, useContext } from 'react'
|
||||||
import { ThemeContext } from 'styled-components'
|
import { ThemeContext } from 'styled-components'
|
||||||
import { parseEther, parseUnits } from '@ethersproject/units'
|
import { parseEther, parseUnits } from '@ethersproject/units'
|
||||||
@ -19,7 +20,7 @@ import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
|
|||||||
import { useAddUserToken, useFetchTokenByAddress } from '../../state/user/hooks'
|
import { useAddUserToken, useFetchTokenByAddress } from '../../state/user/hooks'
|
||||||
import { usePair } from '../../data/Reserves'
|
import { usePair } from '../../data/Reserves'
|
||||||
import { useAllTokens, useToken } from '../../contexts/Tokens'
|
import { useAllTokens, useToken } from '../../contexts/Tokens'
|
||||||
import { usePendingApproval, useTransactionAdder } from '../../contexts/Transactions'
|
import { useHasPendingApproval, useTransactionAdder } from '../../state/transactions/hooks'
|
||||||
import { useTokenContract, useWeb3React } from '../../hooks'
|
import { useTokenContract, useWeb3React } from '../../hooks'
|
||||||
import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades'
|
import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades'
|
||||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||||
@ -28,10 +29,10 @@ import { Link } from '../../theme/components'
|
|||||||
import {
|
import {
|
||||||
calculateGasMargin,
|
calculateGasMargin,
|
||||||
getEtherscanLink,
|
getEtherscanLink,
|
||||||
getProviderOrSigner,
|
|
||||||
getRouterContract,
|
getRouterContract,
|
||||||
QueryParams,
|
QueryParams,
|
||||||
calculateSlippageAmount
|
calculateSlippageAmount,
|
||||||
|
getSigner
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import Copy from '../AccountDetails/Copy'
|
import Copy from '../AccountDetails/Copy'
|
||||||
import AddressInputPanel from '../AddressInputPanel'
|
import AddressInputPanel from '../AddressInputPanel'
|
||||||
@ -210,7 +211,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
(!!inputApproval &&
|
(!!inputApproval &&
|
||||||
!!parsedAmounts[Field.INPUT] &&
|
!!parsedAmounts[Field.INPUT] &&
|
||||||
JSBI.greaterThanOrEqual(inputApproval.raw, parsedAmounts[Field.INPUT].raw))
|
JSBI.greaterThanOrEqual(inputApproval.raw, parsedAmounts[Field.INPUT].raw))
|
||||||
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
|
const pendingApprovalInput = useHasPendingApproval(tokens[Field.INPUT]?.address)
|
||||||
|
|
||||||
const feeAsPercent = new Percent(JSBI.BigInt(3), JSBI.BigInt(1000))
|
const feeAsPercent = new Percent(JSBI.BigInt(3), JSBI.BigInt(1000))
|
||||||
const feeTimesInputRaw =
|
const feeTimesInputRaw =
|
||||||
@ -370,23 +371,23 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
async function onSend() {
|
async function onSend() {
|
||||||
setAttemptingTxn(true)
|
setAttemptingTxn(true)
|
||||||
|
|
||||||
const signer = await getProviderOrSigner(library, account)
|
const signer = getSigner(library, account)
|
||||||
// get token contract if needed
|
// get token contract if needed
|
||||||
let estimate: Function, method: Function, args
|
let estimate: Function, method: Function, args
|
||||||
if (tokens[Field.INPUT].equals(WETH[chainId])) {
|
if (tokens[Field.INPUT].equals(WETH[chainId])) {
|
||||||
;(signer as any)
|
signer
|
||||||
.sendTransaction({ to: recipient.toString(), value: BigNumber.from(parsedAmounts[Field.INPUT].raw.toString()) })
|
.sendTransaction({ to: recipient.toString(), value: BigNumber.from(parsedAmounts[Field.INPUT].raw.toString()) })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
setTxHash(response.hash)
|
setTxHash(response.hash)
|
||||||
addTransaction(
|
addTransaction(response, {
|
||||||
response,
|
summary:
|
||||||
'Send ' +
|
'Send ' +
|
||||||
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
||||||
' ' +
|
' ' +
|
||||||
tokens[Field.INPUT]?.symbol +
|
tokens[Field.INPUT]?.symbol +
|
||||||
' to ' +
|
' to ' +
|
||||||
recipient
|
recipient
|
||||||
)
|
})
|
||||||
setPendingConfirmation(false)
|
setPendingConfirmation(false)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -403,15 +404,15 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
gasLimit: calculateGasMargin(estimatedGasLimit)
|
gasLimit: calculateGasMargin(estimatedGasLimit)
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
setTxHash(response.hash)
|
setTxHash(response.hash)
|
||||||
addTransaction(
|
addTransaction(response, {
|
||||||
response,
|
summary:
|
||||||
'Send ' +
|
'Send ' +
|
||||||
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
||||||
' ' +
|
' ' +
|
||||||
tokens[Field.INPUT]?.symbol +
|
tokens[Field.INPUT]?.symbol +
|
||||||
' to ' +
|
' to ' +
|
||||||
recipient
|
recipient
|
||||||
)
|
})
|
||||||
setPendingConfirmation(false)
|
setPendingConfirmation(false)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -514,9 +515,9 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
gasLimit: calculateGasMargin(estimatedGasLimit)
|
gasLimit: calculateGasMargin(estimatedGasLimit)
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
setTxHash(response.hash)
|
setTxHash(response.hash)
|
||||||
addTransaction(
|
addTransaction(response, {
|
||||||
response,
|
summary:
|
||||||
'Swap ' +
|
'Swap ' +
|
||||||
slippageAdjustedAmounts?.[Field.INPUT]?.toSignificant(3) +
|
slippageAdjustedAmounts?.[Field.INPUT]?.toSignificant(3) +
|
||||||
' ' +
|
' ' +
|
||||||
tokens[Field.INPUT]?.symbol +
|
tokens[Field.INPUT]?.symbol +
|
||||||
@ -524,7 +525,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
slippageAdjustedAmounts?.[Field.OUTPUT]?.toSignificant(3) +
|
slippageAdjustedAmounts?.[Field.OUTPUT]?.toSignificant(3) +
|
||||||
' ' +
|
' ' +
|
||||||
tokens[Field.OUTPUT]?.symbol
|
tokens[Field.OUTPUT]?.symbol
|
||||||
)
|
})
|
||||||
setPendingConfirmation(false)
|
setPendingConfirmation(false)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -550,7 +551,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
gasLimit: calculateGasMargin(estimatedGas)
|
gasLimit: calculateGasMargin(estimatedGas)
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
addTransaction(response, 'Approve ' + tokens[field]?.symbol, { approval: tokens[field]?.address })
|
addTransaction(response, {
|
||||||
|
summary: 'Approve ' + tokens[field]?.symbol,
|
||||||
|
approvalOfToken: tokens[field].address
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { useMediaLayout } from 'use-media'
|
|||||||
|
|
||||||
import { X } from 'react-feather'
|
import { X } from 'react-feather'
|
||||||
import { PopupContent } from '../../state/application/actions'
|
import { PopupContent } from '../../state/application/actions'
|
||||||
import { usePopups } from '../../state/application/hooks'
|
import { useActivePopups, useRemovePopup } from '../../state/application/hooks'
|
||||||
import { Link } from '../../theme'
|
import { Link } from '../../theme'
|
||||||
import { AutoColumn } from '../Column'
|
import { AutoColumn } from '../Column'
|
||||||
import DoubleTokenLogo from '../DoubleLogo'
|
import DoubleTokenLogo from '../DoubleLogo'
|
||||||
@ -102,7 +102,8 @@ function PopupItem({ content, popKey }: { content: PopupContent; popKey: string
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
const theme = useContext(ThemeContext)
|
const theme = useContext(ThemeContext)
|
||||||
// get all popups
|
// get all popups
|
||||||
const [activePopups, , removePopup] = usePopups()
|
const activePopups = useActivePopups()
|
||||||
|
const removePopup = useRemovePopup()
|
||||||
|
|
||||||
// switch view settings on mobile
|
// switch view settings on mobile
|
||||||
const isMobile = useMediaLayout({ maxWidth: '600px' })
|
const isMobile = useMediaLayout({ maxWidth: '600px' })
|
||||||
|
@ -6,7 +6,7 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import { useWeb3React } from '../../hooks'
|
import { useWeb3React } from '../../hooks'
|
||||||
import useInterval from '../../hooks/useInterval'
|
import useInterval from '../../hooks/useInterval'
|
||||||
import { usePopups } from '../../state/application/hooks'
|
import { useRemovePopup } from '../../state/application/hooks'
|
||||||
import { TYPE } from '../../theme'
|
import { TYPE } from '../../theme'
|
||||||
|
|
||||||
import { Link } from '../../theme/components'
|
import { Link } from '../../theme/components'
|
||||||
@ -41,7 +41,7 @@ export default function TxnPopup({
|
|||||||
const [count, setCount] = useState(1)
|
const [count, setCount] = useState(1)
|
||||||
|
|
||||||
const [isRunning, setIsRunning] = useState(true)
|
const [isRunning, setIsRunning] = useState(true)
|
||||||
const [, , removePopup] = usePopups()
|
const removePopup = useRemovePopup()
|
||||||
|
|
||||||
useInterval(
|
useInterval(
|
||||||
() => {
|
() => {
|
||||||
|
@ -20,7 +20,7 @@ import LightCircle from '../../assets/svg/lightcircle.svg'
|
|||||||
import { RowBetween } from '../Row'
|
import { RowBetween } from '../Row'
|
||||||
import { useENSName } from '../../hooks'
|
import { useENSName } from '../../hooks'
|
||||||
import { shortenAddress } from '../../utils'
|
import { shortenAddress } from '../../utils'
|
||||||
import { useAllTransactions } from '../../contexts/Transactions'
|
import { useAllTransactions } from '../../state/transactions/hooks'
|
||||||
import { NetworkContextName } from '../../constants'
|
import { NetworkContextName } from '../../constants'
|
||||||
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
|
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
|
||||||
|
|
||||||
|
@ -1,213 +0,0 @@
|
|||||||
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
|
|
||||||
|
|
||||||
import { useWeb3React } from '../hooks'
|
|
||||||
import { useBlockNumber, usePopups } from '../state/application/hooks'
|
|
||||||
|
|
||||||
const ADD = 'ADD'
|
|
||||||
const CHECK = 'CHECK'
|
|
||||||
const FINALIZE = 'FINALIZE'
|
|
||||||
|
|
||||||
interface TransactionState {
|
|
||||||
[chainId: number]: {
|
|
||||||
[txHash: string]: {
|
|
||||||
blockNumberChecked: any
|
|
||||||
response: {
|
|
||||||
customData?: any
|
|
||||||
summary: any
|
|
||||||
}
|
|
||||||
receipt: any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TransactionsContext = createContext<[TransactionState, { [updater: string]: (...args: any[]) => void }]>([{}, {}])
|
|
||||||
|
|
||||||
export function useTransactionsContext() {
|
|
||||||
return useContext(TransactionsContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
function reducer(state: TransactionState, { type, payload }): TransactionState {
|
|
||||||
switch (type) {
|
|
||||||
case ADD: {
|
|
||||||
const { networkId, hash, response } = payload
|
|
||||||
|
|
||||||
if (state[networkId]?.[hash]) {
|
|
||||||
throw Error('Attempted to add existing transaction.')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
[networkId]: {
|
|
||||||
...state[networkId],
|
|
||||||
[hash]: {
|
|
||||||
response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case CHECK: {
|
|
||||||
const { networkId, hash, blockNumber } = payload
|
|
||||||
|
|
||||||
if (!state[networkId]?.[hash]) {
|
|
||||||
throw Error('Attempted to check non-existent transaction.')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
[networkId]: {
|
|
||||||
...state[networkId],
|
|
||||||
[hash]: {
|
|
||||||
...state[networkId]?.[hash],
|
|
||||||
blockNumberChecked: blockNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case FINALIZE: {
|
|
||||||
const { networkId, hash, receipt } = payload
|
|
||||||
|
|
||||||
if (!state[networkId]?.[hash]) {
|
|
||||||
throw Error('Attempted to finalize non-existent transaction.')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
[networkId]: {
|
|
||||||
...state[networkId],
|
|
||||||
[hash]: {
|
|
||||||
...state[networkId]?.[hash],
|
|
||||||
receipt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw Error(`Unexpected action type in TransactionsContext reducer: '${type}'.`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Provider({ children }: { children: React.ReactNode }) {
|
|
||||||
const [state, dispatch] = useReducer(reducer, {})
|
|
||||||
|
|
||||||
const add = useCallback((networkId, hash, response) => {
|
|
||||||
dispatch({ type: ADD, payload: { networkId, hash, response } })
|
|
||||||
}, [])
|
|
||||||
const check = useCallback((networkId, hash, blockNumber) => {
|
|
||||||
dispatch({ type: CHECK, payload: { networkId, hash, blockNumber } })
|
|
||||||
}, [])
|
|
||||||
const finalize = useCallback((networkId, hash, receipt) => {
|
|
||||||
dispatch({ type: FINALIZE, payload: { networkId, hash, receipt } })
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TransactionsContext.Provider
|
|
||||||
value={useMemo(() => [state, { add, check, finalize }], [state, add, check, finalize])}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</TransactionsContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Updater() {
|
|
||||||
const { chainId, library } = useWeb3React()
|
|
||||||
|
|
||||||
const globalBlockNumber = useBlockNumber()
|
|
||||||
|
|
||||||
const [state, { check, finalize }] = useTransactionsContext()
|
|
||||||
const allTransactions = state[chainId] ?? {}
|
|
||||||
|
|
||||||
// show popup on confirm
|
|
||||||
const [, addPopup] = usePopups()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if ((chainId || chainId === 0) && library) {
|
|
||||||
let stale = false
|
|
||||||
Object.keys(allTransactions)
|
|
||||||
.filter(
|
|
||||||
hash => !allTransactions[hash].receipt && allTransactions[hash].blockNumberChecked !== globalBlockNumber
|
|
||||||
)
|
|
||||||
.forEach(hash => {
|
|
||||||
library
|
|
||||||
.getTransactionReceipt(hash)
|
|
||||||
.then(receipt => {
|
|
||||||
if (!stale) {
|
|
||||||
if (!receipt) {
|
|
||||||
check(chainId, hash, globalBlockNumber)
|
|
||||||
} else {
|
|
||||||
finalize(chainId, hash, receipt)
|
|
||||||
// add success or failure popup
|
|
||||||
if (receipt.status === 1) {
|
|
||||||
addPopup({
|
|
||||||
txn: {
|
|
||||||
hash,
|
|
||||||
success: true,
|
|
||||||
summary: allTransactions[hash]?.response?.summary
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
addPopup({
|
|
||||||
txn: { hash, success: false, summary: allTransactions[hash]?.response?.summary }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
check(chainId, hash, globalBlockNumber)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
stale = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [chainId, library, allTransactions, globalBlockNumber, check, finalize, addPopup])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTransactionAdder() {
|
|
||||||
const { chainId } = useWeb3React()
|
|
||||||
|
|
||||||
const [, { add }] = useTransactionsContext()
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
(response, summary = '', customData = {}) => {
|
|
||||||
if (!(chainId || chainId === 0)) {
|
|
||||||
throw Error(`Invalid networkId '${chainId}`)
|
|
||||||
}
|
|
||||||
const hash = response?.hash
|
|
||||||
if (!hash) {
|
|
||||||
throw Error('No transaction hash found.')
|
|
||||||
}
|
|
||||||
add(chainId, hash, { ...response, customData: customData, summary })
|
|
||||||
},
|
|
||||||
[chainId, add]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAllTransactions() {
|
|
||||||
const { chainId } = useWeb3React()
|
|
||||||
|
|
||||||
const [state] = useTransactionsContext()
|
|
||||||
|
|
||||||
return state[chainId] || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePendingApproval(tokenAddress) {
|
|
||||||
const allTransactions = useAllTransactions()
|
|
||||||
return (
|
|
||||||
Object.keys(allTransactions).filter(hash => {
|
|
||||||
if (allTransactions[hash]?.receipt) {
|
|
||||||
return false
|
|
||||||
} else if (!allTransactions[hash]?.response) {
|
|
||||||
return false
|
|
||||||
} else if (allTransactions[hash]?.response?.customData?.approval !== tokenAddress) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}).length >= 1
|
|
||||||
)
|
|
||||||
}
|
|
@ -8,7 +8,7 @@ import { Provider } from 'react-redux'
|
|||||||
import { NetworkContextName } from './constants'
|
import { NetworkContextName } from './constants'
|
||||||
import { isMobile } from 'react-device-detect'
|
import { isMobile } from 'react-device-detect'
|
||||||
import { Updater as LocalStorageContextUpdater } from './state/user/hooks'
|
import { Updater as LocalStorageContextUpdater } from './state/user/hooks'
|
||||||
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
|
import { Updater as TransactionContextUpdater } from './state/transactions/hooks'
|
||||||
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
|
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
|
||||||
import App from './pages/App'
|
import App from './pages/App'
|
||||||
import store from './state'
|
import store from './state'
|
||||||
@ -36,11 +36,7 @@ if (process.env.NODE_ENV === 'production') {
|
|||||||
ReactGA.pageview(window.location.pathname + window.location.search)
|
ReactGA.pageview(window.location.pathname + window.location.search)
|
||||||
|
|
||||||
function ContextProviders({ children }: { children: React.ReactNode }) {
|
function ContextProviders({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return <BalancesContextProvider>{children}</BalancesContextProvider>
|
||||||
<TransactionContextProvider>
|
|
||||||
<BalancesContextProvider>{children}</BalancesContextProvider>
|
|
||||||
</TransactionContextProvider>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Updaters() {
|
function Updaters() {
|
||||||
|
@ -25,7 +25,7 @@ import { useAddressBalance } from '../../contexts/Balances'
|
|||||||
import { useTokenAllowance } from '../../data/Allowances'
|
import { useTokenAllowance } from '../../data/Allowances'
|
||||||
import { useTotalSupply } from '../../data/TotalSupply'
|
import { useTotalSupply } from '../../data/TotalSupply'
|
||||||
import { useWeb3React, useTokenContract } from '../../hooks'
|
import { useWeb3React, useTokenContract } from '../../hooks'
|
||||||
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
|
import { useTransactionAdder, useHasPendingApproval } from '../../state/transactions/hooks'
|
||||||
|
|
||||||
import { ROUTER_ADDRESS } from '../../constants'
|
import { ROUTER_ADDRESS } from '../../constants'
|
||||||
import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
|
import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
|
||||||
@ -305,8 +305,8 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
|||||||
!!parsedAmounts[Field.OUTPUT] &&
|
!!parsedAmounts[Field.OUTPUT] &&
|
||||||
JSBI.greaterThanOrEqual(outputApproval.raw, parsedAmounts[Field.OUTPUT].raw))
|
JSBI.greaterThanOrEqual(outputApproval.raw, parsedAmounts[Field.OUTPUT].raw))
|
||||||
// check on pending approvals for token amounts
|
// check on pending approvals for token amounts
|
||||||
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
|
const pendingApprovalInput = useHasPendingApproval(tokens[Field.INPUT]?.address)
|
||||||
const pendingApprovalOutput = usePendingApproval(tokens[Field.OUTPUT]?.address)
|
const pendingApprovalOutput = useHasPendingApproval(tokens[Field.OUTPUT]?.address)
|
||||||
|
|
||||||
// used for displaying approximate starting price in UI
|
// used for displaying approximate starting price in UI
|
||||||
const derivedPrice =
|
const derivedPrice =
|
||||||
@ -497,9 +497,9 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
|||||||
gasLimit: calculateGasMargin(estimatedGasLimit)
|
gasLimit: calculateGasMargin(estimatedGasLimit)
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
setTxHash(response.hash)
|
setTxHash(response.hash)
|
||||||
addTransaction(
|
addTransaction(response, {
|
||||||
response,
|
summary:
|
||||||
'Add ' +
|
'Add ' +
|
||||||
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
||||||
' ' +
|
' ' +
|
||||||
tokens[Field.INPUT]?.symbol +
|
tokens[Field.INPUT]?.symbol +
|
||||||
@ -507,7 +507,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
|||||||
parsedAmounts[Field.OUTPUT]?.toSignificant(3) +
|
parsedAmounts[Field.OUTPUT]?.toSignificant(3) +
|
||||||
' ' +
|
' ' +
|
||||||
tokens[Field.OUTPUT]?.symbol
|
tokens[Field.OUTPUT]?.symbol
|
||||||
)
|
})
|
||||||
setPendingConfirmation(false)
|
setPendingConfirmation(false)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -534,7 +534,10 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
|||||||
gasLimit: calculateGasMargin(estimatedGas)
|
gasLimit: calculateGasMargin(estimatedGas)
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
addTransaction(response, 'Approve ' + tokens[field]?.symbol, { approval: tokens[field]?.address })
|
addTransaction(response, {
|
||||||
|
summary: 'Approve ' + tokens[field]?.symbol,
|
||||||
|
approvalOfToken: tokens[field].address
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import { useToken } from '../../contexts/Tokens'
|
|||||||
import { useWeb3React } from '../../hooks'
|
import { useWeb3React } from '../../hooks'
|
||||||
import { useAllBalances } from '../../contexts/Balances'
|
import { useAllBalances } from '../../contexts/Balances'
|
||||||
import { usePairContract } from '../../hooks'
|
import { usePairContract } from '../../hooks'
|
||||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||||
import { useTotalSupply } from '../../data/TotalSupply'
|
import { useTotalSupply } from '../../data/TotalSupply'
|
||||||
|
|
||||||
import { splitSignature } from '@ethersproject/bytes'
|
import { splitSignature } from '@ethersproject/bytes'
|
||||||
@ -493,9 +493,9 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
|
|||||||
}).then(response => {
|
}).then(response => {
|
||||||
setPendingConfirmation(false)
|
setPendingConfirmation(false)
|
||||||
setTxHash(response.hash)
|
setTxHash(response.hash)
|
||||||
addTransaction(
|
addTransaction(response, {
|
||||||
response,
|
summary:
|
||||||
'Remove ' +
|
'Remove ' +
|
||||||
parsedAmounts[Field.TOKEN0]?.toSignificant(3) +
|
parsedAmounts[Field.TOKEN0]?.toSignificant(3) +
|
||||||
' ' +
|
' ' +
|
||||||
tokens[Field.TOKEN0]?.symbol +
|
tokens[Field.TOKEN0]?.symbol +
|
||||||
@ -503,7 +503,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
|
|||||||
parsedAmounts[Field.TOKEN1]?.toSignificant(3) +
|
parsedAmounts[Field.TOKEN1]?.toSignificant(3) +
|
||||||
' ' +
|
' ' +
|
||||||
tokens[Field.TOKEN1]?.symbol
|
tokens[Field.TOKEN1]?.symbol
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import { useWeb3React } from '../../hooks'
|
import { useWeb3React } from '../../hooks'
|
||||||
import { addPopup, PopupContent, removePopup, toggleWalletModal } from './actions'
|
import { addPopup, PopupContent, removePopup, toggleWalletModal } from './actions'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
@ -23,27 +23,31 @@ export function useUserAdvanced() {
|
|||||||
return useSelector((state: AppState) => state.application.userAdvanced)
|
return useSelector((state: AppState) => state.application.userAdvanced)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePopups(): [
|
// returns a function that allows adding a popup
|
||||||
AppState['application']['popupList'],
|
export function useAddPopup(): (content: PopupContent) => void {
|
||||||
(content: PopupContent) => void,
|
|
||||||
(key: string) => void
|
|
||||||
] {
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const activePopups = useSelector((state: AppState) => state.application.popupList.filter(item => item.show))
|
|
||||||
|
|
||||||
const wrappedAddPopup = useCallback(
|
return useCallback(
|
||||||
(content: PopupContent) => {
|
(content: PopupContent) => {
|
||||||
dispatch(addPopup({ content }))
|
dispatch(addPopup({ content }))
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const wrappedRemovePopup = useCallback(
|
// returns a function that allows removing a popup via its key
|
||||||
|
export function useRemovePopup(): (key: string) => void {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
return useCallback(
|
||||||
(key: string) => {
|
(key: string) => {
|
||||||
dispatch(removePopup({ key }))
|
dispatch(removePopup({ key }))
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return [activePopups, wrappedAddPopup, wrappedRemovePopup]
|
|
||||||
|
// get the list of active popups
|
||||||
|
export function useActivePopups(): AppState['application']['popupList'] {
|
||||||
|
const list = useSelector((state: AppState) => state.application.popupList)
|
||||||
|
return useMemo(() => list.filter(item => item.show), [list])
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
|
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
|
||||||
import application from './application/reducer'
|
import application from './application/reducer'
|
||||||
import user from './user/reducer'
|
import user from './user/reducer'
|
||||||
|
import transactions from './transactions/reducer'
|
||||||
import { save, load } from 'redux-localstorage-simple'
|
import { save, load } from 'redux-localstorage-simple'
|
||||||
|
|
||||||
const PERSISTED_KEYS: string[] = ['user']
|
const PERSISTED_KEYS: string[] = ['user', 'transactions']
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
application,
|
application,
|
||||||
user
|
user,
|
||||||
|
transactions
|
||||||
},
|
},
|
||||||
middleware: [...getDefaultMiddleware(), save({ states: PERSISTED_KEYS })],
|
middleware: [...getDefaultMiddleware(), save({ states: PERSISTED_KEYS })],
|
||||||
preloadedState: load({ states: PERSISTED_KEYS })
|
preloadedState: load({ states: PERSISTED_KEYS })
|
||||||
|
13
src/state/transactions/actions.ts
Normal file
13
src/state/transactions/actions.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { TransactionReceipt } from '@ethersproject/providers'
|
||||||
|
import { createAction } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
export const addTransaction = createAction<{
|
||||||
|
chainId: number
|
||||||
|
hash: string
|
||||||
|
approvalOfToken?: string
|
||||||
|
summary?: string
|
||||||
|
}>('addTransaction')
|
||||||
|
export const checkTransaction = createAction<{ chainId: number; hash: string; blockNumber: number }>('checkTransaction')
|
||||||
|
export const finalizeTransaction = createAction<{ chainId: number; hash: string; receipt: TransactionReceipt }>(
|
||||||
|
'finalizeTransaction'
|
||||||
|
)
|
113
src/state/transactions/hooks.tsx
Normal file
113
src/state/transactions/hooks.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { TransactionResponse } from '@ethersproject/providers'
|
||||||
|
import { useCallback, useEffect } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
import { useWeb3React } from '../../hooks'
|
||||||
|
import { useAddPopup, useBlockNumber } from '../application/hooks'
|
||||||
|
import { AppDispatch, AppState } from '../index'
|
||||||
|
import { addTransaction, checkTransaction, finalizeTransaction } from './actions'
|
||||||
|
import { TransactionDetails } from './reducer'
|
||||||
|
|
||||||
|
export function Updater() {
|
||||||
|
const { chainId, library } = useWeb3React()
|
||||||
|
|
||||||
|
const globalBlockNumber = useBlockNumber()
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
const transactions = useSelector<AppState>(state => state.transactions)
|
||||||
|
|
||||||
|
const allTransactions = transactions[chainId] ?? {}
|
||||||
|
|
||||||
|
// show popup on confirm
|
||||||
|
const addPopup = useAddPopup()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ((chainId || chainId === 0) && library) {
|
||||||
|
let stale = false
|
||||||
|
Object.keys(allTransactions)
|
||||||
|
.filter(
|
||||||
|
hash => !allTransactions[hash].receipt && allTransactions[hash].blockNumberChecked !== globalBlockNumber
|
||||||
|
)
|
||||||
|
.forEach(hash => {
|
||||||
|
library
|
||||||
|
.getTransactionReceipt(hash)
|
||||||
|
.then(receipt => {
|
||||||
|
if (!stale) {
|
||||||
|
if (!receipt) {
|
||||||
|
dispatch(checkTransaction({ chainId, hash, blockNumber: globalBlockNumber }))
|
||||||
|
} else {
|
||||||
|
dispatch(finalizeTransaction({ chainId, hash, receipt }))
|
||||||
|
// add success or failure popup
|
||||||
|
if (receipt.status === 1) {
|
||||||
|
addPopup({
|
||||||
|
txn: {
|
||||||
|
hash,
|
||||||
|
success: true,
|
||||||
|
summary: allTransactions[hash]?.response?.summary
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addPopup({
|
||||||
|
txn: { hash, success: false, summary: allTransactions[hash]?.response?.summary }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
dispatch(checkTransaction({ chainId, hash, blockNumber: globalBlockNumber }))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stale = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [chainId, library, allTransactions, globalBlockNumber, dispatch, addPopup])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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; approvalOfToken?: string }
|
||||||
|
) => void {
|
||||||
|
const { chainId } = useWeb3React()
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(
|
||||||
|
response: TransactionResponse,
|
||||||
|
{ summary, approvalOfToken }: { summary?: string; approvalOfToken?: string } = {}
|
||||||
|
) => {
|
||||||
|
const { hash } = response
|
||||||
|
if (!hash) {
|
||||||
|
throw Error('No transaction hash found.')
|
||||||
|
}
|
||||||
|
dispatch(addTransaction({ hash, chainId, approvalOfToken, summary }))
|
||||||
|
},
|
||||||
|
[dispatch, chainId]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns all the transactions for the current chain
|
||||||
|
export function useAllTransactions(): { [txHash: string]: TransactionDetails } {
|
||||||
|
const { chainId } = useWeb3React()
|
||||||
|
|
||||||
|
const state = useSelector<AppState>(state => state.transactions)
|
||||||
|
|
||||||
|
return state[chainId] ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns whether a token has a pending approval transaction
|
||||||
|
export function useHasPendingApproval(tokenAddress: string): boolean {
|
||||||
|
const allTransactions = useAllTransactions()
|
||||||
|
return Object.keys(allTransactions).some(hash => {
|
||||||
|
if (allTransactions[hash]?.receipt) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return allTransactions[hash]?.approvalOfToken === tokenAddress
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
43
src/state/transactions/reducer.ts
Normal file
43
src/state/transactions/reducer.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { TransactionReceipt } from '@ethersproject/providers'
|
||||||
|
import { createReducer } from '@reduxjs/toolkit'
|
||||||
|
import { addTransaction, checkTransaction, finalizeTransaction } from './actions'
|
||||||
|
|
||||||
|
export interface TransactionDetails {
|
||||||
|
approvalOfToken?: string
|
||||||
|
blockNumberChecked?: number
|
||||||
|
summary?: string
|
||||||
|
receipt?: TransactionReceipt
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionState {
|
||||||
|
[chainId: number]: {
|
||||||
|
[txHash: string]: TransactionDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: TransactionState = {}
|
||||||
|
|
||||||
|
export default createReducer(initialState, builder =>
|
||||||
|
builder
|
||||||
|
.addCase(addTransaction, (state, { payload: { chainId, hash, approvalOfToken, summary } }) => {
|
||||||
|
if (state[chainId]?.[hash]) {
|
||||||
|
throw Error('Attempted to add existing transaction.')
|
||||||
|
}
|
||||||
|
state[chainId] = state[chainId] ?? {}
|
||||||
|
state[chainId][hash] = { approvalOfToken, summary }
|
||||||
|
})
|
||||||
|
.addCase(checkTransaction, (state, { payload: { chainId, blockNumber, hash } }) => {
|
||||||
|
if (!state[chainId]?.[hash]) {
|
||||||
|
throw Error('Attempted to check non-existent transaction.')
|
||||||
|
}
|
||||||
|
|
||||||
|
state[chainId][hash].blockNumberChecked = blockNumber
|
||||||
|
})
|
||||||
|
.addCase(finalizeTransaction, (state, { payload: { hash, chainId, receipt } }) => {
|
||||||
|
if (!state[chainId]?.[hash]) {
|
||||||
|
throw Error('Attempted to finalize non-existent transaction.')
|
||||||
|
}
|
||||||
|
state[chainId] = state[chainId] ?? {}
|
||||||
|
state[chainId][hash].receipt = receipt
|
||||||
|
})
|
||||||
|
)
|
@ -1,6 +1,7 @@
|
|||||||
import { Contract } from '@ethersproject/contracts'
|
import { Contract } from '@ethersproject/contracts'
|
||||||
import { getAddress } from '@ethersproject/address'
|
import { getAddress } from '@ethersproject/address'
|
||||||
import { AddressZero } from '@ethersproject/constants'
|
import { AddressZero } from '@ethersproject/constants'
|
||||||
|
import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
|
||||||
import { parseBytes32String } from '@ethersproject/strings'
|
import { parseBytes32String } from '@ethersproject/strings'
|
||||||
import { BigNumber } from '@ethersproject/bignumber'
|
import { BigNumber } from '@ethersproject/bignumber'
|
||||||
|
|
||||||
@ -116,13 +117,18 @@ export function calculateSlippageAmount(value: TokenAmount, slippage: number): [
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// account is optional
|
// account is not optional
|
||||||
export function getProviderOrSigner(library: any, account?: string): any {
|
export function getSigner(library: Web3Provider, account: string): JsonRpcSigner {
|
||||||
return account ? library.getSigner(account).connectUnchecked() : library
|
return library.getSigner(account).connectUnchecked()
|
||||||
}
|
}
|
||||||
|
|
||||||
// account is optional
|
// account is optional
|
||||||
export function getContract(address: string, ABI: any, library: any, account?: string): Contract {
|
export function getProviderOrSigner(library: Web3Provider, account?: string): Web3Provider | JsonRpcSigner {
|
||||||
|
return account ? getSigner(library, account) : library
|
||||||
|
}
|
||||||
|
|
||||||
|
// account is optional
|
||||||
|
export function getContract(address: string, ABI: any, library: Web3Provider, account?: string): Contract {
|
||||||
if (!isAddress(address) || address === AddressZero) {
|
if (!isAddress(address) || address === AddressZero) {
|
||||||
throw Error(`Invalid 'address' parameter '${address}'.`)
|
throw Error(`Invalid 'address' parameter '${address}'.`)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user