Commit 690a1212 authored by Moody Salem's avatar Moody Salem Committed by GitHub

Strict typescript (#792)

* Make typescript compilation strict in some new code

* Fix other errors, change how we set dark mode to always respond to url parameter

* Fix bug in block number
parent 3fe0a5a9
import { useEffect } from 'react'
import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom'
// fires a GA pageview every time the route changes
export default function GoogleAnalyticsReporter({ location: { pathname, search } }: RouteComponentProps) {
useEffect(() => {
ReactGA.pageview(`${pathname}${search}`)
}, [pathname, search])
return null
}
......@@ -42,11 +42,11 @@ function useMockV1Pair(token?: Token) {
}
export function useV1TradeLinkIfBetter(
isExactIn: boolean,
inputToken: Token,
outputToken: Token,
exactAmount: TokenAmount,
v2Trade: Trade,
isExactIn?: boolean,
inputToken?: Token,
outputToken?: Token,
exactAmount?: TokenAmount,
v2Trade?: Trade,
minimumDelta: Percent = new Percent('0')
): string {
const { chainId } = useWeb3React()
......
import React, { Suspense, useEffect } from 'react'
import styled from 'styled-components'
import ReactGA from 'react-ga'
import React, { Suspense } from 'react'
import { BrowserRouter, Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom'
import styled from 'styled-components'
import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter'
import Create from '../components/CreatePool'
import Footer from '../components/Footer'
import Header from '../components/Header'
import Footer from '../components/Footer'
import NavigationTabs from '../components/NavigationTabs'
import Web3ReactManager from '../components/Web3ReactManager'
import Find from '../components/PoolFinder'
import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import { isAddress } from '../utils'
import Swap from './Swap'
import Send from './Send'
import Pool from './Pool'
import Add from './Pool/AddLiquidity'
import Remove from './Pool/RemoveLiquidity'
import Find from '../components/PoolFinder'
import Create from '../components/CreatePool'
import Send from './Send'
import Swap from './Swap'
const AppWrapper = styled.div`
display: flex;
......@@ -90,14 +91,6 @@ const StyledRed = styled.div`
}
`
// fires a GA pageview every time the route changes
function GoogleAnalyticsReporter({ location: { pathname, search } }: RouteComponentProps) {
useEffect(() => {
ReactGA.pageview(`${pathname}${search}`)
}, [pathname, search])
return null
}
// Redirects to swap but only replace the pathname
function RedirectPathToSwapOnly({ location }: RouteComponentProps) {
return <Redirect to={{ ...location, pathname: '/swap' }} />
......@@ -132,6 +125,7 @@ export default function App() {
<Suspense fallback={null}>
<BrowserRouter>
<Route component={GoogleAnalyticsReporter} />
<Route component={DarkModeQueryParamReader} />
<AppWrapper>
<HeaderWrapper>
<Header />
......
......@@ -21,7 +21,7 @@ export type PopupContent =
}
}
export const updateBlockNumber = createAction<{ networkId: number; blockNumber: number | null }>('updateBlockNumber')
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('updateBlockNumber')
export const toggleWalletModal = createAction<void>('toggleWalletModal')
export const addPopup = createAction<{ content: PopupContent }>('addPopup')
export const removePopup = createAction<{ key: string }>('removePopup')
......@@ -4,17 +4,17 @@ import { addPopup, PopupContent, removePopup, toggleWalletModal } from './action
import { useSelector, useDispatch } from 'react-redux'
import { AppState } from '../index'
export function useBlockNumber() {
export function useBlockNumber(): number | undefined {
const { chainId } = useWeb3React()
return useSelector((state: AppState) => state.application.blockNumber[chainId])
return useSelector((state: AppState) => state.application.blockNumber[chainId ?? -1])
}
export function useWalletModalOpen() {
export function useWalletModalOpen(): boolean {
return useSelector((state: AppState) => state.application.walletModalOpen)
}
export function useWalletModalToggle() {
export function useWalletModalToggle(): () => void {
const dispatch = useDispatch()
return useCallback(() => dispatch(toggleWalletModal()), [dispatch])
}
......
......@@ -18,8 +18,12 @@ const initialState: ApplicationState = {
export default createReducer(initialState, builder =>
builder
.addCase(updateBlockNumber, (state, action) => {
const { networkId, blockNumber } = action.payload
state.blockNumber[networkId] = blockNumber
const { chainId, blockNumber } = action.payload
if (typeof state.blockNumber[chainId] !== 'number') {
state.blockNumber[chainId] = blockNumber
} else {
state.blockNumber[chainId] = Math.max(blockNumber, state.blockNumber[chainId])
}
})
.addCase(toggleWalletModal, state => {
state.walletModalOpen = !state.walletModalOpen
......
......@@ -9,31 +9,20 @@ export default function Updater() {
// update block number
useEffect(() => {
if (library) {
let stale = false
if (!library || !chainId) return
const update = () => {
library
.getBlockNumber()
.then(blockNumber => {
if (!stale) {
dispatch(updateBlockNumber({ networkId: chainId, blockNumber }))
}
})
.catch(() => {
if (!stale) {
dispatch(updateBlockNumber({ networkId: chainId, blockNumber: null }))
}
})
}
const blockListener = (blockNumber: number) => {
dispatch(updateBlockNumber({ chainId, blockNumber }))
}
update()
library.on('block', update)
library
.getBlockNumber()
.then(blockNumber => updateBlockNumber({ chainId, blockNumber }))
.catch(error => console.error(`Failed to get block number for chainId ${chainId}`, error))
return () => {
stale = true
library.removeListener('block', update)
}
library.on('block', blockListener)
return () => {
library.removeListener('block', blockListener)
}
}, [dispatch, chainId, library])
......
......@@ -53,14 +53,20 @@ export function useSwapActionHandlers(): {
// try to parse a user entered amount for a given token
function tryParseAmount(value?: string, token?: Token): TokenAmount | undefined {
if (!value || !token) return
if (!value || !token) {
return
}
try {
const typedValueParsed = parseUnits(value, token.decimals).toString()
if (typedValueParsed !== '0') return new TokenAmount(token, JSBI.BigInt(typedValueParsed))
if (typedValueParsed !== '0') {
return new TokenAmount(token, JSBI.BigInt(typedValueParsed))
}
} catch (error) {
// should fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
console.debug(`Failed to parse input amount: "${value}"`, error)
}
// necessary for all paths to return a value
return
}
// from the current swap inputs, compute the best trade and return it.
......@@ -68,7 +74,7 @@ export function useDerivedSwapInfo(): {
tokens: { [field in Field]?: Token }
tokenBalances: { [field in Field]?: TokenAmount }
parsedAmounts: { [field in Field]?: TokenAmount }
bestTrade?: Trade
bestTrade: Trade | null
error?: string
v1TradeLinkIfBetter?: string
} {
......@@ -84,13 +90,13 @@ export function useDerivedSwapInfo(): {
const tokenIn = useTokenByAddressAndAutomaticallyAdd(tokenInAddress)
const tokenOut = useTokenByAddressAndAutomaticallyAdd(tokenOutAddress)
const relevantTokenBalances = useTokenBalancesTreatWETHAsETH(account, [tokenIn, tokenOut])
const relevantTokenBalances = useTokenBalancesTreatWETHAsETH(account ?? undefined, [tokenIn, tokenOut])
const isExactIn: boolean = independentField === Field.INPUT
const amount = tryParseAmount(typedValue, isExactIn ? tokenIn : tokenOut)
const bestTradeExactIn = useTradeExactIn(isExactIn ? amount : null, tokenOut)
const bestTradeExactOut = useTradeExactOut(tokenIn, !isExactIn ? amount : null)
const bestTradeExactIn = useTradeExactIn(isExactIn ? amount : undefined, tokenOut)
const bestTradeExactOut = useTradeExactOut(tokenIn, !isExactIn ? amount : undefined)
const bestTrade = isExactIn ? bestTradeExactIn : bestTradeExactOut
......@@ -100,11 +106,11 @@ export function useDerivedSwapInfo(): {
}
const tokenBalances = {
[Field.INPUT]: relevantTokenBalances?.[tokenIn?.address],
[Field.OUTPUT]: relevantTokenBalances?.[tokenOut?.address]
[Field.INPUT]: relevantTokenBalances?.[tokenIn?.address ?? ''],
[Field.OUTPUT]: relevantTokenBalances?.[tokenOut?.address ?? '']
}
const tokens = {
const tokens: { [field in Field]?: Token } = {
[Field.INPUT]: tokenIn,
[Field.OUTPUT]: tokenOut
}
......@@ -115,7 +121,7 @@ export function useDerivedSwapInfo(): {
tokens[Field.INPUT],
tokens[Field.OUTPUT],
isExactIn ? parsedAmounts[Field.INPUT] : parsedAmounts[Field.OUTPUT],
bestTrade,
bestTrade ?? undefined,
V1_TRADE_LINK_THRESHOLD
)
......@@ -132,12 +138,9 @@ export function useDerivedSwapInfo(): {
error = error ?? 'Enter an amount'
}
if (
tokenBalances[Field.INPUT] &&
parsedAmounts[Field.INPUT] &&
tokenBalances[Field.INPUT].lessThan(parsedAmounts[Field.INPUT])
) {
error = 'Insufficient ' + tokens[Field.INPUT]?.symbol + ' balance'
const [balanceIn, amountIn] = [tokenBalances[Field.INPUT], parsedAmounts[Field.INPUT]]
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
error = 'Insufficient ' + amountIn.token.symbol + ' balance'
}
return {
......@@ -156,6 +159,7 @@ export function useDefaultsFromURL(search?: string) {
const { chainId } = useWeb3React()
const dispatch = useDispatch<AppDispatch>()
useEffect(() => {
if (!chainId) return
dispatch(setDefaultsFromURL({ chainId, queryString: search }))
}, [dispatch, search, chainId])
}
import { ChainId, WETH } from '@uniswap/sdk'
import { createStore } from 'redux'
import { createStore, Store } from 'redux'
import { Field, setDefaultsFromURL } from './actions'
import reducer from './reducer'
import reducer, { SwapState } from './reducer'
describe('swap reducer', () => {
let store
let store: Store<SwapState>
beforeEach(() => {
store = createStore(reducer, {
......
import { parse } from 'qs'
import { createReducer } from '@reduxjs/toolkit'
import { WETH } from '@uniswap/sdk'
import { ChainId, WETH } from '@uniswap/sdk'
import { isAddress } from '../../utils'
import { Field, selectToken, setDefaultsFromURL, switchTokens, typeInput } from './actions'
......@@ -30,11 +30,11 @@ function parseCurrencyFromURLParameter(urlParam: any, chainId: number): string {
if (typeof urlParam === 'string') {
const valid = isAddress(urlParam)
if (valid) return valid
if (urlParam.toLowerCase() === 'eth') return WETH[chainId]?.address ?? ''
if (valid === false) return WETH[chainId]?.address ?? ''
if (urlParam.toLowerCase() === 'eth') return WETH[chainId as ChainId]?.address ?? ''
if (valid === false) return WETH[chainId as ChainId]?.address ?? ''
}
return WETH[chainId]?.address
return WETH[chainId as ChainId]?.address
}
function parseTokenAmountURLParameter(urlParam: any): string {
......@@ -49,7 +49,7 @@ export default createReducer<SwapState>(initialState, builder =>
builder
.addCase(setDefaultsFromURL, (state, { payload: { queryString, chainId } }) => {
if (queryString && queryString.length > 1) {
const parsedQs = parse(queryString.substr(1), { parseArrays: false })
const parsedQs = parse(queryString, { parseArrays: false, ignoreQueryPrefix: true })
let inputCurrency = parseCurrencyFromURLParameter(parsedQs.inputCurrency, chainId)
let outputCurrency = parseCurrencyFromURLParameter(parsedQs.outputCurrency, chainId)
......@@ -76,7 +76,7 @@ export default createReducer<SwapState>(initialState, builder =>
return {
...initialState,
[Field.INPUT]: {
address: WETH[chainId]?.address
address: WETH[chainId as ChainId]?.address ?? ''
}
}
})
......
......@@ -5,7 +5,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { useWeb3React } from '../../hooks'
import { AppDispatch, AppState } from '../index'
import { addTransaction } from './actions'
import { TransactionDetails } from './reducer'
import { TransactionDetails, TransactionState } from './reducer'
// helper that can take a ethers library transaction response and add it to the list of transactions
export function useTransactionAdder(): (
......@@ -20,6 +20,9 @@ export function useTransactionAdder(): (
response: TransactionResponse,
{ summary, approvalOfToken }: { summary?: string; approvalOfToken?: string } = {}
) => {
if (!account) return
if (!chainId) return
const { hash } = response
if (!hash) {
throw Error('No transaction hash found.')
......@@ -34,9 +37,9 @@ export function useTransactionAdder(): (
export function useAllTransactions(): { [txHash: string]: TransactionDetails } {
const { chainId } = useWeb3React()
const state = useSelector<AppState>(state => state.transactions)
const state = useSelector<AppState, TransactionState>(state => state.transactions)
return state[chainId] ?? {}
return state[chainId ?? -1] ?? {}
}
// returns whether a token has a pending approval transaction
......
......@@ -11,65 +11,68 @@ export default function Updater() {
const lastBlockNumber = useBlockNumber()
const dispatch = useDispatch<AppDispatch>()
const transactions = useSelector<AppState>(state => state.transactions)
const transactions = useSelector<AppState, AppState['transactions']>(state => state.transactions)
const allTransactions = transactions[chainId] ?? {}
const allTransactions = transactions[chainId ?? -1] ?? {}
// show popup on confirm
const addPopup = useAddPopup()
useEffect(() => {
if (typeof chainId === 'number' && library) {
Object.keys(allTransactions)
.filter(hash => !allTransactions[hash].receipt)
.filter(
hash =>
!allTransactions[hash].blockNumberChecked || allTransactions[hash].blockNumberChecked < lastBlockNumber
)
.forEach(hash => {
library
.getTransactionReceipt(hash)
.then(receipt => {
if (!receipt) {
dispatch(checkTransaction({ chainId, hash, blockNumber: lastBlockNumber }))
} else {
dispatch(
finalizeTransaction({
chainId,
if (!chainId) return
if (!library) return
if (!lastBlockNumber) return
Object.keys(allTransactions)
.filter(hash => !allTransactions[hash].receipt)
.filter(hash => {
const lastChecked = allTransactions[hash].blockNumberChecked
return !lastChecked || lastChecked < lastBlockNumber
})
.forEach(hash => {
library
.getTransactionReceipt(hash)
.then(receipt => {
if (!receipt) {
dispatch(checkTransaction({ chainId, hash, blockNumber: lastBlockNumber }))
} else {
dispatch(
finalizeTransaction({
chainId,
hash,
receipt: {
blockHash: receipt.blockHash,
blockNumber: receipt.blockNumber,
contractAddress: receipt.contractAddress,
from: receipt.from,
status: receipt.status,
to: receipt.to,
transactionHash: receipt.transactionHash,
transactionIndex: receipt.transactionIndex
}
})
)
// add success or failure popup
if (receipt.status === 1) {
addPopup({
txn: {
hash,
receipt: {
blockHash: receipt.blockHash,
blockNumber: receipt.blockNumber,
contractAddress: receipt.contractAddress,
from: receipt.from,
status: receipt.status,
to: receipt.to,
transactionHash: receipt.transactionHash,
transactionIndex: receipt.transactionIndex
}
})
)
// add success or failure popup
if (receipt.status === 1) {
addPopup({
txn: {
hash,
success: true,
summary: allTransactions[hash]?.summary
}
})
} else {
addPopup({
txn: { hash, success: false, summary: allTransactions[hash]?.summary }
})
}
success: true,
summary: allTransactions[hash]?.summary
}
})
} else {
addPopup({
txn: { hash, success: false, summary: allTransactions[hash]?.summary }
})
}
})
.catch(error => {
console.error(`failed to check transaction hash: ${hash}`, error)
})
})
}
}
})
.catch(error => {
console.error(`failed to check transaction hash: ${hash}`, error)
})
})
}, [chainId, library, allTransactions, lastBlockNumber, dispatch, addPopup])
return null
......
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}
\ No newline at end of file
......@@ -4,8 +4,8 @@ export interface SerializedToken {
chainId: number
address: string
decimals: number
symbol: string
name: string
symbol?: string
name?: string
}
export interface SerializedPair {
......
......@@ -35,7 +35,10 @@ function deserializeToken(serializedToken: SerializedToken): Token {
}
export function useIsDarkMode(): boolean {
const { userDarkMode, matchesDarkMode } = useSelector<AppState, { userDarkMode: boolean; matchesDarkMode: boolean }>(
const { userDarkMode, matchesDarkMode } = useSelector<
AppState,
{ userDarkMode: boolean | null; matchesDarkMode: boolean }
>(
({ user: { matchesDarkMode, userDarkMode } }) => ({
userDarkMode,
matchesDarkMode
......@@ -61,7 +64,8 @@ export function useFetchTokenByAddress(): (address: string) => Promise<Token | n
const { library, chainId } = useWeb3React()
return useCallback(
async (address: string) => {
async (address: string): Promise<Token | null> => {
if (!library || !chainId) return null
const [decimals, symbol, name] = await Promise.all([
getTokenDecimals(address, library).catch(() => null),
getTokenSymbol(address, library).catch(() => 'UNKNOWN'),
......@@ -103,7 +107,7 @@ export function useUserAddedTokens(): Token[] {
const serializedTokensMap = useSelector<AppState, AppState['user']['tokens']>(({ user: { tokens } }) => tokens)
return useMemo(() => {
return Object.values(serializedTokensMap[chainId] ?? {}).map(deserializeToken)
return Object.values(serializedTokensMap[chainId as ChainId] ?? {}).map(deserializeToken)
}, [serializedTokensMap, chainId])
}
......@@ -156,17 +160,17 @@ export function useAllDummyPairs(): Pair[] {
return new Pair(new TokenAmount(base, ZERO), new TokenAmount(token, ZERO))
}
})
.filter(pair => !!pair)
.filter(pair => !!pair) as Pair[]
)
}),
[tokens, chainId]
)
const savedSerializedPairs = useSelector<AppState>(({ user: { pairs } }) => pairs)
const savedSerializedPairs = useSelector<AppState, AppState['user']['pairs']>(({ user: { pairs } }) => pairs)
const userPairs = useMemo(
() =>
Object.values<SerializedPair>(savedSerializedPairs[chainId] ?? {}).map(
Object.values<SerializedPair>(savedSerializedPairs[chainId ?? -1] ?? {}).map(
pair =>
new Pair(
new TokenAmount(deserializeToken(pair.token0), ZERO),
......
......@@ -51,12 +51,13 @@ const initialState: UserState = {
timestamp: currentTimestamp()
}
const GIT_COMMIT_HASH: string | undefined = process.env.REACT_APP_GIT_COMMIT_HASH
export default createReducer(initialState, builder =>
builder
.addCase(updateVersion, state => {
if (state.lastVersion !== process.env.REACT_APP_GIT_COMMIT_HASH) {
state.lastVersion = process.env.REACT_APP_GIT_COMMIT_HASH
// other stuff
if (GIT_COMMIT_HASH && state.lastVersion !== GIT_COMMIT_HASH) {
state.lastVersion = GIT_COMMIT_HASH
}
state.timestamp = currentTimestamp()
})
......
import { getAddress } from '@ethersproject/address'
import { JSBI, Token, TokenAmount, WETH } from '@uniswap/sdk'
import { ChainId, JSBI, Token, TokenAmount, WETH } from '@uniswap/sdk'
import { useEffect, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useAllTokens } from '../../hooks/Tokens'
......@@ -26,7 +26,7 @@ export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): { [
() =>
uncheckedAddresses
? uncheckedAddresses
.filter(isAddress)
.filter((a): a is string => isAddress(a) !== false)
.map(getAddress)
.sort()
: [],
......@@ -38,12 +38,15 @@ export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): { [
if (addresses.length === 0) return
dispatch(startListeningForBalance({ addresses }))
return () => dispatch(stopListeningForBalance({ addresses }))
return () => {
dispatch(stopListeningForBalance({ addresses }))
}
}, [addresses, dispatch])
const rawBalanceMap = useSelector<AppState>(({ wallet: { balances } }) => balances)
const rawBalanceMap = useSelector<AppState, AppState['wallet']['balances']>(({ wallet: { balances } }) => balances)
return useMemo(() => {
if (!chainId) return {}
return addresses.reduce<{ [address: string]: JSBI }>((map, address) => {
const key = balanceKey({ address, chainId })
const { value } = rawBalanceMap[key] ?? {}
......@@ -65,7 +68,10 @@ export function useTokenBalances(
const dispatch = useDispatch<AppDispatch>()
const { chainId } = useWeb3React()
const validTokens: Token[] = useMemo(() => tokens?.filter(t => isAddress(t?.address)) ?? [], [tokens])
const validTokens: Token[] = useMemo(
() => tokens?.filter((t?: Token): t is Token => isAddress(t?.address) !== false) ?? [],
[tokens]
)
const tokenAddresses: string[] = useMemo(() => validTokens.map(t => t.address).sort(), [validTokens])
// keep the listeners up to date
......@@ -75,13 +81,15 @@ export function useTokenBalances(
const combos: TokenBalanceListenerKey[] = tokenAddresses.map(tokenAddress => ({ address, tokenAddress }))
dispatch(startListeningForTokenBalances(combos))
return () => dispatch(stopListeningForTokenBalances(combos))
return () => {
dispatch(stopListeningForTokenBalances(combos))
}
}, [address, tokenAddresses, dispatch])
const rawBalanceMap = useSelector<AppState>(({ wallet: { balances } }) => balances)
const rawBalanceMap = useSelector<AppState, AppState['wallet']['balances']>(({ wallet: { balances } }) => balances)
return useMemo(() => {
if (!address || validTokens.length === 0) {
if (!address || validTokens.length === 0 || !chainId) {
return {}
}
return (
......@@ -110,7 +118,8 @@ export function useTokenBalancesTreatWETHAsETH(
}
let includesWETH = false
const tokensWithoutWETH = tokens.filter(t => {
const isWETH = t?.equals(WETH[chainId]) ?? false
if (!chainId) return true
const isWETH = t?.equals(WETH[chainId as ChainId]) ?? false
if (isWETH) includesWETH = true
return !isWETH
})
......@@ -121,8 +130,9 @@ export function useTokenBalancesTreatWETHAsETH(
const ETHBalance = useETHBalances(includesWETH ? [address] : [])
return useMemo(() => {
if (!chainId || !address) return {}
if (includesWETH) {
const weth = WETH[chainId]
const weth = WETH[chainId as ChainId]
const ethBalance = ETHBalance[address]
return {
...balancesWithoutWETH,
......@@ -136,13 +146,16 @@ export function useTokenBalancesTreatWETHAsETH(
// get the balance for a single token/account combo
export function useTokenBalance(account?: string, token?: Token): TokenAmount | undefined {
return useTokenBalances(account, [token])?.[token?.address]
const tokenBalances = useTokenBalances(account, [token])
if (!token) return
return tokenBalances[token.address]
}
// mimics the behavior of useAddressBalance
export function useTokenBalanceTreatingWETHasETH(account?: string, token?: Token): TokenAmount | undefined {
const balances = useTokenBalancesTreatWETHAsETH(account, [token])
return token && token.address && balances?.[token.address]
if (!token) return
return balances?.[token.address]
}
// mimics useAllBalances
......@@ -152,6 +165,6 @@ export function useAllTokenBalancesTreatingWETHasETH(): {
const { account } = useWeb3React()
const allTokens = useAllTokens()
const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [allTokens])
const balances = useTokenBalancesTreatWETHAsETH(account, allTokensArray)
const balances = useTokenBalancesTreatWETHAsETH(account ?? undefined, allTokensArray)
return account ? { [account]: balances } : {}
}
......@@ -18,13 +18,13 @@ export default function Updater() {
const { chainId, library } = useWeb3React()
const lastBlockNumber = useBlockNumber()
const dispatch = useDispatch<AppDispatch>()
const ethBalanceListeners = useSelector<AppState>(state => {
const ethBalanceListeners = useSelector<AppState, AppState['wallet']['balanceListeners']>(state => {
return state.wallet.balanceListeners
})
const tokenBalanceListeners = useSelector<AppState>(state => {
const tokenBalanceListeners = useSelector<AppState, AppState['wallet']['tokenBalanceListeners']>(state => {
return state.wallet.tokenBalanceListeners
})
const allBalances = useSelector<AppState>(state => state.wallet.balances)
const allBalances = useSelector<AppState, AppState['wallet']['balances']>(state => state.wallet.balances)
const activeETHListeners: string[] = useMemo(() => {
return Object.keys(ethBalanceListeners).filter(address => ethBalanceListeners[address] > 0) // redundant check
......@@ -41,18 +41,22 @@ export default function Updater() {
}, [tokenBalanceListeners])
const ethBalancesNeedUpdate: string[] = useMemo(() => {
if (!chainId || !lastBlockNumber) return []
return activeETHListeners.filter(address => {
const data = allBalances[balanceKey({ chainId, address })]
return !data || data.blockNumber < lastBlockNumber
if (!data || !data.blockNumber) return true
return data.blockNumber < lastBlockNumber
})
}, [activeETHListeners, allBalances, chainId, lastBlockNumber])
const tokenBalancesNeedUpdate: { [address: string]: string[] } = useMemo(() => {
if (!chainId || !lastBlockNumber) return {}
return Object.keys(activeTokenBalanceListeners).reduce<{ [address: string]: string[] }>((map, address) => {
const needsUpdate =
activeTokenBalanceListeners[address]?.filter(tokenAddress => {
const data = allBalances[balanceKey({ chainId, tokenAddress, address })]
return !data || data.blockNumber < lastBlockNumber
if (!data || !data.blockNumber) return true
return data.blockNumber < lastBlockNumber
}) ?? []
if (needsUpdate.length > 0) {
map[address] = needsUpdate
......@@ -62,8 +66,7 @@ export default function Updater() {
}, [activeTokenBalanceListeners, allBalances, chainId, lastBlockNumber])
useEffect(() => {
if (!library) return
if (ethBalancesNeedUpdate.length === 0) return
if (!library || !chainId || !lastBlockNumber || ethBalancesNeedUpdate.length === 0) return
getEtherBalances(library, ethBalancesNeedUpdate)
.then(balanceMap => {
dispatch(
......@@ -80,7 +83,7 @@ export default function Updater() {
}, [library, ethBalancesNeedUpdate, dispatch, lastBlockNumber, chainId])
useEffect(() => {
if (!library) return
if (!library || !chainId || !lastBlockNumber) return
Object.keys(tokenBalancesNeedUpdate).forEach(address => {
if (tokenBalancesNeedUpdate[address].length === 0) return
getTokensBalance(library, address, tokenBalancesNeedUpdate[address])
......
import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { RouteComponentProps } from 'react-router-dom'
import { parse } from 'qs'
import { AppDispatch } from '../state'
import { updateUserDarkMode } from '../state/user/actions'
export default function DarkModeQueryParamReader({ location: { search } }: RouteComponentProps) {
const dispatch = useDispatch<AppDispatch>()
useEffect(() => {
if (!search) return
if (search.length < 2) return
const parsed = parse(search, {
parseArrays: false,
ignoreQueryPrefix: true
})
const theme = parsed.theme
if (typeof theme !== 'string') return
if (theme.toLowerCase() === 'light') {
dispatch(updateUserDarkMode({ userDarkMode: false }))
} else if (theme.toLowerCase() === 'dark') {
dispatch(updateUserDarkMode({ userDarkMode: true }))
}
}, [dispatch, search])
return null
}
import React, { useEffect, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import React, { useMemo } from 'react'
import styled, {
ThemeProvider as StyledComponentsThemeProvider,
createGlobalStyle,
css,
DefaultTheme
} from 'styled-components'
import { AppDispatch, AppState } from '../state'
import { updateUserDarkMode } from '../state/user/actions'
import { getQueryParam } from '../utils'
import { useIsDarkMode } from '../state/user/hooks'
import { Text, TextProps } from 'rebass'
import { Colors } from './styled'
......@@ -24,8 +20,8 @@ const MEDIA_WIDTHS = {
const mediaWidthTemplates: { [width in keyof typeof MEDIA_WIDTHS]: typeof css } = Object.keys(MEDIA_WIDTHS).reduce(
(accumulator, size) => {
accumulator[size] = (a, b, c) => css`
@media (max-width: ${MEDIA_WIDTHS[size]}px) {
;(accumulator as any)[size] = (a: any, b: any, c: any) => css`
@media (max-width: ${(MEDIA_WIDTHS as any)[size]}px) {
${css(a, b, c)}
}
`
......@@ -117,32 +113,15 @@ export function theme(darkMode: boolean): DefaultTheme {
}
export default function ThemeProvider({ children }: { children: React.ReactNode }) {
const dispatch = useDispatch<AppDispatch>()
const userDarkMode = useSelector<AppState, boolean | null>(state => state.user.userDarkMode)
const darkMode = useIsDarkMode()
const themeURL = getQueryParam(window.location, 'theme')
const urlContainsDarkMode: boolean | null = themeURL
? themeURL.toUpperCase() === 'DARK'
? true
: themeURL.toUpperCase() === 'LIGHT'
? false
: null
: null
useEffect(() => {
if (urlContainsDarkMode !== null && userDarkMode === null) {
dispatch(updateUserDarkMode({ userDarkMode: urlContainsDarkMode }))
}
}, [dispatch, userDarkMode, urlContainsDarkMode])
const themeObject = useMemo(() => theme(darkMode), [darkMode])
return <StyledComponentsThemeProvider theme={themeObject}>{children}</StyledComponentsThemeProvider>
}
const TextWrapper = styled(Text)<{ color: keyof Colors }>`
color: ${({ color, theme }) => theme[color]};
color: ${({ color, theme }) => (theme as any)[color]};
`
export const TYPE = {
......
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}
\ No newline at end of file
......@@ -22,11 +22,6 @@ export function isAddress(value: any): string | false {
}
}
export enum ERROR_CODES {
TOKEN_SYMBOL = 1,
TOKEN_DECIMALS = 2
}
const ETHERSCAN_PREFIXES: { [chainId in ChainId]: string } = {
1: '',
3: 'ropsten.',
......@@ -49,11 +44,6 @@ export function getEtherscanLink(chainId: ChainId, data: string, type: 'transact
}
}
export function getQueryParam(windowLocation: Location, name: string): string | undefined {
const q = windowLocation.search.match(new RegExp('[?&]' + name + '=([^&#?]*)'))
return q && q[1]
}
// shorten the checksummed version of the input address to have 0x + 4 characters at start and end
export function shortenAddress(address: string, chars = 4): string {
const parsed = isAddress(address)
......@@ -103,17 +93,17 @@ export function getContract(address: string, ABI: any, library: Web3Provider, ac
}
// account is optional
export function getRouterContract(chainId, library, account) {
export function getRouterContract(chainId: number, library: Web3Provider, account?: string) {
return getContract(ROUTER_ADDRESS, IUniswapV2Router01ABI, library, account)
}
// account is optional
export function getExchangeContract(pairAddress, library, account) {
export function getExchangeContract(pairAddress: string, library: Web3Provider, account?: string) {
return getContract(pairAddress, IUniswapV2PairABI, library, account)
}
// get token name
export async function getTokenName(tokenAddress, library) {
export async function getTokenName(tokenAddress: string, library: Web3Provider) {
if (!isAddress(tokenAddress)) {
throw Error(`Invalid 'tokenAddress' parameter '${tokenAddress}'.`)
}
......@@ -125,14 +115,10 @@ export async function getTokenName(tokenAddress, library) {
.name()
.then(parseBytes32String)
)
.catch(error => {
error.code = ERROR_CODES.TOKEN_SYMBOL
throw error
})
}
// get token symbol
export async function getTokenSymbol(tokenAddress, library) {
export async function getTokenSymbol(tokenAddress: string, library: Web3Provider) {
if (!isAddress(tokenAddress)) {
throw Error(`Invalid 'tokenAddress' parameter '${tokenAddress}'.`)
}
......@@ -143,24 +129,15 @@ export async function getTokenSymbol(tokenAddress, library) {
const contractBytes32 = getContract(tokenAddress, ERC20_BYTES32_ABI, library)
return contractBytes32.symbol().then(parseBytes32String)
})
.catch(error => {
error.code = ERROR_CODES.TOKEN_SYMBOL
throw error
})
}
// get token decimals
export async function getTokenDecimals(tokenAddress, library) {
export async function getTokenDecimals(tokenAddress: string, library: Web3Provider) {
if (!isAddress(tokenAddress)) {
throw Error(`Invalid 'tokenAddress' parameter '${tokenAddress}'.`)
}
return getContract(tokenAddress, ERC20_ABI, library)
.decimals()
.catch(error => {
error.code = ERROR_CODES.TOKEN_DECIMALS
throw error
})
return getContract(tokenAddress, ERC20_ABI, library).decimals()
}
export function escapeRegExp(string: string): string {
......
......@@ -6,6 +6,7 @@ import { basisPointsToPercent } from './index'
const BASE_FEE = new Percent(JSBI.BigInt(30), JSBI.BigInt(10000))
const ONE_HUNDRED_PERCENT = new Percent(JSBI.BigInt(10000), JSBI.BigInt(10000))
const INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(BASE_FEE)
// computes price breakdown for the trade
export function computeTradePriceBreakdown(
trade?: Trade
......@@ -22,7 +23,7 @@ export function computeTradePriceBreakdown(
)
// remove lp fees from price impact
const priceImpactWithoutFeeFraction = trade?.slippage?.subtract(realizedLPFee)
const priceImpactWithoutFeeFraction = trade && realizedLPFee ? trade.slippage.subtract(realizedLPFee) : undefined
// the x*y=k impact
const priceImpactWithoutFeePercent = priceImpactWithoutFeeFraction
......@@ -31,7 +32,9 @@ export function computeTradePriceBreakdown(
// the amount of the input that accrues to LPs
const realizedLPFeeAmount =
realizedLPFee && new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
realizedLPFee &&
trade &&
new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
return { priceImpactWithoutFee: priceImpactWithoutFeePercent, realizedLPFee: realizedLPFeeAmount }
}
......
{
"extends": "../../tsconfig.strict.json",
"include": ["**/*"]
}
\ No newline at end of file
{
"extends": "./tsconfig.json",
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"alwaysStrict": true,
"strictNullChecks": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitUseStrict": true
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment