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 { transparentize } from 'polished'
|
||||
import { useAllTransactions } from '../../contexts/Transactions'
|
||||
import { useAllTransactions } from '../../state/transactions/hooks'
|
||||
|
||||
const TransactionStatusWrapper = styled.div`
|
||||
display: flex;
|
||||
@ -74,7 +74,7 @@ export default function Transaction({ hash, pending }: { hash: string; pending:
|
||||
const { chainId } = useWeb3React()
|
||||
const allTransactions = useAllTransactions()
|
||||
|
||||
const summary = allTransactions?.[hash]?.response?.summary
|
||||
const summary = allTransactions?.[hash]?.summary
|
||||
|
||||
return (
|
||||
<TransactionWrapper key={hash}>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import React, { useState, useCallback, useEffect, useContext } from 'react'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { parseEther, parseUnits } from '@ethersproject/units'
|
||||
@ -19,7 +20,7 @@ import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
|
||||
import { useAddUserToken, useFetchTokenByAddress } from '../../state/user/hooks'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
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 { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
@ -28,10 +29,10 @@ import { Link } from '../../theme/components'
|
||||
import {
|
||||
calculateGasMargin,
|
||||
getEtherscanLink,
|
||||
getProviderOrSigner,
|
||||
getRouterContract,
|
||||
QueryParams,
|
||||
calculateSlippageAmount
|
||||
calculateSlippageAmount,
|
||||
getSigner
|
||||
} from '../../utils'
|
||||
import Copy from '../AccountDetails/Copy'
|
||||
import AddressInputPanel from '../AddressInputPanel'
|
||||
@ -210,7 +211,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
||||
(!!inputApproval &&
|
||||
!!parsedAmounts[Field.INPUT] &&
|
||||
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 feeTimesInputRaw =
|
||||
@ -370,23 +371,23 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
||||
async function onSend() {
|
||||
setAttemptingTxn(true)
|
||||
|
||||
const signer = await getProviderOrSigner(library, account)
|
||||
const signer = getSigner(library, account)
|
||||
// get token contract if needed
|
||||
let estimate: Function, method: Function, args
|
||||
if (tokens[Field.INPUT].equals(WETH[chainId])) {
|
||||
;(signer as any)
|
||||
signer
|
||||
.sendTransaction({ to: recipient.toString(), value: BigNumber.from(parsedAmounts[Field.INPUT].raw.toString()) })
|
||||
.then(response => {
|
||||
setTxHash(response.hash)
|
||||
addTransaction(
|
||||
response,
|
||||
'Send ' +
|
||||
addTransaction(response, {
|
||||
summary:
|
||||
'Send ' +
|
||||
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.INPUT]?.symbol +
|
||||
' to ' +
|
||||
recipient
|
||||
)
|
||||
})
|
||||
setPendingConfirmation(false)
|
||||
})
|
||||
.catch(() => {
|
||||
@ -403,15 +404,15 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
||||
gasLimit: calculateGasMargin(estimatedGasLimit)
|
||||
}).then(response => {
|
||||
setTxHash(response.hash)
|
||||
addTransaction(
|
||||
response,
|
||||
'Send ' +
|
||||
addTransaction(response, {
|
||||
summary:
|
||||
'Send ' +
|
||||
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.INPUT]?.symbol +
|
||||
' to ' +
|
||||
recipient
|
||||
)
|
||||
})
|
||||
setPendingConfirmation(false)
|
||||
})
|
||||
)
|
||||
@ -514,9 +515,9 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
||||
gasLimit: calculateGasMargin(estimatedGasLimit)
|
||||
}).then(response => {
|
||||
setTxHash(response.hash)
|
||||
addTransaction(
|
||||
response,
|
||||
'Swap ' +
|
||||
addTransaction(response, {
|
||||
summary:
|
||||
'Swap ' +
|
||||
slippageAdjustedAmounts?.[Field.INPUT]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.INPUT]?.symbol +
|
||||
@ -524,7 +525,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
||||
slippageAdjustedAmounts?.[Field.OUTPUT]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.OUTPUT]?.symbol
|
||||
)
|
||||
})
|
||||
setPendingConfirmation(false)
|
||||
})
|
||||
)
|
||||
@ -550,7 +551,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
||||
gasLimit: calculateGasMargin(estimatedGas)
|
||||
})
|
||||
.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 { PopupContent } from '../../state/application/actions'
|
||||
import { usePopups } from '../../state/application/hooks'
|
||||
import { useActivePopups, useRemovePopup } from '../../state/application/hooks'
|
||||
import { Link } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
import DoubleTokenLogo from '../DoubleLogo'
|
||||
@ -102,7 +102,8 @@ function PopupItem({ content, popKey }: { content: PopupContent; popKey: string
|
||||
export default function App() {
|
||||
const theme = useContext(ThemeContext)
|
||||
// get all popups
|
||||
const [activePopups, , removePopup] = usePopups()
|
||||
const activePopups = useActivePopups()
|
||||
const removePopup = useRemovePopup()
|
||||
|
||||
// switch view settings on mobile
|
||||
const isMobile = useMediaLayout({ maxWidth: '600px' })
|
||||
|
@ -6,7 +6,7 @@ import styled from 'styled-components'
|
||||
|
||||
import { useWeb3React } from '../../hooks'
|
||||
import useInterval from '../../hooks/useInterval'
|
||||
import { usePopups } from '../../state/application/hooks'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
import { Link } from '../../theme/components'
|
||||
@ -41,7 +41,7 @@ export default function TxnPopup({
|
||||
const [count, setCount] = useState(1)
|
||||
|
||||
const [isRunning, setIsRunning] = useState(true)
|
||||
const [, , removePopup] = usePopups()
|
||||
const removePopup = useRemovePopup()
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
|
@ -20,7 +20,7 @@ import LightCircle from '../../assets/svg/lightcircle.svg'
|
||||
import { RowBetween } from '../Row'
|
||||
import { useENSName } from '../../hooks'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { useAllTransactions } from '../../contexts/Transactions'
|
||||
import { useAllTransactions } from '../../state/transactions/hooks'
|
||||
import { NetworkContextName } from '../../constants'
|
||||
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 { isMobile } from 'react-device-detect'
|
||||
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 App from './pages/App'
|
||||
import store from './state'
|
||||
@ -36,11 +36,7 @@ if (process.env.NODE_ENV === 'production') {
|
||||
ReactGA.pageview(window.location.pathname + window.location.search)
|
||||
|
||||
function ContextProviders({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<TransactionContextProvider>
|
||||
<BalancesContextProvider>{children}</BalancesContextProvider>
|
||||
</TransactionContextProvider>
|
||||
)
|
||||
return <BalancesContextProvider>{children}</BalancesContextProvider>
|
||||
}
|
||||
|
||||
function Updaters() {
|
||||
|
@ -25,7 +25,7 @@ import { useAddressBalance } from '../../contexts/Balances'
|
||||
import { useTokenAllowance } from '../../data/Allowances'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { useWeb3React, useTokenContract } from '../../hooks'
|
||||
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
|
||||
import { useTransactionAdder, useHasPendingApproval } from '../../state/transactions/hooks'
|
||||
|
||||
import { ROUTER_ADDRESS } from '../../constants'
|
||||
import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
|
||||
@ -305,8 +305,8 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
||||
!!parsedAmounts[Field.OUTPUT] &&
|
||||
JSBI.greaterThanOrEqual(outputApproval.raw, parsedAmounts[Field.OUTPUT].raw))
|
||||
// check on pending approvals for token amounts
|
||||
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
|
||||
const pendingApprovalOutput = usePendingApproval(tokens[Field.OUTPUT]?.address)
|
||||
const pendingApprovalInput = useHasPendingApproval(tokens[Field.INPUT]?.address)
|
||||
const pendingApprovalOutput = useHasPendingApproval(tokens[Field.OUTPUT]?.address)
|
||||
|
||||
// used for displaying approximate starting price in UI
|
||||
const derivedPrice =
|
||||
@ -497,9 +497,9 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
||||
gasLimit: calculateGasMargin(estimatedGasLimit)
|
||||
}).then(response => {
|
||||
setTxHash(response.hash)
|
||||
addTransaction(
|
||||
response,
|
||||
'Add ' +
|
||||
addTransaction(response, {
|
||||
summary:
|
||||
'Add ' +
|
||||
parsedAmounts[Field.INPUT]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.INPUT]?.symbol +
|
||||
@ -507,7 +507,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
||||
parsedAmounts[Field.OUTPUT]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.OUTPUT]?.symbol
|
||||
)
|
||||
})
|
||||
setPendingConfirmation(false)
|
||||
})
|
||||
)
|
||||
@ -534,7 +534,10 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
||||
gasLimit: calculateGasMargin(estimatedGas)
|
||||
})
|
||||
.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 { useAllBalances } from '../../contexts/Balances'
|
||||
import { usePairContract } from '../../hooks'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
import { splitSignature } from '@ethersproject/bytes'
|
||||
@ -493,9 +493,9 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
|
||||
}).then(response => {
|
||||
setPendingConfirmation(false)
|
||||
setTxHash(response.hash)
|
||||
addTransaction(
|
||||
response,
|
||||
'Remove ' +
|
||||
addTransaction(response, {
|
||||
summary:
|
||||
'Remove ' +
|
||||
parsedAmounts[Field.TOKEN0]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.TOKEN0]?.symbol +
|
||||
@ -503,7 +503,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
|
||||
parsedAmounts[Field.TOKEN1]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.TOKEN1]?.symbol
|
||||
)
|
||||
})
|
||||
})
|
||||
)
|
||||
.catch(e => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useWeb3React } from '../../hooks'
|
||||
import { addPopup, PopupContent, removePopup, toggleWalletModal } from './actions'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
@ -23,27 +23,31 @@ export function useUserAdvanced() {
|
||||
return useSelector((state: AppState) => state.application.userAdvanced)
|
||||
}
|
||||
|
||||
export function usePopups(): [
|
||||
AppState['application']['popupList'],
|
||||
(content: PopupContent) => void,
|
||||
(key: string) => void
|
||||
] {
|
||||
// returns a function that allows adding a popup
|
||||
export function useAddPopup(): (content: PopupContent) => void {
|
||||
const dispatch = useDispatch()
|
||||
const activePopups = useSelector((state: AppState) => state.application.popupList.filter(item => item.show))
|
||||
|
||||
const wrappedAddPopup = useCallback(
|
||||
return useCallback(
|
||||
(content: PopupContent) => {
|
||||
dispatch(addPopup({ content }))
|
||||
},
|
||||
[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) => {
|
||||
dispatch(removePopup({ key }))
|
||||
},
|
||||
[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 application from './application/reducer'
|
||||
import user from './user/reducer'
|
||||
import transactions from './transactions/reducer'
|
||||
import { save, load } from 'redux-localstorage-simple'
|
||||
|
||||
const PERSISTED_KEYS: string[] = ['user']
|
||||
const PERSISTED_KEYS: string[] = ['user', 'transactions']
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
application,
|
||||
user
|
||||
user,
|
||||
transactions
|
||||
},
|
||||
middleware: [...getDefaultMiddleware(), save({ 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 { getAddress } from '@ethersproject/address'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
|
||||
import { parseBytes32String } from '@ethersproject/strings'
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
|
||||
@ -116,13 +117,18 @@ export function calculateSlippageAmount(value: TokenAmount, slippage: number): [
|
||||
]
|
||||
}
|
||||
|
||||
// account is optional
|
||||
export function getProviderOrSigner(library: any, account?: string): any {
|
||||
return account ? library.getSigner(account).connectUnchecked() : library
|
||||
// account is not optional
|
||||
export function getSigner(library: Web3Provider, account: string): JsonRpcSigner {
|
||||
return library.getSigner(account).connectUnchecked()
|
||||
}
|
||||
|
||||
// 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) {
|
||||
throw Error(`Invalid 'address' parameter '${address}'.`)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user